ZMAP v0.4 — draft proposal
Status: DRAFT. 2026-09-04. Supersedes the 2026-09-03 draft, which argued this the wrong way round.
The proposal in one line
Adopt ZIP 227's asset-identity derivation, because it is the best available design — not because ZSAs might activate.
Why the earlier framing was wrong
The first draft argued "prepare for L1 ZSAs, so assets survive migration." That invites the obvious
reply: "so you're waiting for NU7?" — and it makes the whole case depend on an upgrade whose ZIP
reads Status: Draft, Deployment: TBD, and which ECC has pushed back on.
Test it the other way. If ZSAs never activate, is this change still worth making?
| Needs NU7? | |
|---|---|
asset_id bound to the issuer key → identity forgery becomes impossible |
No |
| Signed issuance record | No |
finalize → capped, publicly auditable supply |
No |
Canonical asset_desc |
No |
| Explicit burn, so a destroyed asset is distinguishable from a silent holder | No |
| Enforced supply — consensus rejects inflation | Yes |
Only the last line waits. Everything above it is a better protocol on 2026-09-04.
And following ZIP 227 exactly costs nothing over inventing a scheme. Same key, same hash, same work. It buys two things free: byte-compatibility if NU7 ever lands, and a derivation already reviewed by people who do this full time. A bespoke scheme buys a review burden.
So the claim is not "ZMAP is ready for ZSAs." It is:
ZMAP uses ZIP 227 asset identity, working on Zcash mainnet today.
Migration is a footnote, not the motivation.
§4 (revised) — asset_id derivation
What v0.3 does, and what it costs
asset_id = sha256_hex(image_bytes)
Content-addressed, and §4's reasoning for that is sound: anyone holding the artwork recomputes the id without trusting a registry.
But it binds identity to content and not to issuer. Anyone can hash the same PNG and mint a
carrier claiming that asset_id. That is §12.1 identity forgery, and v0.3 closes it only
afterwards — via proof-of-issuance, attestation, and a buyer who checks. Detectable, not
impossible.
What ZIP 227 does
From zcash-shielded-assets/orchard, src/note/asset_base.rs:
asset_id.push(0u8); // version
asset_id.extend(ik.encode()); // issuance validating key
asset_id.extend_from_slice(&asset_desc_hash[..]); // 32-byte description hash
// asset_digest = BLAKE2b-512(ZSA_ASSET_DIGEST_PERSONALIZATION, asset_id)
// AssetBase = hash_to_curve(ZSA_ASSET_BASE_PERSONALIZATION)(asset_digest)
Identity commits to the issuer's key. Without ik, an id deriving from it cannot be constructed.
The change
Correcting an error in the first draft. It said sha256(image) "drops into the
asset_desc_hash slot unchanged". It fits dimensionally — both are 32 bytes — but that is not
what ZIP 227 puts there:
// src/zsa/issuance/mod.rs:157
pub fn compute_asset_desc_hash(asset_desc: &NonEmpty<u8>) -> [u8; 32] {
if String::from_utf8(…).is_err() {
panic!("asset_desc is not a well-formed Unicode string");
}
Params::new().hash_length(32).personal(b"ZSA-AssetDescCRH") …
}
asset_desc is a UTF-8 description string, hashed with BLAKE2b-256. Not artwork bytes.
ZIP 227 has no concept of images, because L1 ZSAs are fungible tokens — asset_desc is
ticker-like, "Zecboat Gold Token". Binding artwork is ZMAP's problem, and the description string is
the only hook the derivation offers.
Binding the artwork through the description
asset_desc = "<name>|sha256:<64 lowercase hex>"
asset_desc_hash = BLAKE2b-256( personal = "ZSA-AssetDescCRH", asset_desc )
asset_id_bytes = 0x00 ‖ ik ‖ asset_desc_hash
asset_id = BLAKE2b-512( personal = "ZMAP_AssetDigest", asset_id_bytes )
Example:
Happy Birthday #001|sha256:26a1563f6aa4fab5ec0a50f88b2242b165649f76231043689e8941ef5d027190
Identity transitively commits to the exact file, and the construction stays ZIP 227-literal — it is only a string. v0.3's content-addressing is preserved inside the new shape rather than fighting it.
Why not the alternatives
| Rejected because | |
|---|---|
| Description is a URL | commits to a location, not content. URLs change. This is how Ethereum-era NFTs got it wrong. |
| Description is canonical JSON | a hash over JSON is a hash over your serialiser's habits — key order, whitespace, Unicode normalisation, number formatting. A known source of consensus bugs. |
| Image hash outside the identity | two different files sharing a name would share an asset_id. Weaker than v0.3, which at least bound identity to content. |
Rules the implementation imposes, so they belong in the spec
- Valid UTF-8.
compute_asset_desc_hashpanics on invalid input; it does not return an error. A malformed description crashes the minter. - Non-empty. The parameter type is
NonEmpty<u8>. - Byte-exact, one canonical form. A trailing space is a different asset. Specify the separator
(
|), the prefix (sha256:), and lowercase hex, and hash exactly those bytes.
What it costs
Content-addressing weakens. Under v0.3 artwork alone recomputes the id. Under v0.4 a verifier
needs the artwork, the name, and ik.
image_hashbecomes MANDATORY in the memo, as does the exactasset_descstring. Without both, the id cannot be recomputed and the asset is unverifiable.ikis published in the issuance manifest (§7) and is not secret — it is a validating key.- Artwork verification is otherwise unchanged: fetch,
sha256, exact match. A fetch failure is still not verification.
The guarantee earned is worth the extra input: not this is the file the memo names, but this issuer issued this exact file.
§4a (new) — Version byte
ZIP 227 prefixes 0x00. v0.3 asset ids carry no version at all. Adopt it: one byte, and every future
change to the derivation becomes expressible rather than a flag day.
A parser MUST treat a 64-hex-char id with no known preimage as v0.3 legacy.
§12.3 (revised) — The issuance key. This is the blocker.
The issuance key MUST be a ZIP 227 IssueValidatingKey — secp256k1 Schnorr, BIP-340 x-only.
From src/zsa/issuance/auth.rs:29:
use secp256k1::{schnorr, Keypair, Message, Secp256k1, SecretKey, XOnlyPublicKey};
pub struct ZSASchnorr;
The existing FROST vault cannot supply this
The existing vault holds a 2-of-2 FROST group key on RedPallas, used as an Orchard spend authorizing key:
zcash-sign generate --ak <GROUP_KEY>
ak authorises spending notes from an address. isk/ik authorises issuing an asset. Different
curve, different role. secp256k1 and Pallas are unrelated groups — there is no derivation between
them.
A new key ceremony is required. This is not negotiable and it blocks everything else in v0.4.
Why that is cheap rather than painful
ZMAP has never had an issuance key. Assets are identified by sha256(image); nothing signs them
into existence. So there is no key to migrate — only one to create. The cost is one ceremony, and it
will never be lower than it is now.
FROST supports secp256k1, so threshold issuance remains achievable. It is a second vault, not an
extension of the first, and frost-tools / zcash-sign were built around the Orchard path.
This separation is the design working, not a wrinkle. Spending from the mint address and creating assets are different powers. ZIP 227 separates them deliberately; v0.3 §12.3 already applies the same reasoning to attestation keys.
§13 (new) — Finalization
ZIP 227 lets an issuer permanently close issuance. ZMAP has no equivalent, so a buyer cannot know the supply is capped.
finalization = Sign_issuance( "zmap:final:v1" ‖ asset_id ‖ total_units ‖ height )
Published in the issuance manifest. Say what it is: an issuer's signed, dated, public commitment
— not a consensus guarantee. Breaking it is detectable. It maps one-to-one onto ZIP 227's finalize
flag. §12.2 split-detection still does the real work of catching inflation.
§14 (new) — Burn
v0.3 treats burn only as a hazard — a carrier spent as fee change, which is why the carrier lock and
assert_no_zsa_notes_at_risk exist.
{ "zmap": 1, "asset_id": "…", "burn": true }
A carrier spent to an output with this memo is deliberately destroyed. Attestation status becomes
burned; supply arithmetic subtracts it.
Without this, an asset that is gone and an asset whose holder stopped answering look identical.
Legacy assets — the line is 2026-09-04
Existing ZMAP assets are not migrated. They are legacy, and that is the correct answer.
Happy Birthday, Cyber-Nomads, Zecoil, wSOLzsa. Their value was proving the mechanism works on mainnet, and they did that. The assets themselves carry little beyond that — the only holder of any HBD asset is the project.
So: no dual-id mapping, no doubled verification surface, no retro-binding. They cannot be retro-bound anyway — every one predates any issuance key existing.
- v0.3 assets remain verifiable under v0.3 rules, which continue to work.
- A parser MUST distinguish them: no version byte, id equals the image hash.
- wSOLzsa stays a 1-of-1 historical artifact, as the market plan already treats it.
Drawing the line makes the second blocking question disappear. It was only hard while assuming the old assets had to come along.
Migration, as a footnote
If ZIP 226/227 activate:
- Every v0.4 asset already carries
(ik, asset_desc_hash)— the two inputs ZIP 227 needs. AssetBaseis computable for each without a new ceremony.- Issue the real ZSA; publish a signed statement binding ZMAP
asset_idtoAssetBase; retire the carrier via §14 burn. - Provenance carries across rather than restarting.
And do not build the §7a ZK circuit. provenance.md §7a — a verifier cannot confirm a spent
nullifier belongs to the previous hop's carrier without the holder's nk — is a ZMAP problem that
enforced supply dissolves. Expensive work that activation makes redundant. Build the attestation
federation, which is needed regardless; skip the circuit.
Not proposed
- No curve points in memos. No value base, no circuit, no per-asset commitments.
CARRIER_ZATstays 10,000, exact.- The attestation federation stays. It is what makes audited supply work.
- No claim that ZMAP becomes forgery-proof. Identity forgery closes. Supply inflation by splitting (§12.2) does not — that needs consensus.
Order of work
1. The issuance key ceremony. secp256k1 Schnorr, threshold if desired. Everything else is
unimplementable until ik exists.
2. Implement §4 in zmap.rs — derivation, version byte, mandatory image_hash, and a legacy
path for v0.3 ids.
3. Mint HBD_zsa_v0.4 and prove the round trip: mint → detect → send → receive → verify. See
the ZSA update notes.
4. §13 and §14 — finalization and burn. Both are memo conventions plus a signature; neither blocks the mint test.
5. Update the published surfaces — Ocean /protocol, the registry, zmapSpec.ts, the reference
implementations. Each is a compatibility break and should land together.
Open questions
- BLAKE2b-512 or SHA-256? BLAKE2b matches ZIP 227 and supports personalization; SHA-256 does not. Leaning BLAKE2b for that reason alone.
- Threshold or single-key issuance? FROST over secp256k1 works. A 2-of-2 raises the bar for forgery and the bar for minting. Decide before the ceremony, not after.
- Does
asset_descstaysha256(image), or become a structured description hashed to 32 bytes? ZIP 227 allows arbitrary bytes. Keeping the image hash preserves v0.3's content-addressing inside the new scheme.
Provenance
Derivation read from zcash-shielded-assets/orchard @
4cf06bc19e52d1e8e43438cdbdd703ac2565bff6 — src/note/asset_base.rs,
src/zsa/issuance/mod.rs, src/zsa/issuance/auth.rs. ZIP status from zips.z.cash/zip-0227.
Competitive context: a separate analysis.