-
Notifications
You must be signed in to change notification settings - Fork 618
feat(aztec-nr): wire constrained message delivery #23866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c552943
000162b
36681e6
0271756
98b556a
5699c52
389493b
c334adc
cd2fbea
33a3b65
18a8df5
b258ccd
153b84a
a851c53
170f8b9
e371203
9d61ebb
df8704e
09bae38
614e435
65ac26d
72009eb
dbf792b
6919a75
58343a9
dbc04f4
0eb6e78
91b3840
af18bc5
fd84572
48aa220
bcf9c76
25b6540
2f8cf57
446957f
417482d
ba1be2d
3d10cae
26aae03
0834425
00cc819
bed55f4
fbadea0
d7065a9
a66cedd
54520c9
4c13ccb
7628eab
fd07996
375800b
08c35b0
269fb80
99b0a9c
32cdf5d
20ae9f6
f6b9c71
05ffc5a
45a9a9d
b6eb170
f73130b
41411a4
73b3c40
56d84b2
833f58e
c98d885
7d4e98e
a22266d
76c29c8
3d1f990
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| //! Sender-side helpers for constrained message delivery. | ||
| //! | ||
| //! Constrained messages form per-`(sender, recipient, secret)` sequences, each send anchored to the handshake | ||
| //! registry at an incrementing index. Two consequences shape the whole flow: sends on one sequence are strictly | ||
| //! ordered across transactions (parallel sends collide or fail the predecessor check, while distinct recipients are | ||
| //! distinct sequences and parallelize), and batching several sends onto one sequence within a transaction requires | ||
| //! an already-committed handshake. | ||
| //! | ||
| //! See [`constrain_secret`] for how a send is anchored to the registry, | ||
|
|
||
| use crate::context::PrivateContext; | ||
| use crate::messages::delivery::OnchainDeliveryMode; | ||
| use crate::nullifier::utils::compute_nullifier_existence_request; | ||
|
|
||
| use crate::protocol::{ | ||
| abis::function_selector::FunctionSelector, address::AztecAddress, constants::DOM_SEP__CONSTRAINED_MSG_NULLIFIER, | ||
| hash::poseidon2_hash_with_separator, traits::ToField, | ||
| }; | ||
|
|
||
| // The helper cannot import the handshake registry interface because the registry contract depends on aztec-nr. The | ||
| // registry's test suite compares this against its macro-generated `HandshakeRegistry::at(...).method(...).selector` | ||
| // value so signature drift fails in tests. | ||
| pub global VALIDATE_HANDSHAKE_SELECTOR: FunctionSelector = | ||
| comptime { FunctionSelector::from_signature("validate_handshake((Field),(Field),(u8),Field)") }; | ||
|
|
||
| pub(crate) fn constrain_secret_and_emit_nullifier( | ||
| context: &mut PrivateContext, | ||
| registry: AztecAddress, | ||
| sender: AztecAddress, | ||
| recipient: AztecAddress, | ||
| secret: Field, | ||
| bootstrapped: bool, | ||
| index: u32, | ||
| ) { | ||
| constrain_secret( | ||
| context, | ||
| registry, | ||
| sender, | ||
| recipient, | ||
| secret, | ||
| bootstrapped, | ||
| index, | ||
| ); | ||
| context.push_nullifier_unsafe(compute_constrained_msg_nullifier(sender, recipient, secret, index)); | ||
| } | ||
|
|
||
| /// Anchors an untrusted `(secret, index)` to the registry before its constrained tag is emitted. | ||
| /// | ||
| /// - bootstrapped: the secret is the constrained `non_interactive_handshake` return (source of | ||
| /// truth), so only its `index == 0` start is asserted. | ||
| /// - reuse at index 0: `validate_handshake` binds the oracle-supplied secret to the stored handshake. | ||
| /// - reuse at index > 0: the prior sequence nullifier must exist, anchoring back to the index-0 check. | ||
| fn constrain_secret( | ||
| context: &mut PrivateContext, | ||
| registry: AztecAddress, | ||
| sender: AztecAddress, | ||
| recipient: AztecAddress, | ||
| secret: Field, | ||
| bootstrapped: bool, | ||
| index: u32, | ||
| ) { | ||
| let caller = context.this_address(); | ||
| let mode = OnchainDeliveryMode::onchain_constrained(); | ||
| let mode_field = mode.to_field(); | ||
|
|
||
| if bootstrapped { | ||
| assert(index == 0, "freshly bootstrapped secret must start at index 0"); | ||
| } else if index == 0 { | ||
| let _ = context.call_private_function( | ||
| registry, | ||
| VALIDATE_HANDSHAKE_SELECTOR, | ||
| [sender.to_field(), recipient.to_field(), mode_field, secret], | ||
| ); | ||
| } else { | ||
| let prev_nullifier = compute_constrained_msg_nullifier(sender, recipient, secret, index - 1); | ||
| context.assert_nullifier_exists(compute_nullifier_existence_request(prev_nullifier, caller)); | ||
| } | ||
| } | ||
|
|
||
| /// Computes a constrained send's sequence nullifier. | ||
| /// | ||
| /// Every constrained send emits this nullifier so the next send under the same `(sender, recipient, secret)` sequence | ||
| /// can prove its predecessor exists. | ||
| pub(crate) fn compute_constrained_msg_nullifier( | ||
| sender: AztecAddress, | ||
| recipient: AztecAddress, | ||
| secret: Field, | ||
| index: u32, | ||
| ) -> Field { | ||
| poseidon2_hash_with_separator( | ||
| [sender.to_field(), recipient.to_field(), secret, index as Field], | ||
| DOM_SEP__CONSTRAINED_MSG_NULLIFIER, | ||
| ) | ||
| } | ||
|
|
||
| mod test { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it me, or are we missing quite a lot of tests here? I think we have a lot of behaviors and decision trees on this file that we are not testing. Could we?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of the coverage is contained in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added some unit tests |
||
| use crate::context::PrivateContext; | ||
| use crate::hash::hash_args; | ||
| use crate::messages::delivery::OnchainDeliveryMode; | ||
| use crate::protocol::{address::AztecAddress, hash::compute_siloed_nullifier, traits::{FromField, ToField}}; | ||
| use crate::test::helpers::test_environment::TestEnvironment; | ||
| use super::{compute_constrained_msg_nullifier, constrain_secret_and_emit_nullifier, VALIDATE_HANDSHAKE_SELECTOR}; | ||
| use std::test::OracleMock; | ||
|
|
||
| fn assert_current_nullifier_emitted( | ||
| context: &mut PrivateContext, | ||
| sender: AztecAddress, | ||
| recipient: AztecAddress, | ||
| secret: Field, | ||
| index: u32, | ||
| ) { | ||
| assert_eq(context.nullifiers.len(), 1); | ||
| assert_eq( | ||
| context.nullifiers.get(0).inner.value, | ||
| compute_constrained_msg_nullifier(sender, recipient, secret, index), | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn constrained_helper_emits_current_nullifier() { | ||
| let env = TestEnvironment::new(); | ||
| let registry = AztecAddress::from_field(1); | ||
| let sender = AztecAddress::from_field(2); | ||
| let recipient = AztecAddress::from_field(4); | ||
| let secret: Field = 1234; | ||
| let index: u32 = 0; | ||
|
|
||
| env.private_context(|context| { | ||
| constrain_secret_and_emit_nullifier(context, registry, sender, recipient, secret, true, index); | ||
|
|
||
| assert_current_nullifier_emitted(context, sender, recipient, secret, index); | ||
| assert_eq(context.private_call_requests.len(), 0); | ||
| assert_eq(context.nullifier_read_requests.len(), 0); | ||
| }); | ||
| } | ||
|
|
||
| #[test(should_fail_with = "freshly bootstrapped secret must start at index 0")] | ||
| unconstrained fn bootstrapped_secret_must_start_at_index_zero() { | ||
| let env = TestEnvironment::new(); | ||
| let registry = AztecAddress::from_field(1); | ||
| let sender = AztecAddress::from_field(2); | ||
| let recipient = AztecAddress::from_field(4); | ||
|
|
||
| env.private_context(|context| { | ||
| constrain_secret_and_emit_nullifier(context, registry, sender, recipient, 1234, true, 1); | ||
| }); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn reused_secret_at_index_zero_validates_registry_and_emits_nullifier() { | ||
| let env = TestEnvironment::new(); | ||
| let registry = AztecAddress::from_field(1); | ||
| let sender = AztecAddress::from_field(2); | ||
| let recipient = AztecAddress::from_field(4); | ||
| let secret: Field = 1234; | ||
| let index: u32 = 0; | ||
|
|
||
| env.private_context(|context| { | ||
| // The real registry call is covered by integration tests; this unit test only needs a coherent child | ||
| // call result so `PrivateContext` records the request. | ||
| let child_call_end_counter = (context.side_effect_counter + 1) as Field; | ||
| let empty_returns_hash: Field = 0; | ||
| let _ = OracleMock::mock("aztec_prv_callPrivateFunction") | ||
| .returns([child_call_end_counter, empty_returns_hash]) | ||
| .times(1); | ||
|
|
||
| constrain_secret_and_emit_nullifier(context, registry, sender, recipient, secret, false, index); | ||
|
|
||
| assert_current_nullifier_emitted(context, sender, recipient, secret, index); | ||
| assert_eq(context.nullifier_read_requests.len(), 0); | ||
| assert_eq(context.private_call_requests.len(), 1); | ||
|
|
||
| let request = context.private_call_requests.get(0); | ||
| assert_eq(request.call_context.msg_sender, context.this_address()); | ||
| assert_eq(request.call_context.contract_address, registry); | ||
| assert_eq(request.call_context.function_selector, VALIDATE_HANDSHAKE_SELECTOR); | ||
| assert(!request.call_context.is_static_call); | ||
| assert_eq( | ||
| request.args_hash, | ||
| hash_args([ | ||
| sender.to_field(), | ||
| recipient.to_field(), | ||
| OnchainDeliveryMode::onchain_constrained().to_field(), | ||
| secret, | ||
| ]), | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| #[test] | ||
| unconstrained fn reused_secret_above_index_zero_reads_previous_nullifier_and_emits_current_nullifier() { | ||
| let env = TestEnvironment::new(); | ||
| let registry = AztecAddress::from_field(1); | ||
| let sender = AztecAddress::from_field(2); | ||
| let recipient = AztecAddress::from_field(4); | ||
| let secret: Field = 1234; | ||
| let index: u32 = 3; | ||
|
|
||
| env.private_context(|context| { | ||
| let _ = OracleMock::mock("aztec_prv_isNullifierPending").returns(false).times(1); | ||
|
|
||
| constrain_secret_and_emit_nullifier(context, registry, sender, recipient, secret, false, index); | ||
|
|
||
| assert_current_nullifier_emitted(context, sender, recipient, secret, index); | ||
| assert_eq(context.private_call_requests.len(), 0); | ||
| assert_eq(context.nullifier_read_requests.len(), 1); | ||
|
|
||
| let read_request = context.nullifier_read_requests.get(0); | ||
| assert_eq(read_request.contract_address, AztecAddress::zero()); | ||
| assert_eq( | ||
| read_request.inner.inner, | ||
| compute_siloed_nullifier( | ||
| context.this_address(), | ||
| compute_constrained_msg_nullifier(sender, recipient, secret, index - 1), | ||
| ), | ||
| ); | ||
| }); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.