A demonstration of creating private notes in Aztec smart contracts with controlled randomness, enabling verification that computed note hashes match on-chain note hashes. This project showcases the complete note hash computation chain in Aztec v5.
This project implements:
- Custom UintNote: A note type with controllable randomness (default: 6969) for reproducible hash computation
- Note Hash Computation: Scripts demonstrating the v5 note hash formula
- Hash Verification: Tests that verify computed unique note hashes match on-chain hashes
Aztec Version: 5.0.0-rc.1
The v5 note hash is computed in stages, following the partial-note pattern:
1. commitment = poseidon2([owner, randomness], DOM_SEP__PARTIAL_NOTE_COMMITMENT)
2. note_hash = poseidon2([storage_slot, commitment, value], DOM_SEP__NOTE_HASH)
3. siloed_note_hash = poseidon2([contract_address, note_hash], DOM_SEP__SILOED_NOTE_HASH)
4. unique_note_hash = poseidon2([nonce, siloed_note_hash], DOM_SEP__UNIQUE_NOTE_HASH)
The unique note hash is what gets stored on-chain in the note hash tree.
To set the correct Aztec version:
aztec-up 5.0.0-rc.1.
├── sample-contract/ # Aztec smart contract
│ ├── src/main.nr # GettingStarted contract with note creation
│ └── Nargo.toml # Contract configuration
├── uint-note/ # Custom UintNote library with controlled randomness
│ ├── src/uint_note.nr # UintNote with create_note_with_randomness function
│ └── Nargo.toml # Library configuration
├── contract/
│ └── artifacts/ # Generated TypeScript bindings
├── scripts/ # TypeScript utilities
│ └── generate_data.ts # Creates notes and verifies hash computation
├── tests/ # Integration tests
│ └── note_creation.test.ts # Hash verification test suite
├── package.json # Node.js package configuration
├── tsconfig.json # TypeScript configuration
├── jest.config.js # Jest test configuration
└── data.json # Generated note hash data (created by `yarn data`)
yarn installbash -i <(curl -s https://install.aztec.network)aztec-up 5.0.0-rc.1yarn cccThis command:
- Compiles the Aztec contract using
aztec compile - Generates TypeScript bindings in
contract/artifacts/
Start the local Aztec network:
aztec start --local-networkKeep this running in a separate terminal. The network runs at http://localhost:8080.
yarn testThe tests will:
- Deploy the GettingStarted contract
- Create a note with a known value and fixed randomness (6969)
- Compute the expected note hash using the v5 formula
- Verify that the computed hash matches the on-chain hash
For a fresh setup, run these commands in order:
# 1. Install dependencies
yarn install
# 2. Setup Aztec
aztec-up 5.0.0-rc.1
# 3. Compile contract and generate TypeScript bindings
yarn ccc
# 4. Start local network (in a new terminal)
aztec start --local-network
# 5. Run tests (in original terminal)
yarn testThe project includes a comprehensive test suite for note hash verification:
yarn testThe test suite (tests/note_creation.test.ts) includes:
- Contract deployment verification
- Note creation with fixed randomness
- Note hash computation verification (computed hash matches on-chain)
- Multiple note creation with different values
The GettingStarted contract creates notes with a fixed randomness value:
global NOTE_RANDOMNESS: Field = 6969;
#[external("private")]
fn create_note_for_user(value: u128) {
let note = UintNote::new(value);
create_note_with_randomness(
self.context,
self.context.msg_sender().unwrap(),
1, // storage_slot
note,
NOTE_RANDOMNESS,
);
}The custom UintNote library provides create_note_with_randomness which bypasses the default random() call, enabling deterministic hash computation:
pub fn create_note_with_randomness<Note>(
context: &mut PrivateContext,
owner: AztecAddress,
storage_slot: Field,
note: Note,
randomness: Field,
) where
Note: NoteType + NoteHash + Packable,
{
let note_hash = note.compute_note_hash(owner, storage_slot, randomness);
notify_created_note(owner, storage_slot, randomness, ...);
context.push_note_hash(note_hash);
}The test computes the same hash off-chain and verifies it matches:
// v5 hash computation (partial-note pattern)
const commitment = await poseidon2HashWithSeparator(
[owner, randomness],
DOM_SEP__PARTIAL_NOTE_COMMITMENT
);
const noteHash = await poseidon2HashWithSeparator(
[storage_slot, commitment, value],
DOM_SEP__NOTE_HASH
);
// Complete the chain: note_hash -> siloed -> unique
const siloedNoteHash = await siloNoteHash(contractAddress, noteHash);
const uniqueNoteHash = await computeUniqueNoteHash(nonce, siloedNoteHash);
// Verify against on-chain
const txReceipt = await node.getTxReceipt(txHash, { includeTxEffect: true });
expect(uniqueNoteHash).toBe(txReceipt.txEffect.noteHashes[0]);-
"Cannot find module './contract/artifacts/GettingStarted'"
- Run
yarn cccto generate the contract artifacts
- Run
-
"Failed to connect to PXE" or Network Errors
- Ensure the Aztec local network is running:
aztec start --local-network - Check it's accessible at
http://localhost:8080
- Ensure the Aztec local network is running:
-
TypeScript/Module errors
- Run
yarn installto ensure all dependencies are installed - Recompile with
yarn ccc
- Run
If you encounter issues, try a clean rebuild:
# Remove generated files
rm -rf sample-contract/target uint-note/target contract/artifacts node_modules
# Rebuild everything
yarn install
yarn ccc
yarn testyarn ccc: Compile contract and generate TypeScript artifactsyarn test: Run integration test suiteyarn data: Generate note data and verify hash computationyarn clean: Remove generated data files
- recursive_verification - Demonstrates Noir proof verification in Aztec contracts