Skip to content
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

feat: pxe.addNullifiedNote(...) #6948

Merged
merged 15 commits into from
Jun 11, 2024
2 changes: 1 addition & 1 deletion boxes/boxes/react/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ export class PrivateEnv {

export const deployerEnv = new PrivateEnv(SECRET_KEY, process.env.PXE_URL || 'http://localhost:8080');

const IGNORE_FUNCTIONS = ['constructor', 'compute_note_hash_and_nullifier'];
const IGNORE_FUNCTIONS = ['constructor', 'compute_note_hash_and_optionally_a_nullifier'];
export const filteredInterface = BoxReactContractArtifact.functions.filter(f => !IGNORE_FUNCTIONS.includes(f.name));
2 changes: 1 addition & 1 deletion docs/docs/getting_started/aztecnr-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ The `increment` function works very similarly to the `constructor`, but instead

## Prevent double spending

Because our counters are private, the network can't directly verify if a note was spent or not, which could lead to double-spending. To solve this, we use a nullifier - a unique identifier generated from each spent note and its nullifier key. Although this isn't really an issue in this simple smart contract, Aztec injects a special function called `compute_note_hash_and_nullifier` to determine these values for any given note produced by this contract.
Because our counters are private, the network can't directly verify if a note was spent or not, which could lead to double-spending. To solve this, we use a nullifier - a unique identifier generated from each spent note and its nullifier key. Although this isn't really an issue in this simple smart contract, Aztec injects a special function called `compute_note_hash_and_optionally_a_nullifier` to determine these values for any given note produced by this contract.

## Getting a counter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ If the decryption is successful, the PXE will store the decrypted note inside a
If the decryption fails, the specific log will be discarded.

For the PXE to successfully process the decrypted note we need to compute the note's 'note hash' and 'nullifier'.
Aztec.nr enables smart contract developers to design custom notes, meaning developers can also customize how a note's note hash and nullifier should be computed. Because of this customizability, and because there will be a potentially-unlimited number of smart contracts deployed to Aztec, an PXE needs to be 'taught' how to compute the custom note hashes and nullifiers for a particular contract. This is done by a function called `compute_note_hash_and_nullifier`, which is automatically injected into every contract when compiled.
Aztec.nr enables smart contract developers to design custom notes, meaning developers can also customize how a note's note hash and nullifier should be computed. Because of this customizability, and because there will be a potentially-unlimited number of smart contracts deployed to Aztec, an PXE needs to be 'taught' how to compute the custom note hashes and nullifiers for a particular contract. This is done by a function called `compute_note_hash_and_optionally_a_nullifier`, which is automatically injected into every contract when compiled.

## Encrypted Events

Expand Down
8 changes: 4 additions & 4 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ library Constants {
uint256 internal constant DA_GAS_PER_BYTE = 16;
uint256 internal constant FIXED_DA_GAS = 512;
uint256 internal constant CANONICAL_KEY_REGISTRY_ADDRESS =
9735143693259978736521448915549382765209954358646272896519366195578572330622;
2153455745675440165069577621832684870696142028027528497509357256345838682961;
uint256 internal constant DEPLOYER_CONTRACT_ADDRESS =
1330791240588942273989478952163154931941860232471291360599950658792066893795;
19511485909966796736993840362353440247573331327062358513665772226446629198132;
uint256 internal constant REGISTERER_CONTRACT_ADDRESS =
12230492553436229472833564540666503591270810173190529382505862577652523721217;
21791696151759019003097706094037044371210776294983020497737005968946992649239;
uint256 internal constant GAS_TOKEN_ADDRESS =
21054354231481372816168706751151469079551620620213512837742215289221210616379;
3159976153131520272419617514531889581796079438158800470341967144801191524489;
uint256 internal constant AZTEC_ADDRESS_LENGTH = 1;
uint256 internal constant GAS_FEES_LENGTH = 2;
uint256 internal constant GAS_LENGTH = 2;
Expand Down
17 changes: 11 additions & 6 deletions noir-projects/aztec-nr/aztec/src/note/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ pub fn compute_note_hash_for_consumption<Note, N, M>(note: Note) -> Field where
}
}

