Skip to content

Commit 5dfa431

Browse files
fix: incorrect detection of note inputs length during note creation (#2066)
1 parent 02236cb commit 5dfa431

5 files changed

Lines changed: 79 additions & 61 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Changelog
22

3-
## 0.12.1 (11-05-2025)
3+
## 0.12.1 (2025-11-06)
44

5+
- Fixed incorrect detection of note inputs length during note creation ([#2066](https://github.com/0xMiden/miden-base/pull/2066)).
56
- Made `InitStorageData::map_entries()` public ([#2055](https://github.com/0xMiden/miden-base/pull/2055)).
67
- Enabled handling of empty maps in account component templates ([#2056](https://github.com/0xMiden/miden-base/pull/2056)).
78
- Changed auth components to increment nonce if it is zero ([#2060](https://github.com/0xMiden/miden-base/pull/2060)).
89

9-
## 0.12.0 (11-05-2025)
10+
## 0.12.0 (2025-11-05)
1011

1112
### Features
1213

crates/miden-lib/asm/miden/note.masm

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,34 @@ end
122122
#! - num_inputs is greater than 128.
123123
#!
124124
#! Invocation: exec
125-
export.build_recipient
125+
export.build_recipient.1
126126
dup.1 dup.1
127127
# => [inputs_ptr, num_inputs, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
128128

129129
exec.compute_inputs_commitment
130130
# => [INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
131131

132+
# store num_inputs into local memory
133+
dup.5 loc_store.0
134+
# => [INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
135+
136+
locaddr.0 add.1
137+
locaddr.0
138+
# => [num_inputs_start_ptr, num_inputs_end_ptr, INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
139+
140+
dup.5 dup.5 dup.5 dup.5
141+
# => [INPUTS_COMMITMENT, num_inputs_start_ptr, num_inputs_end_ptr,
142+
# INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
143+
144+
# compute the advice map key for num_inputs by hashing the inputs commitment
145+
hash
146+
# => [hash(INPUTS_COMMITMENT), num_inputs_start_ptr, num_inputs_end_ptr,
147+
# INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
148+
149+
adv.insert_mem
150+
dropw drop drop
151+
# => [INPUTS_COMMITMENT, inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
152+
132153
movup.5 movup.5 dup movdn.2
133154
# => [inputs_ptr, num_inputs, inputs_ptr, INPUTS_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT]
134155

crates/miden-objects/src/transaction/tx_args.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ impl TransactionArgs {
175175
(sn_script_hash, concat_words(sn_hash, script.root())),
176176
(note_recipient.digest(), concat_words(sn_script_hash, inputs.commitment())),
177177
(inputs.commitment(), inputs.to_elements()),
178+
(
179+
Hasher::hash_elements(inputs.commitment().as_elements()),
180+
vec![Felt::from(inputs.num_values())],
181+
),
178182
(script.root(), script_encoded),
179183
];
180184

crates/miden-testing/tests/scripts/faucet.rs

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -333,16 +333,16 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul
333333
let target_account_suffix = recipient_account_id.suffix();
334334
let target_account_prefix = recipient_account_id.prefix().as_felt();
335335

336-
// Adding extra 0 values to inputs to test trial unhashing in extract_note_inputs fn
336+
// Use a length that is not a multiple of 8 (double word size) to make sure note inputs padding
337+
// is correctly handled
337338
let note_inputs = NoteInputs::new(vec![
338339
target_account_suffix,
339340
target_account_prefix,
340341
Felt::new(0),
341342
Felt::new(0),
342343
Felt::new(0),
343-
Felt::new(0),
344-
Felt::new(0),
345344
Felt::new(1),
345+
Felt::new(0),
346346
])?;
347347

348348
let note_recipient =
@@ -362,23 +362,21 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul
362362
# Build recipient hash from SERIAL_NUM, SCRIPT_ROOT, and INPUTS_COMMITMENT
363363
push.{script_root}
364364
# => [SCRIPT_ROOT]
365-
365+
366366
push.{serial_num}
367367
# => [SERIAL_NUM, SCRIPT_ROOT]
368368
369-
# Store note inputs in memory at address 0
370-
# First word: inputs[0..4]
371-
push.{input0}.{input1}.{input2}.{input3}
372-
mem_storew_be.0 dropw
373-
# Memory[0] = [input0, input1, input2, input3]
369+
# Store note inputs in memory
370+
push.{input0} mem_store.0
371+
push.{input1} mem_store.1
372+
push.{input2} mem_store.2
373+
push.{input3} mem_store.3
374+
push.{input4} mem_store.4
375+
push.{input5} mem_store.5
376+
push.{input6} mem_store.6
374377
375-
# Second word: inputs[4..8]
376-
push.{input4}.{input5}.{input6}.{input7}
377-
mem_storew_be.4 dropw
378-
# Memory[1] = [input4, input5, input6, input7]
379-
380-
push.8 push.0
381-
# => [inputs_ptr, num_inputs, SERIAL_NUM, SCRIPT_ROOT]
378+
push.7 push.0
379+
# => [inputs_ptr, num_inputs = 7, SERIAL_NUM, SCRIPT_ROOT]
382380
383381
exec.note::build_recipient
384382
# => [RECIPIENT]
@@ -406,7 +404,6 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul
406404
input4 = note_inputs.values()[4],
407405
input5 = note_inputs.values()[5],
408406
input6 = note_inputs.values()[6],
409-
input7 = note_inputs.values()[7],
410407
script_root = output_script_root,
411408
serial_num = serial_num,
412409
aux = aux,
@@ -467,6 +464,11 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul
467464
note_inputs.commitment(),
468465
"Output note inputs commitment should match expected inputs commitment"
469466
);
467+
assert_eq!(
468+
full_note.recipient().inputs().num_values(),
469+
note_inputs.num_values(),
470+
"Output note inputs length should match expected inputs length"
471+
);
470472

471473
// Verify the output note ID matches the expected note ID
472474
assert_eq!(full_note.id(), expected_note.id());

crates/miden-tx/src/host/kernel_process.rs

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use miden_lib::transaction::memory::{
55
};
66
use miden_objects::account::AccountId;
77
use miden_objects::note::{NoteId, NoteInputs};
8-
use miden_objects::{Word, ZERO};
8+
use miden_objects::{Hasher, Word};
99
use miden_processor::{EventError, ExecutionError, Felt, ProcessState};
1010

1111
use crate::errors::TransactionKernelError;
@@ -149,55 +149,45 @@ impl<'a> TransactionKernelProcess for ProcessState<'a> {
149149
Ok((inputs, script_root, serial_num))
150150
}
151151

152-
/// Extracts and validates note inputs from the advice provider using trial unhashing.
153-
///
154-
/// This function tries to determine the correct number of inputs by:
155-
/// 1. Finding the last non-zero element as a starting point
156-
/// 2. Building NoteInputs and checking if the hash matches inputs_commitment
157-
/// 3. If not, incrementing num_inputs and trying again (up to 6 more times)
158-
/// 4. If num_inputs grows to the size of inputs_data and there's still no match, returning an
159-
/// error
152+
/// Extracts and validates note inputs from the advice provider.
160153
fn read_note_inputs_from_adv_map(
161154
&self,
162155
inputs_commitment: &Word,
163156
) -> Result<NoteInputs, TransactionKernelError> {
164157
let inputs_data = self.advice_provider().get_mapped_values(inputs_commitment);
165158

166-
let inputs = match inputs_data {
167-
None => NoteInputs::default(),
159+
match inputs_data {
160+
None => Ok(NoteInputs::default()),
168161
Some(inputs) => {
169-
// Start with the last non-zero element as a hint
170-
let initial_num_inputs =
171-
inputs.iter().rposition(|&x| x != ZERO).map(|pos| pos + 1).unwrap_or(0);
172-
173-
// Try different input counts using trial unhashing
174-
let mut num_inputs = initial_num_inputs;
175-
176-
loop {
177-
let candidate_inputs = NoteInputs::new(inputs[0..num_inputs].to_vec())
178-
.map_err(TransactionKernelError::MalformedNoteInputs)?;
179-
180-
if candidate_inputs.commitment() == *inputs_commitment {
181-
return Ok(candidate_inputs);
182-
}
183-
184-
num_inputs += 1;
185-
if num_inputs > inputs.len() {
186-
break;
187-
}
162+
let inputs_commitment_hash = Hasher::hash_elements(inputs_commitment.as_elements());
163+
let num_inputs = self
164+
.advice_provider()
165+
.get_mapped_values(&inputs_commitment_hash)
166+
.ok_or_else(|| {
167+
TransactionKernelError::other(
168+
"expected num_inputs to be present in advice provider",
169+
)
170+
})?;
171+
if num_inputs.len() != 1 {
172+
return Err(TransactionKernelError::other(
173+
"expected num_inputs advice entry to contain exactly one element",
174+
));
175+
}
176+
let num_inputs = num_inputs[0].as_int() as usize;
177+
178+
let note_inputs = NoteInputs::new(inputs[0..num_inputs].to_vec())
179+
.map_err(TransactionKernelError::MalformedNoteInputs)?;
180+
181+
if &note_inputs.commitment() == inputs_commitment {
182+
Ok(note_inputs)
183+
} else {
184+
Err(TransactionKernelError::InvalidNoteInputs {
185+
expected: *inputs_commitment,
186+
actual: note_inputs.commitment(),
187+
})
188188
}
189-
190-
// If we've exhausted all attempts, return an error
191-
return Err(TransactionKernelError::InvalidNoteInputs {
192-
expected: *inputs_commitment,
193-
actual: NoteInputs::new(inputs[0..num_inputs.min(inputs.len())].to_vec())
194-
.map(|i| i.commitment())
195-
.unwrap_or_default(),
196-
});
197189
},
198-
};
199-
200-
Ok(inputs)
190+
}
201191
}
202192

203193
fn has_advice_map_entry(&self, key: Word) -> bool {

0 commit comments

Comments
 (0)