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

Replace ConsumedNoteInfo with Nullifier #360

Merged
merged 1 commit into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 33 additions & 12 deletions miden-lib/asm/miden/sat/internal/prologue.masm
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ end
#! Process the consumed notes data provided via the advice provider. This involves reading the data
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
#! from the advice provider and storing it at the appropriate memory addresses. As each note is
#! consumed its hash and nullifier is computed. The transaction nullifier commitment is computed
#! via a sequential hash of all (nullifier, script_root) pairs for all consumed notes.
#! via a sequential hash of all (nullifier, ZERO) pairs for all consumed notes.
#!
#! Stack: []
#! Advice stack: [num_cn,
Expand All @@ -679,78 +679,99 @@ end
proc.process_consumed_notes_data
# load the consumed notes data onto the advice stack
exec.layout::get_nullifier_com adv.push_mapval dropw
# => [...]
bobbinth marked this conversation as resolved.
Show resolved Hide resolved

# read the number of consumed notes from the advice provider
adv_push.1
# => [num_notes, ...]

# store the number of consumed notes
dup exec.layout::set_total_num_consumed_notes
# => [num_notes, ...]

# assert the number of consumed notes is within limits
dup exec.constants::get_max_num_consumed_notes lte assert
# assert the number of consumed notes is within limits; since max number of consumed notes is
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
# expected to be smaller than 2^32, we can use a more efficient u32 comparison
dup exec.constants::get_max_num_consumed_notes u32assert2 u32lte assert
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
# => [num_notes, ...]

# loop over consumed notes and read data
# ---------------------------------------------------------------------------------------------

# initialize counter for consumed notes
# initialize counter of already processed notes
push.0
# => [num_processed_notes = 0, num_notes, ...]

# check if the number of consumed notes is greater then 0. Conditional for the while loop.
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
dup.1 dup.1 neq
# => [has_more_notes, num_processed_notes, num_notes, ...]

# loop and read note data from the advice provider
while.true
dup exec.process_consumed_note
# => [num_processed_notes, num_notes, ...]

# increment consumed note counter and check if we should loop again
# increment processed note counter and check if we should loop again
add.1 dup.1 dup.1 neq
# => [has_more_notes, num_processed_notes + 1, num_notes, ...]
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
end

# drop counter
drop
# => [num_notes, ...]

# compute nullifier commitment
# ---------------------------------------------------------------------------------------------

# initiate counter for nullifiers
# initiate counter of notes processed for nullifier hashing
push.0
# => [num_processed_notes = 0, num_notes, ...]

# initiate stack for sequential hash to compute nullifier commitment
padw padw padw
# => [R1, R0, CAP, num_processed_notes, num_notes, ...]

# check if the number of consumed notes is greater then 0. Conditional for the while loop.
dup.13 dup.13 neq
# => [has_more_notes, R1, R0, CAP, num_processed_notes, num_notes, ...]

# loop and sequentially hash hperm(nullifier, script_root) over all consumed notes
# loop and sequentially hash hperm(nullifier, ZERO) over all consumed notes
while.true
# clear hasher rate
dropw dropw
# => [CAP, num_processed_notes, num_notes, ...]

# get consumed note nullifier
dup.4 exec.layout::get_consumed_note_nullifier
# => [NULLIFIER, CAP, num_processed_notes, num_notes, ...]

# get consumed note script root
dup.8 exec.layout::get_consumed_note_ptr exec.layout::get_consumed_note_script_root
# pad the stack
padw
# => [ZERO, NULLIFIER, CAP, num_processed_notes, num_notes, ...]

# compute hperm(nullifier, script_root)
# compute hperm(nullifier, ZERO)
hperm
# => [PERM, PERM, CAP, num_processed_notes, num_notes, ...]

# increment nullifier counter and check if we should loop again
# increment processed note counter and check if we should loop again
movup.12 add.1 dup movdn.13 dup.14 neq
# => [has_more_notes, PERM, PERM, CAP, num_processed_notes + 1, num_notes, ...]
end

# extract nullifier hash
dropw swapw dropw
# => [NULLIFIER_COM, num_processed_notes + 1, num_notes, ...]

# assert nullifier hash is what we would expect
exec.layout::get_nullifier_com assert_eqw
# => [num_processed_notes + 1, num_notes, ...]

# clear stack
drop drop
# => [...]

# set the current consumed note pointer to the first consumed note
push.0 exec.layout::get_consumed_note_ptr exec.layout::set_current_consumed_note_ptr
# => []
# => [...]
end

