The ZMAP Protocol

The Zcash Memo Asset Protocol — how a shielded note carries an asset.

✓ CurrentVersion 0.5 · 2026-10-09

In force for every live collection. Lists what changed from v0.3; lineage (check 7) is Wake, not yet externally reviewed.

No independent security review yet. Wrong or underspecified? That is a defect in the text: tell us.

ZMAP v0.5 — the Zcash Memo Asset Protocol, with lineage proofs

Status: DRAFT. No external security review has been performed. Everything in this document handles funds. Implement accordingly.

Version 0.5 (draft)
Date 2026-09-24; revised 2026-10-02 to match the code; §15 delivery replaced 2026-10-06
Network Zcash mainnet, NU6.3 "Ironwood" and later
Supersedes v0.3 (published 2026-08-27); absorbs the v0.4 draft of 2026-09-04
Reference implementation wallet: zecboat-core (zmap.rs, wake.rs, native_builder.rs, issuance_v1.rs, collections.rs, collection_audit.rs, wake_memo.rs); circuits, verifier and Knotline: zmap-wake; issuer: zecboat_mint_wake (identity.rs, zmap.rs, close.rs, registry.rs, audit.rs)
Licence Public. Implement it freely, with or without attribution.

How to read this. v0.3 stays the base text. Sections not listed in the change table below are unchanged and are not reproduced here. v0.4's changes (identity, finalization, burn) are folded in as written in its draft. v0.5 adds one thing, lineage proofs, and removes the machinery that existed because they did not.

What changes from v0.3

Section v0.3 v0.5
§2 carrier value exactly 10,000 zat unchanged. Still the detection rule for wallets
§4 asset_id sha256(image) identity v1 (§4 below): BLAKE2b-256(personal "ZMAP-AssetID-v1", 0x01 ‖ ik ‖ desc). Legacy ids parse as v0.3
§5 transfer byte-identical memo reproduction §5.3: a transfer memo carries asset_id, ik, desc, consignment_hash and nothing else; §5.5: the carrier output MUST share an Action with the carrier spend; §5.4 amended: lock notes whose rho is a spent carrier's nullifier
§7 manifest issuance, ed25519 signed BIP-340 under ik over the raw bytes, plus genesis[]: (txid, action_index) per unit, ik, §7.3: every generation's hash pinned on chain, and §7.4: the manifest itself is carried on chain in the close transaction, and the collection is registered
§8 disclosure (txid, action_index, ock) optional. Still the way to show a memo publicly; no longer the way to prove a note
§9 verification six checks seven: check 7, lineage (WAKE §8, W1–W5; no nullifier-set lookup); check 2 now requires the on-chain pin; light-client clause; full-node trust only on loopback
§12.3 provenance bundle + attestation replaced by lineage proofs; attestation demoted to bridge and compaction
§13 finalization — from v0.4
§14 burn — from v0.4
§15 consignment — new: the lading (WAKE §7.3) that carries lineage proofs with the note, sealed to the recipient, and the memo rule that binds it

§4 (replaced) — Asset identity v1

desc      = "zmap-desc-v1" ‖ "|" ‖ collection ‖ "|" ‖ unit ‖ "|" ‖ name ‖ "|" ‖ symbol ‖ "|" ‖ sha256(image) as 64 hex
asset_id  = BLAKE2b-256( personal = "ZMAP-AssetID-v1", input = 0x01 ‖ ik ‖ desc )      lower-case hex
ik        = BIP-340 x-only public key (secp256k1), 32 bytes; the collection's issuance key

desc has exactly six |-separated parts and the sixth is 64 hex characters; a reader MUST refuse any other shape. The names MUST NOT contain |. ik and desc travel byte for byte with every carrier, so any reader can recompute asset_id and MUST refuse a memo whose asset_id does not derive from its own ik and desc. Reference: wallet issuance_v1.rs derive_asset_id, mint identity.rs; the two are tested against the same vectors.

