Skip to content

Commit f82959a

Browse files
authored
Merge pull request #54 from AztecProtocol/master
Preparation for release v2.1.96
2 parents f3bff85 + cfccfbc commit f82959a

File tree

21 files changed

+1613
-212
lines changed

21 files changed

+1613
-212
lines changed

aztec-connect-cpp/src/rollup/constants.hpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ constexpr bool is_circuit_change_expected = 0;
3838
constraints. They need to be changed when there is a circuit change. */
3939
constexpr uint32_t ACCOUNT = 23967;
4040
constexpr uint32_t JOIN_SPLIT = 64047;
41-
constexpr uint32_t CLAIM = 22684;
41+
constexpr uint32_t CLAIM = 23050;
4242
constexpr uint32_t ROLLUP = 1173221;
4343
constexpr uint32_t ROOT_ROLLUP = 5481327;
4444
constexpr uint32_t ROOT_VERIFIER = 7435892;
@@ -61,12 +61,12 @@ namespace circuit_vk_hash {
6161
to comply with the from_buffer<>() method. */
6262
constexpr auto ACCOUNT = uint256_t(0xcd6d70c733eaf823, 0x6505d3402817ad3d, 0xbf9e2b6a262589cf, 0xafcc546b55cc45e3);
6363
constexpr auto JOIN_SPLIT = uint256_t(0xb23c7772f47bc823, 0x5493625d4f08603c, 0x21ac50a5929576f9, 0xb7b3113c131460e5);
64-
constexpr auto CLAIM = uint256_t(0x878301ebba40ab60, 0x931466762c62d661, 0x40aad71ec3496905, 0x9f47aaa109759d0a);
65-
constexpr auto ROLLUP = uint256_t(0x8712bcbeb11180c5, 0x598412e4f700c484, 0xfe50ad453c8e4288, 0xa7340fac5feb663f);
66-
constexpr auto ROOT_ROLLUP = uint256_t(0xcf2fee21f089b32f, 0x90c6187354cf70d4, 0x3a5a90b8c86d8c64, 0xd55af088ddc86db7);
64+
constexpr auto CLAIM = uint256_t(0xa753ce523719749e, 0x80216aff7f8bc9ce, 0xa9b0f69bbd24ac33, 0xae17c5fb7d488138);
65+
constexpr auto ROLLUP = uint256_t(0x5f2f6590e5553f19, 0x62c287e01b897621, 0xf32d03437085e2d, 0x567b0be24dc99966);
66+
constexpr auto ROOT_ROLLUP = uint256_t(0x64e5e03cf9534ed6, 0x7fdc871935b9e4fe, 0xd2b81e990cc15f3d, 0x47f00f76d92e5e4d);
6767
;
6868
constexpr auto ROOT_VERIFIER =
69-
uint256_t(0xe91df73df393fb5f, 0x99a9fa13abfbb206, 0x2ffe8c891cbde8c2, 0xdcb051e8ca06df5e);
69+
uint256_t(0xb4349747ae6ea507, 0xfaafa0f2e384c984, 0x9325870bcc594daf, 0x50163a2572c67363);
7070
}; // namespace circuit_vk_hash
7171

