# PoC

Description of initial PoC version that will be deployed to testnet only.&#x20;

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

## 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

```
relation PoC::NewAccount

inputs:
    - h_note:          Scalar
    - h_id:            Scalar
    - initial_deposit: Scalar

witnesses:
    - id:        Scalar
    - nullifier: Scalar
    - trapdoor:  Scalar

assumptions:
    - initial_deposit <= AMOUNT_BOUND

constraints:
    // id is correctly exposed as a public input
    - h_id = hash(id)
    // note is well-formed
    - h_note = hash(id, nullifier, trapdoor, hash(initial_deposit))
    
suggestions:
    - nullifier = hash(id, 0, 0)
    - trapdoor  = hash(id, 0, 1)
```

<figure><img src="/files/xDmwazwI9c3yEciujtzL" alt=""><figcaption></figcaption></figure>

### Deposit

```
relation PoC::Deposit

inputs:
    - merkle_root:     Scalar
    - h_nullifier_old: Scalar
    - h_note_new:      Scalar
    - value:           Scalar

witnesses:
    - id:                  Scalar
    - nullifier_old:       Scalar
    - trapdoor_old:        Scalar
    - account_balance_old: Scalar
    - merkle_path:         [[Scalar; MERKLE_ARITY]; MERKLE_HEIGHT]
    - nullifier_new:       Scalar
    - trapdoor_new:        Scalar

assumptions:
    - value <= AMOUNT_BOUND
    - account_balance_old <= AMOUNT_BOUND

constraints:
    // new value satisfies the bound
    - account_balance_old + value <= AMOUNT_BOUND
    // old nullifier is correctly exposed as a public input
    - h_nullifier_old = hash(nullifier_old)
    // new note is well-formed
    - h_note_new = hash(id, nullifier_new, trapdoor_new, hash(account_balance_old + value))
    // membership proof is valid
    - merkle_path is a valid path to merkle_root
    // membership proof relates to the old note
    - hash(id, nullifier_old, trapdoor_old, account_balance_old) is belongs to the first layer of merkle_path
    

suggestions:
    - nullifier_old = hash(id, n, 0)
    - nullifier_new = hash(id, n+1, 0)
    - trapdoor_old = hash(id, n, 1)
    - trapdoor_new = hash(id, n+1, 1)
```

<figure><img src="/files/HrrTRCQyojZafcmWOiGu" alt=""><figcaption></figcaption></figure>

### Withdraw

```
relation PoC::Withdraw

inputs:
    - merkle_root:        Scalar
    - h_nullifier_old:    Scalar
    - h_note_new:         Scalar
    - value:              Scalar
    - relayer:            Scalar
    - fee:                Scalar
    - withdrawal_address: Scalar

witnesses:
    - id:                  Scalar
    - nullifier_old:       Scalar
    - trapdoor_old:        Scalar
    - account_balance_old: Scalar
    - merkle_path:         [[Scalar; MERKLE_ARITY]; MERKLE_HEIGHT]
    - nullifier_new:       Scalar
    - trapdoor_new:        Scalar

assumptions:
    - value <= AMOUNT_BOUND
    - fee <= AMOUNT_BOUND
    - account_balance_old <= AMOUNT_BOUND

constraints:
    // new value satisfies the bound
    - account_balance_old >= value + fee
    // old nullifier is correctly exposed as a public input
    - h_nullifier_old = hash(nullifier_old)
    // new note is well-formed
    - h_note_new = hash(id, nullifier_new, trapdoor_new, hash(account_balance_old - value - fee))
    // membership proof is valid
    - merkle_path is a valid path to merkle_root
    // membership proof relates to the old note
    - hash(id, nullifier_old, trapdoor_old, account_balance_old) is belongs to the first layer of merkle_path
    

suggestions:
    - nullifier_old = hash(id, n, 0)
    - nullifier_new = hash(id, n+1, 0)
    - trapdoor_old = hash(id, n, 1)
    - trapdoor_new = hash(id, n+1, 1)
```

<figure><img src="/files/YpZ1XAqoYRzSyvfht0zY" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.alephzero.org/aleph-zero/protocol-details/shielder/poc.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
