Skip to main content
Version: v3.0.0-nightly.20260101

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 was deployed 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 trait:

history_import
use dep::aztec::history::note_inclusion::ProveNoteInclusion;
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();
header.prove_note_inclusion(proof_retrieved_note);
Source code: noir-projects/noir-contracts/contracts/app/claim_contract/src/main.nr#L55-L58

Prove note validity

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

use dep::aztec::history::note_validity::ProveNoteValidity;

let header = self.context.get_anchor_block_header();
header.prove_note_validity(retrieved_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):

let historical_header = self.context.get_block_header_at(block_number);
historical_header.prove_note_inclusion(retrieved_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::nullifier_inclusion::ProveNoteIsNullified;

let header = self.context.get_anchor_block_header();
header.prove_note_is_nullified(retrieved_note, &mut self.context);

Prove contract deployment

To prove a contract was deployed at a historical block:

use dep::aztec::history::contract_inclusion::ProveContractDeployment;

let header = self.context.get_anchor_block_header();
header.prove_contract_deployment(contract_address);

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

use dep::aztec::history::contract_inclusion::ProveContractInitialization;

let header = self.context.get_anchor_block_header();
header.prove_contract_initialization(contract_address);

Available proof traits

The aztec::history module provides these traits:

TraitPurpose
ProveNoteInclusionProve note exists in note hash tree
ProveNoteValidityProve note exists and is not nullified
ProveNoteIsNullifiedProve note's nullifier is in nullifier tree
ProveNoteNotNullifiedProve note's nullifier is not in nullifier tree
ProveNullifierInclusionProve a raw nullifier exists
ProveNullifierNonInclusionProve a raw nullifier does not exist
ProveContractDeploymentProve a contract was deployed
ProveContractNonDeploymentProve a contract was not deployed
ProveContractInitializationProve a contract was initialized
ProveContractNonInitializationProve a contract was not initialized