pub fn compute_note_hash_and_nullifier<T, N, M, S>(
// docs:start:compute_note_hash_and_nullifier_args
pub fn compute_note_hash_and_optionally_a_nullifier<T, N, M, S>(
// docs:start:compute_note_hash_and_optionally_a_nullifier_args
deserialize_content: fn([Field; N]) -> T,
note_header: NoteHeader,
serialized_note: [Field; S] // docs:end:compute_note_hash_and_nullifier_args
compute_nullifier: bool,
serialized_note: [Field; S] // docs:end:compute_note_hash_and_optionally_a_nullifier_args
) -> [Field; 4] where T: NoteInterface<N, M> {
let mut note = deserialize_content(arr_copy_slice(serialized_note, [0; N], 0));
// TODO: change this to note.set_header(header) once https://github.com/noir-lang/noir/issues/4095 is fixed
Expand All @@ -126,8 +127,12 @@ pub fn compute_note_hash_and_nullifier<T, N, M, S>(

let siloed_note_hash = compute_siloed_hash(note_header.contract_address, unique_note_hash);

let inner_nullifier = note.compute_nullifier_without_context();
// docs:start:compute_note_hash_and_nullifier_returns
let inner_nullifier = if compute_nullifier {
note.compute_nullifier_without_context()
} else {
0
};
// docs:start:compute_note_hash_and_optionally_a_nullifier_returns
[inner_note_hash, unique_note_hash, siloed_note_hash, inner_nullifier]
// docs:end:compute_note_hash_and_nullifier_returns
// docs:end:compute_note_hash_and_optionally_a_nullifier_returns
}
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/prelude.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
note::{
note_header::NoteHeader, note_interface::NoteInterface, note_getter_options::NoteGetterOptions,
note_viewer_options::NoteViewerOptions,
utils::compute_note_hash_and_nullifier as utils_compute_note_hash_and_nullifier
utils::compute_note_hash_and_optionally_a_nullifier as utils_compute_note_hash_and_optionally_a_nullifier
}
};
// docs:end:prelude
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ global DA_GAS_PER_BYTE: u32 = 16;
global FIXED_DA_GAS: u32 = 512;

// CANONICAL CONTRACT ADDRESSES
global CANONICAL_KEY_REGISTRY_ADDRESS = 0x1585e564a60e6ec974bc151b62705292ebfc75c33341986a47fd9749cedb567e;
global DEPLOYER_CONTRACT_ADDRESS = 0x02f1337e8c79dd0247ccbde85241ad65ee991ae283a63479e095e51f0abbc7e3;
global REGISTERER_CONTRACT_ADDRESS = 0x1b0a36a60d2a1a358a328feb38cb38244429d8f57d25510083795d0491e1e201;
global GAS_TOKEN_ADDRESS = 0x2e8c579a24417ffdfe9e28139023ed46748e64508e20d7d63163ef7a0732b23b;
global CANONICAL_KEY_REGISTRY_ADDRESS = 0x04c2d010f88e8c238882fbbcbce5c81fdc1dc8ece85e8dbf3f602b4d81ec0351;
global DEPLOYER_CONTRACT_ADDRESS = 0x2b231c13768709b1ba51c1f86275b47e38dfac16e3d7f242cb578d92a4e2d934;
global REGISTERER_CONTRACT_ADDRESS = 0x302da9b6000a76691341b250565ca5c67723261fa99af1435ffe5178ccb21417;
global GAS_TOKEN_ADDRESS = 0x06fc7badd50bb8ee32439b52e8874b5a16ddd2aa1d5647ec46b2a0f51356f889;

// LENGTH OF STRUCTS SERIALIZED TO FIELDS
global AZTEC_ADDRESS_LENGTH = 1;
Expand Down
4 changes: 2 additions & 2 deletions noir/noir-repo/aztec_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod utils;

use noirc_errors::Location;
use transforms::{
compute_note_hash_and_nullifier::inject_compute_note_hash_and_nullifier,
compute_note_hash_and_optionally_a_nullifier::inject_compute_note_hash_and_optionally_a_nullifier,
contract_interface::{
generate_contract_interface, stub_function, update_fn_signatures_in_contract_interface,
},
Expand Down Expand Up @@ -236,7 +236,7 @@ fn transform_hir(
) -> Result<(), (AztecMacroError, FileId)> {
if has_aztec_dependency(crate_id, context) {
transform_events(crate_id, context)?;
inject_compute_note_hash_and_nullifier(crate_id, context)?;
inject_compute_note_hash_and_optionally_a_nullifier(crate_id, context)?;
assign_storage_slots(crate_id, context)?;
inject_note_exports(crate_id, context)?;
update_fn_signatures_in_contract_interface(crate_id, context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,26 @@ use crate::utils::{
},
};

// Check if "compute_note_hash_and_nullifier(AztecAddress,Field,Field,Field,[Field; N]) -> [Field; 4]" is defined
fn check_for_compute_note_hash_and_nullifier_definition(
// Check if "compute_note_hash_and_optionally_a_nullifier(AztecAddress,Field,Field,Field,bool,[Field; N]) -> [Field; 4]" is defined
fn check_for_compute_note_hash_and_optionally_a_nullifier_definition(
crate_id: &CrateId,
context: &HirContext,
) -> bool {
collect_crate_functions(crate_id, context).iter().any(|funct_id| {
let func_data = context.def_interner.function_meta(funct_id);
let func_name = context.def_interner.function_name(funct_id);
func_name == "compute_note_hash_and_nullifier"
&& func_data.parameters.len() == 5
func_name == "compute_note_hash_and_optionally_a_nullifier"
Thunkar marked this conversation as resolved.
Show resolved Hide resolved
&& func_data.parameters.len() == 6
&& func_data.parameters.0.first().is_some_and(| (_, typ, _) | match typ {
Type::Struct(struct_typ, _) => struct_typ.borrow().name.0.contents == "AztecAddress",
_ => false
})
&& func_data.parameters.0.get(1).is_some_and(|(_, typ, _)| typ.is_field())
&& func_data.parameters.0.get(2).is_some_and(|(_, typ, _)| typ.is_field())
&& func_data.parameters.0.get(3).is_some_and(|(_, typ, _)| typ.is_field())
// checks if the 5th parameter is an array and contains only fields
&& func_data.parameters.0.get(4).is_some_and(|(_, typ, _)| match typ {
&& func_data.parameters.0.get(4).is_some_and(|(_, typ, _)| typ.is_bool())
// checks if the 6th parameter is an array and contains only fields
&& func_data.parameters.0.get(5).is_some_and(|(_, typ, _)| match typ {
Type::Array(_, inner_type) => inner_type.to_owned().is_field(),
_ => false
})
Expand All @@ -49,16 +50,16 @@ fn check_for_compute_note_hash_and_nullifier_definition(
})
}

pub fn inject_compute_note_hash_and_nullifier(
pub fn inject_compute_note_hash_and_optionally_a_nullifier(
crate_id: &CrateId,
context: &mut HirContext,
) -> Result<(), (AztecMacroError, FileId)> {
if let Some((_, module_id, file_id)) = get_contract_module_data(context, crate_id) {
// If compute_note_hash_and_nullifier is already defined by the user, we skip auto-generation in order to provide an
// If compute_note_hash_and_optionally_a_nullifier is already defined by the user, we skip auto-generation in order to provide an
// escape hatch for this mechanism.
// TODO(#4647): improve this diagnosis and error messaging.
if context.crate_graph.root_crate_id() != crate_id
|| check_for_compute_note_hash_and_nullifier_definition(crate_id, context)
|| check_for_compute_note_hash_and_optionally_a_nullifier_definition(crate_id, context)
{
return Ok(());
}
Expand All @@ -69,14 +70,14 @@ pub fn inject_compute_note_hash_and_nullifier(
let max_note_length_const = get_global_numberic_const(context, "MAX_NOTE_FIELDS_LENGTH")
.map_err(|err| {
(
AztecMacroError::CouldNotImplementComputeNoteHashAndNullifier {
AztecMacroError::CouldNotImplementComputeNoteHashAndOptionallyANullifier {
secondary_message: Some(err.primary_message),
},
file_id,
)
})?;

// In order to implement compute_note_hash_and_nullifier, we need to know all of the different note types the
// In order to implement compute_note_hash_and_optionally_a_nullifier, we need to know all of the different note types the
// contract might use and their serialized lengths. These are the types that are marked as #[aztec(note)].
let mut notes_and_lengths = vec![];

Expand All @@ -89,7 +90,7 @@ pub fn inject_compute_note_hash_and_nullifier(
)
.map_err(|_err| {
(
AztecMacroError::CouldNotImplementComputeNoteHashAndNullifier {
AztecMacroError::CouldNotImplementComputeNoteHashAndOptionallyANullifier {
secondary_message: Some(format!(
"Failed to get serialized length for note type {}",
path
Expand All @@ -102,7 +103,7 @@ pub fn inject_compute_note_hash_and_nullifier(

if serialized_len > max_note_length_const {
return Err((
AztecMacroError::CouldNotImplementComputeNoteHashAndNullifier {
AztecMacroError::CouldNotImplementComputeNoteHashAndOptionallyANullifier {
secondary_message: Some(format!(
"Note type {} as {} fields, which is more than the maximum allowed length of {}.",
path,
Expand All @@ -120,11 +121,12 @@ pub fn inject_compute_note_hash_and_nullifier(
let max_note_length: u128 =
*notes_and_lengths.iter().map(|(_, serialized_len)| serialized_len).max().unwrap_or(&0);

let note_types =
let note_types: Vec<String> =
notes_and_lengths.iter().map(|(note_type, _)| note_type.clone()).collect::<Vec<_>>();

// We can now generate a version of compute_note_hash_and_nullifier tailored for the contract in this crate.
let func = generate_compute_note_hash_and_nullifier(&note_types, max_note_length);
// We can now generate a version of compute_note_hash_and_optionally_a_nullifier tailored for the contract in this crate.
let func =
generate_compute_note_hash_and_optionally_a_nullifier(&note_types, max_note_length);

// And inject the newly created function into the contract.

Expand All @@ -134,7 +136,7 @@ pub fn inject_compute_note_hash_and_nullifier(

inject_fn(crate_id, context, func, location, module_id, file_id).map_err(|err| {
(
AztecMacroError::CouldNotImplementComputeNoteHashAndNullifier {
AztecMacroError::CouldNotImplementComputeNoteHashAndOptionallyANullifier {
secondary_message: err.secondary_message,
},
file_id,
Expand All @@ -144,12 +146,12 @@ pub fn inject_compute_note_hash_and_nullifier(
Ok(())
}

fn generate_compute_note_hash_and_nullifier(
fn generate_compute_note_hash_and_optionally_a_nullifier(
note_types: &[String],
max_note_length: u128,
) -> NoirFunction {
let function_source =
generate_compute_note_hash_and_nullifier_source(note_types, max_note_length);
generate_compute_note_hash_and_optionally_a_nullifier_source(note_types, max_note_length);

let (function_ast, errors) = parse_program(&function_source);
if !errors.is_empty() {
Expand All @@ -161,7 +163,7 @@ fn generate_compute_note_hash_and_nullifier(
function_ast.functions.remove(0)
}

fn generate_compute_note_hash_and_nullifier_source(
fn generate_compute_note_hash_and_optionally_a_nullifier_source(
note_types: &[String],
max_note_length: u128,
) -> String {
Expand All @@ -173,12 +175,13 @@ fn generate_compute_note_hash_and_nullifier_source(
// so we include a dummy version.
format!(
"
unconstrained fn compute_note_hash_and_nullifier(
unconstrained fn compute_note_hash_and_optionally_a_nullifier(
contract_address: dep::aztec::protocol_types::address::AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
serialized_note: [Field; {}]
compute_nullifier: bool,
serialized_note: [Field; {}],
) -> pub [Field; 4] {{
assert(false, \"This contract does not use private notes\");
[0, 0, 0, 0]
Expand All @@ -191,7 +194,7 @@ fn generate_compute_note_hash_and_nullifier_source(

let if_statements: Vec<String> = note_types.iter().map(|note_type| format!(
"if (note_type_id == {0}::get_note_type_id()) {{
dep::aztec::note::utils::compute_note_hash_and_nullifier({0}::deserialize_content, note_header, serialized_note)
dep::aztec::note::utils::compute_note_hash_and_optionally_a_nullifier({0}::deserialize_content, note_header, compute_nullifier, serialized_note)
}}"
, note_type)).collect();

Expand All @@ -204,12 +207,13 @@ fn generate_compute_note_hash_and_nullifier_source(

format!(
"
unconstrained fn compute_note_hash_and_nullifier(
unconstrained fn compute_note_hash_and_optionally_a_nullifier(
contract_address: dep::aztec::protocol_types::address::AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
serialized_note: [Field; {}]
compute_nullifier: bool,
serialized_note: [Field; {}],
) -> pub [Field; 4] {{
let note_header = dep::aztec::prelude::NoteHeader::new(contract_address, nonce, storage_slot);

Expand Down
2 changes: 1 addition & 1 deletion noir/noir-repo/aztec_macros/src/transforms/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod compute_note_hash_and_nullifier;
pub mod compute_note_hash_and_optionally_a_nullifier;
pub mod contract_interface;
pub mod events;
pub mod functions;
Expand Down
6 changes: 3 additions & 3 deletions noir/noir-repo/aztec_macros/src/utils/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum AztecMacroError {
UnsupportedFunctionReturnType { span: Span, typ: ast::UnresolvedTypeData },
UnsupportedStorageType { span: Option<Span>, typ: ast::UnresolvedTypeData },
CouldNotAssignStorageSlots { secondary_message: Option<String> },
CouldNotImplementComputeNoteHashAndNullifier { secondary_message: Option<String> },
CouldNotImplementComputeNoteHashAndOptionallyANullifier { secondary_message: Option<String> },
CouldNotImplementNoteInterface { span: Option<Span>, secondary_message: Option<String> },
MultipleStorageDefinitions { span: Option<Span> },
CouldNotExportStorageLayout { span: Option<Span>, secondary_message: Option<String> },
Expand Down Expand Up @@ -57,8 +57,8 @@ impl From<AztecMacroError> for MacroError {
secondary_message,
span: None,
},
AztecMacroError::CouldNotImplementComputeNoteHashAndNullifier { secondary_message } => MacroError {
primary_message: "Could not implement compute_note_hash_and_nullifier automatically, please provide an implementation".to_string(),
AztecMacroError::CouldNotImplementComputeNoteHashAndOptionallyANullifier { secondary_message } => MacroError {
primary_message: "Could not implement compute_note_hash_and_optionally_a_nullifier automatically, please provide an implementation".to_string(),
secondary_message,
span: None,
},
Expand Down
4 changes: 4 additions & 0 deletions noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,10 @@ impl Type {
matches!(self.follow_bindings(), Type::FieldElement)
}

pub fn is_bool(&self) -> bool {
matches!(self.follow_bindings(), Type::Bool)
}

pub fn is_signed(&self) -> bool {
matches!(self.follow_bindings(), Type::Integer(Signedness::Signed, _))
}
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ export abstract class BaseWallet implements Wallet {
addNote(note: ExtendedNote): Promise<void> {
return this.pxe.addNote(note);
}
addNullifiedNote(note: ExtendedNote): Promise<void> {
return this.pxe.addNullifiedNote(note);
}
getBlock(number: number): Promise<L2Block | undefined> {
return this.pxe.getBlock(number);
}
Expand Down
10 changes: 10 additions & 0 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,16 @@ export interface PXE {
*/
addNote(note: ExtendedNote): Promise<void>;

/**
* Adds a nullified note to the database.
* @throws If the note hash of the note doesn't exist in the tree.
* @param note - The note to add.
* @dev We are not deriving a nullifier in this function since that would require having the nullifier secret key
* which is undesirable. Instead, we are just adding the note to the database as nullified and the nullifier is set
* to 0 in the db.
*/
addNullifiedNote(note: ExtendedNote): Promise<void>;

/**
* Get the given block.
* @param number - The block number being requested.
Expand Down
Loading
Loading