Skip to content

Commit 7e97e87

Browse files
committed
feat: verify batch tx list, tx headers, and input-notes commitment
Fill in the batch kernel to verify the batch's transaction list against its BATCH_ID (Layer 1), verify each transaction header (Layer 2), and recompute the batch INPUT_NOTES_COMMITMENT from the verified per-tx input notes (Layer 3). Output-notes (BATCH_NOTE_TREE_ROOT) and the expiration running-min stay as zero placeholders, wired up in follow-up PRs.
1 parent 763a6a7 commit 7e97e87

11 files changed

Lines changed: 750 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Changes
66
- Added a skeleton batch kernel ([#1122](https://github.com/0xMiden/protocol/issues/1122)) wired through `LocalBatchProver::prove` and attached to `ProvenBatch` as an `ExecutionProof`. It does not yet perform any verification.
7+
- The batch kernel now verifies the batch's transaction list against its `BATCH_ID`, verifies each transaction header, and computes the batch `INPUT_NOTES_COMMITMENT` ([#2905](https://github.com/0xMiden/protocol/pull/2905)).
78

89
- [BREAKING] Renamed `AccountStorageDelta` to `AccountStoragePatch` ([#3002](https://github.com/0xMiden/protocol/pull/3002)).
910
- [BREAKING] Extracted `NullifierTreeBackendReader` and `AccountTreeBackendReader` traits from existing `NullifierTreeBackend` and `AccountTreeBackend` traits ([#2755](https://github.com/0xMiden/protocol/pull/2755)).
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# MEMORY LAYOUT
2+
# =================================================================================================
3+
#
4+
# Below is the memory layout used by the batch kernel:
5+
#
6+
# +-------------------+-------------------------+----------------------------------+
7+
# | Address range | Constant | Contents |
8+
# +-------------------+-------------------------+----------------------------------+
9+
# | 0 | NUM_TRANSACTIONS_PTR | num_transactions (1 felt). |
10+
# | 4..8 | BATCH_HASHER_RATE0_PTR | RATE0 of the batch-level |
11+
# | | | poseidon2 hasher state. |
12+
# | 8..12 | BATCH_HASHER_RATE1_PTR | RATE1 of the batch-level hasher. |
13+
# | 12..16 | BATCH_HASHER_CAP_PTR | CAPACITY of the batch-level |
14+
# | | | hasher. |
15+
# | 16 | SCRATCH_WORDS_COUNT_PTR | num_words piped into |
16+
# | | | TX_NOTES_SCRATCH_PTR for the |
17+
# | | | current transaction. |
18+
# | 17 | SCRATCH_WORD_INDEX_PTR | current iteration cursor for the |
19+
# | | | scratch absorption loop. |
20+
# | 20..8212 | TX_TUPLES_PTR | Layer 1 piped data: per |
21+
# | | | transaction `[tx_id[4], |
22+
# | | | account_id_prefix, |
23+
# | | | account_id_suffix, 0, 0]` |
24+
# | | | (8 felts each, sized for up to |
25+
# | | | 1024 transactions). |
26+
# | 8212..32788 | TX_HEADERS_PTR | Layer 2 piped data: per |
27+
# | | | transaction the felt sequence |
28+
# | | | TransactionId::new hashes |
29+
# | | | (24 felts each, sized for up to |
30+
# | | | 1024 transactions). |
31+
# | 32788..40980 | TX_NOTES_SCRATCH_PTR | Per-transaction scratch space |
32+
# | | | for Layer 3 / Layer 3' note |
33+
# | | | data (overwritten each tx). |
34+
# +-------------------+-------------------------+----------------------------------+
35+
36+
# BOOK KEEPING
37+
# =================================================================================================
38+
39+
#! Single-felt slot holding `num_transactions` after Layer 1 verification.
40+
const NUM_TRANSACTIONS_PTR=0
41+
42+
# BATCH HASHER STATE
43+
# =================================================================================================
44+
45+
#! Word holding the RATE0 portion of the batch-level poseidon2 hasher state.
46+
const BATCH_HASHER_RATE0_PTR=4
47+
48+
#! Word holding the RATE1 portion of the batch-level poseidon2 hasher state.
49+
const BATCH_HASHER_RATE1_PTR=8
50+
51+
#! Word holding the CAPACITY portion of the batch-level poseidon2 hasher state.
52+
const BATCH_HASHER_CAP_PTR=12
53+
54+
# SCRATCH BOOKKEEPING
55+
# =================================================================================================
56+
57+
#! Number of words piped into TX_NOTES_SCRATCH_PTR for the transaction whose Layer 3 / 3' is
58+
#! being absorbed.
59+
const SCRATCH_WORDS_COUNT_PTR=16
60+
61+
#! Iteration cursor (index in words) into TX_NOTES_SCRATCH_PTR for the absorption loop.
62+
const SCRATCH_WORD_INDEX_PTR=17
63+
64+
# PIPED DATA REGIONS
65+
# =================================================================================================
66+
67+
#! Base of the Layer 1 piped data region. Per transaction, 8 felts:
68+
#! `[tx_id[4], account_id_prefix, account_id_suffix, 0, 0]`.
69+
pub const TX_TUPLES_PTR=20
70+
71+
#! Number of felts each transaction occupies in TX_TUPLES_PTR.
72+
const TX_TUPLE_FELT_LEN=8
73+
74+
#! Base of the Layer 2 piped data region. Per transaction, 24 felts:
75+
#! `[INIT[4], FINAL[4], INPUT_NOTES_COMMITMENT[4], OUTPUT_NOTES_COMMITMENT[4], FEE_ASSET[8]]`.
76+
#! This must match the felt-sequence layout of `TransactionId::new`.
77+
const TX_HEADERS_PTR=8212
78+
79+
#! Number of felts each transaction occupies in TX_HEADERS_PTR.
80+
const TX_HEADER_FELT_LEN=24
81+
82+
#! Felt offset within a transaction header where INPUT_NOTES_COMMITMENT starts.
83+
const TX_HEADER_INPUT_NOTES_OFFSET=8
84+
85+
#! Felt offset within a transaction header where OUTPUT_NOTES_COMMITMENT starts.
86+
const TX_HEADER_OUTPUT_NOTES_OFFSET=12
87+
88+
#! Per-transaction scratch space for Layer 3 note data, overwritten between iterations.
89+
pub const TX_NOTES_SCRATCH_PTR=32788
90+
91+
# NUM TRANSACTIONS
92+
# =================================================================================================
93+
94+
#! Stores `num_transactions`.
95+
#!
96+
#! Inputs: [num_transactions]
97+
#! Outputs: []
98+
pub proc set_num_transactions
99+
mem_store.NUM_TRANSACTIONS_PTR
100+
end
101+
102+
#! Returns `num_transactions`.
103+
#!
104+
#! Inputs: []
105+
#! Outputs: [num_transactions]
106+
pub proc get_num_transactions
107+
mem_load.NUM_TRANSACTIONS_PTR
108+
end
109+
110+
# BATCH HASHER STATE
111+
# =================================================================================================
112+
113+
#! Persists the batch hasher state from the operand stack into memory.
114+
#!
115+
#! Inputs: [RATE0, RATE1, CAPACITY]
116+
#! Outputs: []
117+
pub proc save_batch_hasher_state
118+
mem_storew_le.BATCH_HASHER_RATE0_PTR dropw
119+
mem_storew_le.BATCH_HASHER_RATE1_PTR dropw
120+
mem_storew_le.BATCH_HASHER_CAP_PTR dropw
121+
end
122+
123+
#! Loads the batch hasher state from memory onto the operand stack.
124+
#!
125+
#! Inputs: []
126+
#! Outputs: [RATE0, RATE1, CAPACITY]
127+
pub proc load_batch_hasher_state
128+
padw mem_loadw_le.BATCH_HASHER_CAP_PTR
129+
padw mem_loadw_le.BATCH_HASHER_RATE1_PTR
130+
padw mem_loadw_le.BATCH_HASHER_RATE0_PTR
131+
end
132+
133+
# SCRATCH BOOKKEEPING
134+
# =================================================================================================
135+
136+
#! Stores the count (in words) of data piped into the per-transaction scratch.
137+
#!
138+
#! Inputs: [num_words]
139+
#! Outputs: []
140+
pub proc set_scratch_words_count
141+
mem_store.SCRATCH_WORDS_COUNT_PTR
142+
end
143+
144+
#! Returns the count (in words) of data piped into the per-transaction scratch.
145+
#!
146+
#! Inputs: []
147+
#! Outputs: [num_words]
148+
pub proc get_scratch_words_count
149+
mem_load.SCRATCH_WORDS_COUNT_PTR
150+
end
151+
152+
#! Stores the absorption iteration cursor (index in words).
153+
#!
154+
#! Inputs: [word_index]
155+
#! Outputs: []
156+
pub proc set_scratch_word_index
157+
mem_store.SCRATCH_WORD_INDEX_PTR
158+
end
159+
160+
#! Returns the absorption iteration cursor (index in words).
161+
#!
162+
#! Inputs: []
163+
#! Outputs: [word_index]
164+
pub proc get_scratch_word_index
165+
mem_load.SCRATCH_WORD_INDEX_PTR
166+
end
167+
168+
# TRANSACTION TUPLE / HEADER ACCESSORS
169+
# =================================================================================================
170+
171+
#! Returns a pointer to transaction `tx_index`'s entry in TX_TUPLES_PTR.
172+
#!
173+
#! Inputs: [tx_index]
174+
#! Outputs: [tx_tuple_ptr]
175+
pub proc tx_tuple_ptr
176+
mul.TX_TUPLE_FELT_LEN add.TX_TUPLES_PTR
177+
end
178+
179+
#! Returns the verified `tx_id` for transaction `tx_index` (loaded from TX_TUPLES_PTR).
180+
#!
181+
#! Inputs: [tx_index]
182+
#! Outputs: [TX_ID]
183+
pub proc get_tx_id
184+
exec.tx_tuple_ptr padw movup.4 mem_loadw_le
185+
end
186+
187+
#! Returns a pointer to transaction `tx_index`'s entry in TX_HEADERS_PTR.
188+
#!
189+
#! Inputs: [tx_index]
190+
#! Outputs: [tx_header_ptr]
191+
pub proc tx_header_ptr
192+
mul.TX_HEADER_FELT_LEN add.TX_HEADERS_PTR
193+
end
194+
195+
#! Returns the verified per-transaction INPUT_NOTES_COMMITMENT for transaction `tx_index`.
196+
#!
197+
#! Inputs: [tx_index]
198+
#! Outputs: [INPUT_NOTES_COMMITMENT_i]
199+
pub proc get_tx_input_notes_commitment
200+
exec.tx_header_ptr add.TX_HEADER_INPUT_NOTES_OFFSET padw movup.4 mem_loadw_le
201+
end
202+
203+
#! Returns the verified per-transaction OUTPUT_NOTES_COMMITMENT for transaction `tx_index`.
204+
#!
205+
#! Inputs: [tx_index]
206+
#! Outputs: [OUTPUT_NOTES_COMMITMENT_i]
207+
pub proc get_tx_output_notes_commitment
208+
exec.tx_header_ptr add.TX_HEADER_OUTPUT_NOTES_OFFSET padw movup.4 mem_loadw_le
209+
end
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use miden::core::mem
2+
use miden::core::crypto::hashes::poseidon2
3+
use miden::core::word
4+
5+
use miden::batch_kernel::memory
6+
use miden::batch_kernel::memory::TX_NOTES_SCRATCH_PTR
7+
8+
# ERRORS
9+
# =================================================================================================
10+
11+
const ERR_BATCH_INPUT_NOTES_MISMATCH="per-transaction input notes data piped from the advice map does not match its INPUT_NOTES_COMMITMENT"
12+
13+
# ABSORPTION HELPER
14+
# =================================================================================================
15+
16+
#! Absorbs the contents of [`memory::TX_NOTES_SCRATCH_PTR`] into the batch hasher state held in
17+
#! [`memory::BATCH_HASHER_*_PTR`] memory slots.
18+
#!
19+
#! The scratch region is read as `(NULLIFIER, EMPTY_OR_NOTE_ID)` 8-felt tuples, in the order they
20+
#! were piped (which matches `build_input_note_commitment` in Rust).
21+
#!
22+
#! The number of words to absorb is read from [`memory::SCRATCH_WORDS_COUNT_PTR`].
23+
#!
24+
#! Inputs: []
25+
#! Outputs: []
26+
proc absorb_scratch_into_batch_hasher
27+
push.0 exec.memory::set_scratch_word_index
28+
29+
exec.memory::load_batch_hasher_state
30+
# Stack: [RATE0, RATE1, CAPACITY]
31+
32+
exec.memory::get_scratch_word_index
33+
exec.memory::get_scratch_words_count
34+
u32lt
35+
# Stack: [should_loop, RATE0, RATE1, CAPACITY]
36+
37+
while.true
38+
exec.memory::get_scratch_word_index
39+
mul.4 add.TX_NOTES_SCRATCH_PTR
40+
# Stack: [scratch_ptr, RATE0, RATE1, CAPACITY]
41+
42+
padw dup.4 mem_loadw_le
43+
# Stack: [DATA1, scratch_ptr, RATE0, RATE1, CAPACITY]
44+
45+
padw dup.8 add.4 mem_loadw_le
46+
# Stack: [DATA2, DATA1, scratch_ptr, RATE0, RATE1, CAPACITY]
47+
48+
movup.8 drop
49+
# Stack: [DATA2, DATA1, RATE0, RATE1, CAPACITY]
50+
51+
# Replace RATE0 + RATE1 with DATA1 + DATA2 (DATA1 first, DATA2 second), then permute.
52+
swapdw
53+
dropw dropw
54+
swapw
55+
exec.poseidon2::permute
56+
# Stack: [RATE0', RATE1', CAPACITY']
57+
58+
exec.memory::get_scratch_word_index add.2 exec.memory::set_scratch_word_index
59+
exec.memory::get_scratch_word_index
60+
exec.memory::get_scratch_words_count
61+
u32lt
62+
# Stack: [should_loop, RATE0, RATE1, CAPACITY]
63+
end
64+
65+
exec.memory::save_batch_hasher_state
66+
end
67+
68+
# INPUT NOTES COMMITMENT
69+
# =================================================================================================
70+
71+
#! Computes the batch's INPUT_NOTES_COMMITMENT.
72+
#!
73+
#! For each transaction in transaction order, verifies the per-transaction
74+
#! `INPUT_NOTES_COMMITMENT_i` against its advice-map value (the `(NULLIFIER, EMPTY_OR_COMMITMENT)`
75+
#! tuple list), then absorbs that verified data into the batch-level sequential hasher.
76+
#!
77+
#! Inputs: []
78+
#! Outputs: [INPUT_NOTES_COMMITMENT]
79+
#!
80+
#! Errors:
81+
#! - ERR_BATCH_INPUT_NOTES_MISMATCH: a transaction's `(NULLIFIER, EMPTY_OR_COMMITMENT)` tuple
82+
#! list piped from the advice map does not hash to its verified per-tx
83+
#! `INPUT_NOTES_COMMITMENT_i`.
84+
#!
85+
#! TODO: erase intra-batch unauthenticated notes from the absorbed sequence (see
86+
#! `InputOutputNoteTracker::from_transactions`).
87+
#! TODO: re-sort + dedupe by nullifier so the result equals
88+
#! `proposed_batch.input_notes().commitment()`.
89+
#! TODO: authenticate unauthenticated input notes against `BLOCK_HASH`'s chain MMR.
90+
#! TODO: enforce `MAX_INPUT_NOTES_PER_BATCH`.
91+
pub proc compute_input_notes_commitment
92+
exec.poseidon2::init_no_padding
93+
exec.memory::save_batch_hasher_state
94+
95+
exec.memory::get_num_transactions
96+
push.0
97+
# Stack: [tx_index, num_transactions]
98+
99+
dup.1 dup.1 neq
100+
# Stack: [should_loop, tx_index, num_transactions]
101+
102+
while.true
103+
dup exec.memory::get_tx_input_notes_commitment
104+
# Stack: [INPUT_NOTES_COMMITMENT_i, tx_index, num_transactions]
105+
106+
dupw exec.word::eqz
107+
# Stack: [is_empty, INPUT_NOTES_COMMITMENT_i, tx_index, num_transactions]
108+
109+
if.true
110+
dropw
111+
else
112+
push.TX_NOTES_SCRATCH_PTR movdn.4
113+
# Stack: [INPUT_NOTES_COMMITMENT_i, TX_NOTES_SCRATCH_PTR, tx_index, num_transactions]
114+
115+
adv.push_mapvaln
116+
adv_push div.4
117+
# Stack: [num_words, INPUT_NOTES_COMMITMENT_i, TX_NOTES_SCRATCH_PTR, tx_index, num_transactions]
118+
119+
dup exec.memory::set_scratch_words_count
120+
121+
movup.5 swap
122+
# Stack: [num_words, TX_NOTES_SCRATCH_PTR, INPUT_NOTES_COMMITMENT_i, tx_index, num_transactions]
123+
124+
exec.mem::pipe_words_to_memory
125+
exec.poseidon2::squeeze_digest
126+
movup.4 drop
127+
# Stack: [DIGEST, INPUT_NOTES_COMMITMENT_i, tx_index, num_transactions]
128+
129+
assert_eqw.err=ERR_BATCH_INPUT_NOTES_MISMATCH
130+
131+
exec.absorb_scratch_into_batch_hasher
132+
end
133+
# Stack: [tx_index, num_transactions]
134+
135+
add.1
136+
dup.1 dup.1 neq
137+
# Stack: [should_loop, tx_index, num_transactions]
138+
end
139+
140+
drop drop
141+
142+
exec.memory::load_batch_hasher_state
143+
exec.poseidon2::squeeze_digest
144+
# Stack: [INPUT_NOTES_COMMITMENT]
145+
end

0 commit comments

Comments
 (0)