Disclosure Key Format
An orbdisc: key carries the preimage of one note commitment. Anyone holding the key can check
that it opens the commitment and that the chain holds that commitment, and gains no ability to
spend the note. For what a note and its commitment are, see Notes. For
sharing a key from the app, see Note Disclosure.
Wire format
orbdisc:<base64url(JSON)>
The JSON payload has six fields:
| Field | Holds | Range |
|---|---|---|
v | Format version, the number 1 | exactly 1 |
c | Commitment | [0, r) |
val | Note value, in the asset's smallest unit | u128 |
aid | Asset id | u32 |
opk | Owner key: the Baby JubJub Ax coordinate | [0, r) |
bld | Blinding | [0, r) |
Here r is the BN254 scalar field order. Each numeric field is a JSON string holding
0x followed by the number's big-endian hex, unpadded: zero is 0x0, and leading zeros are
dropped. A field may have at most 64 hex digits. A whole key may be at most 1024 characters. A real
key is about 370–400.
{
"v": 1,
"c": "0x2d0aba56a9fb579a007a1980a6164f248682546aa460ea46956be66fabeb8ea7",
"val": "0x152d02c7e14af6800000",
"aid": "0x0",
"opk": "0x1d4a09a1ab9ffff52fbef32c393617d70c03c92835bc6331fbb4a45e381d9735",
"bld": "0x2dcbb6f4d229a19c91e1a4ee057bafa3a96573fa36559644864c811b13ae8d62"
}
The key holds no spending key, no nullifier and no viewing key.
Byte order: key versus chain
The same commitment appears in two encodings:
| Where | Encoding |
|---|---|
Disclosure key (c) | The field element as a number, in big-endian hex, unpadded |
On-chain (storage, events, privacy_* RPCs, explorers) | The field element's canonical 32-byte little-endian encoding, shown as hex in byte order |
For the example above:
Disclosure key c: 0x2d0aba56a9fb579a007a1980a6164f248682546aa460ea46956be66fabeb8ea7
On-chain bytes: 0xa78eebab6fe66b9546ea60a46a548286244f16a680197a009a57fba956ba0a2d
Because the example commitment needs all 32 bytes, one string is the byte-reversal of the other.
When a value is shorter, the on-chain form also carries the zero padding that the key leaves out.
commitmentHexOf(commitment) in @orbinum/protocol converts a commitment to its on-chain form.
How verification works
- Decode and range-check. Poseidon reduces its inputs mod
r, so a hash match alone is not enough. A key claimingval + rwould hash likeval, and the holder of a 1-planck note could appear to disclose about 2²⁵⁴. Every field is therefore checked against its range before hashing. - Recompute the commitment.
Poseidon4(val, aid, opk, bld)must equalc. A key with any edited field fails here. This step is local and needs no network. - Check the chain. Convert
cto its little-endian form and ask a node for it, for example withprivacy_getMerkleProofByCommitment. That call returns the leaf index, or an error if the pool does not hold the commitment. Asking a node tells that node which commitment you are checking.
A valid key proves that its creator knew the note's preimage. That does not prove ownership: the sender of a note knows the same preimage. It also says nothing about whether the note has been spent.
opk is a per-note stealth keyEach received note carries its own owner key, derived for that note. As a result, two keys
disclosing two notes of the same person show unrelated opk values. A disclosure proves this
note's contents, not that several notes share an owner. See
Stealth addresses.
Generating and verifying with @orbinum/protocol
createNoteDisclosureKey builds a key from a note's preimage, and decodeNoteDisclosureKey runs
steps 1 and 2 above, returning the verified fields or null. A complete verifier, including the
on-chain lookup and how to handle each result, is in
Verifying Disclosure Keys.
Related
- Note Disclosure (app): sharing and verifying a key in the app
- Verifying Disclosure Keys: checking a key with
@orbinum/protocol - Notes: what the commitment's four fields are
- Note Discovery: stealth owner keys
- Merkle Forest: where commitments live and the
privacy_*RPCs