Skip to main content

airbender_crypto/secp256k1/
recover.rs

1use crate::k256::{
2    elliptic_curve::{
3        bigint::{CheckedAdd, U256},
4        Curve, FieldBytesEncoding,
5    },
6    Secp256k1,
7};
8
9use super::{
10    context::{
11        ECMultContext, ECMULT_TABLE_SIZE_A, ECMULT_TABLE_SIZE_G, WINDOW_A, WINDOW_G, WNAF_BITS,
12    },
13    field::FieldElement,
14    points::{Affine, AffineStorage, Jacobian},
15    scalars::Scalar,
16    Secp256k1Err,
17};
18
19#[cfg(feature = "secp256k1-static-context")]
20pub fn recover(
21    message: &crate::k256::Scalar,
22    signature: &crate::k256::ecdsa::Signature,
23    recovery_id: &crate::k256::ecdsa::RecoveryId,
24) -> Result<Affine, Secp256k1Err> {
25    use super::context::ECRECOVER_CONTEXT;
26
27    recover_with_context(message, signature, recovery_id, &ECRECOVER_CONTEXT)
28}
29
30pub fn recover_with_context(
31    message: &crate::k256::Scalar,
32    signature: &crate::k256::ecdsa::Signature,
33    recovery_id: &crate::k256::ecdsa::RecoveryId,
34    context: &ECMultContext,
35) -> Result<Affine, Secp256k1Err> {
36    recover_with_context_and_hooks(
37        message,
38        signature,
39        recovery_id,
40        context,
41        &mut super::hooks::DefaultSecp256k1Hooks,
42    )
43}
44
45#[cfg(feature = "secp256k1-static-context")]
46pub fn recover_with_hooks<H: super::hooks::Secp256k1Hooks>(
47    message: &crate::k256::Scalar,
48    signature: &crate::k256::ecdsa::Signature,
49    recovery_id: &crate::k256::ecdsa::RecoveryId,
50    hooks: &mut H,
51) -> Result<Affine, Secp256k1Err> {
52    use super::context::ECRECOVER_CONTEXT;
53
54    recover_with_context_and_hooks(message, signature, recovery_id, &ECRECOVER_CONTEXT, hooks)
55}
56
57pub fn recover_with_context_and_hooks<H: super::hooks::Secp256k1Hooks>(
58    message: &crate::k256::Scalar,
59    signature: &crate::k256::ecdsa::Signature,
60    recovery_id: &crate::k256::ecdsa::RecoveryId,
61    context: &ECMultContext,
62    hooks: &mut H,
63) -> Result<Affine, Secp256k1Err> {
64    let (mut sigr, mut sigs) = Scalar::from_signature(signature);
65    let message = Scalar::from_k256_scalar(*message);
66
67    // We go through bytes because it's mod GROUP_ORDER and later we need mod BASE FIELD
68    let mut brx = sigr.to_repr();
69
70    if recovery_id.is_x_reduced() {
71        match <U256 as FieldBytesEncoding<Secp256k1>>::decode_field_bytes(&brx)
72            .checked_add(&Secp256k1::ORDER)
73            .into_option()
74        {
75            Some(restored) => {
76                brx = <U256 as FieldBytesEncoding<Secp256k1>>::encode_field_bytes(&restored);
77            }
78            None => return Err(Secp256k1Err::OperationOverflow),
79        }
80    }
81
82    let is_odd = recovery_id.is_y_odd();
83    let x =
84        Affine::decompress_with_hooks(&brx, is_odd, hooks).ok_or(Secp256k1Err::InvalidParams)?;
85
86    let xj = x.to_jacobian();
87
88    hooks.scalar_invert_and_assign(&mut sigr);
89    sigs *= sigr;
90
91    sigr *= message;
92    sigr.negate_in_place();
93
94    let mut pk = ecmult(&xj, &sigs, &sigr, context).to_affine_with_hooks(hooks);
95    pk.normalize_in_place();
96
97    if pk.is_infinity() {
98        return Err(Secp256k1Err::RecoveredInfinity);
99    }
100
101    Ok(pk)
102}
103
104/// Compute na*a+ng*g where g is the generator.
105/// Algorithm adapted from https://github.com/bitcoin-core/secp256k1/blob/master/src/ecmult_impl.h#L237
106fn ecmult(a: &Jacobian, na: &Scalar, ng: &Scalar, context: &ECMultContext) -> Jacobian {
107    let mut z = FieldElement::ONE;
108
109    let mut prea: [Affine; ECMULT_TABLE_SIZE_A] = [Affine::DEFAULT; ECMULT_TABLE_SIZE_A];
110    let mut aux: [FieldElement; ECMULT_TABLE_SIZE_A] = [FieldElement::ZERO; ECMULT_TABLE_SIZE_A];
111
112    let mut bits_na_1 = 0;
113    let mut bits_na_lam = 0;
114
115    let mut bits_ng_1 = 0;
116    let mut bits_ng_128 = 0;
117
118    let mut wnaf_na_1 = [0i32; WNAF_BITS];
119    let mut wnaf_na_lam = [0i32; WNAF_BITS];
120
121    let mut wnaf_ng_1 = [0i32; WNAF_BITS];
122    let mut wnaf_ng_128 = [0i32; WNAF_BITS];
123
124    let mut bits = 0;
125
126    if !na.is_zero() && !a.is_infinity() {
127        // split na into 129 bit scalars
128        // where na_1 + na_lam * lambda = na
129
130        // NOTE: this action moves integer representation to the normal form
131        let (na_1, na_lam) = na.decompose();
132
133        // build wnaf representation
134        bits_na_1 = wnaf(&mut wnaf_na_1, &na_1, WINDOW_A);
135        bits_na_lam = wnaf(&mut wnaf_na_lam, &na_lam, WINDOW_A);
136
137        debug_assert!(bits_na_1 <= WNAF_BITS as i32);
138        debug_assert!(bits_na_lam <= WNAF_BITS as i32);
139
140        if bits_na_1 > bits_na_lam {
141            bits = bits_na_1;
142        } else {
143            bits = bits_na_lam;
144        }
145
146        // Calculate odd multiples of a.
147        // All multiples are brought to the same `z` "denominator".
148        // Due to secp256k1 we can pretend the z coordinate is 1 and use affine addition formulas,
149        // and correct the result at the end
150        odd_multiples_table_windowa(&mut prea, &mut aux, &mut z, a);
151        table_set_globalz_windowa(&mut prea, &aux);
152
153        for i in 0..ECMULT_TABLE_SIZE_A {
154            aux[i] = FieldElement::BETA;
155            aux[i] *= prea[i].x;
156        }
157    }
158
159    if !ng.is_zero() {
160        // TODO: use u128 instead
161
162        // split ng into ~128 bit scalars.
163        // NOTE: it's NOT endomorphism decomposition, so it's 128 bits exact decomposition
164        // where ng_1 + ng_128*2^128 = ng
165        let (ng_1, ng_128) = ng.decompose_128(); // NOTE: must return normal form
166
167        // build wnaf representation
168        bits_ng_1 = wnaf(&mut wnaf_ng_1, &ng_1, WINDOW_G);
169        bits_ng_128 = wnaf(&mut wnaf_ng_128, &ng_128, WINDOW_G);
170
171        if bits_ng_1 > bits {
172            bits = bits_ng_1;
173        }
174        if bits_ng_128 > bits {
175            bits = bits_ng_128;
176        }
177    }
178
179    let mut r = Jacobian::INFINITY;
180
181    for i in (0..bits).rev() {
182        r.double_in_place(None);
183
184        let n = wnaf_na_1[i as usize];
185        if i < bits_na_1 && n != 0 {
186            r.add_ge_in_place(table_get_ge(&prea, n, WINDOW_A), None);
187        }
188
189        let n = wnaf_na_lam[i as usize];
190        if i < bits_na_lam && n != 0 {
191            r.add_ge_in_place(table_get_ge_lambda(&prea, &aux, n, WINDOW_A), None);
192        }
193
194        let n = wnaf_ng_1[i as usize];
195        if i < bits_ng_1 && n != 0 {
196            r.add_zinv_in_place(table_get_ge_storage(&context.pre_g, n, WINDOW_G), &z);
197        }
198
199        let n = wnaf_ng_128[i as usize];
200        if i < bits_ng_128 && n != 0 {
201            r.add_zinv_in_place(table_get_ge_storage(&context.pre_g_128, n, WINDOW_G), &z);
202        }
203    }
204
205    if !r.is_infinity() {
206        r.z *= z
207    }
208
209    r
210}
211
212/// Fill `pre_a` with odd multiples of a.
213/// Although pre_a is an array of affine points, it actually represents elements in jacobian coordinates
214/// with their z coordinate omitted. The omitted z-coordinate can be recovered with `zr` and `z`.
215/// Using `b.z` to denote the omitted z-coordinate of b:
216/// - `pre_a[n-1].z = z`
217/// - `pre_a[i-1].z = pre_a[i].z / zr[i]` for `n > i > 0`
218///
219/// Lastly, `zr[0]` is set so that `a.z = pre_a[0].z / zr[0]`
220/// Based on https://github.com/bitcoin-core/secp256k1/blob/master/src/ecmult_impl.h#L73
221fn odd_multiples_table_windowa(
222    pre_a: &mut [Affine; ECMULT_TABLE_SIZE_A],
223    zr: &mut [FieldElement; ECMULT_TABLE_SIZE_A],
224    z: &mut FieldElement,
225    a: &Jacobian,
226) {
227    debug_assert!(!a.is_infinity());
228
229    let mut d = *a;
230    d.double_in_place(None);
231
232    // we perform additions using an isomorphic curve Y^2 = X^3 + 7*C^6 where  C := d.z
233    // The isomorphism, phi, is given by (x,y) -> (x*C^2, y*C^3).
234    // In Jacobian coordinates, phi is given by (x, y, z) -> (x*C^2, y*C^3, z) = (x, y, z/C)
235    // So
236    //      d_ge = phi(d) = (d.x, d.y, 1)
237    //      ai = phi(a) = (a.x*C^2, a.y*C^3, a.z)
238    // This lets us use the faster add_ge_var
239
240    let d_ge = Affine {
241        x: d.x,
242        y: d.y,
243        infinity: false,
244    };
245
246    pre_a[0].set_gej_zinv(a, &d.z);
247
248    let mut ai = Jacobian {
249        x: pre_a[0].x,
250        y: pre_a[0].y,
251        z: a.z,
252    };
253
254    // pre_a[0] is the point (a.x*C^2, a.y*C^3, a.z*C) which is equivalent to a.
255    // Set zr[0] to C, which is the ratio between the omitted z(pre_a[0]) value and a.z.
256    zr[0] = d.z;
257
258    for i in 1..ECMULT_TABLE_SIZE_A {
259        ai.add_ge_in_place(d_ge, Some(&mut zr[i]));
260        pre_a[i] = Affine {
261            x: ai.x,
262            y: ai.y,
263            infinity: false,
264        };
265    }
266
267    // Multiply the last z-coordinate by C to undo the isomorphism.
268    // Since the z-coordinates of the pre_a values are implied by the zr array of z-coordinate ratios,
269    // undoing the isomorphism here undoes the isomorphism for all pre_a values.
270    *z = ai.z;
271    *z *= d.z;
272}
273
274fn table_set_globalz_windowa(
275    pre_a: &mut [Affine; ECMULT_TABLE_SIZE_A],
276    zr: &[FieldElement; ECMULT_TABLE_SIZE_A],
277) {
278    let mut i = ECMULT_TABLE_SIZE_A - 1;
279
280    pre_a[i].y.normalize_in_place();
281
282    let mut zs = zr[i];
283
284    i -= 1;
285
286    let mut ai = pre_a[i];
287    pre_a[i].set_ge_zinv(&ai, &zs);
288
289    while i > 0 {
290        zs *= zr[i];
291        i -= 1;
292
293        ai = pre_a[i];
294        pre_a[i].set_ge_zinv(&ai, &zs);
295    }
296}
297
298/// Convert a scalar to a wnaf representation,
299/// i.e. `a=sum(2^i * wnaf[i])`, with te following guarantees:
300///     - each `wnaf[i]` is either 0, or an odd integer between `-(1<<(w-1) - 1)` and `1<<(w-1) - 1`
301///     - two non-zero entries are separated by at least `w-1` zeros
302///     - the number of set values in wnaf is returned
303///
304/// NOTE: the function assumes that `wnaf` is zeroed
305fn wnaf(wnaf: &mut [i32], s: &Scalar, w: usize) -> i32 {
306    debug_assert!(wnaf.len() <= 256);
307    debug_assert!((2..=31).contains(&w));
308    debug_assert!(wnaf.iter().all(|&x| x == 0));
309
310    let mut s = *s;
311
312    let mut last_set_bit: i32 = -1;
313    let mut bit = 0;
314    let mut sign = 1;
315    let mut carry = 0;
316
317    if s.bits(255, 1) > 0 {
318        // Negation is the same in any form
319        s.negate_in_place();
320        sign = -1;
321    }
322
323    while bit < wnaf.len() {
324        if s.bits(bit, 1) == carry as u32 {
325            bit += 1;
326            continue;
327        }
328
329        let mut now = w;
330        if now > wnaf.len() - bit {
331            now = wnaf.len() - bit;
332        }
333
334        let mut word = (s.bits_var(bit, now) as i32) + carry;
335
336        carry = (word >> (w - 1)) & 1;
337        word -= carry << w;
338
339        wnaf[bit] = sign * word;
340        last_set_bit = bit as i32;
341
342        bit += now;
343    }
344    debug_assert_eq!(carry, 0);
345    debug_assert!({
346        let mut t = true;
347        while bit < 256 {
348            t = t && (s.bits(bit, 1) == 0);
349            bit += 1;
350        }
351        t
352    });
353
354    last_set_bit + 1
355}
356
357fn table_get_ge(pre: &[Affine], n: i32, w: usize) -> Affine {
358    debug_assert!(table_verify(n, w));
359
360    if n > 0 {
361        pre[(n - 1) as usize / 2]
362    } else {
363        let mut r = pre[(-n - 1) as usize / 2];
364        r.y.negate_in_place(1);
365        r
366    }
367}
368
369fn table_get_ge_lambda(pre: &[Affine], aux: &[FieldElement], n: i32, w: usize) -> Affine {
370    debug_assert!(table_verify(n, w));
371
372    if n > 0 {
373        Affine {
374            x: aux[(n - 1) as usize / 2],
375            y: pre[(n - 1) as usize / 2].y,
376            infinity: false,
377        }
378    } else {
379        let mut y = pre[(-n - 1) as usize / 2].y;
380        y.negate_in_place(1);
381
382        Affine {
383            x: aux[(-n - 1) as usize / 2],
384            y,
385            infinity: false,
386        }
387    }
388}
389
390fn table_get_ge_storage(pre: &[AffineStorage; ECMULT_TABLE_SIZE_G], n: i32, w: usize) -> Affine {
391    debug_assert!(table_verify(n, w));
392
393    if n > 0 {
394        pre[(n - 1) as usize / 2].to_affine()
395    } else {
396        let mut r = pre[(-n - 1) as usize / 2].to_affine();
397        r.y.negate_in_place(1);
398        r
399    }
400}
401
402fn table_verify(n: i32, w: usize) -> bool {
403    (2..=31).contains(&w) && ((n & 1) == 1) && (n >= -((1 << (w - 1)) - 1)) && (n < (1 << (w - 1)))
404}
405
406#[cfg(test)]
407mod tests {
408    #![allow(non_snake_case)]
409
410    use super::ecmult;
411    use crate::secp256k1::scalars::Scalar;
412    use crate::secp256k1::{context::ECRECOVER_CONTEXT, test_vectors::MUL_TEST_VECTORS};
413    use crate::secp256k1::{
414        field::FieldElement,
415        points::{Affine, Jacobian},
416    };
417
418    use proptest::{prop_assert_eq, proptest};
419
420    #[cfg(feature = "secp256k1-static-context")]
421    #[test]
422    fn ecmult_0I_0G() {
423        assert_eq!(ECRECOVER_CONTEXT.pre_g[0].to_affine(), Affine::GENERATOR);
424
425        // 0*infinity + 0*G = 0
426        let res = ecmult(
427            &Jacobian::INFINITY,
428            &Scalar::ZERO,
429            &Scalar::ZERO,
430            &ECRECOVER_CONTEXT,
431        )
432        .to_affine();
433
434        assert!(res.is_infinity());
435    }
436
437    #[cfg(feature = "secp256k1-static-context")]
438    #[test]
439    fn ecmult_0I_1G() {
440        // 0*infinity + 1*G = G
441        let mut res = ecmult(
442            &Jacobian::INFINITY,
443            &Scalar::ZERO,
444            &Scalar::ONE,
445            &ECRECOVER_CONTEXT,
446        )
447        .to_affine();
448
449        res.normalize_in_place();
450
451        assert_eq!(res, Affine::GENERATOR);
452    }
453
454    #[cfg(feature = "secp256k1-static-context")]
455    #[test]
456    fn ecmult_0I_3G() {
457        // 0*infinity + 3*G = 3*G
458        let res = ecmult(
459            &Jacobian::INFINITY,
460            &Scalar::ZERO,
461            &Scalar::from_u128(3),
462            &ECRECOVER_CONTEXT,
463        )
464        .to_affine();
465
466        assert_eq!(res, ECRECOVER_CONTEXT.pre_g[1].to_affine())
467    }
468
469    #[cfg(feature = "secp256k1-static-context")]
470    #[test]
471    fn ecmult_0I_5G() {
472        let res = ecmult(
473            &Jacobian::INFINITY,
474            &Scalar::ZERO,
475            &Scalar::from_u128(5),
476            &ECRECOVER_CONTEXT,
477        )
478        .to_affine();
479
480        assert_eq!(res, ECRECOVER_CONTEXT.pre_g[2].to_affine());
481    }
482
483    #[cfg(feature = "secp256k1-static-context")]
484    #[test]
485    fn ecmult_0I_8G() {
486        // t = 5G + 3G
487        let mut t = ECRECOVER_CONTEXT.pre_g[2].to_affine().to_jacobian();
488        t.add_ge_in_place(ECRECOVER_CONTEXT.pre_g[1].to_affine(), None);
489
490        let t = t.to_affine();
491
492        // 0*infinity + 8*G = 8*G
493        let res = ecmult(
494            &Jacobian::INFINITY,
495            &Scalar::ZERO,
496            &Scalar::from_u128(8),
497            &ECRECOVER_CONTEXT,
498        )
499        .to_affine();
500
501        assert_eq!(res, t);
502    }
503
504    #[cfg(feature = "secp256k1-static-context")]
505    #[test]
506    fn ecmult_1G_0G() {
507        let res = ecmult(
508            &Affine::GENERATOR.to_jacobian(),
509            &Scalar::ONE,
510            &Scalar::ZERO,
511            &ECRECOVER_CONTEXT,
512        )
513        .to_affine();
514
515        assert_eq!(res, Affine::GENERATOR);
516    }
517
518    #[cfg(feature = "secp256k1-static-context")]
519    #[test]
520    fn ecmult_1G_1G() {
521        let res = ecmult(
522            &Affine::GENERATOR.to_jacobian(),
523            &Scalar::ONE,
524            &Scalar::ONE,
525            &ECRECOVER_CONTEXT,
526        )
527        .to_affine();
528
529        let mut expected = Affine::GENERATOR.to_jacobian();
530        expected.double_in_place(None);
531
532        assert_eq!(res, expected.to_affine())
533    }
534
535    #[cfg(feature = "secp256k1-static-context")]
536    #[test]
537    fn ecmult_1G_2G() {
538        let res = ecmult(
539            &Affine::GENERATOR.to_jacobian(),
540            &Scalar::ONE,
541            &Scalar::from_u128(2),
542            &ECRECOVER_CONTEXT,
543        )
544        .to_affine();
545
546        assert_eq!(res, ECRECOVER_CONTEXT.pre_g[1].to_affine());
547    }
548
549    #[cfg(feature = "secp256k1-static-context")]
550    #[test]
551    fn ecmult_2G_1G() {
552        let mut res = ecmult(
553            &Affine::GENERATOR.to_jacobian(),
554            &Scalar::from_u128(2),
555            &Scalar::ONE,
556            &ECRECOVER_CONTEXT,
557        )
558        .to_affine();
559
560        res.normalize_in_place();
561
562        assert_eq!(res, ECRECOVER_CONTEXT.pre_g[1].to_affine());
563    }
564
565    #[cfg(feature = "secp256k1-static-context")]
566    #[test]
567    fn compare_ecmult() {
568        proptest!(|(k: Scalar)| {
569            let res1 = ecmult(
570                &Jacobian::INFINITY,
571                &Scalar::ZERO,
572                &k,
573                &ECRECOVER_CONTEXT
574            ).to_affine();
575
576            let res2 = ecmult(
577                &Affine::GENERATOR.to_jacobian(),
578                &k,
579                &Scalar::ZERO,
580                &ECRECOVER_CONTEXT
581            ).to_affine();
582
583            prop_assert_eq!(res1, res2);
584        })
585    }
586
587    #[cfg(feature = "secp256k1-static-context")]
588    #[test]
589    fn test_generator_multipules() {
590        for (k, x, y) in MUL_TEST_VECTORS {
591            let k = Scalar::from_repr((*k).into());
592
593            let computed_ctx =
594                ecmult(&Jacobian::INFINITY, &Scalar::ZERO, &k, &ECRECOVER_CONTEXT).to_affine();
595
596            let computed = ecmult(
597                &Affine::GENERATOR.to_jacobian(),
598                &k,
599                &Scalar::ZERO,
600                &ECRECOVER_CONTEXT,
601            )
602            .to_affine();
603
604            let expected = Affine {
605                x: FieldElement::from_bytes_unchecked(x),
606                y: FieldElement::from_bytes_unchecked(y),
607                infinity: false,
608            };
609
610            assert_eq!(computed_ctx, computed);
611            assert_eq!(computed_ctx, expected);
612        }
613    }
614
615    #[cfg(feature = "secp256k1-static-context")]
616    #[test]
617    fn test_regressions() {
618        // recover point at infinity
619        assert_eq!(
620            recover_from_digest(
621                [
622                    107, 141, 44, 129, 177, 27, 45, 105, 149, 40, 221, 228, 136, 219, 223, 47, 148,
623                    41, 61, 13, 51, 195, 46, 52, 127, 37, 95, 164, 166, 193, 240, 169
624                ],
625                [
626                    121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2,
627                    155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152
628                ],
629                [
630                    107, 141, 44, 129, 177, 27, 45, 105, 149, 40, 221, 228, 136, 219, 223, 47, 148,
631                    41, 61, 13, 51, 195, 46, 52, 127, 37, 95, 164, 166, 193, 240, 169
632                ],
633                0
634            )
635            .unwrap_err(),
636            super::Secp256k1Err::RecoveredInfinity
637        );
638
639        // geth
640        assert_eq!(
641            recover_from_digest(
642                [
643                    56, 209, 138, 203, 103, 210, 92, 139, 185, 148, 39, 100, 182, 47, 24, 225, 112,
644                    84, 246, 106, 129, 123, 212, 41, 84, 35, 173, 249, 237, 152, 135, 62
645                ],
646                [
647                    56, 209, 138, 203, 103, 210, 92, 139, 185, 148, 39, 100, 182, 47, 24, 225, 112,
648                    84, 246, 106, 129, 123, 212, 41, 84, 35, 173, 249, 237, 152, 135, 62
649                ],
650                [
651                    120, 157, 29, 212, 35, 210, 95, 7, 114, 210, 116, 141, 96, 247, 228, 184, 27,
652                    177, 77, 8, 110, 186, 142, 142, 142, 251, 109, 207, 248, 164, 174, 2
653                ],
654                0
655            ),
656            Ok(Affine {
657                x: FieldElement::from_bytes_unchecked(&[
658                    134, 18, 84, 164, 207, 141, 253, 45, 96, 226, 163, 62, 49, 67, 234, 198, 40,
659                    88, 134, 240, 174, 217, 23, 17, 171, 126, 44, 1, 63, 38, 92, 85
660                ]),
661                y: FieldElement::from_bytes_unchecked(&[
662                    253, 69, 102, 103, 243, 176, 134, 87, 121, 95, 230, 117, 75, 111, 188, 17, 24,
663                    103, 20, 196, 228, 244, 141, 91, 133, 104, 0, 227, 232, 28, 48, 200
664                ]),
665                infinity: false
666            })
667        )
668    }
669
670    #[cfg(feature = "secp256k1-static-context")]
671    fn recover_from_digest(
672        digest: [u8; 32],
673        r: [u8; 32],
674        s: [u8; 32],
675        rec_id: u8,
676    ) -> Result<Affine, super::Secp256k1Err> {
677        use k256::ecdsa::{RecoveryId, Signature};
678        use {
679            k256::elliptic_curve::ops::Reduce,
680            k256::{ecdsa::hazmat::bits2field, Scalar},
681        };
682
683        let signature = Signature::from_scalars(r, s).unwrap();
684        let recovery_id = RecoveryId::try_from(rec_id).unwrap();
685
686        let message = <Scalar as Reduce<k256::U256>>::reduce_bytes(
687            &bits2field::<k256::Secp256k1>(&digest).unwrap(),
688        );
689
690        super::recover(&message, &signature, &recovery_id)
691    }
692
693    #[cfg(feature = "secp256k1-static-context")]
694    #[test]
695    fn recover_with_default_hooks_matches_recover() {
696        use crate::secp256k1::hooks::DefaultSecp256k1Hooks;
697        use k256::ecdsa::{RecoveryId, Signature};
698        use {
699            k256::elliptic_curve::ops::Reduce,
700            k256::{ecdsa::hazmat::bits2field, Scalar},
701        };
702
703        let digest = [
704            56, 209, 138, 203, 103, 210, 92, 139, 185, 148, 39, 100, 182, 47, 24, 225, 112, 84,
705            246, 106, 129, 123, 212, 41, 84, 35, 173, 249, 237, 152, 135, 62,
706        ];
707        let r = digest;
708        let s = [
709            120, 157, 29, 212, 35, 210, 95, 7, 114, 210, 116, 141, 96, 247, 228, 184, 27, 177, 77,
710            8, 110, 186, 142, 142, 142, 251, 109, 207, 248, 164, 174, 2,
711        ];
712
713        let signature = Signature::from_scalars(r, s).unwrap();
714        let recovery_id = RecoveryId::try_from(0u8).unwrap();
715        let message = <Scalar as Reduce<k256::U256>>::reduce_bytes(
716            &bits2field::<k256::Secp256k1>(&digest).unwrap(),
717        );
718
719        let without_hooks = super::recover(&message, &signature, &recovery_id).unwrap();
720        let with_hooks = super::recover_with_hooks(
721            &message,
722            &signature,
723            &recovery_id,
724            &mut DefaultSecp256k1Hooks,
725        )
726        .unwrap();
727
728        assert_eq!(without_hooks, with_hooks);
729    }
730}