The issuance memo (as minted) is JSON: asset_id, ik, desc, and MAY carry name, symbol, decimals, image_hash for readers that predate v1. Those four are redundant with desc and a v1 reader derives them from it. Memos of the old prose dialect and pre-v1 JSON (no ik/desc) keep their v0.3 meaning. A JSON memo carrying one of ik and desc without the other is neither dialect and is not a unit of anything (2026-10-09). An ik names ONE collection: an issuer MUST NOT register or mint a second collection under a key, and a reader MAY take a key's manifest, once the collection is closed, as the whole of what that key ever issued. A reader's issuance verdict for a unit no held manifest lists is drawn from the memos it holds for that asset id, counting a memo only when its ik and desc derive the id; a memo that does not derive the id claims nothing, whatever key it names.

Size. A memo is 512 bytes. The transfer memo of §5.3 adds 86 bytes to the three identity fields, so desc MUST be at most about 265 bytes (collection name, unit name and symbol together under about 180 characters). The issuer's tool MUST refuse to mint a unit whose memo would not fit after a transfer (zecboat_mint_wake memo_bytes_transferable). Found the hard way on 2026-10-01: two collections minted with the long form could not be sent (534 and 537 bytes) until §5.3 existed.

§5.3 (new) — The transfer memo

A transfer of a v1 carrier writes, on the recipient's carrier output, exactly

{"asset_id":"…","ik":"…","desc":"…","consignment_hash":"…"}

and nothing else. asset_id, ik and desc are copied byte for byte from the carrier being spent; consignment_hash is §15's. Key order is the serialiser's; the length is what matters. This replaces v0.3's "byte-identical memo reproduction" for v1 carriers. Legacy carriers (no ik/desc) are reproduced as before with consignment_hash appended, and refused if that exceeds 512 bytes; nothing is truncated. A sender MUST verify derive_asset_id(ik, desc) == asset_id before writing the memo, and MUST verify the memo it writes parses to the same asset_id as the one it spends. Carriers already on the chain are not touched; only the next hop writes the short form. Reference: wallet zmap.rs memo_with_consignment_hash.

Exception, stated so nobody assumes otherwise (2026-10-09): the pre-v0.4 prose dialect (<name>. Image Hash: <64 hex>. …) has no room for consignment_hash and is not JSON; a carrier of that dialect travels with no commitment to a lading, and the reference wallet delivers no lading to a carrier whose memo commits to none: such a unit's lineage ends at its first transfer, and a sender's knots for it are never read (the reference still writes them; 2026-10-09, regression review, Issue L.3). No collection issued under v0.4 or later uses that dialect; its units are the original Happy Birthday Zcash collection only.

§5.4 (amended) — Carrier protection covers lineage notes

v0.3 locks every 10,000-zatoshi note carrying a valid ZMAP memo. Under v0.5 the asset's identity lives in the lineage, not in the memo, and the two can diverge: after any transfer that did not honour §5.5, the lineage sits in an ordinary change note with no memo, unlocked, and the next fee spend burns it silently while a valid-looking memo carrier is still displayed.

A wallet SHOULD additionally lock any note whose rho equals the nullifier of a carrier that wallet has spent. This is one query over received_notes.rho against the wallet's own spent carriers, needs no proof, and recovers lineages stranded by pre-v0.5 transfers.

Implementation note (2026-10-02). The reference locks such notes whatever their value and follows the chain forward through the wallet's own later spends (zmap.rs read_lineage_rows, lineage_locks). That is deliberate: a 465,000-zat change note that inherited a carrier's rho on 2026-10-01 is exactly the stranded lineage this rule exists for. What is not yet specified is release: when the lineage has been proven onward, or the note is known not to be the successor the holder cares about, the lock has no further purpose. A release rule is an open item (review M2); until then the lock is lifted only by the owner.

