@@ -77,50 +77,108 @@ where
7777 randomness : & Self :: Randomness ,
7878 message : & [ u8 ; MESSAGE_LENGTH ] ,
7979 ) -> Result < Vec < u8 > , HypercubeHashError > {
80+ // Compile-time parameter validation for AbortingHypercubeMessageHash
81+ //
82+ // This hash implements H^hc_{w,v,z,Q} from §6.1 of HHKTW26. It uses
83+ // rejection sampling to uniformly map Poseidon field elements into
84+ // the hypercube Z_w^v, avoiding big-integer arithmetic entirely:
85+ //
86+ // 1. Compute (A_1, ..., A_ℓ) := Poseidon(R || P || T || M)
87+ // 2. For each A_i: reject if A_i ≥ Q·w^z (ensures uniformity)
88+ // 3. Decompose d_i = ⌊A_i / Q⌋ into z base-w digits
89+ // 4. Collect the first v digits as the output
90+ //
91+ // The field prime decomposes as p = Q·w^z + α (α ≥ 0).
92+ // Rejection happens with per-element probability α/p, and the
93+ // overall abort probability is θ = 1 - ((Q·w^z)/p)^ℓ (Lemma 8).
94+ //
95+ // By Theorem 4 of HHKTW26, this construction is indifferentiable
96+ // from a θ-aborting random oracle when Poseidon is modeled as a
97+ // standard random oracle.
98+ //
99+ // DKKW25: https://eprint.iacr.org/2025/055
100+ // HHKTW26: https://eprint.iacr.org/2026/016
80101 const {
81- // Check that Poseidon of width 24 is enough
102+ // Poseidon capacity constraints
103+ //
104+ // We use Poseidon in compression mode with a width-24 permutation.
105+ // All inputs must fit in one call, and the output is extracted
106+ // from the same state.
82107 assert ! (
83108 PARAMETER_LEN + RAND_LEN_FE + TWEAK_LEN_FE + MSG_LEN_FE <= 24 ,
84- "Poseidon of width 24 is not enough"
109+ "Poseidon of width 24 is not enough for the input "
85110 ) ;
86- assert ! ( HASH_LEN_FE <= 24 , "Poseidon of width 24 is not enough" ) ;
87-
88- // Check that we have enough hash output field elements
89111 assert ! (
90- HASH_LEN_FE >= DIMENSION . div_ceil ( Z ) ,
91- "Not enough hash output field elements for the requested dimension "
112+ HASH_LEN_FE <= 24 ,
113+ "Poseidon of width 24 is not enough for the output "
92114 ) ;
115+
116+ // Poseidon compression mode can only produce as many output
117+ // field elements as there are input elements.
93118 assert ! (
94119 PARAMETER_LEN + RAND_LEN_FE + TWEAK_LEN_FE + MSG_LEN_FE >= HASH_LEN_FE ,
95120 "Input shorter than requested output"
96121 ) ;
97122
98- // Base check
123+ // Hypercube decomposition parameters
124+ //
125+ // Each good field element A_i < Q·w^z is decomposed into z
126+ // base-w digits, so we need ℓ = ⌈v/z⌉ field elements to get
127+ // at least v digits. HASH_LEN_FE must supply enough elements.
99128 assert ! (
100- BASE <= 1 << 8 ,
101- "Aborting Hypercube Message Hash: Base must be at most 2^8"
129+ DIMENSION >= 1 ,
130+ "AbortingHypercubeMessageHash: DIMENSION (v) must be at least 1"
131+ ) ;
132+ assert ! (
133+ Z >= 1 ,
134+ "AbortingHypercubeMessageHash: Z (digits per field element) must be at least 1"
135+ ) ;
136+ assert ! (
137+ HASH_LEN_FE >= DIMENSION . div_ceil( Z ) ,
138+ "Not enough hash output field elements: need ceil(v/z)"
102139 ) ;
103140
104- // Check that Q * w^z fits within the field
141+ // Q is the quotient in the decomposition A_i = Q·d_i + c_i,
142+ // where c_i ∈ {0, ..., Q-1} is discarded and d_i ∈ {0, ..., w^z-1}
143+ // carries the uniform digits. Q must be positive for a valid range.
144+ assert ! ( Q >= 1 , "AbortingHypercubeMessageHash: Q must be at least 1" ) ;
145+
146+ // The rejection threshold Q·w^z must not exceed the field order p,
147+ // since field elements A_i live in {0, ..., p-1}. The remainder
148+ // α = p - Q·w^z determines the per-element abort probability α/p.
149+ //
150+ // Example (KoalaBear): p = 2^31 - 2^24 + 1 = 127·8^8 + 1
151+ // ⟹ Q=127, w=8, z=8, α=1, abort prob ≈ 4.7e-10 per element.
105152 assert ! (
106153 Q as u64 * ( BASE as u64 ) . pow( Z as u32 ) <= F :: ORDER_U64 ,
107- "Q * w^z exceeds field order"
154+ "Q * w^z exceeds field order p "
108155 ) ;
109156
110- // floor(log2(ORDER))
111- let bits_per_fe = F :: ORDER_U64 . ilog2 ( ) as usize ;
157+ // Representation constraints
158+ //
159+ // Same as the Poseidon message hash: chunks and chain indices
160+ // are stored as u8 in signatures and tweak encodings.
161+ assert ! (
162+ BASE >= 2 ,
163+ "AbortingHypercubeMessageHash: BASE (w) must be at least 2 (Definition 13, DKKW25)"
164+ ) ;
165+ assert ! (
166+ BASE <= 1 << 8 ,
167+ "AbortingHypercubeMessageHash: BASE (w) must fit in u8"
168+ ) ;
112169
113- // Check that we have enough bits to encode message
170+ // Injective encoding of inputs
171+ //
172+ // Same requirements as the standard Poseidon message hash:
173+ // message and epoch must be losslessly encodable as field elements.
174+ let bits_per_fe = F :: ORDER_U64 . ilog2 ( ) as usize ;
114175 assert ! (
115176 bits_per_fe * MSG_LEN_FE >= 8 * MESSAGE_LENGTH ,
116- "Aborting Hypercube Message Hash : not enough field elements to encode the message"
177+ "AbortingHypercubeMessageHash : not enough field elements to encode the message"
117178 ) ;
118-
119- // Check that we have enough bits to encode tweak
120- // Epoch is a u32, and we have one domain separator byte
121179 assert ! (
122180 bits_per_fe * TWEAK_LEN_FE >= 40 ,
123- "Aborting Hypercube Message Hash : not enough field elements to encode the epoch tweak"
181+ "AbortingHypercubeMessageHash : not enough field elements to encode the epoch tweak"
124182 ) ;
125183 }
126184
0 commit comments