1
1
use crate:: {
2
2
context::PrivateContext , note:: {note_emission::NoteEmission , note_interface::NoteInterface },
3
- keys ::{getters:: get_current_public_keys , public_keys ::{OvpkM , IvpkM }},
3
+ keys ::{getters ::{ get_current_public_keys , get_ovsk_app } , public_keys ::{OvpkM , IvpkM }},
4
4
encrypted_logs::payload:: compute_encrypted_note_log , oracle::logs_traits::LensForEncryptedLog
5
5
};
6
6
use dep::protocol_types:: {hash::sha256_to_field , address::AztecAddress , abis::note_hash::NoteHash };
7
7
8
- unconstrained fn compute_unconstrained <Note , let N : u32 , let NB : u32 , let M : u32 >(
9
- contract_address : AztecAddress ,
10
- storage_slot : Field ,
11
- ovsk_app : Field ,
12
- ovpk : OvpkM ,
13
- ivpk : IvpkM ,
14
- recipient : AztecAddress ,
15
- note : Note
16
- ) -> ([u8 ; M ], Field ) where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
17
- compute (
18
- contract_address ,
19
- storage_slot ,
20
- ovsk_app ,
21
- ovpk ,
22
- ivpk ,
23
- recipient ,
24
- note
25
- )
26
- }
27
-
28
- fn compute <Note , let N : u32 , let NB : u32 , let M : u32 >(
29
- contract_address : AztecAddress ,
30
- storage_slot : Field ,
31
- ovsk_app : Field ,
32
- ovpk : OvpkM ,
33
- ivpk : IvpkM ,
34
- recipient : AztecAddress ,
35
- note : Note
36
- ) -> ([u8 ; M ], Field ) where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
37
- let encrypted_log : [u8 ; M ] = compute_encrypted_note_log (
38
- contract_address ,
39
- storage_slot ,
40
- ovsk_app ,
41
- ovpk ,
42
- ivpk ,
43
- recipient ,
44
- note
45
- );
46
- let log_hash = sha256_to_field (encrypted_log );
47
- (encrypted_log , log_hash )
48
- }
49
-
50
- fn emit_with_keys <Note , let N : u32 , let NB : u32 , let M : u32 >(
51
- context : &mut PrivateContext ,
8
+ fn compute_raw_note_log <Note , let N : u32 , let NB : u32 , let M : u32 >(
9
+ context : PrivateContext ,
52
10
note : Note ,
11
+ ovsk_app : Field ,
53
12
ovpk : OvpkM ,
54
13
ivpk : IvpkM ,
55
- recipient : AztecAddress ,
56
- inner_compute : fn (AztecAddress , Field , Field , OvpkM , IvpkM , AztecAddress , Note ) -> ([u8 ; M ], Field )
57
- ) where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
14
+ recipient : AztecAddress
15
+ ) -> (u32 , [u8 ; M ], Field ) where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
58
16
let note_header = note .get_header ();
59
17
let note_hash_counter = note_header .note_hash_counter ;
60
18
let storage_slot = note_header .storage_slot ;
61
19
20
+ // TODO(#8589): use typesystem to skip this check when not needed
62
21
let note_exists = context .note_hashes .storage .any (|n : NoteHash | n .counter == note_hash_counter );
63
22
assert (note_exists , "Can only emit a note log for an existing note." );
64
23
65
24
let contract_address : AztecAddress = context .this_address ();
66
- let ovsk_app : Field = context .request_ovsk_app (ovpk .hash ());
67
25
68
- let ( encrypted_log , log_hash ) = inner_compute (
26
+ let encrypted_log : [ u8 ; M ] = compute_encrypted_note_log (
69
27
contract_address ,
70
28
storage_slot ,
71
29
ovsk_app ,
@@ -74,8 +32,20 @@ fn emit_with_keys<Note, let N: u32, let NB: u32, let M: u32>(
74
32
recipient ,
75
33
note
76
34
);
35
+ let log_hash = sha256_to_field (encrypted_log );
36
+
37
+ (note_hash_counter , encrypted_log , log_hash )
38
+ }
77
39
78
- context .emit_raw_note_log (note_hash_counter , encrypted_log , log_hash );
40
+ unconstrained fn compute_raw_note_log_unconstrained <Note , let N : u32 , let NB : u32 , let M : u32 >(
41
+ context : PrivateContext ,
42
+ note : Note ,
43
+ ovpk : OvpkM ,
44
+ ivpk : IvpkM ,
45
+ recipient : AztecAddress
46
+ ) -> (u32 , [u8 ; M ], Field ) where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
47
+ let ovsk_app = get_ovsk_app (ovpk .hash ());
48
+ compute_raw_note_log (context , note , ovsk_app , ovpk , ivpk , recipient )
79
49
}
80
50
81
51
pub fn encode_and_encrypt_note <Note , let N : u32 , let NB : u32 , let M : u32 >(
@@ -86,7 +56,10 @@ pub fn encode_and_encrypt_note<Note, let N: u32, let NB: u32, let M: u32>(
86
56
| e : NoteEmission <Note > | {
87
57
let ovpk = get_current_public_keys (context , ov ).ovpk_m ;
88
58
let ivpk = get_current_public_keys (context , iv ).ivpk_m ;
89
- emit_with_keys (context , e .note , ovpk , ivpk , iv , compute );
59
+ let ovsk_app : Field = context .request_ovsk_app (ovpk .hash ());
60
+
61
+ let (note_hash_counter , encrypted_log , log_hash ) = compute_raw_note_log (*context , e .note , ovsk_app , ovpk , ivpk , iv );
62
+ context .emit_raw_note_log (note_hash_counter , encrypted_log , log_hash );
90
63
}
91
64
}
92
65
@@ -96,9 +69,17 @@ pub fn encode_and_encrypt_note_unconstrained<Note, let N: u32, let NB: u32, let
96
69
iv : AztecAddress
97
70
) -> fn [(&mut PrivateContext , AztecAddress , AztecAddress )](NoteEmission <Note >) -> () where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
98
71
| e : NoteEmission <Note > | {
72
+ // Note: We could save a lot of gates by obtaining the following keys in an unconstrained context but this
73
+ // function is currently not used anywhere so we are not optimizing it.
99
74
let ovpk = get_current_public_keys (context , ov ).ovpk_m ;
100
75
let ivpk = get_current_public_keys (context , iv ).ivpk_m ;
101
- emit_with_keys (context , e .note , ovpk , ivpk , iv , compute_unconstrained );
76
+
77
+ // See the comment in `encode_and_encrypt_note_with_keys_unconstrained` for why having note hash counter
78
+ // and log hash unconstrained here is fine.
79
+ let (note_hash_counter , encrypted_log , log_hash ) = unsafe {
80
+ compute_raw_note_log_unconstrained (*context , e .note , ovpk , ivpk , iv )
81
+ };
82
+ context .emit_raw_note_log (note_hash_counter , encrypted_log , log_hash );
102
83
}
103
84
}
104
85
@@ -109,7 +90,10 @@ pub fn encode_and_encrypt_note_with_keys<Note, let N: u32, let NB: u32, let M: u
109
90
recipient : AztecAddress
110
91
) -> fn [(&mut PrivateContext , OvpkM , IvpkM , AztecAddress )](NoteEmission <Note >) -> () where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
111
92
| e : NoteEmission <Note > | {
112
- emit_with_keys (context , e .note , ovpk , ivpk , recipient , compute );
93
+ let ovsk_app : Field = context .request_ovsk_app (ovpk .hash ());
94
+
95
+ let (note_hash_counter , encrypted_log , log_hash ) = compute_raw_note_log (*context , e .note , ovsk_app , ovpk , ivpk , recipient );
96
+ context .emit_raw_note_log (note_hash_counter , encrypted_log , log_hash );
113
97
}
114
98
}
115
99
@@ -120,6 +104,28 @@ pub fn encode_and_encrypt_note_with_keys_unconstrained<Note, let N: u32, let NB:
120
104
recipient : AztecAddress
121
105
) -> fn [(&mut PrivateContext , OvpkM , IvpkM , AztecAddress )](NoteEmission <Note >) -> () where Note : NoteInterface <N , NB >, [Field ; N ]: LensForEncryptedLog <N , M > {
122
106
| e : NoteEmission <Note > | {
123
- emit_with_keys (context , e .note , ovpk , ivpk , recipient , compute_unconstrained );
107
+ // Having the log hash be unconstrained here is fine because the way this works is we send the log hash
108
+ // to the kernel, and it gets included as part of its public inputs. Then we send the tx to the sequencer,
109
+ // which includes the kernel proof and the log preimages. The sequencer computes the hashes of the logs
110
+ // and checks that they are the ones in the public inputs of the kernel, and drops the tx otherwise (proposing
111
+ // the block on L1 would later fail if it didn't because of txs effects hash mismatch).
112
+ // So if we don't constrain the log hash, then a malicious sender can compute the correct log, submit a bad
113
+ // log hash to the kernel, and then submit the bad log preimage to the sequencer. All checks will pass, but
114
+ // the submitted log will not be the one that was computed by the app.
115
+ // In the unconstrained case, we don't care about the log at all because we don't do anything with it,
116
+ // and because it's unconstrained: it could be anything. So if a sender chooses to broadcast the tx with a log
117
+ // that is different from the one that was used in the circuit, then they'll be able to, but they were already
118
+ // able to change the log before anyway, so the end result is the same. It's important here that we do not
119
+ // return the log from this function to the app, otherwise it could try to do stuff with it and then that might
120
+ // be wrong.
121
+ // Regarding the note hash counter, this is used for squashing. The kernel assumes that a given note can have
122
+ // more than one log and removes all of the matching ones, so all a malicious sender could do is either: cause
123
+ // for the log to be deleted when it shouldn't have (which is fine - they can already make the content be
124
+ // whatever), or cause for the log to not be deleted when it should have (which is also fine - it'll be a log
125
+ // for a note that doesn't exist).
126
+ let (note_hash_counter , encrypted_log , log_hash ) = unsafe {
127
+ compute_raw_note_log_unconstrained (*context , e .note , ovpk , ivpk , recipient )
128
+ };
129
+ context .emit_raw_note_log (note_hash_counter , encrypted_log , log_hash );
124
130
}
125
131
}
0 commit comments