§5.5 (new) — The carrier output MUST share an Action with the carrier spend

An Ironwood bundle is a list of Actions, each exactly one spend and one output. Consensus constrains every output's rho to the nullifier of the spend in the same Action. The lineage of a carrier therefore continues in whichever output shares the carrier spend's Action.

A transfer that places the recipient's carrier output in a different Action delivers the memo and loses the lineage: the successor is the sender's change note. The asset arrives unprovable.

Implementations MUST verify, before proving, that the carrier spend and the carrier output have the same Action index, and MUST rebuild the bundle until they do. The reference builder pairs spends and outputs at random and reports the pairing in BundleMetadata; pairing precedes proving, so a rebuild is cheap. This rule cannot be retrofitted to transfers already made.

§7 (amended) — Manifest additions

"schema":   "zecboat-proof-of-issuance/2",
"identity": { "scheme": "zmap-asset-id-v1", "ik": "<BIP-340 x-only public key, hex>",
              "signature": "bip340", "issuance_ufvk": "…" },
"generation": 1,
"assets": [ { "asset_id": "…", "asset_desc": "…",
              "issuance": { "txid": "…", "action_index": 7, "height": 3480081, "cmx": "…" } }, … ]

assets[].issuance lists the mint output of every unit ((txid, action_index), its block and the genesis cmx). A verifier reads the output's cmx from their own node at that position and compares. The manifest is signed BIP-340 under ik over its raw bytes (§7.3.1). Elsewhere in this document "genesis[]" (the name used by earlier drafts) means this list. This replaces the need for an OCK to establish origin; the OCK (§8) remains for showing the memo's contents publicly.

§7.3 (new) — Every manifest generation is pinned on chain

A manifest served over HTTPS is a claim by a web server. Two attacks follow from leaving it there: a compromised key or host publishes a new generation with new units, visible only to whoever fetched it; and an issuer serves different manifests to different buyers at the same generation, undetectable because §7.1's anti-rollback log is per verifier.

The pin. Before serving generation g, the issuer MUST publish, from the issuance address, a transaction with one shielded output to itself whose memo is exactly

"zmap:manifest:v1" ‖ I2BEOSP_32(g) ‖ SHA-256(manifest_bytes)

52 bytes (16 + 4 + 32; an earlier draft said 53), then NUL padding to 512 as any memo. manifest_bytes are defined in §7.3.1. Reference: zmap-wake/src/pin.rs, 2026-09-25.

Where the pin travels: in the close (§7.4). The pin is NOT a field of the manifest. The reference carries it, with the manifest and its 64-byte BIP-340 signature, in the close transaction, read with the collection's published viewing key.

Withdrawn 2026-10-08: an HTTPS sidecar (issuance_manifest.pin.json with an OCK, beside the manifest and its .sig) was specified here and never implemented by the wallet. It is dropped: an OCK authenticates the output to its recipient, not its sender, so an issuer could pin two manifests for one generation and serve each to different buyers, and nothing in the sidecar made the pin unique. (The table also called the .sig ed25519; v1 signatures are BIP-340.)

§7.3.1 Which bytes the pin hashes — settled

manifest_bytes is the exact byte sequence of issuance_manifest.json as served, the same bytes the .sig covers. No canonicalisation, no re-serialisation, no field blanking. A verifier hashes what it fetched.

This is why the pin is not a field of the manifest. A pin inside the manifest would have to be hashed "with the field blanked", which forces every verifier to parse, mutate and re-serialise JSON and agree byte-for-byte on the result. That is the canonicalisation trap the 2026-09-12 sign-manifest incident already fell into and this specification already forbids for signatures (§7). One rule for both: the signature and the pin cover the same raw bytes.

