Skip to content

Commit

Permalink
feat: note hashes as points (#7618)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Jul 31, 2024
1 parent 6c19c7e commit 8ed8f92
Show file tree
Hide file tree
Showing 71 changed files with 497 additions and 404 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ When a struct is annotated with `#[aztec(note)]`, the Aztec macro applies a seri
- `serialize_content` and `deserialize_content`
- `get_header` and `set_header`
- `get_note_type_id`
- `compute_note_content_hash`
- `compute_note_hiding_point`
- `to_be_bytes`
- A `properties` method in the note's implementation

Expand All @@ -219,7 +219,7 @@ struct CustomNote {
}
```

### After expansaion
### After expansion

```rust
impl CustomNote {
Expand Down Expand Up @@ -248,10 +248,10 @@ impl CustomNote {
self.header = header;
}

fn compute_note_content_hash(self: CustomNote) -> Field {
aztec::hash::pedersen_hash(
fn compute_note_hiding_point(self: CustomNote) -> Point {
aztec::hash::pedersen_commitment(
self.serialize_content(),
aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_CONTENT_HASH
aztec::protocol_types::constants::GENERATOR_INDEX__NOTE_HIDING_POINT
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ For the case of the example, we will look at what is inserted into the note hash

#include_code increase_private_balance noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

This function is creating a new note and inserting it into the balance set of the recipient `to`. Recall that to ensure privacy, only the note commitment is really inserted into the note hashes tree. To share the contents of the note with `to` the contract can emit an encrypted log (which this one does), or it can require an out-of-band data transfer sharing the information. Below, we will walk through the steps of how the note commitment is computed and inserted into the tree. For this, we don't care about the encrypted log, so we are going to ignore that part of the function call for now.
This function is creating a new note and inserting it into the balance set of the recipient `to`. Recall that to ensure privacy, only the note hash is really inserted into the note hashes tree. To share the contents of the note with `to` the contract can emit an encrypted log (which this one does), or it can require an out-of-band data transfer sharing the information. Below, we will walk through the steps of how the note hash is computed and inserted into the tree. For this, we don't care about the encrypted log, so we are going to ignore that part of the function call for now.

Outlining it in more detail below as a sequence diagram, we can see how the calls make their way down the stack.
In the end a siloed note hash is computed in the kernel.
Expand All @@ -30,25 +30,27 @@ sequenceDiagram
BalanceSet->>Set: insert(note)
Set->>LifeCycle: create_note(derived_slot, note)
LifeCycle->>LifeCycle: note.header = NoteHeader { contract_address, <br> storage_slot: derived_slot, nonce: 0, note_hash_counter }
LifeCycle->>Utils: compute_inner_note_hash(note)
Utils->>TokenNote: note.compute_note_content_hash()
TokenNote->>Utils: note_hash = H(amount, to, randomness)
Utils->>NoteHash: compute_inner_hash(derived_slot, note_hash)
NoteHash->>LifeCycle: inner_note_hash = H(derived_slot, note_hash)
LifeCycle->>Context: push_note_hash(inner_note_hash)
LifeCycle->>Utils: compute_slotted_note_hash(note)
Utils->>TokenNote: note.compute_note_hiding_point()
TokenNote->>Utils: note_hiding_point = MSM([G_amt, G_to, G_rand], [amount, to, randomness])
Utils->>NoteHash: compute_slotted_note_hash(derived_slot, note_hiding_point)
NoteHash->>LifeCycle: slotted_note_hash = CURVE_ADD(derived_slot_point, note_hiding_point).x
LifeCycle->>Context: push_note_hash(slotted_note_hash)
end
Context->>Kernel: siloed_note_hash = H(contract_address, inner_note_hash)
Context->>Kernel: siloed_note_hash = H(contract_address, slotted_note_hash)
```

Notice the `siloed_note_hash` at the very end. It's a commitment that will be inserted into the note hashes tree. To clarify what this really is, we "unroll" the values to their simplest components. This gives us a better idea around what is actually inserted into the tree.
Notice the `siloed_note_hash` at the very end. It's a hash that will be inserted into the note hashes tree. To clarify what this really is, we "unroll" the values to their simplest components. This gives us a better idea around what is actually inserted into the tree.

```rust
siloed_note_hash = H(contract_address, inner_note_hash)
siloed_note_hash = H(contract_address, H(derived_slot, note_hash))
siloed_note_hash = H(contract_address, H(H(map_slot, to), note_hash))
siloed_note_hash = H(contract_address, H(H(map_slot, to), H(amount, to, randomness)))
siloed_note_hash = H(contract_address, slotted_note_hash)
siloed_note_hash = H(contract_address, CURVE_ADD(derived_slot_point, note_hiding_point).x)
siloed_note_hash = H(contract_address, CURVE_ADD(MSM([G_slot], [derived_slot]), note_hiding_point).x)
siloed_note_hash = H(contract_address, CURVE_ADD(MSM([G_slot], [derived_slot]), MSM([G_amt, G_to, G_rand], [amount, to, randomness])).x)
```

CURVE_ADD is a point addition and MSM is a multi scalar multiplication on a grumpkin curve and G_* values are generators.

And `to` is the actor who receives the note, `amount` of the note and `randomness` is the randomness used to make the note hiding. Without the `randomness` the note could just as well be plaintext (computational cost of a preimage attack would be trivial in such a case).

:::info
Expand Down
10 changes: 6 additions & 4 deletions noir-projects/aztec-nr/aztec/src/encrypted_logs/header.nr
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl EncryptedLogHeader {
}

#[test]
fn test_encrypted_log_header() {
fn test_encrypted_log_header_matches_noir() {
let address = AztecAddress::from_field(0xdeadbeef);
let header = EncryptedLogHeader::new(address);
let secret = Scalar {
Expand All @@ -44,9 +44,11 @@ fn test_encrypted_log_header() {

let ciphertext = header.compute_ciphertext(secret, point);

let expected_header_ciphertext = [
166, 212, 106, 246, 139, 59, 228, 9, 133, 152, 127, 172, 141, 166, 237, 199, 55, 203, 226, 19, 114, 103, 58, 237, 108, 231, 35, 198, 54, 61, 190, 255, 241, 225, 151, 180, 6, 163, 124, 27, 151, 78, 237, 65, 120, 106, 255, 236
// The following value was generated by `encrypted_log_header.test.ts`.
// --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data.
let expected_header_ciphertext_from_typescript = [
226, 240, 253, 6, 28, 52, 19, 131, 33, 132, 178, 212, 245, 62, 14, 190, 194, 44, 7, 131, 160, 83, 64, 181, 98, 38, 153, 214, 62, 171, 253, 161, 111, 191, 28, 247, 216, 26, 222, 171, 176, 218, 48, 209, 73, 89, 200, 209
];

assert_eq(ciphertext, expected_header_ciphertext);
assert_eq(ciphertext, expected_header_ciphertext_from_typescript);
}
43 changes: 27 additions & 16 deletions noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ impl<let M: u32> EncryptedLogIncomingBody<M> {
}

mod test {
use crate::encrypted_logs::incoming_body::EncryptedLogIncomingBody;
use dep::protocol_types::{
address::AztecAddress, traits::Empty, constants::GENERATOR_INDEX__NOTE_NULLIFIER,
scalar::Scalar, point::Point, traits::Serialize, abis::event_selector::EventSelector
address::AztecAddress, scalar::Scalar, point::Point, traits::Serialize,
abis::event_selector::EventSelector
};

use crate::{
note::{note_header::NoteHeader, note_interface::NoteInterface},
event::event_interface::EventInterface, oracle::unsafe_rand::unsafe_rand,
encrypted_logs::incoming_body::EncryptedLogIncomingBody, event::event_interface::EventInterface,
context::PrivateContext
};

Expand All @@ -60,7 +59,9 @@ mod test {
global ADDRESS_NOTE_BYTES_LEN = 32 * 3 + 64;

impl NoteInterface<ADDRESS_NOTE_LEN, ADDRESS_NOTE_BYTES_LEN> for AddressNote {
fn compute_note_content_hash(_self: Self) -> Field {1}
fn compute_note_hiding_point(self) -> Point {
crate::generators::Ga1
}

fn get_note_type_id() -> Field {
1
Expand Down Expand Up @@ -112,7 +113,8 @@ mod test {
}

#[test]
fn test_encrypted_note_log_incoming_body() {
fn test_encrypted_note_log_incoming_body_matches_typescript() {
// All the values in this test were copied over from `encrypted_note_log_incoming_body.test.ts`
let note = AddressNote::new(
AztecAddress::from_field(0x1),
AztecAddress::from_field(0x2),
Expand All @@ -131,18 +133,25 @@ mod test {
is_infinite: false
};

/// 1. `EncryptedLogIncomingBody::from_note` calls `note.to_be_bytes(storage_slot)` function which serializes
/// the note to bytes - note that in the case of `AddressNote` the `to_be_bytes` function was automatically
/// implemented by Aztec macros.
let body = EncryptedLogIncomingBody::from_note(note, storage_slot);

/// 2. `body.compute_ciphertext(...)` function then derives symmetric key from `eph_sk` and `ivpk` and encrypts
// the note plaintext using AES-128.
let ciphertext = body.compute_ciphertext(eph_sk, ivpk);

let expected_note_body_ciphertext = [
166, 212, 106, 246, 139, 59, 228, 9, 133, 152, 127, 172, 141, 166, 237, 199, 195, 85, 255, 81, 66, 72, 192, 192, 96, 10, 54, 139, 136, 153, 252, 114, 248, 128, 253, 66, 249, 16, 71, 45, 2, 213, 250, 193, 241, 75, 90, 70, 39, 26, 104, 139, 20, 45, 1, 1, 166, 72, 133, 55, 247, 142, 150, 215, 217, 224, 84, 23, 245, 71, 207, 166, 136, 34, 221, 76, 90, 166, 44, 217, 246, 98, 157, 34, 198, 164, 99, 117, 15, 185, 145, 231, 189, 140, 201, 241, 135, 94, 71, 131, 156, 86, 144, 131, 248, 242, 83, 101, 18, 189, 1, 94, 25, 238, 76, 106, 85, 205, 4, 70, 21, 9, 64, 63, 27, 164, 73, 181, 75, 199, 86, 255, 105, 239, 216, 34, 217, 184, 154, 76, 67, 1, 210, 251, 23, 185, 114, 146, 195, 28, 76, 219, 150, 175, 37, 76, 144, 227, 99, 243, 123, 161, 66, 171, 148, 181, 162, 2, 196, 53, 207, 154, 114, 166, 155, 166
// The following value was generated by `encrypted_note_log_incoming_body.test.ts`.
// --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data.
let note_body_ciphertext_from_typescript = [
226, 240, 253, 6, 28, 52, 19, 131, 33, 132, 178, 212, 245, 62, 14, 190, 147, 228, 160, 190, 146, 61, 95, 203, 124, 153, 68, 168, 17, 150, 92, 0, 99, 214, 85, 64, 191, 78, 157, 131, 149, 96, 236, 253, 96, 172, 157, 30, 27, 176, 228, 74, 242, 190, 138, 48, 33, 93, 46, 37, 223, 130, 25, 245, 188, 163, 159, 223, 187, 24, 139, 206, 131, 154, 159, 130, 37, 17, 158, 114, 242, 141, 124, 193, 232, 54, 146, 96, 145, 100, 125, 234, 57, 43, 95, 115, 183, 39, 121, 232, 134, 229, 148, 25, 46, 77, 87, 127, 95, 7, 77, 188, 37, 234, 245, 142, 232, 87, 252, 28, 67, 67, 90, 214, 254, 89, 47, 68, 66, 187, 227, 8, 59, 162, 25, 141, 97, 141, 217, 197, 115, 15, 212, 202, 157, 41, 150, 62, 219, 57, 224, 92, 185, 212, 142, 94, 146, 41, 178, 145, 68, 169, 23, 185, 206, 138, 70, 47, 176, 210, 165, 236, 23, 206, 229, 108
];

assert_eq(expected_note_body_ciphertext.len(), ciphertext.len());
assert_eq(note_body_ciphertext_from_typescript.len(), ciphertext.len());

for i in 0..expected_note_body_ciphertext.len() {
assert_eq(ciphertext[i], expected_note_body_ciphertext[i]);
for i in 0..note_body_ciphertext_from_typescript.len() {
assert_eq(ciphertext[i], note_body_ciphertext_from_typescript[i]);
}
}

Expand Down Expand Up @@ -237,14 +246,16 @@ mod test {

let ciphertext = body.compute_ciphertext(eph_sk, ivpk);

let expected_event_body_ciphertext = [
166, 212, 106, 246, 139, 59, 228, 9, 133, 152, 127, 172, 141, 166, 237, 199, 195, 85, 255, 81, 66, 72, 192, 192, 96, 10, 54, 139, 136, 153, 252, 114, 248, 128, 253, 66, 249, 16, 71, 45, 2, 213, 250, 193, 241, 75, 90, 70, 19, 153, 62, 117, 71, 55, 48, 114, 160, 232, 97, 118, 93, 53, 145, 92, 0, 225, 51, 81, 156, 69, 72, 224, 10, 89, 32, 121, 167, 197, 84, 245, 188, 235, 143, 202, 179, 197, 164, 121, 11, 105, 116, 239, 46, 222, 50, 138, 112, 237, 97, 8, 176, 199, 1, 151, 89, 218, 60, 45, 91, 85, 16, 38, 195, 127, 157, 182, 0, 10, 232, 184, 148, 76, 244, 63, 40, 222, 219, 139, 236, 169, 213, 17, 32, 210, 50, 6, 5, 83, 80, 1, 111, 246, 197, 83, 166, 71, 31, 246, 234, 75, 12, 151, 227, 247, 143, 229, 95, 219, 159, 75, 174, 232, 64, 7, 102, 76, 207, 45, 143, 208, 101, 113, 175, 37, 83, 166
// The following value was generated by `encrypted_event_log_incoming_body.test.ts`
// --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data.
let event_body_ciphertext_from_typescript = [
226, 240, 253, 6, 28, 52, 19, 131, 33, 132, 178, 212, 245, 62, 14, 190, 147, 228, 160, 190, 146, 61, 95, 203, 124, 153, 68, 168, 17, 150, 92, 0, 99, 214, 85, 64, 191, 78, 157, 131, 149, 96, 236, 253, 96, 172, 157, 30, 185, 29, 14, 152, 216, 130, 219, 151, 80, 185, 43, 223, 167, 8, 89, 189, 88, 188, 101, 137, 255, 136, 84, 252, 79, 18, 52, 3, 110, 54, 54, 206, 244, 209, 246, 226, 207, 247, 143, 253, 211, 75, 160, 224, 172, 41, 45, 7, 208, 137, 90, 56, 59, 4, 234, 48, 53, 23, 130, 230, 49, 249, 142, 243, 170, 72, 183, 242, 49, 124, 46, 52, 198, 75, 55, 102, 56, 89, 254, 67, 59, 157, 249, 120, 184, 67, 154, 16, 148, 227, 93, 37, 120, 199, 93, 166, 80, 127, 173, 52, 80, 135, 87, 1, 168, 164, 51, 48, 126, 120, 47, 102, 211, 227, 234, 170, 208, 99, 111, 198, 170, 226, 156, 244, 241, 174, 206, 30
];

assert_eq(expected_event_body_ciphertext.len(), ciphertext.len());
assert_eq(event_body_ciphertext_from_typescript.len(), ciphertext.len());

for i in 0..expected_event_body_ciphertext.len() {
assert_eq(ciphertext[i], expected_event_body_ciphertext[i]);
for i in 0..event_body_ciphertext_from_typescript.len() {
assert_eq(ciphertext[i], event_body_ciphertext_from_typescript[i]);
}
}
}
16 changes: 10 additions & 6 deletions noir-projects/aztec-nr/aztec/src/encrypted_logs/outgoing_body.nr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ impl EncryptedLogOutgoingBody {
Self { eph_sk, recipient, recipient_ivpk }
}

/// Encrypts ephemeral secret key and recipient's ivpk --> with this information the recipient of outgoing will
/// be able to derive the key with which the incoming log can be decrypted.
pub fn compute_ciphertext(self, ovsk_app: Scalar, eph_pk: Point) -> [u8; 144] {
// Again, we could compute `eph_pk` here, but we keep the interface more similar
// and also make it easier to optimise it later as we just pass it along
Expand Down Expand Up @@ -68,7 +70,7 @@ mod test {
use crate::context::PrivateContext;

#[test]
fn test_encrypted_log_outgoing_body() {
fn test_encrypted_log_outgoing_body_matches_typescript() {
let eph_sk = Scalar {
lo: 0x00000000000000000000000000000000d0d302ee245dfaf2807e604eec4715fe,
hi: 0x000000000000000000000000000000000f096b423017226a18461115fa8d34bb
Expand All @@ -91,13 +93,15 @@ mod test {

let ciphertext = body.compute_ciphertext(sender_ovsk_app, eph_pk);

let expected_outgoing_body_ciphertext = [
127, 84, 96, 176, 101, 107, 236, 57, 68, 8, 53, 202, 138, 74, 186, 54, 74, 193, 245, 7, 109, 59, 218, 33, 1, 31, 205, 225, 241, 209, 64, 222, 94, 245, 4, 150, 47, 241, 187, 64, 152, 20, 102, 158, 200, 217, 213, 82, 1, 240, 170, 185, 51, 80, 27, 109, 63, 231, 235, 120, 174, 44, 133, 248, 10, 97, 60, 40, 222, 190, 147, 76, 187, 48, 91, 206, 48, 106, 56, 118, 38, 127, 82, 4, 182, 188, 44, 224, 31, 129, 47, 107, 134, 252, 20, 25, 249, 193, 215, 137, 195, 43, 98, 42, 54, 96, 254, 89, 134, 31, 103, 142, 16, 43, 92, 211, 145, 113, 217, 253, 161, 240, 121, 205, 146, 200, 168, 160, 221, 32, 229, 116, 26, 216, 86, 189, 78, 120, 10, 224, 85, 52, 40, 244
// The following value was generated by `encrypted_log_outgoing_body.test.ts`
// --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data.
let outgoing_body_ciphertext_from_typescript = [
126, 10, 214, 39, 130, 143, 96, 143, 79, 143, 22, 36, 55, 41, 234, 255, 226, 26, 138, 236, 91, 188, 204, 216, 172, 133, 134, 69, 161, 237, 134, 5, 75, 192, 10, 6, 229, 54, 194, 56, 103, 243, 57, 248, 147, 237, 4, 3, 39, 28, 226, 30, 237, 228, 212, 115, 246, 244, 105, 39, 129, 119, 126, 207, 176, 14, 75, 134, 241, 23, 2, 187, 239, 86, 47, 56, 239, 20, 92, 176, 70, 12, 219, 226, 150, 70, 192, 43, 125, 53, 230, 153, 135, 228, 210, 197, 227, 106, 242, 138, 119, 83, 182, 150, 233, 111, 9, 104, 128, 222, 85, 136, 205, 244, 77, 230, 210, 217, 223, 106, 220, 4, 115, 33, 157, 212, 217, 133, 87, 179, 67, 158, 81, 85, 226, 105, 22, 8, 154, 130, 193, 214, 144, 212
];

for i in 0..expected_outgoing_body_ciphertext.len() {
assert_eq(ciphertext[i], expected_outgoing_body_ciphertext[i]);
for i in 0..outgoing_body_ciphertext_from_typescript.len() {
assert_eq(ciphertext[i], outgoing_body_ciphertext_from_typescript[i]);
}
assert_eq(expected_outgoing_body_ciphertext.len(), ciphertext.len());
assert_eq(outgoing_body_ciphertext_from_typescript.len(), ciphertext.len());
}
}
Loading

0 comments on commit 8ed8f92

Please sign in to comment.