Knotline v1 — a lading kept on the chain as a line of sealed knots
Status: DRAFT. No external security review has been performed. The wire format in §3 is frozen (2026-10-06): three mainnet transactions of that day carry it, and a test in the reference implementation pins it.
2026-10-08: revised after an internal automated review (not an external one): §4 gains the rule that a sender chains only to a line it rebuilt itself; §5 bounds the walk at 512 knots and separates "unknown transaction" from "wrong key"; §6 is restated as a cryptographic claim with the shape caveat; §7 says what the block cover holds against; §10 corrects the comparison with a file lading and names the sender's OVK as a second key route (discarding it is MAY, not SHOULD: decided the same day). No change to the wire format.
| Version | 1 |
| Date | 2026-10-06 |
| Network | Zcash mainnet, NU6.3 "Ironwood" and later |
| Depends on | WAKE v1 (the lading and its proofs), ZMAP v0.5 (§5.3 transfer memo, §5.5 pairing, §15) |
| Reference implementation | zmap-wake/src/knotline.rs; wallet: zecboat-core/src/wake_memo.rs, wake.rs, native_builder.rs |
| Licence | Public. Implement it freely, with or without attribution. |
The key words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119.
1. What it is for
WAKE gives a note a lading: the list of proofs that it descends from a named genesis output. ZMAP v0.5 §15 requires the lading to reach the next holder, and until now it travelled outside the chain, as a sealed file moved by hand. Carrying the whole lading inside the transfer removes that step but resends every proof on every transfer.
The Knotline carries the lading on the chain, one proof per transfer:
- each transfer writes a knot: its own hop proof, readable only with a fresh key;
- that key is handed to the new holder inside the transfer;
- each knot carries the key and the transaction of the knot before it.
A new holder follows the line back to the first knot and rebuilds the lading. An earlier holder never receives later keys. Nobody else can read a knot. No server is involved, and the cost of a transfer does not depend on the length of the history.
The Knotline changes how a lading travels. It does not change WAKE's circuits, proofs, or the
lading's bytes: the rebuilt lading is byte-identical to one delivered whole, and the
consignment_hash of the transfer memo (ZMAP §5.3) binds it as before.
2. Terms
| Term | Meaning |
|---|---|
| lading | WAKE §7.3: the genesis outpoint and the ordered hop proofs of a note |
| knot | one entry of the line: one or more hop proofs, and the link to the previous knot |
| knot key | 32 random bytes, generated by the sender for one knot |
| knot address | the Orchard-format address derived from a knot key (§3.1); nobody spends from it |
| key memo | the memo that hands a knot key to the recipient (§3.3) |
| line | the knots of one note's lineage, newest to oldest |
3. Wire format (frozen)
All integers are little-endian. Every output defined here is an Ironwood output of value zero.
Every memo defined here has the ZIP 302 lead byte 0xF5 ("arbitrary data"); an implementation
MUST NOT display or export the bytes of such a memo as text.
3.1 Knot address
for counter = 0, 1, 2, …:
seed = BLAKE2b-256(personalization = "ZbWakeRecordV1__", input = knot_key || [counter])
if seed is a valid Orchard spending key: stop
fvk = FullViewingKey(SpendingKey(seed))
knot_address = fvk.address_at(diversifier index 0, external scope)
The personalization is the 16 ASCII bytes shown (the name predates "Knotline" and is on the
chain). The reader derives fvk the same way and uses its external incoming viewing key.
3.2 Knot
A knot is the following bytes, split over memos (§3.4) of outputs sent to the knot address:
| Field | Size | Present |
|---|---|---|
version = 1 |
1 | always |
| flags: bit 0 has-previous, bit 1 has-genesis; other bits zero | 1 | always |
| previous knot key | 32 | if has-previous |
| previous knot's transaction id, in protocol byte order | 32 | if has-previous |
| genesis transaction id, as in the lading (display order) | 32 | if has-genesis |
| genesis action index | 4 | if has-genesis |
| number of hops, at least 1 | 2 | always |
| hops |
Each hop:
| Field | Size |
|---|---|
circuit: 0 genesis, 1 hop |
1 |
| instance: three field elements as in WAKE §7.2 | 96 |
| anchor height | 4 |
| proof length | 2 |
proof (kind is wake-v1) |
as given |
Exactly one of has-previous and has-genesis MUST be set. A knot with has-genesis is the first knot of a line.
3.3 Key memo
One output to the recipient of the carrier, with the knot key as a single chunk (§3.4)
under the tag WKK1: nine header bytes, the 32 key bytes, zero padding.
3.4 Chunks
memo = 0xF5 || tag (4 ASCII bytes) || chunk index (u16) || chunk count (u16) || data, zero-padded to 512
data is up to 503 bytes. Knot chunks use the tag WKR1. A reader accepts a set of chunks only
if every index from 0 to count−1 is present exactly once with the same count.
A knot with one WAKE v1 hop is 5,103 bytes (first knot) or 5,131 bytes (later knot): eleven chunks either way. With the key memo a transfer carries twelve outputs beyond its own. There is no padding: a line that starts with one proof and every later knot have the same shape.
4. Sending
When a carrier is transferred with a lading of n proofs (the n-th made for this transfer,
WAKE §8 "prove before broadcast"):
- If the transaction in which the sender received the carrier contains a key memo addressed to
the sender's own receiver (the account and address the carrier came to), and the sender's
own lading for this carrier was rebuilt from the line under exactly that key, starting at
exactly that transaction (the stored copy records the pair and the number of knots walked),
the line already holds proofs
1 … n−1. The knot is: has-previous, with that key and that transaction's id, and hopnonly. A line the reader will refuse is not extended: once the recorded walk reachedMAX_KNOTS − 1knots the item starts a new line instead (§5.3). A key memo alone MUST NOT be chained to: an earlier holder can add one with a random key and hand the real lading over another way, and every later sender would then tie knots nobody can open. A sender MUST only chain if the lading it is about to commit to is in canonical serialisation (WAKE §7.3), since the recipient will rebuild it and compare hashes. - Otherwise the item starts its line: the knot is has-genesis, with the lading's genesis
outpoint and all
nhops. (A wallet MAY decline to start a line with a long history and fall back to §8; the reference wallet declines above six proofs.) - Generate a fresh knot key from a cryptographic random source. A knot key MUST NOT be reused.
- Add to the same transaction as the carrier: the knot's chunks as zero-value outputs to the knot address, and the key memo as a zero-value output to the recipient.
- The carrier's memo is unchanged by any of this. In particular nothing is added to it: it is
close to the 512-byte limit already. It carries
consignment_hashover the full lading as ZMAP §5.3 requires. - ZMAP §5.5 applies unchanged: the carrier spend and the carrier output MUST share an Action. The extra outputs make a random pairing less likely to satisfy it; an implementation MUST check before proving and MUST NOT send an unpaired transfer.
5. Reading
When a carrier arrives whose memo commits to a lading the wallet does not hold:
- Look for a key memo among the wallet's own outputs of that transaction. Without one, the lading did not travel this way (§8).
- Derive the knot address's viewing key (§3.1), trial-decrypt the transaction's Ironwood actions with it, and reassemble the knot (§3.4).
- If the knot is has-previous, repeat step 2 with the previous key on the previous transaction, until a has-genesis knot is reached. A reader MUST stop with an error on a repeated transaction, MUST bound the number of knots it follows (reference: 512; each knot is a round trip to the chain source), MUST refuse a has-previous knot with more than one hop and a has-genesis knot with more than a stated number of hops (reference: 64), and MUST bound the bytes a walk holds (reference: 8 MB). A wallet SHOULD walk automatically only for a carrier whose asset is listed in a manifest the wallet holds and has verified (a passed registry audit's chain copy, or a copy built into the wallet); a memo that merely names a trusted key is no reason, since anyone can write one. Any other carrier's walk is offered on demand (opening the send of such an item counts as asking). Each walk runs under its own time and byte budget, so one line cannot spend another's; a pass that runs out of time records nothing against carriers it did not reach; a walk nobody asked for MUST NOT hold the display of holdings or a send; a failed line SHOULD back off with a growing delay that survives a restart, applied to the walk only (the transfer's own memos are still read); a source that did not answer is not a failed line and gets a short, flat delay. The send collects only the carrier being sent, and a carrier whose memo commits to a lading the wallet has not collected is NOT sent as unproven by default: the lading exists; abandoning it is a separate choice under its own warning (review 2026-10-09, regression over 12-15). "The source does not know the transaction" and "the key opens no knot in it" are different outcomes and MUST be reported as such; neither is "broken".
- Build the lading: the genesis outpoint of the first knot, then every hop in order from the first knot to the newest, and write it in WAKE's canonical serialisation (WAKE §7.3): the reader and the sender must arrive at the same bytes from the same content.
- The SHA-256 of those bytes MUST equal the carrier memo's
consignment_hash. Otherwise the line is rejected as a whole; a partial lading MUST NOT be stored. - Verify the lading as WAKE §8 prescribes. The Knotline adds nothing to and removes nothing from that verification.
A transaction fetched from a server MUST be checked against the id it was asked for (the id is recomputed from the bytes). A server that does not know a transaction has given no evidence about the lading: the result is "could not be checked", never "broken".
6. Who can read what
| Party | Can read |
|---|---|
| The new holder | every knot of the line |
| The sender, and any earlier holder | the knots up to their own transfer; none after it |
| Anyone else | no knot; they see a transfer with twelve more outputs than usual |
An earlier holder also cannot decrypt or cryptographically recognise later knots: they know the commitment of the note they created, not its nullifier. That is the whole of the claim. A knot transfer has a fixed public shape (14 actions, about 47 KB, a 70,000-zat fee), and while such transfers are few, an earlier holder watching the chain can guess that the next one is the resale; with a colluding provider the guess is near-certain (§7). §6 is a cryptographic guarantee, not a statistical one. A uniform shape for every carrier transfer would close the gap at a cost; it is not in this version.
7. What a chain server can observe
A wallet with its own full node asks nobody. A wallet behind a light server asks it for the transactions of the line and for the tree states of the anchors, so that server can learn that the wallet is checking a lineage, and the blocks in which the item changed hands.
To narrow this, a reader that knows the block of a wanted transaction SHOULD fetch that block's
shielded transactions together and keep the one it needs. The block is known: a hop's anchor
height is the block its note was mined in, which is the block of the previous knot's
transaction. What this achieves, stated plainly (2026-10-09): it hides the wanted
transaction only from an observer who sees the list of fetched ids and nothing else. The server
itself sees each transaction's action count in the compact blocks it serves, and a knot transfer
(14 actions; 34 or more for a start of line) is the outlier in nearly every block, so an honest
server picks the wanted transaction out of the cover (measured on the three mainnet transfers of
§9). Through a provider, a walk reveals the item's transfer chain to that provider, with the
reader's network address; a reader with its own node reveals nothing. The remedies are a uniform
transfer shape (planned for 0.7.2: every transfer padded to the knot shape) and an anonymising
transport. A hinted reader MUST make every request of the cover set whatever each answers, MUST
treat a hint above the tip, or a block that does not list the wanted transaction, as a wrong
hint and stop, and MUST compare the height the server reports with the hint. That rule is for
knot fetches, where the hint is a Knotline fact (a hop's anchor is its note's block, §6); a
reader MUST NOT hint the genesis (W1) fetch from a lading's own anchor_height (WAKE-v1.md
§8), and on a wrong hint it MAY ask once more without one. A reader's byte budget for a walk
counts every transaction the server sent, the cover included (2026-10-09, gate second pass,
Issues B and F). When the hinted block holds nothing but the wanted transaction
(a genesis often does), the reader SHOULD list the two blocks on either side and take its
cover from them; only when those hold nothing either MAY it ask for the transaction by itself.
An honest server learns nothing new from that ask, since its own block list named one
candidate; a server that answers five block lists with nothing can force it, and learns the
id. That trade, availability over resistance to an actively lying server, was chosen on
2026-10-09 after a genesis alone in its block proved uncheckable through a provider under the
earlier rule (reference: COVER_SPAN in oracle_lightwalletd.rs). It is a mitigation. An
anonymising transport is the remedy and is outside this version.
8. When the Knotline is not used
A wallet whose user has turned the Knotline off, a recipient whose wallet does not implement it, or a history too long to start a line with: the transfer carries no knot, the lading stays with the sender, and it is handed over as ZMAP §15 describes (a sealed file by any channel). A unit that later moves with the Knotline on starts a line then.
9. Measured cost (2026-10-06, mainnet)
| Transfer | Actions | Size | Fee (ZIP 317) |
|---|---|---|---|
| without lineage in the transfer | 2 | 9,143 bytes | 0.0001 ZEC |
| one knot | 14 | 47,038 bytes | 0.0007 ZEC |
| a line started with three proofs of earlier history | 34 | 110,160 bytes | 0.0017 ZEC |
About 20 seconds to build, prove and sign one knot's transfer on an eight-thread desktop. A new holder verifies one proof per hop, once.
Transactions of the reference run, with a throwaway test item: start
c3cea88ccf0b429a74071fc03bc134d6d7bfb5ee992b357f2717bbb8f21fb591; knots
fdbc024f5a46b81ca660c386acb510903086cd5949a342db338f3ec47d002252 and
b48b84a089168cb458adb1aec21479c00e17bfb4021f14fcbbf348193c622a70.
10. Security considerations
- Integrity does not rest on the Knotline. A sender can write a wrong knot or a wrong
previous key; the recipient then cannot rebuild a lading that matches
consignment_hash, and the unit shows as unproven, exactly as if nothing had been delivered. Forging a lineage still requires forging a WAKE proof. - Confidentiality rests on Zcash note encryption to the knot address and on the secrecy of the knot key. Compromise of a holder's viewing key reveals the knot key in their key memo and with it the line up to that point. A lading delivered as a sealed file is never on the chain; a line is, permanently, so the two are not equivalent under a future break of note encryption. Knot and key-memo outputs are also decryptable with the sender's outgoing viewing key unless the sender discards it for those outputs; a sender MAY. The reference sender does not (decided 2026-10-08): a wallet restored from its seed rebuilds its sent History from those outputs, and what the OVK exposes is a key the sender already held. Whoever's viewing key is published (an issuance account, for one) SHOULD NOT send units from that account.
- A leaked knot key reveals the line up to that knot and nothing after it.
- Permanence. A knot cannot be withdrawn. Whatever a future break of the encryption reveals about old knots stays revealed. A start-of-line knot publishes, under a key every later holder receives, the hops of earlier holders who had sent by file; those holders are not asked. A wallet MAY decline to start a line that contains other parties' hops.
- The sender's own records. Besides the OVK route, the sender's wallet database keeps the key memo and the knot outputs it sent as plaintext sent-note memos (the backend's schema), so a copied database is a second way to the keys the sender handed out, independent of the OVK decision; discarding the OVK alone would not close it.
- Shape. A transfer carrying a knot is larger than a plain one and can be recognised as such. Because every knot has the same shape, the shape does not reveal the length of the history; a line started with earlier history is the exception, once.
- The derivation in §3.1 makes the knot address a function of the key alone. Every holder of the key can derive its spending key; the outputs carry no value, and an implementation MUST NOT spend or sweep knot notes (their nullifiers would link the line forward).
- Data from the chain is untrusted input. Readers MUST bound lengths and counts (§3.4, §5.3).
11. Limits
- About 47 KB of chain space per transfer. Suitable for collections in the hundreds or low thousands; at mass scale it is a load on the chain and on every wallet that scans it.
- Verification grows with the number of hops; the transfer does not.
- A constant-size lineage proof (ZMAP §16) would shrink a knot and make verification constant; it does not replace the need to carry it.
12. Review status
Designed, implemented and tested by one party. The claims of §6 and §10 have had no outside review. Until they have, a wallet SHOULD present the Knotline as part of the same "awaiting external review" status it gives WAKE.