Consequences an implementer must accept:

  • Changing a single byte of the served manifest, including whitespace, invalidates both the signature and the pin. That is the point.
  • The issuer's order of operations is fixed: finalise bytes → sign → compute hash → publish the close carrying pin, manifest and signature (§7.4). The manifest bytes do not change after the hash is taken, so nothing is circular.
  • Re-publishing the same generation with different bytes is a split view (§9 check 2), even if the issuer signs it. Fixing a typo is generation g+1 with a new pin.

§7.3.2 Verifier rule

A verifier MUST refuse a manifest of generation g unless, scanning the issuance account with its published viewing key:

  • the pin memo carries g and SHA-256 of the manifest bytes reassembled from the chunks;
  • the transaction carrying the pin spends a note of the issuance account (a memo merely sent to the account is anyone's; §7.4, review F1);
  • the account holds one pin for generation g and exactly one close; a second pin for the same generation with another manifest hash is a split view (the same pin sent twice is not: it names the same bytes), and any output of the account mined after the close, or in the close's block from any transaction but the close, is a breach (§7.4). (Wording aligned with the reference's checks 4 and 9 on 2026-10-09.)

One generation, one pin, visible to anyone holding the viewing key: the chain decides which manifest exists. This holds as far as the chain source is honest; a verifier that scans through a server says so (§9, "Light clients"). Reference: wallet collection_audit.rs checks exactly one close (check 1), the pin in the close's own transaction and that no other pin of the account names that generation with another hash (check 4, stray_pins), and every later output (check 9). Pins for other generations are reported in check 4's detail, not failed.

§7.4 (new, implemented) — The close transaction and the Registry

Where genesis outputs go (2026-10-09): an issuer MUST mint genesis outputs to its own account, never directly to a buyer. The issuance account's viewing key is published, so every genesis output's recipient address is readable by anyone; a unit minted straight to a buyer names that buyer as its first holder in public. The first sale, from that account, is an ordinary transfer and reveals nothing (WAKE §9).

The reference carries the pin, the manifest and its signature on chain, in one transaction from the issuer's account to itself (the close), so a verifier with the collection's viewing key needs no HTTPS fetch at all. Memos, each NUL-padded to 512 bytes:

pin    "zmap:manifest:v1" ‖ I2BEOSP_32(g) ‖ sha256(manifest)                            52 bytes (§7.3)
close  "zmap:close:v1" ‖ mode(1) ‖ I2BEOSP_32(g) ‖ I2BEOSP_32(N) ‖ sha256(manifest) ‖ ik ‖ body
         mode 0: body = the N genesis cmx values, 32 bytes each, in unit order (N ≤ 13)
         mode 1: body = sha256(cmx_1 ‖ … ‖ cmx_N)                                        (larger N)
chunk  "zmap:chunk:v1" ‖ kind(1) ‖ I2BEOSP_16(i) ‖ I2BEOSP_16(n) ‖ bytes
         kind 0: the manifest bytes, 494 per chunk; kind 1: the 64-byte BIP-340 signature

Chunks are reassembled by index (Orchard shuffles Actions) and the result MUST hash to the value in the pin and in the close record. Every output of the issuer's account mined after the close is a breach, visible to anyone holding the published viewing key (check 9 of the wallet's audit). Reference: mint close.rs, wallet collection_audit.rs parse_close.

Padding rule (review F2/F3, 2026-10-02). A reader MUST take each field by its declared length and MUST NOT strip trailing zero bytes from a hash, a cmx or a signature; a value that genuinely ends in 0x00 is otherwise corrupted silently. Mode 0 MUST refuse N = 0 and N > 13 before allocating. Reference: close.rs padded, collection_audit.rs.

The Registry. A collection is listed by one registration transaction to the Zecboat Registry address, whose viewing key is pinned in the wallet:

header "zmap:registry:v1" ‖ ik ‖ sha256(UFVK) ‖ close_txid ‖ I2BEOSP_32(mint_height)
       ‖ I2BEOSP_32(close_height) ‖ sha256(manifest) ‖ BIP-340 signature by ik ‖ name
