Claim circuit
This circuit enables converting a claim note into two value notes, according to the defi interaction result.
Diagrams
Before the claim circuit
A defi interaction is a multi-step process which ends with the claim circuit being verified on-chain. There are more complete explanations of the whole process for many individual dApps on hackmd under the 'Aztec Connect' tag. Here's a very brief summary of the defi interaction process:
- A user wishes to interact with an Ethereum L1 dApp privately. They can use Aztec Connect to hide their identity from observers. The values they send will still be visible (but not traceable back to them). Let's use Uniswap as an example.
- The user wishes to convert 1 ETH to DAI tokens.
- They submit a 'defi deposit' of 1 ETH.
- A join-split proof is generated in 'defi deposit' mode, which spends the 1 ETH and creates a partial claim note (see the diagrams or the join-split markdown file).
- The rollup provider bundles (sums) the user's request to deposit 1 ETH into uniswap with the requests of any other users who wanted to deposit ETH to uniswap. User costs are amortised this way.
- The rollup provider is able to assign a
bridge_call_data
to each 'bundle', and with knowledge of thisbridge_call_data
and thetotal_input_value
being deposited, the rollup provider can 'complete' each user's partial claim note. I.e. the rollup provider creates a completed 'claim note' for each user. This claim note can be used later in the process to withdraw DAI via the claim circuit. - This bundled (summed) deposit of X ETH is sent to a 'Defi Bridge Contract' - a contract specifically designed to perform swaps between Aztec Connect users and Uniswap.
- The Defi Bridge Contract sends the
total_input_value = X
ETH to Uniswap (along with some parameters which we won't go into here), and receives back Y DAI. - The rollup contract emits an event which says "X ETH was swapped for Y DAI, and here is a 'defi interaction nonce' which represents this interaction".
- The rollup provider listens for this event to learn whether their interaction was successful, and to learn the new data:
total_output_value_a = Y
,defi_interaction_nonce = defi_interaction_nonce
. - The rollup provider is now in a position to submit a claim (using the claim circuit) on behalf of each user who partook in the defi interaction.
- Take note of this. Submission of a claim needn't be done by the user; no private key is required. The rollup provider is incentivised to generate a claim proof by being offered a fee via the earlier join-split proof.
Now we can get into the details of the claim circuit.
Claim circuit
At a high level, the claim circuit does the following:
- Spends a user's claim note;
- Refers to a particular defi interaction note (which contains uniquely-identifying details of a particular defi interaction);
- Outputs up-to two output 'value notes' whose values are proportional to the amount originally defi-deposited by this user.
output_note_1.value = ( user_input_amount / total_input_amount ) * total_output_amount_a
output_note_2.value = ( user_input_amount / total_input_amount ) * total_output_amount_b
(In our earlier example, ouput_note_1.value = ( 1 / X ) * Y
DAI).
Details
Inputs
Recall that all inner circuits must have the same number of public inputs as they will be used homogenously by the rollup circuit. Hence, some of the inputs to a claim circuit are unused and so set to 0.
Public Inputs
proof_id = ProofIds::DEFI_CLAIM
output_note_commitment_1
output_note_commitment_2
nullifier1
nullifier2
public_value = 0
public_owner = 0
asset_id = 0
data_root
claim_note.fee
claim_note_data.bridge_call_data_local.input_asset_id
claim_note.bridge_call_data
defi_deposit_value = 0
defi_root
backward_link = 0
allow_chain = 0
Private Inputs
claim_note_index
claim_note_path
claim_note: {
deposit_value,
bridge_call_data, // actually a public input
defi_interaction_nonce,
fee, // actually a public input
value_note_partial_commitment,
input_nullifier,
}defi_interaction_note_path
defi_interaction_note: {
bridge_call_data,
defi_interaction_nonce,
total_input_value,
total_output_value_a,
total_output_value_b,
defi_interaction_result,
commitment,
}output_value_a
output_value_b
Circuit Logic (Pseudocode)
Note: for Pedersen commitments, different generators are used for different types of commitment.
Computed vars:
Extract data from the
claim_note.bridge_call_data
:bridge_call_data_local = {
bridge_address_id, // represents a defi bridge contract address
input_asset_id_a,
input_asset_id_b, // if virtual, is the defi_interaction nonce from when a loan/LP position was opened
output_asset_id_a,
output_asset_id_b,
// during some earlier defi interaction by the user
bit_config,
aux_data,
}
The same data is also currently extracted from the
defi_interaction_note.bridge_call_data
. This is redundant, but we'll only need to remove these extra constraints if we ever approach the next power of 2.Extract config data from
bit_config
:bit_config = {
second_input_in_use,
second_output_in_use,
}
claim_note.commitment = pedersen(
pedersen(deposit_value, bridge_call_data, value_note_partial_commitment, input_nullifier),
defi_interaction_nonce,
fee,
)defi_interaction_note.commitment = pedersen(
bridge_call_data,
total_input_value,
total_output_value_a,
total_output_value_b,
defi_interaction_nonce,
defi_interaction_result,
)output_value_1 = defi_interaction_result ? output_value_a : claim_note.deposit_value
(give a refund if the interaction failed).output_asset_id_1 = defi_interaction_result ? output_asset_id_a : input_asset_id
output_value_2 = second_output_virtual ? output_value_a : output_value_b
- If the second output is virtual, its value must equal that of the first output.
output_asset_id_2 = second_output_virtual ? concat(1, defi_interaction_nonce) : output_asset_id_b
- If virtual, attach a record of this 'opening defi interaction nonce' to the note, via the asset_id field.
Checks:
- Many values are range-checked. See constants.hpp and constants.hpp for the variables whose bit-lengths are constrained.
- Check
bit_config
vars: - Extract
second_input_in_use
andsecond_output_in_use
fromclaim_note_data.bridge_call_data_local.config
// The below six constraints are exercised in bridge_call_data.hpp, see comments there for elaboration !(input_asset_id_b.is_zero) must_imply config.second_input_in_use
!(input_asset_id_b.is_zero) must_imply config.second_output_in_use
config.second_input_in_use must_imply input_asset_id_a != input_asset_id_b
config.second_output_in_use && both_outputs_real must_imply output_asset_id_a != output_asset_id_b
first_output_virtual must_imply output_asset_id_a == virtual_asset_id_placeholder
second_output_virtual must_imply output_asset_id_b == virtual_asset_id_placeholder
require(claim_note.deposit_value != 0)
require(deposit_value <= total_input_value)
require(output_value_a <= total_output_value_a)
require(output_value_b <= total_output_value_b)
require(claim_note.bridge_call_data == defi_interaction_note.bridge_call_data)
require(claim_note.defi_interaction_nonce == defi_interaction_note.defi_interaction_nonce)
- Check claim note exists in the data tree using
data_root, claim_note_path, claim_note_index, claim_note.commitment
. - Check defi interaction note exists in the data tree using
defi_root, defi_interaction_note_path, defi_interaction_nonce
.- Note: the leaf index of a defi interaction note is its
defi_interaction_nonce
. Thedefi_interaction_nonce
is derived in the rollup circuit at the time the defi deposit (join split) is processed.
- Note: the leaf index of a defi interaction note is its
Ratio Checks (very complex code):
- Ensure
output_value_a == (deposit / total_input_value) * total_output_value_a
, unlessoutput_value_a == 0 && total_output_value_a == 0
(i.e. unless no value was returned by the bridge contract for output_a). - Ensure
output_value_b == (deposit / total_input_value) * total_output_value_b
, unlessoutput_value_b == 0 && total_output_value_b == 0
(i.e. unless no value was returned by the bridge contract for output_b). - (Also prevent zero denominators
total_input_value
,total_output_value_a
, andtotal_output_value_b
).
Computed public inputs:
nullifier_1 = pedersen(claim_note.commitment)
nullifier_2 = pedersen(defi_interaction_note.commitment, claim_note.commitment)
output_note_commitment1 = pedersen(value_note_partial_commitment, output_value_1, output_asset_id_1, nullifier_1)
output_note_commitment2 = (second_output_virtual ^ second_output_real) ? pedersen(value_note_partial_commitment, output_value_2, output_asset_id_2, nullifier_2) : 0