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+1with 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
gandSHA-256of 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
gand 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):
- Parse the lading. Verify
sha256(lading bytes exactly as delivered)equals the memo'sconsignment_hash. - W1:
hops[0]is a genesis proof and itscmx_0equals thecmxthe verifier's own node reports atgenesis[unit]. - W2: every
anchor_jis a root of the note-commitment tree at the proof'sanchor_heightand that height is at leastMIN_DEPTH(100) blocks below the tip. - W3: every proof verifies under the pinned key for its circuit. W4:
nf_prevof each hop equalsnfof the previous. - W5: the note in hand has
rho == nf_last, the final proof's publicnf.
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
rhois some other spend's nullifier. No valid proof can exist for it. Supply islen(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:
- Bridge. A statement for a note whose lineage was broken: Orchard-era carriers (the
Orchard→Ironwood boundary breaks the
rholink, since Orchard bundles now fabricate a self-output per spend) and any carrier delivered while the builder paired Actions at random. The attestedcmxbecomes that note's genesis for check 7. This is permanent, not transitional: therholink 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. - 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_prevbecomes 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_jandcm_{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
rhoinside the note commitment; arhosupplied beside it proves nothing. anchor_jis a public input and MUST be checked against a real root by the verifier.- Merkle membership MUST use Orchard's
MerkleCRHdomain. nkMUST 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, forgednk, 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 differentikis a differentasset_id, not a forgery of the first; buyers checking the pinnedikare 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. |