# TRANSACTION SCRIPT
Expand Down
13 changes: 7 additions & 6 deletions miden-tx/src/verifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use miden_lib::outputs::{
CREATED_NOTES_COMMITMENT_WORD_IDX, FINAL_ACCOUNT_HASH_WORD_IDX, TX_SCRIPT_ROOT_WORD_IDX,
};
use miden_objects::{
notes::NoteEnvelope,
transaction::{ConsumedNoteInfo, ProvenTransaction},
notes::{NoteEnvelope, Nullifier},
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
transaction::ProvenTransaction,
Felt, Word, WORD_SIZE, ZERO,
};
use miden_verifier::verify;
Expand Down Expand Up @@ -58,12 +58,13 @@ impl TransactionVerifier {

// HELPERS
// --------------------------------------------------------------------------------------------

/// Returns the consumed notes commitment.
fn compute_consumed_notes_hash(consumed_notes: &[ConsumedNoteInfo]) -> Digest {
fn compute_consumed_notes_hash(consumed_notes: &[Nullifier]) -> Digest {
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
let mut elements: Vec<Felt> = Vec::with_capacity(consumed_notes.len() * 8);
for note in consumed_notes.iter() {
elements.extend_from_slice(note.nullifier().as_elements());
elements.extend_from_slice(note.script_root().as_elements());
for nullifier in consumed_notes.iter() {
elements.extend_from_slice(nullifier.inner().as_elements());
elements.extend_from_slice(&Word::default());
}
Hasher::hash_elements(&elements)
}
Expand Down
2 changes: 1 addition & 1 deletion mock/src/mock/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ impl<R: Rng + SeedableRng> MockChain<R> {
return Err(MockError::DuplicatedNote);
}

self.check_nullifier_unknown(note.nullifier());
self.check_nullifier_unknown(note.nullifier().inner());
self.pending_objects.notes.push(note);
Ok(())
}
Expand Down
22 changes: 5 additions & 17 deletions objects/src/notes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub use inputs::NoteInputs;
mod metadata;
pub use metadata::NoteMetadata;

mod nullifier;
pub use nullifier::Nullifier;

mod origin;
use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
pub use origin::{NoteInclusionProof, NoteOrigin};
Expand Down Expand Up @@ -177,23 +180,8 @@ impl Note {
}

/// Returns the nullifier for this note.
///
/// The nullifier is computed as hash(serial_num, script_hash, input_hash, vault_hash).
/// This achieves the following properties:
/// - Every note can be reduced to a single unique nullifier.
/// - We cannot derive a note's hash from its nullifier.
/// - To compute the nullifier we must know all components of the note: serial_num,
/// script_hash, input_hash and vault hash.
pub fn nullifier(&self) -> Digest {
// The total number of elements to be hashed is 16. We can absorb them in
// exactly two permutations
let target_num_elements = 4 * WORD_SIZE;
let mut elements: Vec<Felt> = Vec::with_capacity(target_num_elements);
elements.extend_from_slice(&self.serial_num);
elements.extend_from_slice(self.script.hash().as_elements());
elements.extend_from_slice(self.inputs.hash().as_elements());
elements.extend_from_slice(self.vault.hash().as_elements());
Hasher::hash_elements(&elements)
pub fn nullifier(&self) -> Nullifier {
self.into()
}
}

Expand Down
110 changes: 110 additions & 0 deletions objects/src/notes/nullifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use super::{Digest, Felt, Hasher, Note, Word, WORD_SIZE, ZERO};
use crate::utils::serde::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};

// NULLIFIER
// ================================================================================================

/// A note's nullifier.
///
/// A note's nullifier is computed as hash(serial_num, script_hash, input_hash, vault_hash).
///
/// This achieves the following properties:
/// - Every note can be reduced to a single unique nullifier.
/// - We cannot derive a note's hash from its nullifier, or a note's nullifier from its hash.
/// - To compute the nullifier we must know all components of the note: serial_num, script_hash,
/// input_hash and vault_hash.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Nullifier(Digest);

impl Nullifier {
/// Returns a new note [Nullifier] instantiated from the provided digest.
pub fn new(
script_hash: Digest,
inputs_hash: Digest,
vault_hash: Digest,
serial_num: Word,
) -> Self {
let mut elements = [ZERO; 4 * WORD_SIZE];
elements[..4].copy_from_slice(&serial_num);
elements[4..8].copy_from_slice(script_hash.as_elements());
elements[8..12].copy_from_slice(inputs_hash.as_elements());
elements[12..].copy_from_slice(vault_hash.as_elements());
Self(Hasher::hash_elements(&elements))
}

/// Returns the elements of this nullifier.
pub fn as_elements(&self) -> &[Felt] {
self.0.as_elements()
}

/// Returns the digest defining this nullifier.
pub fn inner(&self) -> Digest {
self.0
}
}

// CONVERSIONS INTO NULLIFIER
// ================================================================================================

impl From<&Note> for Nullifier {
fn from(note: &Note) -> Self {
Self::new(note.script.hash(), note.inputs.hash(), note.vault.hash(), note.serial_num)
}
}

impl From<Word> for Nullifier {
fn from(value: Word) -> Self {
Self(value.into())
}
}

impl From<Digest> for Nullifier {
fn from(value: Digest) -> Self {
Self(value)
}
}

// CONVERSIONS FROM NULLIFIER
// ================================================================================================

impl From<Nullifier> for Word {
fn from(nullifier: Nullifier) -> Self {
nullifier.0.into()
}
}

impl From<Nullifier> for [u8; 32] {
fn from(nullifier: Nullifier) -> Self {
nullifier.0.into()
}
}

impl From<&Nullifier> for Word {
fn from(nullifier: &Nullifier) -> Self {
nullifier.0.into()
}
}

impl From<&Nullifier> for [u8; 32] {
fn from(nullifier: &Nullifier) -> Self {
nullifier.0.into()
}
}

// SERIALIZATION
// ================================================================================================

impl Serializable for Nullifier {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_bytes(&self.0.to_bytes());
}
}

impl Deserializable for Nullifier {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let nullifier = Digest::read_from(source)?;
Ok(Self(nullifier))
}
}
5 changes: 3 additions & 2 deletions objects/src/notes/script.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use super::{Assembler, AssemblyContext, CodeBlock, Digest, NoteError, ProgramAst};
use crate::utils::serde::{ByteReader, ByteWriter, Deserializable, Serializable};
use crate::utils::serde::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};
use assembly::ast::AstSerdeOptions;
use vm_processor::DeserializationError;

// CONSTANTS
// ================================================================================================
Expand Down
Loading
Loading