Public NOIR and shielding
NOIR outside the vault is an ordinary ERC-20, how shielding and unshielding move it in and out of notes, and what stays visible on-chain.
NOIR (0xDa2E4659dA030AEd0285F9Ed3496ec07058b2319) is a plain ERC-20 created by Pons: OpenZeppelin ERC20 and ERC20Burnable, no owner and no
transfer restrictions. Any address can hold, send and receive it, and wallets, exchanges, routers and aggregators
handle it like any other token. It has no privacy of its own. Privacy starts when NOIR is shielded into the vault
and ends when it is unshielded back out.
Public NOIR
NOIR held by an address outside the vault is public NOIR. Its balance and every transfer are visible, as for any ERC-20. Holding public NOIR earns nothing from the vault: only notes hold shares (see The vault).
Registering privacy keys does not change public NOIR: a registered address receives NOIR like any other address, and
its balanceOf is its ordinary ERC-20 balance. NOIR becomes private only when its holder shields it.
| Where public NOIR comes from | Example |
|---|---|
A purchase anywhere other than PrivateRouter.buy | The Pons site, the curve or pool directly, an aggregator |
| A transfer from another address | An exchange withdrawal, a payment |
| An unshield | transact with an exit to an address |
| A sale that could not be completed | The vault sends the NOIR to the sale's ETH recipient |
Shielding
function shield(uint256 stub, uint256 amount, bytes calldata ciphertext) external returns (uint32 index);The holder's app creates a note with random rho and r, computes stub = H(opk, rho, r) and encrypts
(rho, r, 0) to the holder's viewing key.
The holder approves the vault for amount on NOIR (approve(vault, amount)).
The holder calls vault.shield(stub, amount, ciphertext). The vault pulls amount with transferFrom, mints
convertToShares(amount) shares, inserts cm = H(1, stub, shares) into the tree and emits
Shielded(from, index, amount, shares) and NoteAdded(index, cm, ciphertext).
The app's shield sends the approval first, only if the current allowance is short, then the shield, both from the
holder's wallet. PrivateRouter.buy
does the same in one transaction for ETH: it buys NOIR, approves the vault and shields for the buyer (see
The market).
Unshielding
An unshield is a transact whose exit goes to an address: exitShares leave the private side and
convertToAssets(exitShares - gasFee) NOIR, rounded as described in The vault, are
transferred to ext.recipient. The vault emits Exited(nullifier, recipient, amount, shares). The recipient can be
any address, including one that never used noircash. From there it is public NOIR again.
A private sale is the same exit with the router as recipient: the router sells the NOIR and sends ETH instead. See Gas and ERC-4337.
What is visible on-chain
| Information | Public? |
|---|---|
| NOIR's total supply, public balances and public transfers | Yes |
| Trades on the curve and on the pool: amounts, price, time, trader | Yes |
| B, S, the gas reserve, the price average | Yes |
| Registered addresses and their public keys | Yes |
| A shield's caller, amount, shares and leaf | Yes (Shielded) |
| A private buy's buyer, ETH amount and NOIR amount | Yes (router Bought, and the curve or pool trade) |
| An exit's recipient and amount; a sale's ETH recipient | Yes (Exited, SaleSettled, router Sold) |
| Which notes a spend consumed | No |
| Sender, recipient and amount of a private send | No |
| A person's private balance | No |
| Whether a shielded note has been spent | No |
Entries into and exits from the private side are public; what happens in between is not. Timing and amounts at entry and exit can still link them when few people use the vault. See the threat model.
Architecture
The contracts and off-chain parts of noircash, who runs each one, and how a purchase, a private send and a private sale move through them.
Notes, commitments and nullifiers
What a note contains, how its commitment and nullifier are computed, how notes are encrypted, and how shielded notes get their shares.