Skip to main content

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:

FieldHoldsRange
vFormat version, the number 1exactly 1
cCommitment[0, r)
valNote value, in the asset's smallest unitu128
aidAsset idu32
opkOwner key: the Baby JubJub Ax coordinate[0, r)
bldBlinding[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:

WhereEncoding
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​

  1. Decode and range-check. Poseidon reduces its inputs mod r, so a hash match alone is not enough. A key claiming val + r would hash like val, and the holder of a 1-planck note could appear to disclose about 2²⁵⁴. Every field is therefore checked against its range before hashing.
  2. Recompute the commitment. Poseidon4(val, aid, opk, bld) must equal c. A key with any edited field fails here. This step is local and needs no network.
  3. Check the chain. Convert c to its little-endian form and ask a node for it, for example with privacy_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.
What a valid key does not prove

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 key

Each 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.