Null Bond
Null Bond lets you hold a claim about an asset privately and prove it to exactly one counterparty at a time. Everything runs in the browser. The chain stores 32 bytes per bond, and only if you choose to anchor it.
1. Why
To prove you hold something onchain today, you usually connect a wallet. That hands the site your whole history: every balance, every transfer, every counterparty. Most of the time the other side needs to know one thing, not everything.
Null Bond separates the two. You seal one asset claim into a bond, keep the receipt to yourself, and show that receipt to the person who needs to check it. They learn that bond and nothing next to it.
2. Terms
| Term | Meaning |
|---|---|
| Asset | A description of what the bond is about: type, network, contract or identifier, amount or token ID, and an optional memo. |
| Commitment | A 32-byte SHA-256 hash that binds the asset to a random salt and key. It reveals none of them. |
| Receipt | The asset, salt, key and commitment together. Whoever holds the receipt can open and prove the bond. |
| Anchor | An optional Robinhood Chain transaction that carries the commitment, giving it a public timestamp. |
| Package | A receipt encrypted under a passphrase, used to hand a bond to someone else. |
3. Sealing
The browser draws a 16-byte salt and a 32-byte key from crypto.getRandomValues, then computes:
A = canonical JSON of the asset
commitment = SHA-256( "nullbond/v1" ‖ u32be(len(A)) ‖ A ‖ salt[16] ‖ key[32] )
The canonical JSON always lists the same five keys in the same order, with string values: type, network, id, amount, memo. The length prefix keeps the asset from bleeding into the salt, and the domain tag keeps a Null Bond hash from being confused with any other SHA-256 value.
Because the salt and key are random, two bonds over the same asset have unrelated commitments, and nobody can guess the asset from the hash. The label you give a bond in the console is never part of the hash and never leaves your device.
4. Receipts
A receipt is a small JSON object:
{
"nullbond": 1,
"asset": { "type", "network", "id", "amount", "memo" },
"salt": "0x…16 bytes",
"key": "0x…32 bytes",
"commitment": "0x…32 bytes",
"created": "ISO-8601",
"anchor": { "chainId": 4663, "tx": "0x…" } | null,
"prev": "0x…previous commitment" | null
}
To paste it in a message, the console encodes it as text: nullbond:receipt:1: followed by the JSON in base64url. The console accepts either form.
5. Anchoring
Anchoring is optional. It sends a zero-value transaction on Robinhood Chain (chain ID 4663) from your wallet to your own address, with this data:
data = 0x4e554c4c ‖ commitment // "NULL" + 32 bytes
The transaction gives the commitment a block number and timestamp that anyone can read. It costs the gas of a plain transfer plus 36 bytes of calldata. Nothing else from your vault is sent.
6. Proving and verifying
To prove a bond, send its receipt to one counterparty, or a proof link that carries the receipt in the URL fragment (/app#verify=…). Browsers never send the fragment to the server.
The verifier's browser:
- recomputes the commitment from the asset, salt and key in the receipt;
- compares it with the commitment in the receipt, and with an expected commitment if the verifier already has one;
- if the receipt names an anchor, reads that transaction on Robinhood Chain and checks that its data is exactly
0x4e554c4c ‖ commitmentand that it succeeded.
The answer is one bit: true or false. The verifier sees the asset in that receipt, and nothing else you own.
7. Handing over
A hand-over moves a bond from one holder to the next without a server:
- The sender encrypts the receipt into a package. The key is derived with PBKDF2-SHA256 (600,000 rounds, 16-byte salt) from a passphrase; the receipt is sealed with AES-256-GCM (12-byte IV) and the associated data
nullbond/package/1. - The sender shares the package and the passphrase over two different channels.
- The recipient decrypts the package, checks that the bond inside verifies, and reseals the same asset under a fresh salt and key. The new receipt records the previous commitment in
prev.
The sender's receipt still verifies after a hand-over until the sender discards it. Retiring the old commitment onchain is the job of the bond contract (section 9).
8. Storage and backups
The console keeps your receipts in this browser's local storage. There is no account and no server copy. You can export the whole vault as one backup file, encrypted the same way as a package (associated data nullbond/vault/1), and import it on another device.
If you lose both the browser data and the backup, the bonds cannot be opened again by anyone.
9. $NULL and roadmap
$NULL is the project token on Robinhood Chain, launched through Pons. Two contracts are planned around it:
- Bond contract. A registry that mints a bond NFT for a commitment, only to wallets that hold $NULL, and lets a hand-over retire the old commitment and register the new one in a single transaction.
- Fee hook. A hook that takes a fee on bond actions and burns 80% of it.
10. Security model and limits
- A bond is a claim, not custody. Sealing proves that someone wrote down this asset description at a given time. It does not prove the asset is still held, or that it was ever held, unless the counterparty checks that separately.
- Receipts are bearer secrets. Anyone with a copy of a receipt can prove its bond. Send receipts to one person at a time and treat proof links like passwords.
- Proving reveals the bond. The verifier sees the full asset description and memo of the receipt you send them.
- Passphrases can be guessed offline. A package or backup is only as strong as its passphrase. Use a long one; PBKDF2 slows guessing but does not stop a weak passphrase.
- Your browser is the trust boundary. A malicious extension or a shared computer can read local storage. Use a clean browser profile for bonds that matter.
- Anchors are public. The anchoring address and time are visible to anyone who has the transaction hash.
11. Test vector
asset = {"type":"ERC-20 token","network":"Robinhood Chain",
"id":"0x0000000000000000000000000000000000000001",
"amount":"1000","memo":"example"} // 134 bytes
salt = 0x00112233445566778899aabbccddeeff
key = 0x1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f
commitment = 0x4dc0dccf29190411cac65b3b8040755b6db5ca0898a3afe799a057c63a771043
anchor data = 0x4e554c4c4dc0dccf29190411cac65b3b8040755b6db5ca0898a3afe799a057c63a771043
The full implementation is a single file, nb-core.js, readable in any browser.