Skip to content

Commit

Permalink
feat: Recursive fn calls to spend more notes. (#1779)
Browse files Browse the repository at this point in the history
Closes #1422 

Also fixed a bug where we allowed any sender to spend notes.
  • Loading branch information
superstar0402 committed Aug 25, 2023
1 parent f5546b3 commit aad25c6
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl EasyPrivateUint {
}
}

// Very similar to `send_note`.
// Very similar to `value_note::utils::increment`.
fn add(
self,
context: &mut PrivateContext,
Expand All @@ -57,7 +57,7 @@ impl EasyPrivateUint {
);
}

// Very similar to `spend_note`.
// Very similar to `value_note::utils::decrement`.
fn sub(
self,
context: &mut PrivateContext,
Expand Down
232 changes: 90 additions & 142 deletions yarn-project/noir-libs/value-note/src/utils.nr
Original file line number Diff line number Diff line change
@@ -1,184 +1,132 @@
use dep::std::option::Option;
use dep::aztec::context::PrivateContext;
// docs:start:encrypted_import

use dep::aztec::log::emit_encrypted_log;

// docs:end:encrypted_import
use dep::aztec::note::note_getter_options::NoteGetterOptions;
use dep::aztec::note::note_getter_options::{NoteGetterOptions, SortOrder};
use dep::aztec::oracle::get_public_key::get_public_key;
use dep::aztec::state_vars::set::Set;
use dep::aztec::types::point::Point;
use crate::{
filter::filter_notes_min_sum,
value_note::{ValueNote, VALUE_NOTE_LEN},
};

fn spend_notes(
// Sort the note values (0th field) in descending order.
// Pick the fewest notes whose sum is equal to or greater than `amount`.
fn create_note_getter_options_for_decreasing_balance(amount: Field) -> NoteGetterOptions<ValueNote, VALUE_NOTE_LEN, Field> {
NoteGetterOptions::with_filter(filter_notes_min_sum, amount).sort(0, SortOrder.DESC)
}

// Creates a new note for the recipient.
// Inserts it to the recipient's set of notes.
fn increment(
context: &mut PrivateContext,
balance: Set<ValueNote, VALUE_NOTE_LEN>,
amount: Field,
owner: Field,
recipient: Field,
) {
let options = NoteGetterOptions::with_filter(filter_notes_min_sum, amount);
let opt_notes = balance.get_notes(context, options);

let mut sum = 0;
for i in 0..opt_notes.len() {
if opt_notes[i].is_some() {
let note = opt_notes[i].unwrap_unchecked();

// Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while
// spending someone else's notes).
assert(note.owner == owner);

// Removes the note from the owner's set of notes.
balance.remove(context, note);

sum += note.value;
}
}

assert(sum as u120 >= amount as u120);

// Creates change note for the owner.
let change_value = sum - amount;
let mut change_note = ValueNote::new(change_value, owner);
balance.insert(context, &mut change_note);

// Emit the newly created encrypted note preimages via oracle calls.
let mut encrypted_data = [0; VALUE_NOTE_LEN];
if change_value != 0 {
encrypted_data = change_note.serialise();
};
let mut note = ValueNote::new(amount, recipient);
create_note(context, balance, recipient, &mut note);

let encryption_pub_key = get_public_key(owner);
emit_encrypted_log(
context,
(*context).this_address(),
balance.storage_slot,
encryption_pub_key,
encrypted_data,
);
// It won't compile if Set.insert() is in an if statement :(
// if amount as u120 > 0 {
// create_note(context, balance, recipient, &mut note);
// }
}

/*
Spends one note from a set of 4 notes.
Details: Reads 4 notes from the user's `balance`: [n_{o}, n_{o+1}, n_{o+2}, n_{o+3}]
where "o" is the note_offset. Then, spends the note: [n_{o + i}]
where "i" is the spend_note_index. This gives more control to the user on which
note is to be spent.
*/
fn spend_one_note(
// Find some of the `owner`'s notes whose values add up to the `amount`.
// Remove those notes.
// If the value of the removed notes exceeds the requested `amount`, create a new note containing the excess value, so that exactly `amount` is removed.
// Fail if the sum of the selected notes is less than the amount.
fn decrement(
context: &mut PrivateContext,
balance: Set<ValueNote, VALUE_NOTE_LEN>,
amount: Field,
owner: Field,
spend_note_offset: u32,
) {
let options = NoteGetterOptions::new().set_limit(1).set_offset(spend_note_offset);
let opt_notes = balance.get_notes(context, options);

// The note should always exist.
let note = opt_notes[0].unwrap();

// Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while
// spending someone else's notes).
assert(owner == note.owner);

// Removes the note from the owner's set of notes.
balance.remove(context, note);

let note_value = note.value;

// Assert that the note chosen to spend has enough funds.
assert(note_value as u64 >= amount as u64);

// Creates change note for the owner.
let change_value = note_value - amount;
let mut change_note = ValueNote::new(change_value, owner);

// Insert the change note to the owner's sets of notes.
balance.insert(context, &mut change_note);

// Emit the newly created encrypted note preimages via oracle calls.
let mut encrypted_data = [0; VALUE_NOTE_LEN];
if change_value != 0 {
encrypted_data = change_note.serialise();
};

let encryption_pub_key = get_public_key(owner);
emit_encrypted_log(
context,
context.inputs.call_context.storage_contract_address,
balance.storage_slot,
encryption_pub_key,
encrypted_data,
);
let sum = decrement_by_at_most(context, balance, amount, owner);
assert(sum == amount);
}

fn send_note(
// Similar to `decrement`, except that it doesn't fail if the decremented amount is less than max_amount.
// The motivation behind this function is that there is an upper-bound on the number of notes a function may
// read and nullify. The requested decrementation `amount` might be spread across too many of the `owner`'s
// notes to 'fit' within this upper-bound, so we might have to remove an amount less than `amount`. A common
// pattern is to repeatedly call this function across many function calls, until enough notes have been nullified to
// equal `amount`.
//
// It returns the decremented amount, which should be less than or equal to max_amount.
fn decrement_by_at_most(
context: &mut PrivateContext,
balance: Set<ValueNote, VALUE_NOTE_LEN>,
amount: Field,
recipient: Field,
) {
// Creates new note for the recipient.
let mut note = ValueNote::new(amount, recipient);

// Insert the new note to the recipient's set of notes.
balance.insert(context, &mut note);

// Emit the newly created encrypted note preimages via oracle calls.
// docs:start:encrypted
max_amount: Field,
owner: Field,
) -> Field {
let options = create_note_getter_options_for_decreasing_balance(max_amount);
let opt_notes = balance.get_notes(context, options);

let application_contract_address = (*context).this_address();
let note_storage_slot = balance.storage_slot;
let encryption_pub_key = get_public_key(recipient);
let encrypted_data = note.serialise();
let mut decremented = 0;
for i in 0..opt_notes.len() {
if opt_notes[i].is_some() {
decremented += destroy_note(context, balance, owner, opt_notes[i].unwrap_unchecked());
}
}

emit_encrypted_log(
context,
application_contract_address,
note_storage_slot,
encryption_pub_key,
encrypted_data,
);
// Add the change value back to the owner's balance.
let mut change_value = 0;
if decremented as u120 > max_amount as u120 {
change_value = decremented - max_amount;
decremented -= change_value;
}
increment(context, balance, change_value, owner);

// docs:end:encrypted
decremented
}

/*
Sends three amounts to three recipients.
Why three? Because one private call currently allows `MAX_NEW_COMMITMENTS_PER_CALL = 4` output commitments.
So we split the output notes as: 3 to recipients + 1 to the owner (the change note).
*/
fn send_notes<NUM_RECIPIENTS>(
fn create_note(
context: &mut PrivateContext,
recipient_balances: [Set<ValueNote, VALUE_NOTE_LEN>; NUM_RECIPIENTS],
amounts: [Field; NUM_RECIPIENTS],
recipients: [Field; NUM_RECIPIENTS],
balance: Set<ValueNote, VALUE_NOTE_LEN>,
owner: Field,
note: &mut ValueNote,
) {
for i in 0..recipients.len() {
// Creates a new note for the i-th recipients
let mut recipient_note = ValueNote::new(amounts[i], recipients[i]);

// Insert the new notes to the i-th recipient's sets of notes.
recipient_balances[i].insert(context, &mut recipient_note);

// Get recipient encryption keys.
let recipient_encryption_pub_key = get_public_key(recipients[i]);
// Insert the new note to the owner's set of notes.
balance.insert(context, note);

// Remove this if statement if we can wrap this create_note function in an if statement.
if note.value != 0 {
// Emit the newly created encrypted note preimages via oracle calls.
let mut recipient_encrypted_data = [0; VALUE_NOTE_LEN];
if recipient_note.value != 0 {
recipient_encrypted_data = recipient_note.serialise();
};
// docs:start:encrypted
let application_contract_address = (*context).this_address();
let note_storage_slot = balance.storage_slot;
let encryption_pub_key = get_public_key(owner);
let encrypted_data = (*note).serialise();

emit_encrypted_log(
context,
context.inputs.call_context.storage_contract_address,
recipient_balances[i].storage_slot,
recipient_encryption_pub_key,
recipient_encrypted_data,
application_contract_address,
note_storage_slot,
encryption_pub_key,
encrypted_data,
);
// docs:end:encrypted
}
}
}

// Removes the note from the owner's set of notes.
// Returns the value of the destroyed note.
fn destroy_note(
context: &mut PrivateContext,
balance: Set<ValueNote, VALUE_NOTE_LEN>,
owner: Field,
note: ValueNote,
) -> Field {
// Ensure the note is actually owned by the owner (to prevent user from generating a valid proof while
// spending someone else's notes).
assert(note.owner == owner);

balance.remove(context, note);

note.value
}

0 comments on commit aad25c6

Please sign in to comment.