@@ -5,7 +5,7 @@ use crate::{
55 logs ::{event:: to_encrypted_private_event_message , utils:: prefix_with_tag },
66 offchain_messages:: emit_offchain_message ,
77 },
8- utils:: remove_constraints ::{ remove_constraints , remove_constraints_if } ,
8+ utils::remove_constraints:: remove_constraints_if ,
99};
1010use dep::protocol_types:: {
1111 address::AztecAddress ,
@@ -14,50 +14,113 @@ use dep::protocol_types::{
1414 traits ::{Serialize , ToField },
1515};
1616
17- pub struct PrivateLogContentConstraintsEnum {
18- /// The contents of the log are entirely unconstrained, and could have any values.
19- ///
20- /// Only use in scenarios where the recipient not receiving the message is an acceptable outcome (e.g. because the
21- /// sender is somehow motivated to ensure the recipient learns of it).
22- pub NO_CONSTRAINTS : u8 ,
23- /// The contents of the log and its encryption are constrained. The tag (and therefore whether the recipient is
24- /// actually able to find the message) is not.
17+ /// Specifies the configuration parameters for message delivery. There are two fundamental aspects to consider:
18+ ///
19+ /// +----------------------------------------------------------------------------------------------------------+
20+ /// | 1. Delivery Mechanism |
21+ /// | - Messages can be delivered either on-chain or out-of-band |
22+ /// | - On-chain delivery uses the Aztec protocol's private log stream, submitted to L1 blobs and consuming DA |
23+ /// | - Out-of-band delivery is implemented by the application (e.g. storing ciphertexts in cloud storage) |
24+ /// | - Out-of-band delivery cannot have any cryptographic constraints since messages are never stored on-chain|
25+ /// +----------------------------------------------------------------------------------------------------------+
26+ ///
27+ /// For on-chain delivery, we must also consider:
28+ ///
29+ /// +----------------------------------------------------------------------------------------------------------+
30+ /// | 2. Message Encryption and Tagging |
31+ /// | - Messages can use either constrained or unconstrained encryption |
32+ /// | - Constrained encryption guarantees the ciphertext is formed correctly but costs more in constraints, |
33+ /// | which results in slower proving times |
34+ /// | - Unconstrained encryption trusts the sender but is cheaper constraint-wise and hence faster to prove |
35+ /// | - Tagging is an indexing mechanism that helps recipients locate their messages |
36+ /// | - If tagging is not performed correctly by the sender, the recipient will not be able to find the message|
37+ /// +----------------------------------------------------------------------------------------------------------+
38+ ///
39+ /// For off-chain delivery, constrained encryption is not relevant since it doesn't provide any additional guarantees
40+ /// over unconstrained encryption and is slower to prove (requiring more constraints).
41+ ///
42+ /// There are three available delivery modes described below.
43+ pub struct MessageDeliveryEnum {
44+ /// 1. Constrained On-chain
45+ /// - Uses constrained encryption and in the future constrained tagging (issue #14565) with on-chain delivery
46+ /// - Provides cryptographic guarantees that recipients can discover and decrypt messages (once #14565 is tackled)
47+ /// - Slowest proving times since encryption is constrained
48+ /// - Expensive since it consumes L1 blob space
49+ /// - Use when smart contracts need to make decisions based on message contents
50+ /// - Example 1: An escrow contract facilitating a private NFT sale that needs to verify payment before releasing
51+ /// the NFT to the buyer.
52+ /// - Example 2: An application with private configuration where changes must be broadcast to all participants.
53+ /// This ensures every user can access the latest configuration. Without notification of config changes,
54+ /// users would be unable to read updated variables and therefore blocked from using the application's
55+ /// functions. This pattern applies to all critical events that require universal broadcast.
2556 ///
26- /// Only use in scenarios where the recipient not receiving the message is an acceptable outcome (e.g. because the
27- /// sender is somehow motivated to ensure the recipient learns of it).
28- // TODO(#14565): This variant requires for tagging to also be constrained, as it is otherwise useless.
29- pub CONSTRAINED_ENCRYPTION : u8 ,
57+ /// Safety: Despite being called CONSTRAINED_ONCHAIN, this delivery mode is currently NOT fully constrained.
58+ /// The tag prefixing is unconstrained, meaning a malicious sender could manipulate the tag to prevent
59+ /// recipient decryption. TODO(#14565): Implement proper constrained tag prefixing.
60+ pub CONSTRAINED_ONCHAIN : u8 ,
61+
62+ /// 2. Unconstrained On-chain
63+ /// - Uses unconstrained encryption and tagging with on-chain delivery
64+ /// - Faster proving times since no constraints are used for encryption
65+ /// - Expensive since it consumes L1 blob space
66+ /// - Suitable when recipients can verify message validity through other means
67+ /// - Use this if you don't need the cryptographic guarantees of constrained encryption and tagging but
68+ /// don't want to deal with setting up out-of-band delivery infrastructure as required by mode 3
69+ /// - Example: Depositing a privately-held NFT into an NFT-sale escrow contract. The buyers know the escrow
70+ /// contract's decryption keys, they receive the message on-chain and are willing to buy the NFT only if the NFT
71+ /// contained in the message is legitimate.
72+ pub UNCONSTRAINED_ONCHAIN : u8 ,
73+
74+ /// 3. Out-of-band
75+ /// - Uses unconstrained encryption with off-chain delivery
76+ /// - Lowest cost since no on-chain storage is needed and short proving times since no constraints are used
77+ /// for encryption
78+ /// - Suitable when recipients can verify message validity through other means
79+ /// - Requires setting up custom infrastructure for handling off-chain delivery (e.g. cloud storage)
80+ /// - Example: A payment app where a merchant receives the message off-chain and is willing to release the goods
81+ /// once he verifies that the payment is correct (i.e. can decrypt the message and verify that it contains
82+ /// a legitimate token note - note with note commitment in the note hash tree).
83+ pub UNCONSTRAINED_OFFCHAIN : u8 ,
3084}
3185
32- pub global PrivateLogContent : PrivateLogContentConstraintsEnum = PrivateLogContentConstraintsEnum {
33- NO_CONSTRAINTS : 1 ,
34- CONSTRAINED_ENCRYPTION : 2 ,
35- // TODO: add constrained tagging and constrained handshaking
86+ pub global MessageDelivery : MessageDeliveryEnum = MessageDeliveryEnum {
87+ CONSTRAINED_ONCHAIN : 1 ,
88+ UNCONSTRAINED_ONCHAIN : 2 ,
89+ UNCONSTRAINED_OFFCHAIN : 3 ,
3690};
3791
38- /// Emits an event in a private log, encrypting it such that only `recipient` will learn of its contents. The log will
39- /// be tagged using a shared secret between `sender` and `recipient`, so that `recipient` can efficiently find the log .
92+ /// Emits an event that can be delivered either via private logs or offchain messages, with configurable encryption and
93+ /// tagging constraints .
4094///
41- /// The `constraints` value determines what parts of this computation will be constrained. See the documentation for
42- /// each value in `PrivateLogContentConstraintsEnum` to learn more about the different variants.
43- pub fn emit_event_in_private_log <Event >(
95+ /// # Arguments
96+ /// * `event` - The event to emit
97+ /// * `context` - The private context to emit the event in
98+ /// * `recipient` - The address that should receive this event
99+ /// * `delivery_mode` - Controls encryption, tagging, and delivery constraints. Must be a compile-time constant.
100+ /// See `MessageDeliveryEnum` for details on the available modes.
101+ pub fn emit_event_in_private <Event >(
44102 event : Event ,
45103 context : &mut PrivateContext ,
46104 recipient : AztecAddress ,
47- constraints : u8 ,
105+ delivery_mode : u8 ,
48106)
49107where
50108 Event : EventInterface + Serialize ,
51109{
52- // This function relies on `constraints` being a constant in order to reduce circuit constraints when unconstrained
53- // usage is requested. If `constraints` were a runtime value then performance would suffer.
54- assert_constant (constraints );
110+ // This function relies on `delivery_mode` being a constant in order to reduce circuit constraints when unconstrained
111+ // usage is requested. If `delivery_mode` were a runtime value then performance would suffer.
112+ assert_constant (delivery_mode );
113+
114+ // The following maps out the 3 dimensions across which we configure message delivery.
115+ let constrained_encryption = delivery_mode == MessageDelivery .CONSTRAINED_ONCHAIN ;
116+ let emit_as_offchain_message = delivery_mode == MessageDelivery .UNCONSTRAINED_OFFCHAIN ;
117+ // TODO(#14565): Add constrained tagging
118+ let _constrained_tagging = delivery_mode == MessageDelivery .CONSTRAINED_ONCHAIN ;
55119
56120 let (ciphertext , randomness ) = remove_constraints_if (
57- constraints == PrivateLogContent . NO_CONSTRAINTS ,
121+ ! constrained_encryption ,
58122 || to_encrypted_private_event_message (event , recipient ),
59123 );
60- let log_content = prefix_with_tag (ciphertext , recipient );
61124
62125 // We generate a cryptographic commitment to the event to ensure its authenticity during out-of-band delivery.
63126 // The nullifier tree is chosen over the note hash tree for this purpose since it provides a simpler mechanism
@@ -71,48 +134,20 @@ where
71134 );
72135 context .push_nullifier (event_commitment );
73136
74- context .emit_private_log (log_content , log_content .len ());
75- }
137+ if emit_as_offchain_message {
138+ // Safety: Offchain messages are by definition unconstrained. They are emitted via the `emit_offchain_effect`
139+ // oracle which we don't use for anything besides its side effects, therefore this is safe to call.
140+ unsafe { emit_offchain_message (ciphertext , recipient ) };
141+ } else {
142+ // Safety: Currently unsafe. See description of CONSTRAINED_ONCHAIN in MessageDeliveryEnum.
143+ // TODO(#14565): Implement proper constrained tag prefixing to make this truly CONSTRAINED_ONCHAIN
144+ let log_content = prefix_with_tag (ciphertext , recipient );
76145
77- /// Emits an event as an offchain message. Similar to private log emission but uses offchain message mechanism instead.
78- ///
79- /// Unlike private log emission, encryption here is always unconstrained. This design choice stems from the nature of
80- /// offchain messages - they lack guaranteed delivery, unlike private logs. Without delivery guarantees, smart
81- /// contracts cannot make assumptions about a message being delivered, making constrained encryption unnecessary.
82- /// However, message integrity remains protected through a cryptographic commitment stored in the nullifier tree,
83- /// preventing tampering even in the absence of guaranteed delivery. See the description of the
84- /// `messages::offchain_message::emit_offchain_message` function for more details on when a guaranteed delivery is
85- /// valuable. If guaranteed delivery is required, the `emit_event_in_private_log` function should be used instead.
86- pub fn emit_event_as_offchain_message <Event >(
87- event : Event ,
88- context : &mut PrivateContext ,
89- recipient : AztecAddress ,
90- )
91- where
92- Event : EventInterface + Serialize ,
93- {
94- // Safety: as explained above, this function places no constraints on the content of the message.
95- let (message_ciphertext , randomness ) =
96- unsafe { remove_constraints (|| to_encrypted_private_event_message (event , recipient )) };
97-
98- // We generate a cryptographic commitment to the event to ensure its authenticity during out-of-band delivery. Note
99- // that the commitment is made from the (constrained) event content, and not the (unconstrained) ciphertext.
100- // The nullifier tree is chosen over the note hash tree for this purpose since it provides a simpler mechanism
101- // - nullifiers require no nonce, and events, being non-spendable, don't need the guarantee that a "spending"
102- // nullifier can be computed.
103- // TODO(#11571): with decryption happening in Noir we can now use the Packable trait instead.
104- let serialized_event_with_randomness = [randomness ].concat (event .serialize ());
105- let event_commitment = poseidon2_hash_with_separator (
106- serialized_event_with_randomness ,
107- GENERATOR_INDEX__EVENT_COMMITMENT ,
108- );
109- context .push_nullifier (event_commitment );
110-
111- // Safety: Offchain effects are by definition unconstrained. They are emitted via an oracle
112- // which we don't use for anything besides its side effects, therefore this is safe to call.
113- unsafe { emit_offchain_message (message_ciphertext , recipient ) };
146+ context .emit_private_log (log_content , log_content .len ());
147+ }
114148}
115149
150+ // TODO(benesjan): rename to emit_event_in_public
116151pub fn emit_event_in_public_log <Event >(event : Event , context : &mut PublicContext )
117152where
118153 Event : EventInterface + Serialize ,
0 commit comments