chunk  "zmap:chunk:v1" kind 2: the collection's UFVK string

A valid registration proves that the holder of ik asked to be listed, nothing about who that is; look-alike names are the registry's known weakness (registry_scalability.md). The wallet re-derives every verdict from the chain with the UFVK; a registration is a pointer, not a vouching. An audit MUST consider only outputs of transactions in which the issuer's account spent something (review F1): memos sent to the issuer by third parties are not issuance. Verdicts expire: the reference marks an audit older than 10,000 blocks as due for re-check and revokes the badge if a later check fails (review F10).

§9 (amended) — Check 7: lineage

# Check Needs Failure means
7 The note in hand descends from a genesis[] output through a valid chain of hop proofs the lading (§15), the note's rho, a node the verifier chooses unproven, distinct from forged: a genuine asset that lost its lading fails here too

Procedure (this is WAKE §8, W1–W5, stated once there; the overlay adds step 1):

  1. Parse the lading. Verify sha256(lading bytes exactly as delivered) equals the memo's consignment_hash.
  2. W1: hops[0] is a genesis proof and its cmx_0 equals the cmx the verifier's own node reports at genesis[unit].
  3. W2: every anchor_j is a root of the note-commitment tree at the proof's anchor_height and that height is at least MIN_DEPTH (100) blocks below the tip.
  4. W3: every proof verifies under the pinned key for its circuit. W4: nf_prev of each hop equals nf of the previous.
  5. W5: the note in hand has rho == nf_last, the final proof's public nf.

Nullifier-set membership is not checked (WAKE §8): the existence of N_{j+1} with ρ = nf_j already means nf_j was revealed, by consensus, and leaving the lookup out removes one thing an oracle can lie about. (An earlier draft of this step required it.)

A verifier MUST NOT report "genuine" on checks 1–6 alone. Check 7 is what proves this note.

How the reference composes this (2026-10-08). It does not compute one "1–7" verdict; it shows two badges, computed independently, and ties their colour together:

  • the lineage badge (check 7): Verified (W1–W5 against a node on this machine, amber until the external review of WAKE §11), checked via provider (W1–W5 passed through a server, own colour and mark, the host named; WAKE §8.1), Unverified (could not be checked right now), No lading, Broken (a check failed; which one is shown);
  • the issuance badge (checks 1, 3, 4, 5, 6): green "Verified — issued by " only when the lineage badge is Verified against the holder's own node AND the issuer's key is pinned in the wallet (a registry-only collection is never green; revised 2026-10-09); otherwise neutral "Design registered by · key <first 12 hex>…<last 12 hex>"; a collection found only in the Registry reads "In the Zecboat Registry as ", with the same long fingerprint, and is never green. A copied memo passes checks 1–6; it never borrows the green.

Check 2 (amended). Freshness now has two parts: generation >= highest seen (as v0.3), and the §7.3.2 pin in the issuance account matches the manifest bytes (§7.3.1). A manifest that passes the first and fails the second is a split view, not a rollback, and MUST be reported as such. The reference wallet does not run the first part as a separate check: a v1 collection has one generation, fixed by its close, and a second would be a breach (§7.4).

Light clients. Anchors come from whatever server the client uses. A prover who also controls that server can serve a fabricated tree root matching a fabricated proof. A light client MUST verify every anchor_j against the block header chain (ZIP 221 history commitments) or MUST report the asset as unverified, never as verified. "A node you choose" is only as good as the client's ability to check that node. A zebrad endpoint counts as the verifier's own node only when it is on this machine (loopback: 127.0.0.0/8, ::1, localhost); a full node reached over the network is a server like any other and yields at best checked via provider, never Verified (WAKE §8.1; review M3, 2026-10-02).

§12.3 (replaced) — Lineage proofs

The fact

