fuel_crypto/secp256/backend/r1/
p256.rs

1//! secp256r1 (P-256) functions
2
3#[cfg(feature = "test-helpers")]
4use crate::secp256::signature_format::encode_signature;
5use crate::{
6    message::Message,
7    secp256::signature_format::decode_signature,
8    Error,
9};
10#[cfg(feature = "test-helpers")]
11use ecdsa::RecoveryId;
12use fuel_types::Bytes64;
13use p256::ecdsa::VerifyingKey;
14
15/// Sign a prehashed message. With the given key.
16#[cfg(feature = "test-helpers")]
17pub fn sign_prehashed(
18    signing_key: &p256::ecdsa::SigningKey,
19    message: &Message,
20) -> Result<Bytes64, Error> {
21    let (signature, _) = signing_key
22        .sign_prehash_recoverable(&**message)
23        .map_err(|_| Error::FailedToSign)?;
24
25    let signature = signature.normalize_s().unwrap_or(signature);
26
27    // TODO: this is a hack to get the recovery id. The signature should be normalized
28    // before computing the recovery id, but p256 library doesn't support this, and
29    // instead always computes the recovery id from non-normalized signature.
30    // So instead the recovery id is determined by checking which variant matches
31    // the original public key.
32
33    let recid1 = RecoveryId::new(false, false);
34    let recid2 = RecoveryId::new(true, false);
35
36    let rec1 = VerifyingKey::recover_from_prehash(&**message, &signature, recid1);
37    let rec2 = VerifyingKey::recover_from_prehash(&**message, &signature, recid2);
38
39    let actual = signing_key.verifying_key();
40
41    let recovery_id = if rec1.map(|r| r == *actual).unwrap_or(false) {
42        recid1
43    } else if rec2.map(|r| r == *actual).unwrap_or(false) {
44        recid2
45    } else {
46        unreachable!("Invalid signature generated");
47    };
48
49    let recovery_id = recovery_id
50        .try_into()
51        .expect("reduced-x recovery ids are never generated");
52    Ok(Bytes64::from(encode_signature(
53        signature.to_bytes().into(),
54        recovery_id,
55    )))
56}
57
58/// Convert the public key point to its uncompressed non-prefixed representation,
59/// i.e. 32 bytes of x coordinate and 32 bytes of y coordinate.
60#[cfg(feature = "test-helpers")]
61pub fn encode_pubkey(key: VerifyingKey) -> [u8; 64] {
62    let point = key.to_encoded_point(false);
63    let mut result = [0u8; 64];
64    result[..32].copy_from_slice(point.x().unwrap());
65    result[32..].copy_from_slice(point.y().unwrap());
66    result
67}
68
69/// Recover a public key from a signature and a message digest. It assumes
70/// a compacted signature
71pub fn recover(signature: &Bytes64, message: &Message) -> Result<Bytes64, Error> {
72    let (sig, recid) = decode_signature(**signature);
73    let sig =
74        p256::ecdsa::Signature::from_slice(&sig).map_err(|_| Error::InvalidSignature)?;
75    let vk = VerifyingKey::recover_from_prehash(&**message, &sig, recid.into())
76        .map_err(|_| Error::InvalidSignature)?;
77    let point = vk.to_encoded_point(false);
78    let mut raw = Bytes64::zeroed();
79    raw[..32].copy_from_slice(point.x().unwrap());
80    raw[32..].copy_from_slice(point.y().unwrap());
81    Ok(raw)
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    use p256::ecdsa::SigningKey;
89    use rand::{
90        rngs::StdRng,
91        Rng,
92        SeedableRng,
93    };
94
95    #[test]
96    fn test_raw_recover() {
97        let mut rng = &mut StdRng::seed_from_u64(1234);
98
99        let signing_key = SigningKey::random(&mut rng);
100        let verifying_key = signing_key.verifying_key();
101
102        let message = Message::new([rng.gen(); 100]);
103
104        let (signature, recovery_id) =
105            signing_key.sign_prehash_recoverable(&*message).unwrap();
106
107        let recovered =
108            VerifyingKey::recover_from_prehash(&*message, &signature, recovery_id)
109                .expect("Unable to recover the public key");
110
111        assert_eq!(recovered, *verifying_key);
112    }
113
114    #[test]
115    fn test_secp256r1_recover_from_msg() {
116        let mut rng = &mut StdRng::seed_from_u64(1234);
117
118        for _ in 0..100 {
119            let signing_key = SigningKey::random(&mut rng);
120            let verifying_key = signing_key.verifying_key();
121
122            let message = Message::new([rng.gen(); 100]);
123            let signature =
124                sign_prehashed(&signing_key, &message).expect("Couldn't sign");
125
126            let Ok(recovered) = recover(&signature, &message) else {
127                panic!("Failed to recover public key from the message");
128            };
129
130            assert_eq!(*recovered, encode_pubkey(*verifying_key));
131        }
132    }
133
134    #[test]
135    fn test_signature_and_recovery_id_encoding_roundtrip() {
136        let mut rng = &mut StdRng::seed_from_u64(1234);
137
138        for _ in 0..100 {
139            let message = Message::new([rng.gen(); 100]);
140            let signing_key = SigningKey::random(&mut rng);
141            let (signature, _) = signing_key.sign_prehash_recoverable(&*message).unwrap();
142            let signature = signature.normalize_s().unwrap_or(signature);
143            let signature: [u8; 64] = signature.to_bytes().into();
144
145            let recovery_id = RecoveryId::from_byte(0).unwrap().try_into().unwrap();
146            let encoded = encode_signature(signature, recovery_id);
147
148            let (de_sig, de_recid) = decode_signature(encoded);
149            assert_eq!(signature, de_sig);
150            assert_eq!(recovery_id, de_recid);
151
152            let recovery_id = RecoveryId::from_byte(1).unwrap().try_into().unwrap();
153            let encoded = encode_signature(signature, recovery_id);
154
155            let (de_sig, de_recid) = decode_signature(encoded);
156            assert_eq!(signature, de_sig);
157            assert_eq!(recovery_id, de_recid);
158        }
159    }
160}