Skip to content

Commit 5bccee1

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 5bccee1

11 files changed

Lines changed: 726 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: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
# ABSORPTION HELPER
9+
# =================================================================================================
10+
11+
#! Absorbs the contents of [`memory::TX_NOTES_SCRATCH_PTR`] into the batch hasher state held in
12+
#! [`memory::BATCH_HASHER_*_PTR`] memory slots.
13+
#!
14+
#! The scratch region is read as `(NULLIFIER, EMPTY_OR_NOTE_ID)` 8-felt tuples, in the order they
15+
#! were piped (which matches `build_input_note_commitment` in Rust).
16+
#!
17+
#! The number of words to absorb is read from [`memory::SCRATCH_WORDS_COUNT_PTR`].
18+
#!
19+
#! Inputs: []
20+
#! Outputs: []
21+
proc absorb_scratch_into_batch_hasher
22+
push.0 exec.memory::set_scratch_word_index
23+
24+
exec.memory::load_batch_hasher_state
25+
# Stack: [RATE0, RATE1, CAPACITY]
26+
27+
exec.memory::get_scratch_word_index
28+
exec.memory::get_scratch_words_count
29+
u32lt
30+
# Stack: [should_loop, RATE0, RATE1, CAPACITY]
31+
32+
while.true
33+
exec.memory::get_scratch_word_index
34+
mul.4 add.TX_NOTES_SCRATCH_PTR
35+
# Stack: [scratch_ptr, RATE0, RATE1, CAPACITY]
36+
37+
padw dup.4 mem_loadw_le
38+
# Stack: [DATA1, scratch_ptr, RATE0, RATE1, CAPACITY]
39+
40+
padw dup.8 add.4 mem_loadw_le
41+
# Stack: [DATA2, DATA1, scratch_ptr, RATE0, RATE1, CAPACITY]
42+
43+
movup.8 drop
44+
# Stack: [DATA2, DATA1, RATE0, RATE1, CAPACITY]
45+
46+
# Replace RATE0 + RATE1 with DATA1 + DATA2 (DATA1 first, DATA2 second), then permute.
47+
swapdw
48+
dropw dropw
49+
swapw
50+
exec.poseidon2::permute
51+
# Stack: [RATE0', RATE1', CAPACITY']
52+
53+
exec.memory::get_scratch_word_index add.2 exec.memory::set_scratch_word_index
54+
exec.memory::get_scratch_word_index
55+
exec.memory::get_scratch_words_count
56+
u32lt
57+
# Stack: [should_loop, RATE0, RATE1, CAPACITY]
58+
end
59+
60+
exec.memory::save_batch_hasher_state
61+
end
62+
63+
# INPUT NOTES COMMITMENT
64+
# =================================================================================================
65+
66+
#! Computes the batch's INPUT_NOTES_COMMITMENT.
67+
#!
68+
#! For each transaction in transaction order, verifies the per-transaction
69+
#! `INPUT_NOTES_COMMITMENT_i` against its advice-map value (the `(NULLIFIER, EMPTY_OR_COMMITMENT)`
70+
#! tuple list), then absorbs that verified data into the batch-level sequential hasher.
71+
#!
72+
#! Inputs: []
73+
#! Outputs: [INPUT_NOTES_COMMITMENT]
74+
#!
75+
#! Panics if a transaction's `(NULLIFIER, EMPTY_OR_COMMITMENT)` tuple list piped from the advice map
76+
#! does not hash to its verified per-tx `INPUT_NOTES_COMMITMENT_i`.
77+
#!
78+
#! TODO: erase intra-batch unauthenticated notes from the absorbed sequence (see
79+
#! `InputOutputNoteTracker::from_transactions`).
80+
#! TODO: re-sort + dedupe by nullifier so the result equals
81+
#! `proposed_batch.input_notes().commitment()`.
82+
#! TODO: authenticate unauthenticated input notes against `BLOCK_HASH`'s chain MMR.
83+
#! TODO: enforce `MAX_INPUT_NOTES_PER_BATCH`.
84+
pub proc compute_input_notes_commitment
85+
exec.poseidon2::init_no_padding
86+
exec.memory::save_batch_hasher_state
87+
88+
exec.memory::get_num_transactions
89+
push.0
90+
# Stack: [tx_index, num_transactions]
91+
92+
dup.1 dup.1 neq
93+
# Stack: [should_loop, tx_index, num_transactions]
94+
95+
while.true
96+
dup exec.memory::get_tx_input_notes_commitment
97+
# Stack: [INPUT_NOTES_COMMITMENT_i, tx_index, num_transactions]
98+
99+
dupw exec.word::eqz
100+
# Stack: [is_empty, INPUT_NOTES_COMMITMENT_i, tx_index, num_transactions]
101+
102+
if.true
103+
dropw
104+
else
105+
push.TX_NOTES_SCRATCH_PTR movdn.4
106+
# Stack: [INPUT_NOTES_COMMITMENT_i, tx_notes_scratch_ptr, tx_index, num_transactions]
107+
108+
adv.push_mapvaln
109+
adv_push div.4
110+
# Stack: [num_words, INPUT_NOTES_COMMITMENT_i, tx_notes_scratch_ptr, tx_index, num_transactions]
111+
112+
dup exec.memory::set_scratch_words_count
113+
114+
movup.5 swap
115+
# Stack: [num_words, tx_notes_scratch_ptr, INPUT_NOTES_COMMITMENT_i, tx_index, num_transactions]
116+
117+
# Pipe the note data into scratch, asserting its poseidon2 hash equals
118+
# INPUT_NOTES_COMMITMENT_i.
119+
exec.mem::pipe_preimage_to_memory
120+
# Stack: [end_ptr, tx_index, num_transactions]
121+
drop
122+
123+
exec.absorb_scratch_into_batch_hasher
124+
end
125+
# Stack: [tx_index, num_transactions]
126+
127+
add.1
128+
dup.1 dup.1 neq
129+
# Stack: [should_loop, tx_index, num_transactions]
130+
end
131+
132+
drop drop
133+
134+
exec.memory::load_batch_hasher_state
135+
exec.poseidon2::squeeze_digest
136+
# Stack: [INPUT_NOTES_COMMITMENT]
137+
end

0 commit comments

Comments
 (0)