7272
namespace ProofIds {

aztec-connect-cpp/src/rollup/proofs/claim/claim.test.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@
33
#include "index.hpp"
44
#include "../inner_proof_data/inner_proof_data.hpp"
55
#include "../notes/native/index.hpp"
6+
#include "../notes/circuit/index.hpp"
7+
#include "./claim_circuit.hpp"
68
#include <common/test.hpp>
9+
#include <cstddef>
10+
#include <iterator>
711
#include <stdlib/merkle_tree/index.hpp>
812
#include <numeric/random/engine.hpp>
13+
#include <string>
14+
#include <utility>
915

16+
#define ENABLE_MALICIOUS_RATIO_CHECK_FOR_TESTS
17+
#include "./malicious_claim_circuit.hpp"
18+
#undef ENABLE_MALICIOUS_RATIO_CHECK_FOR_TESTS
1019
namespace rollup {
1120
namespace proofs {
1221
namespace claim {
@@ -1579,6 +1588,72 @@ TEST_F(claim_tests, test_real_virtual)
15791588
EXPECT_EQ(tx.get_output_notes()[0], result.public_inputs[InnerProofFields::NOTE_COMMITMENT1]);
15801589
EXPECT_EQ(tx.get_output_notes()[1], result.public_inputs[InnerProofFields::NOTE_COMMITMENT2]);
15811590
}
1591+
/**
1592+
* @brief Check that malicious prover can't submit an erroneous claim
1593+
*
1594+
*/
1595+
TEST_F(claim_tests, test_claim_fails_if_prover_is_malicious)
1596+
{
1597+
// Generate notes with malicious values
1598+
const claim_note note1 = { .deposit_value = 1,
1599+
.bridge_call_data = 0,
1600+
.defi_interaction_nonce = 0,
1601+
.fee = 0,
1602+
.value_note_partial_commitment =
1603+
create_partial_commitment(user.note_secret, user.owner.public_key, 0, 0),
1604+
.input_nullifier = fr::random_element(&engine) };
1605+
1606+
const defi_interaction::note note2 = { .bridge_call_data = 0,
1607+
.interaction_nonce = 0,
1608+
.total_input_value = 100,
1609+
.total_output_value_a = 100,
1610+
.total_output_value_b = 100,
1611+
.interaction_result = 1 };
1612+
append_note(note1, data_tree);
1613+
append_note(note2, defi_tree);
1614+
claim_tx tx = create_claim_tx(note1, 0, 0, note2);
1615+
tx.output_value_a = 100;
1616+
tx.output_value_b = 100;
1617+
1618+
// Create one regular composer
1619+
Composer composer = Composer(cd.proving_key, cd.verification_key, cd.num_gates);
1620+
// And one donor
1621+
Composer donor = Composer(cd.proving_key, cd.verification_key, cd.num_gates);
1622+
1623+
// Construct the circuit with malicious witness in the donor
1624+
malicious_claim_circuit(donor, tx);
1625+
1626+
// Construct the regular claim circuit in the regular composer
1627+
claim_circuit(composer, tx);
1628+
1629+
// The witness in the regular will not satisfy the contriaints
1630+
info("Check circuit before transplant: ", composer.check_circuit());
1631+
ASSERT_EQ(composer.variables.size(), donor.variables.size());
1632+
// Copy the values of variables into the regular circuit
1633+
for (size_t i = 0; i < composer.variables.size(); i++) {
1634+
composer.variables[i] = donor.variables[i];
1635+
}
1636+
// If the circuit is undercontrained, the will both pass now
1637+
info("Check donor circuit: ", donor.check_circuit());
1638+
info("Check circuit after transplant: ", composer.check_circuit());
1639+
Timer proof_timer;
1640+
info(": Creating proof...");
1641+
verify_result<Composer> result;
1642+
auto prover = composer.create_unrolled_prover();
1643+
auto proof = prover.construct_proof();
1644+
result.proof_data = proof.proof_data;
1645+
info(": Proof created in ", proof_timer.toString(), "s");
1646+
auto verifier = composer.create_unrolled_verifier();
1647+
result.verified = verifier.verify_proof({ result.proof_data });
1648+
if (!result.verified) {
1649+
info(": Proof validation failed.");
1650+
} else {
1651+
info(": Verified successfully.");
1652+
}
1653+
result.verification_key = composer.circuit_verification_key;
1654+
EXPECT_FALSE(result.verified);
1655+
}
1656+
15821657
} // namespace claim
15831658
} // namespace proofs
15841659
} // namespace rollup
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#pragma once
2+
#include "claim_tx.hpp"
3+
#include <stdlib/types/turbo.hpp>
4+
#include "malicious_ratio_check.hpp"
5+
#include "../notes/circuit/index.hpp"
6+
#include <stdlib/merkle_tree/membership.hpp>
7+
8+
#ifndef ENABLE_MALICIOUS_RATIO_CHECK_FOR_TESTS
9+
static_assert(false, "THIS FILE CAN ONLY BE USED INT TESTS");
10+
#endif
11+
namespace rollup {
12+
namespace proofs {
13+
namespace claim {
14+
15+
using namespace plonk::stdlib::types::turbo;
16+
using namespace plonk::stdlib::merkle_tree;
17+
using namespace notes;
18+
/**
19+
* @brief This function creates the same circuit as the regular claim circuit, but feels it with malicious witness
20+
*
21+
* @param composer
22+
* @param tx
23+
*/
24+
void malicious_claim_circuit(Composer& composer, claim_tx const& tx)
25+
{
26+
// Create witnesses.
27+
const auto proof_id = field_ct(witness_ct(&composer, ProofIds::DEFI_CLAIM));
28+
proof_id.assert_equal(ProofIds::DEFI_CLAIM);
29+
const auto data_root = field_ct(witness_ct(&composer, tx.data_root));
30+
const auto defi_root = field_ct(witness_ct(&composer, tx.defi_root));
31+
const auto claim_note_index =
32+
suint_ct(witness_ct(&composer, tx.claim_note_index), DATA_TREE_DEPTH, "claim_note_index");
33+
const auto claim_note_path = create_witness_hash_path(composer, tx.claim_note_path);
34+
const auto defi_note_index =
35+
suint_ct(witness_ct(&composer, tx.defi_note_index), DEFI_TREE_DEPTH, "defi_note_index");
36+
37+
/**
38+
* Conversion to `claim_note_witness_data` contains:
39+
* - range constraints on the claim note's attributes
40+
* - expansion of bridge_call_data
41+
* - expansion of the bridge_call_data's bit_config
42+
* - sense checks on the bit_config's values
43+
* (some bits can contradict each other)
44+
*/
45+
const auto claim_note_data = circuit::claim::claim_note_witness_data(composer, tx.claim_note);
46+
47+
const auto claim_note = circuit::claim::claim_note(claim_note_data);
48+
const auto defi_interaction_note_path = create_witness_hash_path(composer, tx.defi_interaction_note_path);
49+
50+
/**
51+
* Implicit conversion to `defi_interaction::witness_data` includes:
52+
* - range constraints on the defi_interaction_note's attributes
53+
* - expansion of bridge_call_data
54+
* - expansion of the bridge_call_data's bit_config
55+
* - sense checks on the bit_config's values
56+
* (some bits can contradict each other)
57+
*/
58+
const auto defi_interaction_note = circuit::defi_interaction::note({ composer, tx.defi_interaction_note });
59+
const auto output_value_a =
60+
suint_ct(witness_ct(&composer, tx.output_value_a), NOTE_VALUE_BIT_LENGTH, "output_value_a");
61+
const auto output_value_b =
62+
suint_ct(witness_ct(&composer, tx.output_value_b), NOTE_VALUE_BIT_LENGTH, "output_value_b");
63+
64+
const bool_ct first_output_virtual =
65+
circuit::get_asset_id_flag(claim_note_data.bridge_call_data_local.output_asset_id_a);
66+
const bool_ct second_output_virtual =
67+
circuit::get_asset_id_flag(claim_note_data.bridge_call_data_local.output_asset_id_b);
68+
const bool_ct& second_input_in_use = claim_note_data.bridge_call_data_local.config.second_input_in_use;
69+
const bool_ct& second_output_in_use = claim_note_data.bridge_call_data_local.config.second_output_in_use;
70+
71+
{
72+
// Don't support zero deposits (because they're illogical):
73+
claim_note.deposit_value.value.assert_is_not_zero("Not supported: zero deposit");
74+
// Ensure deposit_value <= total_input_value
75+
defi_interaction_note.total_input_value.subtract(
76+
claim_note.deposit_value, NOTE_VALUE_BIT_LENGTH, "deposit_value > total_input_value");
77+
// These checks are superfluous, but included just in case:
78+
// Ensure output_value_a <= total_output_value_a
79+
defi_interaction_note.total_output_value_a.subtract(
80+
output_value_a, NOTE_VALUE_BIT_LENGTH, "output_value_a > total_output_value_a");
81+
// Ensure output_value_b <= total_output_value_b
82+
defi_interaction_note.total_output_value_b.subtract(
83+
output_value_b, NOTE_VALUE_BIT_LENGTH, "output_value_b > total_output_value_b");
84+
}
85+
86+
{
87+
// Ratio checks.
88+
// Note, these ratio_checks also guarantee:
89+
// defi_interaction_note.total_input_value != 0
90+
// defi_interaction_note.total_output_value_a != 0 (unless output_value_a == 0)
91+
// defi_interaction_note.total_output_value_b != 0 (unless output_value_b == 0)
92+
93+
// Check that (deposit * total_output_value_a) == (output_value_a * total_input_value)
94+
// Rearranging, this ensures output_value_a == (deposit / total_input_value) * total_output_value_a
95+
const bool_ct rc1 = malicious_ratio_check(composer,
96+
{ .a1 = claim_note.deposit_value.value,
97+
.a2 = defi_interaction_note.total_input_value.value,
98+
.b1 = output_value_a.value,
99+
.b2 = defi_interaction_note.total_output_value_a.value });
100+
const bool_ct valid1 = (output_value_a == 0 && defi_interaction_note.total_output_value_a == 0) || rc1;
101+
valid1.assert_equal(true, "ratio check 1 failed");
102+
103+
// Check that (deposit * total_output_value_b) == (output_value_b * total_input_value)
104+
// Rearranging, this ensures output_value_b == (deposit / total_input_value) * total_output_value_b
105+
const bool_ct rc2 = malicious_ratio_check(composer,
106+
{ .a1 = claim_note.deposit_value.value,
107+
.a2 = defi_interaction_note.total_input_value.value,
108+
.b1 = output_value_b.value,
109+
.b2 = defi_interaction_note.total_output_value_b.value });
110+
const bool_ct valid2 = (output_value_b == 0 && defi_interaction_note.total_output_value_b == 0) || rc2;
111+
valid2.assert_equal(true, "ratio check 2 failed");
112+
}
113+
114+
// This nullifier1 is unique because the claim_note.commitment is unique (which itself is unique because it contains
115+
// a unique input_nullifier (from the defi-deposit tx which created it)).
116+
const auto nullifier1 = circuit::claim::compute_nullifier(claim_note.commitment);
117+
118+
// We 'nullify' this (claim note, defi interaction note) combination. Each owner of a claim note can produce a valid
119+
// nullifier.
120+
const auto nullifier2 =
121+
circuit::defi_interaction::compute_nullifier(defi_interaction_note.commitment, claim_note.commitment);
122+
123+
field_ct output_note_commitment1;
124+
field_ct output_note_commitment2;
125+
{
126+
// Compute output notes.
127+
const auto virtual_note_flag = suint_ct(uint256_t(1) << (MAX_NUM_ASSETS_BIT_LENGTH - 1));
128+
129+
// If the defi interaction was unsuccessful, refund the original defi_deposit_value (which was denominated in
130+
// bridge input_asset_id_a) via output note 1.
131+
const bool_ct& interaction_success = defi_interaction_note.interaction_result;
132+
const auto output_value_1 =
133+
suint_ct::conditional_assign(interaction_success, output_value_a, claim_note_data.deposit_value);
134+
const auto output_asset_id_1_if_success =
135+
suint_ct::conditional_assign(first_output_virtual,
136+
virtual_note_flag + claim_note.defi_interaction_nonce,
137+
claim_note_data.bridge_call_data_local.output_asset_id_a);
138+
const auto output_asset_id_1 = suint_ct::conditional_assign(
139+
interaction_success, output_asset_id_1_if_success, claim_note_data.bridge_call_data_local.input_asset_id_a);
140+
output_note_commitment1 = circuit::value::complete_partial_commitment(
141+
claim_note.value_note_partial_commitment, output_value_1, output_asset_id_1, nullifier1);
142+
143+
// If the defi interaction was unsuccessful, refund the original defi_deposit_value (which was denominated in
144+
// bridge input_asset_id_b) via output note 2, and only if there was a second input asset to the bridge.
145+
const auto output_value_2 =
146+
suint_ct::conditional_assign(interaction_success, output_value_b, claim_note_data.deposit_value);
147+
const auto output_asset_id_2_if_success =
148+
suint_ct::conditional_assign(second_output_virtual,
149+
virtual_note_flag + claim_note.defi_interaction_nonce,
150+
claim_note_data.bridge_call_data_local.output_asset_id_b);
151+
const auto output_asset_id_2 = suint_ct::conditional_assign(
152+
interaction_success, output_asset_id_2_if_success, claim_note_data.bridge_call_data_local.input_asset_id_b);
153+
output_note_commitment2 = circuit::value::complete_partial_commitment(
154+
claim_note.value_note_partial_commitment, output_value_2, output_asset_id_2, nullifier2);
155+
156+
// We zero the output_note_commitment2 in two cases:
157+
// - if the bridge interaction succeeded and returned a second output asset; or
158+
// - if the bridge interaction failed and no second asset was ever sent to the bridge.
159+
const bool_ct is_bridge_output_b_in_use = interaction_success && second_output_in_use;
160+
const bool_ct was_bridge_input_b_in_use = !interaction_success && second_input_in_use;
161+
162+
const bool_ct output_note_2_exists = is_bridge_output_b_in_use || was_bridge_input_b_in_use;
163+
164+
output_note_commitment2 = output_note_commitment2 * output_note_2_exists;
165+
}
166+
167+
{
168+
// Check claim note and interaction note are related.
169+
claim_note.bridge_call_data.assert_equal(defi_interaction_note.bridge_call_data,
170+
"note bridge call datas don't match");
171+
claim_note.defi_interaction_nonce.assert_equal(defi_interaction_note.interaction_nonce,
172+
"note nonces don't match");
173+
}
174+
175+
{
176+
// Existence checks
177+
178+
// Check claim note exists:
179+
const bool_ct claim_exists = check_membership(data_root,
180+
claim_note_path,
181+
claim_note.commitment,
182+
claim_note_index.value.decompose_into_bits(DATA_TREE_DEPTH));
183+
claim_exists.assert_equal(true, "claim note not a member");
184+
185+
// Check defi interaction note exists:
186+
const bool_ct din_exists = check_membership(defi_root,
187+
defi_interaction_note_path,
188+
defi_interaction_note.commitment,
189+
defi_note_index.value.decompose_into_bits(DEFI_TREE_DEPTH));
190+
din_exists.assert_equal(true, "defi interaction note not a member");
191+
}
192+
193+
// Force unused public inputs to 0.
194+
const field_ct public_value = witness_ct(&composer, 0);
195+
const field_ct public_owner = witness_ct(&composer, 0);
196+
const field_ct asset_id = witness_ct(&composer, 0);
197+
const field_ct defi_deposit_value = witness_ct(&composer, 0);
198+
const field_ct backward_link = witness_ct(&composer, 0);
199+
const field_ct allow_chain = witness_ct(&composer, 0);
200+
public_value.assert_is_zero();
201+
public_owner.assert_is_zero();
202+
asset_id.assert_is_zero();
203+
defi_deposit_value.assert_is_zero();
204+
backward_link.assert_is_zero();
205+
allow_chain.assert_is_zero();
206+
207+
// The following make up the public inputs to the circuit.
208+
proof_id.set_public();
209+
output_note_commitment1.set_public();
210+
output_note_commitment2.set_public();
211+
nullifier1.set_public();
212+
nullifier2.set_public();
213+
public_value.set_public(); // 0
214+
public_owner.set_public(); // 0
215+
asset_id.set_public(); // 0
216+
data_root.set_public();
217+
claim_note.fee.set_public();
218+
claim_note_data.bridge_call_data_local.input_asset_id_a.set_public();
219+
claim_note.bridge_call_data.set_public();
220+
defi_deposit_value.set_public(); // 0
221+
defi_root.set_public();
222+
backward_link.set_public(); // 0
223+
allow_chain.set_public(); // 0
224+
}
225+
226+
} // namespace claim
227+
} // namespace proofs
228+
} // namespace rollup

0 commit comments

Comments
 (0)