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 above

  • nonce is the counter of how many Shielder operations have been already done using id

  • label is a byte string, either b"nullifier" or b"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.

Last updated