Skip to main content
Version: v4.0.0-nightly.20260204

Proving Historic State

This guide shows you how to prove historical state transitions and note inclusion using Aztec's Archive tree.

Prerequisites

  • An Aztec contract project set up
  • Understanding of Aztec's note and nullifier system

What you can prove

You can create proofs for these elements at any past block height:

  • Note inclusion - prove a note existed in the note hash tree
  • Note validity - prove a note existed and wasn't nullified at a specific block
  • Nullifier inclusion/non-inclusion - prove a nullifier was or wasn't in the nullifier tree
  • Contract deployment - prove a contract's bytecode was published or initialized

Common use cases:

  • Verify ownership of an asset from another contract without revealing which specific note
  • Prove eligibility based on historical state (e.g., "owned tokens at block X")
  • Claim rewards based on past contributions (see the claim contract for a complete example)

Prove note inclusion

Import the function:

history_import
use dep::aztec::history::note::assert_note_existed_by;
Source code: noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr#L5-L7

Prove a note exists in the note hash tree:

prove_note_inclusion
let header = self.context.get_anchor_block_header();
let _ = assert_note_existed_by(header, hinted_note);
Source code: noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr#L49-L52

Prove note validity

To prove a note was valid (existed AND wasn't nullified) at a historical block:

use dep::aztec::history::note::assert_note_was_valid_by;

let header = self.context.get_anchor_block_header();
assert_note_was_valid_by(header, hinted_note, &mut self.context);

This verifies both:

  1. The note was included in the note hash tree
  2. The note's nullifier was not in the nullifier tree

Prove at a specific historical block

To prove against state at a specific past block (not just the anchor block):

use dep::aztec::history::note::assert_note_existed_by;

let historical_header = self.context.get_block_header_at(block_number);
assert_note_existed_by(historical_header, hinted_note);
warning

Using get_block_header_at adds ~3k constraints to prove Archive tree membership. The anchor block header is effectively free since it's verified once per transaction.

Prove a note was nullified

To prove a note has been spent/nullified:

use dep::aztec::history::note::assert_note_was_nullified_by;

let header = self.context.get_anchor_block_header();
assert_note_was_nullified_by(header, confirmed_note, &mut self.context);

Prove contract bytecode was published

To prove a contract's bytecode was published at a historical block:

use dep::aztec::history::deployment::assert_contract_bytecode_was_published_by;

let header = self.context.get_anchor_block_header();
assert_contract_bytecode_was_published_by(header, contract_address);

You can also prove a contract was initialized (constructor was called):

use dep::aztec::history::deployment::assert_contract_was_initialized_by;

let header = self.context.get_anchor_block_header();
assert_contract_was_initialized_by(header, contract_address);

Available proof functions

The aztec::history module provides these functions:

FunctionModulePurpose
assert_note_existed_byhistory::noteProve note exists in note hash tree
assert_note_was_valid_byhistory::noteProve note exists and is not nullified
assert_note_was_nullified_byhistory::noteProve note's nullifier is in nullifier tree
assert_note_was_not_nullified_byhistory::noteProve note's nullifier is not in nullifier tree
assert_nullifier_existed_byhistory::nullifierProve a raw nullifier exists
assert_nullifier_did_not_exist_byhistory::nullifierProve a raw nullifier does not exist
assert_contract_bytecode_was_published_byhistory::deploymentProve a contract's bytecode was published
assert_contract_bytecode_was_not_published_byhistory::deploymentProve a contract's bytecode was not published
assert_contract_was_initialized_byhistory::deploymentProve a contract was initialized
assert_contract_was_not_initialized_byhistory::deploymentProve a contract was not initialized