11use super :: { PairedSecretShare , SecretShare , ShareImage , ShareIndex , VerificationShare } ;
22use alloc:: vec:: Vec ;
33use core:: { marker:: PhantomData , ops:: Deref } ;
4- use secp256kfun:: { poly, prelude:: * } ;
4+ use secp256kfun:: { hash :: Hash32 , poly, prelude:: * } ;
55
66/// A polynomial where the first coefficient (constant term) is the image of a secret `Scalar` that
77/// has been shared in a [Shamir's secret sharing] structure.
@@ -21,7 +21,7 @@ pub struct SharedKey<T = Normal, Z = NonZero> {
2121 ty : PhantomData < ( T , Z ) > ,
2222}
2323
24- impl < T : PointType , Z : ZeroChoice > SharedKey < T , Z > {
24+ impl < T : Normalized , Z : ZeroChoice > SharedKey < T , Z > {
2525 /// "pair" a secret share that belongs to this shared key so you can keep track of tweaks to the
2626 /// public key and the secret share together.
2727 ///
@@ -186,35 +186,32 @@ impl<T: PointType, Z: ZeroChoice> SharedKey<T, Z> {
186186 }
187187 }
188188
189- /// Checks if the polynomial coefficients contain a fingerprint by verifying that
190- /// each coefficient (when hashed with all previous coefficients) has the required
191- /// number of leading zero bits.
189+ /// Checks if the polynomial coefficients contain the specified `fingerprint`.
192190 ///
193- /// This is useful for detecting if shares come from the same DKG session or if
194- /// they have been corrupted.
191+ /// Verifies that each non-constant coefficient, when hashed together with all
192+ /// previous coefficients, produces a hash with at least `fingerprint.bit_length`
193+ /// leading zero bits. This allows detection of shares from the same DKG session
194+ /// and helps identify corrupted or mismatched shares.
195195 ///
196- /// ## Parameters
197- /// - `fingerprint`: The expected fingerprint configuration
198- ///
199- /// ## Returns
200- /// `true` if all coefficients match the fingerprint pattern, `false` otherwise
196+ /// Returns `true` if all coefficients match the fingerprint pattern, `false`
197+ /// if any coefficient fails to meet the difficulty requirement.
201198 pub fn check_fingerprint < H : crate :: fun:: hash:: Hash32 > (
202199 & self ,
203200 fingerprint : & Fingerprint ,
204201 ) -> bool {
205202 use crate :: fun:: hash:: HashAdd ;
206203
207- if self . point_polynomial . is_empty ( ) {
204+ // the fingerprint is only placed on the non-constant coefficients so it
205+ // can't be detected with a length 1 polynomial
206+ if self . point_polynomial . len ( ) <= 1 {
208207 return true ;
209208 }
210209
211- // Start with hash including the tag with length prefix
212- let mut hash_state = H :: default ( ) ;
213- if !fingerprint. tag . is_empty ( ) {
214- hash_state = hash_state. add ( [ fingerprint. tag . len ( ) as u8 ] ) ;
215- hash_state = hash_state. add ( fingerprint. tag . as_bytes ( ) ) ;
216- }
217- hash_state = hash_state. add ( self . point_polynomial [ 0 ] ) ;
210+ let mut hash_state = H :: default ( )
211+ . add ( [ fingerprint. tag . len ( ) as u8 ] )
212+ . add ( fingerprint. tag . as_bytes ( ) )
213+ // the public key is unmolested by the fingerprint
214+ . add ( self . point_polynomial [ 0 ] ) ;
218215
219216 // Check each non-constant coefficient
220217 for i in 1 ..self . point_polynomial . len ( ) {
@@ -231,6 +228,72 @@ impl<T: PointType, Z: ZeroChoice> SharedKey<T, Z> {
231228
232229 true
233230 }
231+
232+ /// Grinds polynomial coefficients to embed the specified `fingerprint` through
233+ /// proof-of-work.
234+ ///
235+ /// For each non-constant coefficient, repeatedly adds the generator point `G`
236+ /// until the cumulative hash (including the tag, public key, and all previous
237+ /// coefficients) has at least `fingerprint.bit_length` leading zero bits.
238+ /// This process modifies the polynomial while preserving the shared secret.
239+ ///
240+ /// Returns a scalar polynomial where the constant term is always zero
241+ /// and the remaining coefficients indicate how many times `G` was added
242+ /// to each polynomial coefficient. This polynomial can be added to secret
243+ /// shares using [`SecretShare::homomorphic_poly_add`] to maintain consistency.
244+ ///
245+ /// The computational cost increases exponentially with `bit_length`: each
246+ /// additional bit doubles the expected work required.
247+ pub fn grind_fingerprint < H : Hash32 > (
248+ & mut self ,
249+ fingerprint : Fingerprint ,
250+ ) -> Vec < Scalar < Public , Zero > > {
251+ let mut tweaks = Vec :: with_capacity ( self . threshold ( ) ) ;
252+ // We don't mutate the first coefficient
253+ tweaks. push ( Scalar :: < Public , _ > :: zero ( ) ) ;
254+
255+ if self . point_polynomial . len ( ) <= 1 {
256+ // only mutate non-constant terms
257+ return tweaks;
258+ }
259+
260+ use secp256kfun:: hash:: HashAdd ;
261+ let mut hash_state = H :: default ( )
262+ . add ( [ fingerprint. tag . len ( ) as u8 ] )
263+ . add ( fingerprint. tag . as_bytes ( ) )
264+ // the public key is unmolested from the fingerprint grinding
265+ . add ( self . point_polynomial [ 0 ] ) ;
266+
267+ for coeff in & mut self . point_polynomial [ 1 ..] {
268+ let mut total_tweak = Scalar :: < Public , Zero > :: zero ( ) ;
269+ let mut current_coeff = * coeff;
270+
271+ loop {
272+ // Clone the hash state from previous coefficients and add current one
273+ let hash = hash_state. clone ( ) . add ( current_coeff) ;
274+ // Check if hash has required number of leading zero bits
275+ let hash_bytes: [ u8 ; 32 ] = hash. clone ( ) . finalize_fixed ( ) . into ( ) ;
276+ if Fingerprint :: leading_zero_bits ( & hash_bytes[ ..] )
277+ >= fingerprint. bit_length as usize
278+ {
279+ // Update hash_state for next coefficient because the next coefficient hash
280+ // includes the current one.
281+ hash_state = hash;
282+ break ;
283+ }
284+
285+ // Add one more G to the coefficient
286+ total_tweak += s ! ( 1 ) ;
287+ current_coeff = g ! ( current_coeff + G ) . normalize ( ) ;
288+ }
289+
290+ // Apply the final tweak to the polynomial coefficient
291+ * coeff = current_coeff;
292+ tweaks. push ( total_tweak) ;
293+ }
294+
295+ tweaks
296+ }
234297}
235298
236299impl SharedKey {
@@ -376,8 +439,13 @@ bincode::impl_borrow_decode!(SharedKey<EvenY, NonZero>);
376439
377440/// Configuration for polynomial fingerprinting in DKG protocols.
378441///
379- /// This allows coordinators to embed identifying information into polynomial coefficients
380- /// to help detect when shares from different DKG sessions are mixed.
442+ /// A `Fingerprint` allows coordinators to embed proof-of-work into polynomial
443+ /// coefficients during distributed key generation. This helps detect when shares
444+ /// from different DKG sessions are accidentally mixed and provides resistance
445+ /// against resource exhaustion attacks.
446+ ///
447+ /// The fingerprint is computed by hashing the public key with each subsequent
448+ /// coefficient, requiring each hash to have a minimum number of leading zero bits.
381449#[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
382450pub struct Fingerprint {
383451 /// Number of leading zero bits required in the hash
0 commit comments