The Orchard/Ironwood Action circuit constrains each output note's rho to equal the nullifier of the note spent in the same Action (orchard 0.15.3, circuit.rs:801). Consequences, all enforced by L1 today:

  • a nullifier is revealed at most once;
  • one Action yields one output, so a spent carrier has exactly one successor;
  • the link is committed inside the successor's note commitment, visible to its holder alone.

Confirmed on mainnet 2026-09-24: the receiving wallet's Happy Birthday carrier has rho equal to the nullifier of the carrier the sending wallet spent, in the same transaction.

The hop relation

The holder of carrier j proves, at spend time, with their own nk:

HOP_j   public   (nf_{j-1}, nf_j, anchor_j)
        private  (g_d, pk_d, v, rho_j, psi_j, rcm_j), nk_j, merkle_path_j

        cm_j  = NoteCommit(g_d, pk_d, v, rho_j, psi_j, rcm_j)
        cm_j  ∈ tree(anchor_j)
        rho_j = nf_{j-1}
        nf_j  = DeriveNullifier(nk_j, rho_j, psi_j, cm_j)

Genesis (π_0) replaces rho_j = nf_{j-1} with cm_0 = cmx_0 as a public input. π_0 is produced by the mint output's first holder from that wallet's viewing key: it needs the note's opening and the holder's nk, which the issuer has only when it mints to itself. No spending key, no OCK. (Corrected 2026-09-25: the earlier text said "the issuer produces π_0 from the mint wallet's viewing key"; for Happy Birthday the mint sent nine outputs to its own wallet and one to a second wallet, and only those wallets can prove them. The issuer's part is to publish genesis[].)

Gadgets are Orchard's own (halo2_gadgets: Sinsemilla commit and Merkle, Poseidon, ECC), instantiated with Orchard's domain constants. The relation is a strict subset of the Action circuit and fits its K = 11. The circuits are specified in WAKE-v1.md and built in zmap-wake (circuit.rs, verifying-key ids in keys.rs, negative tests in tests/negative.rs).

What it closes

  • §12.1 identity forgery. A copied memo has no lineage.
  • §12.2 supply inflation by splitting. The second carrier's rho is some other spend's nullifier. No valid proof can exist for it. Supply is len(genesis[]), conserved by L1.
  • The v0.3 provenance bundle (per-hop txid, nullifier, OCK for every action), the "dedicated transaction" rule, and the attestation as a security mechanism. All three existed because v0.3 §12.1 assumed the receiver cannot see what was spent. The receiver holds rho.

What stays

The attestation (Sign_attest(carrier_nullifier ‖ asset_id ‖ height ‖ "genuine")) remains for two jobs and is otherwise retired:

  1. Bridge. A statement for a note whose lineage was broken: Orchard-era carriers (the Orchard→Ironwood boundary breaks the rho link, since Orchard bundles now fabricate a self-output per spend) and any carrier delivered while the builder paired Actions at random. The attested cmx becomes that note's genesis for check 7. This is permanent, not transitional: the rho link crosses Actions inside one bundle, so every future pool migration breaks it again and needs the bridge again. The trustless answer is a cross-bundle proof of transaction membership (§16); until it exists, every asset that has crossed a pool boundary carries one trusted hop.
  2. Compaction. In v1 the recipient can see the lineage's nullifiers, hence which transactions carried the asset. An issuer attestation lets a holder discard history. Recursive proofs (v2, §16) remove this job.

The attestation key MUST remain separate from the issuance key, as in v0.3.

Client-side validation, stated precisely

This is not consensus in the miner's sense, and the spec does not call it that. Miners do not reject a bad asset transfer. Validity is decided by verifiers, each computing the same deterministic verdict from public chain data plus the consignment, with no trusted party except the issuer for "what was issued". Honest verifiers cannot disagree. The chain will not protect a wallet that does not check, a holder who loses the consignment, a sender who breaks §5.5, or a circuit with a missing constraint. It refuses to vouch; it does not refuse to mine. That line moves only with native assets, which have no scheduled upgrade.

Settlement is out of scope. A seller can deliver the note and withhold the consignment, or pair the carrier spend with their own change, keep the lineage, and sell the memo carrier twice. A buyer detects either only by running check 7 after delivery. There is no atomic swap on Zcash (ZIP 228 is Draft with no target upgrade). Markets MUST choose deliver-then-pay, with or without an escrow, and MUST NOT present v0.5 as solving settlement. It proves what a note is; it does not decide who gets paid.

§13 — Finalization (from v0.4)

finalization = Sign_issuance( "zmap:final:v1" ‖ asset_id ‖ total_units ‖ height )

Published in the manifest. An issuer's signed, dated, public commitment; breaking it is detectable. Under v0.5 it is also checkable: total_units MUST equal len(genesis[]).

Not implemented by the reference (2026-10-08). Neither the mint nor the wallet writes or checks a finalization record. The close (§7.4) does its work: it fixes N and the genesis commitments on chain, and any later output of the issuance account is a breach.

§14 — Burn (from v0.4)

{ "zmap": 1, "asset_id": "…", "burn": true }

A carrier spent to an output carrying this memo is deliberately destroyed. Under v0.5 a burn is also a lineage terminus: the burner MAY publish π_last with nf_last so the unit's chain is provably closed. Supply arithmetic subtracts it.

§15 (new; delivery replaced 2026-10-06) — The consignment

The title deed. Its format is the lading of WAKE §7.3 (wake_lading, genesis, hops[], each proof carrying kind: "wake-v1", circuit, instance, anchor_height, proof); the earlier zmap_consignment/proof_kind: hop-v1 sketch is withdrawn, there is one format. About 7 KB as JSON for one hop, plus one proof per transfer.

  • The transfer memo (§5.3) MUST carry "consignment_hash": sha256(lading bytes), the lading being in WAKE's canonical serialisation.
  • The lading MUST NOT be published in a form anyone but the unit's holders can read. (nf_prev, nf) pairs link transactions.
  • Wallets MUST store ladings, MUST include them in backups, and MUST restore them. An asset without its lading is unproven (§9 check 7), not counterfeit.
  • Prove before broadcast, store against the send. The sender extends the lading before the transaction is broadcast, while the note's Merkle path is held. The stored copy MUST be tied to that transaction's id; if the transaction expires unmined, that copy MUST NOT be presented as the unit's current lading, or the unit is locked out of every later send (review H2; reference: consignments.sent_txid, latest_consignment).
  • Delivery: the Knotline (default). The lading travels on the chain, in the transfer itself: each transfer writes only its own proof as a sealed knot, hands the knot's key to the recipient in a memo, and links the knot to the one before it. The recipient rebuilds the lading by following the line back and checks it against consignment_hash. No server is involved and the cost of a transfer does not grow with the history. Format, procedures and privacy analysis: KNOTLINE-v1.md (wire format frozen 2026-10-06). The carrier's own memo is not touched by it.
  • Delivery: by hand (fallback). When the Knotline is not used (the sender has turned it off, or the history is too long to start a line with), the lading stays with the sender, who seals it to the recipient's Orchard address with the note-encryption key agreement, a BLAKE2b key derivation personalised "WAKE-seal-v1-key", and ChaCha20-Poly1305 (zmap-wake/src/seal.rs), and hands the sealed file over by any channel. The memo binds it the same way.
  • No delivery service. A wallet MUST NOT hand the lading to, or fetch it from, any service: not under its hash, not at an address the user names. An intermediary is never blind; it sees the fetch times and network addresses of both parties. The lading travels on the chain (KNOTLINE-v1.md) or as a sealed file the holder moves by any channel of their own.

§16 — Planned for 0.6

  • Recursive lineage proofs. Each hop verifies the previous proof in-circuit; nf_prev becomes a witness; constant size; the recipient learns only "valid". Halo 2 accumulation over the Pallas/Vesta cycle. Research-grade; do not let it delay 0.5.
  • Fungible split via an allocation root committed in the canonical successor's rseed.
  • Smaller knots. The consignment is on chain since Knotline v1 (§15), at eleven memo outputs per proof. ZIP 231 memo bundles, if activated, would carry a knot without one output per 512 bytes; the recursive proof above would shrink the knot itself.
  • Cross-bundle lineage. Prove that nf_j and cm_{j+1} appear in the ZIP 244 digest tree of one transaction, so a lineage survives a pool boundary without an attestation. BLAKE2b in-circuit; expensive; the only trustless replacement for the bridge in §12.3.

§17 — Security considerations specific to 0.5

  • A hop proof MUST constrain rho inside the note commitment; a rho supplied beside it proves nothing.
  • anchor_j is a public input and MUST be checked against a real root by the verifier.
  • Merkle membership MUST use Orchard's MerkleCRH domain.
  • nk MUST NOT reach any public cell of the nullifier gadget.
  • Prove against anchors at least 100 blocks deep (a block count, not a time: 42 minutes under NU7's 25-second blocks); a reorg invalidates a proof and only the prover can re-prove.
  • Every negative case MUST have a failing test before any "verified" badge depends on the circuit: wrong rho, wrong anchor, forged nk, split attempt, wrong genesis.
  • External constraint-completeness review is a ship gate, not a follow-up.

What v0.5 does not prove

  • Who holds an asset now.
  • That the holder has not burned it silently.
  • That the issuer stops issuing (finalization is a promise, made checkable, not a lock).
  • That the artwork is original.
  • That the same artwork is not issued again under another key. A second collection with the same sha256(image) and a different ik is a different asset_id, not a forgery of the first; buyers checking the pinned ik are unaffected.

Version history

Version Notes
0.3 First public release: manifest, OCK disclosure, six-check verification.
0.4 (draft) ZIP 227 asset identity bound to ik; version byte; finalization; burn. Published as a proposal; nothing was issued under it; folded into 0.5.
0.5 (draft) Lineage proofs. §5.5 Action pairing; check 7; consignment; attestation demoted to bridge and compaction; provenance bundle removed. Revised 2026-09-24 after a hostile review: §7.3 on-chain manifest pin, sidecar file, hash over raw served bytes (§7.3.1); check 2 split-view detection; light-client clause; §5.4 lineage locking; settlement out of scope; bridge stated as permanent; cross-bundle proof named for 0.6; same-artwork-other-key clarified.
0.5 rev. 2026-10-02 Text caught up with the code, after the internal review. §4 identity v1 as built (ZMAP-AssetID-v1, desc, 265-byte ceiling, mint guard); §5.3 short transfer memo (asset_id, ik, desc, consignment_hash), which made BCN26 and HBD10 sendable without re-minting; §5.4 any-value lineage lock stated as deliberate, release open; §7.4 close transaction, Registry, padding rule, issued-outputs rule, audit expiry; §9 check 7 restated as WAKE W1–W5, nullifier-set lookup dropped, loopback-only full-node trust; §15 lading = WAKE §7.3, prove-before-broadcast and sent_txid, sealing stated. Exploratory banner withdrawn.
0.5 rev. 2026-10-06 §15 delivery replaced. The lading travels on the chain as a Knotline (KNOTLINE-v1.md, wire format frozen), by default; a sealed file by hand is the fallback. WAKE §7.3 gains the lading's canonical serialisation; WAKE §8.1 allows a named "checked via provider" state. Nothing about identity, the transfer memo or the circuits changes.
0.5 rev. 2026-10-09 §15: no delivery service. The only routes are the chain (Knotline) and a sealed file moved by hand; a wallet MUST NOT hand the lading to or fetch it from any service. §4: a transfer memo carrying only part of the v1 fields is refused; one issuance key names one collection.