PoC
What will all this look like in the first version?
Description of initial PoC version that will be deployed to testnet only.
Id and secrets
In order to deposit any funds to the Shielder some on-chain transaction must be sent. Thus we assume that a user already has some native account with a corresponding private key (essentially, some 32 random bytes). For the sake of simplicity, we use this key as the user ID in the Shielder system. Note that there are no checks performed relating to the corresponding on-chain account - essentially, you can use arbitrary 32 bytes.
All other (operational) secrets, namely nullifiers and trapdoors, are generated as the Keccak256 hash of id || nonce || label
, where:
id
is the private key, as stated abovenonce
is the counter of how many Shielder operations have been already done usingid
label
is a byte string, eitherb"nullifier"
orb"trapdoor"
Account
In the PoC version, for the sake of simplicity, we only allow for shielding native AZERO tokens. Therefore, the only information (apart from operational secrets) kept in a note is the current shielded AZERO balance.
Recovery and account tracking
If you lose your shielded state (e.g., due to losing your device or accidentally deleting the state file), you can still recover your funds as long as you have your ID.
All Shielder transactions share a common property - every such action invalidates some nullifier. For deposits and withdrawals we just publish the hash of the nullifier of our current note. For the new-account action, we publish the hash of our ID, which can be seen as a special pre-nullifier. Shielder contract maintains a registry of all used (hashes of) nullifiers to prevent double spending or creating multiple accounts for the same ID. This is implemented as a mapping from a used nullifier hash to a block number when it was published.
Thanks to this design we can easily derive a recovery procedure as follows. Starting with a nonce 0, we repeatedly ask the contract whether a corresponding nullifier has already been spent, and if so, we can fetch the proper block, find our transaction and update the local state.
This also helps with account tracking. Since a user can interact with the Shielder from many different devices, we must always ensure, that the local state is up-to-date. Therefore, anytime our app is turned on, we check if the nullifier for the current note has been already spent. If so, then we just have to fetch latest transactions and update the state.
Relations
Preliminaries / Recap
Scalar
is a type for some fixed finite field elements.
hash
function is a hashing function from a sequence of Scalar
elements into a single Scalar
value.
AMOUNT_BOUND
is some limit on the amounts handled by the Shielder, so that arithmetic operations do not overflow in the field. E.g. 2^128
MERKLE_HEIGHT
is the height of the Merkle tree kept in the contract.
MERKLE_ARITY
is the arity of the Merkle tree kept in the contract.
New Account
Deposit
Withdraw
Last updated