Skip to content

Commit 2c07fb9

Browse files
committed
fix: issue with under-constraint byte-address
Basically, the bug allows applying sponge permutation on a given chunk of packed field elements in arbitrary order. That is, prover, instead of supplying given chunk c: [F; 8] in the intended order [c_1, c_2 ...c_8]` to Poseidon2Sponge, can instead supply it in any way he wants! Here is how: Say prover packs two field elements out of address region (0...14) with content [1, 2 ... 14] So corresponding packed values would be let packed_1 = Poseidon2PreimagePack<F> { pub clk: some_clk, pub byte_addr: 0, pub fe_addr: 0, pub bytes: [1, 2 ..7], // 2261738553347342 pub is_executed: 1, } let packed_2 = Poseidon2PreimagePack<F> { pub clk: some_clk, pub byte_addr: 8, pub fe_addr: 1, pub bytes: [8, 9, .. 14], // 283686952306183 pub is_executed: 1, } The idea is that there is no constraint tying byte_addr with fe_addr. That is, the malicious prover can flip the above fe_addrs by (packed_1.fe_addr, packed_2.fe_addr) = (packed_2.fe_addr, packed_1.fe_addr). It won't be detected anywhere! Hence when supplying the above to Poseidon2Sponge, he can emulate that he is supplying 283686952306183 before 2261738553347342. Simply because the former pack has fe_addr=0, and the latter pack has fe_addr=1 after modification. So instead of sponge permutation acting on [2261738553347342, 283686952306183...], it acts on [283686952306183, 2261738553347342...], hence leading to different output. In the former codebase, this can't happen because of CTL against Memory stark, which ensures that input to Poseidon2Sponge is coming in correct order (according to their addresses). But in the current implementation, there is no restriction, because fe_addr is not a real address as you pointed out! I think there is an easy way out though. We change the interpretation of input_addr in Poseidon2Sponge pub struct Poseidon2Sponge<T> { pub clk: T, pub ops: Ops<T>, pub input_addr: T, pub output_addr: T, pub input_len: T, pub preimage: [T; WIDTH], pub output: [T; WIDTH], pub gen_output: T, pub input_addr_padded: T, } Earlier this said "Hey, I am taking 8 bytes in region input_addr...input_addr+8, feeding it to current sponge preimage , apply sponge permutation, and I get output . I have also ordered the trace in increasing order of input_addr which you can check. Hence, you can be sure I am feeding it in correct order."
1 parent d9aa994 commit 2c07fb9

File tree

5 files changed

+8
-44
lines changed

5 files changed

+8
-44
lines changed

Diff for: circuits/src/memory/columns.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl<F: RichField> From<&Poseidon2Sponge<F>> for Vec<Memory<F>> {
119119
// each Field element in preimage represents packed data (packed bytes)
120120
(0..Poseidon2Permutation::<F>::RATE)
121121
.flat_map(|fe_index_inside_preimage| {
122-
let base_address = value.input_addr_padded
122+
let base_address = value.input_addr
123123
+ MozakPoseidon2::data_capacity_fe::<F>()
124124
* F::from_canonical_usize(fe_index_inside_preimage);
125125
let unpacked = MozakPoseidon2::unpack_to_field_elements(

Diff for: circuits/src/poseidon2_preimage_pack/columns.rs

+1-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use crate::stark::mozak_stark::{Poseidon2PreimagePackTable, TableWithTypedOutput
1515
pub struct Poseidon2PreimagePack<F> {
1616
pub clk: F,
1717
pub byte_addr: F,
18-
pub fe_addr: F,
1918
pub bytes: [F; MozakPoseidon2::DATA_CAPACITY_PER_FIELD_ELEMENT],
2019
pub is_executed: F,
2120
}
@@ -40,8 +39,7 @@ impl<F: RichField> From<&Poseidon2Sponge<F>> for Vec<Poseidon2PreimagePack<F>> {
4039
[..MozakPoseidon2::FIELD_ELEMENTS_RATE]
4140
.try_into()
4241
.expect("Should succeed since preimage can't be empty");
43-
let mut byte_base_address = value.input_addr_padded;
44-
let mut fe_base_addr = value.input_addr;
42+
let mut byte_base_address = value.input_addr;
4543
// For each FE of preimage we have BYTES_COUNT bytes
4644
preimage
4745
.iter()
@@ -50,15 +48,9 @@ impl<F: RichField> From<&Poseidon2Sponge<F>> for Vec<Poseidon2PreimagePack<F>> {
5048
let byte_addr = byte_base_address;
5149
// increase by DATA_CAP the byte base address after each iteration
5250
byte_base_address += MozakPoseidon2::data_capacity_fe();
53-
// specific field-el address
54-
let fe_addr = fe_base_addr;
55-
// increase by 1 after each iteration
56-
fe_base_addr += F::ONE;
57-
5851
Poseidon2PreimagePack {
5952
clk: value.clk,
6053
byte_addr,
61-
fe_addr,
6254
bytes: MozakPoseidon2::unpack_to_field_elements(fe),
6355
is_executed: F::ONE,
6456
}
@@ -74,7 +66,6 @@ columns_view_impl!(Poseidon2SpongePreimagePackCtl);
7466
pub struct Poseidon2SpongePreimagePackCtl<T> {
7567
pub clk: T,
7668
pub value: T,
77-
pub fe_addr: T,
7869
pub byte_addr: T,
7970
}
8071
#[must_use]
@@ -84,7 +75,6 @@ pub fn lookup_for_poseidon2_sponge() -> TableWithTypedOutput<Poseidon2SpongePrei
8475
Poseidon2SpongePreimagePackCtl {
8576
clk: PACK.clk,
8677
value: ColumnWithTypedInput::reduce_with_powers(PACK.bytes, 1 << 8),
87-
fe_addr: PACK.fe_addr,
8878
byte_addr: PACK.byte_addr,
8979
},
9080
PACK.is_executed,

Diff for: circuits/src/poseidon2_sponge/columns.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ pub struct Poseidon2Sponge<T> {
3131
pub preimage: [T; WIDTH],
3232
pub output: [T; WIDTH],
3333
pub gen_output: T,
34-
pub input_addr_padded: T,
3534
}
3635

3736
columns_view_impl!(Poseidon2Sponge);
@@ -116,8 +115,7 @@ pub fn lookup_for_preimage_pack(
116115
Poseidon2SpongePreimagePackCtl {
117116
clk: SPONGE.clk,
118117
value,
119-
fe_addr: SPONGE.input_addr + limb_index,
120-
byte_addr: SPONGE.input_addr_padded
118+
byte_addr: SPONGE.input_addr
121119
+ limb_index
122120
* i64::try_from(MozakPoseidon2::DATA_CAPACITY_PER_FIELD_ELEMENT)
123121
.expect("Should be < 255"),

Diff for: circuits/src/poseidon2_sponge/generation.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ fn unroll_sponge_data<F: RichField>(row: &Row<F>) -> Vec<Poseidon2Sponge<F>> {
2121

2222
let output_addr = poseidon2.output_addr;
2323
let mut input_addr = poseidon2.addr;
24-
let mut input_addr_padded = poseidon2.addr;
2524
let mut input_len = poseidon2.len;
2625
for i in 0..unroll_count {
2726
let ops: Ops<F> = Ops {
@@ -41,10 +40,8 @@ fn unroll_sponge_data<F: RichField>(row: &Row<F>) -> Vec<Poseidon2Sponge<F>> {
4140
preimage: sponge_datum.preimage,
4241
output: sponge_datum.output,
4342
gen_output: sponge_datum.gen_output,
44-
input_addr_padded: F::from_canonical_u32(input_addr_padded),
4543
});
46-
input_addr_padded += u32::try_from(MozakPoseidon2::DATA_PADDING).expect("Should succeed");
47-
input_addr += rate_size;
44+
input_addr += u32::try_from(MozakPoseidon2::DATA_PADDING).expect("Should succeed");
4845
input_len -= rate_size;
4946
}
5047

Diff for: circuits/src/poseidon2_sponge/stark.rs

+4-25
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,10 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for Poseidon2Spon
9696
// input
9797
yield_constr
9898
.constraint_transition(not_last_sponge * (lv.input_len - (nv.input_len + rate_scalar)));
99-
// and input_addr increases by RATE
99+
// and input_addr increases by RATE *
100100
yield_constr.constraint_transition(
101-
not_last_sponge * (lv.input_addr - (nv.input_addr - rate_scalar)),
101+
not_last_sponge * (lv.input_addr - (nv.input_addr - padding_scalar)),
102102
);
103-
// and input_addr_padded increases by DATA_PADDING = PACK_CAP * RATE
104-
yield_constr.constraint_transition(
105-
not_last_sponge * (lv.input_addr_padded - (nv.input_addr_padded - padding_scalar)),
106-
);
107-
// and for init permute, input_addr = input_addr_padded
108-
yield_constr.constraint(lv.ops.is_init_permute * (lv.input_addr - lv.input_addr_padded));
109103

110104
// For each init_permute capacity bits are zero.
111105
(rate..state_size).for_each(|i| {
@@ -187,27 +181,12 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for Poseidon2Spon
187181
// length decreases by RATE, note that only actual execution row can consume
188182
// input
189183
yield_constr.constraint_transition(builder, len_check);
190-
// and input_addr increases by RATE
191-
let nv_input_addr_rate = builder.sub_extension(nv.input_addr, rate_ext);
184+
// and input_addr increases by PADDING
185+
let nv_input_addr_rate = builder.sub_extension(nv.input_addr, padding_ext);
192186
let input_addr_diff_rate = builder.sub_extension(lv.input_addr, nv_input_addr_rate);
193187
let addr_check = builder.mul_extension(not_last_sponge, input_addr_diff_rate);
194188
yield_constr.constraint_transition(builder, addr_check);
195189

196-
// and input_addr_padding increases by DATA_PADDING
197-
let nv_input_addr_padding = builder.sub_extension(nv.input_addr_padded, padding_ext);
198-
let input_addr_diff_padding =
199-
builder.sub_extension(lv.input_addr_padded, nv_input_addr_padding);
200-
let padding_addr_check = builder.mul_extension(not_last_sponge, input_addr_diff_padding);
201-
yield_constr.constraint_transition(builder, padding_addr_check);
202-
203-
// and for init permute, input_addr = input_addr_padded
204-
// yield_constr.constraint(lv.ops.is_init_permute * (lv.input_addr -
205-
// lv.input_addr_padded));
206-
let lv_diff_addr_padding = builder.sub_extension(lv.input_addr, lv.input_addr_padded);
207-
let padding_addr_init_check =
208-
builder.mul_extension(lv.ops.is_init_permute, lv_diff_addr_padding);
209-
yield_constr.constraint(builder, padding_addr_init_check);
210-
211190
let zero = builder.constant_extension(F::Extension::ZERO);
212191
// For each init_permute capacity bits are zero.
213192
(rate..state_size).for_each(|i| {

0 commit comments

Comments
 (0)