Skip to main content

valence_crypto_utils/
signature.rs

1use alloc::vec::Vec;
2use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
3
4use crate::{Ecdsa, Signer};
5
6impl Signer {
7    /// Signs a pre-hashed message.
8    pub fn sign(&self, digest: &[u8]) -> anyhow::Result<Vec<u8>> {
9        anyhow::ensure!(digest.len() == 32, "unexpected digest length.");
10
11        match self {
12            Signer::SecretEccNistP256(secret) => {
13                let (sig, recid) = secret
14                    .sign_prehash_recoverable(digest)
15                    .map_err(|e| anyhow::anyhow!("failed to sign digest: {e}"))?;
16
17                Ok([sig.to_bytes().as_slice(), &[recid.to_byte()]].concat())
18            }
19        }
20    }
21}
22
23impl Ecdsa {
24    /// Recovers the public identity from the signature and pre-hashed message.
25    pub fn recover(sig: &[u8], digest: &[u8]) -> anyhow::Result<Vec<u8>> {
26        anyhow::ensure!(digest.len() == 32, "unexpected digest len");
27        anyhow::ensure!(sig.len() == 65, "expected signature with recovery id");
28
29        let (sig, recid) = sig
30            .split_at_checked(64)
31            .ok_or_else(|| anyhow::anyhow!("invalid signature length"))?;
32
33        let recid = recid[0];
34        let recid = if recid > 3 {
35            recid.saturating_sub(27)
36        } else {
37            recid
38        };
39
40        let sig = Signature::try_from(sig)
41            .map_err(|e| anyhow::anyhow!("failed to decode signature: {e}"))?;
42        let recid =
43            RecoveryId::try_from(recid).map_err(|e| anyhow::anyhow!("invalid recovery id: {e}"))?;
44
45        let vk = VerifyingKey::recover_from_prehash(digest, &sig, recid)
46            .map_err(|e| anyhow::anyhow!("failed to recover pk: {e}"))?;
47
48        Ok(vk.to_sec1_bytes().to_vec())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use k256::ecdsa::SigningKey;
55    use proptest::prelude::*;
56    use rand::{RngCore as _, SeedableRng as _, rngs::StdRng};
57
58    use super::*;
59
60    #[test]
61    fn recover_works() {
62        let secret = "b0cb16ba60d3b770eb5e001efaa8f5b94270f4e8e858f673f5e896db11d21698";
63        let digest = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
64
65        let secret = const_hex::decode(secret).unwrap();
66        let digest = const_hex::decode(digest).unwrap();
67
68        let signer = Signer::from_secret(&secret).unwrap();
69        let public = signer.to_public();
70        let signature = signer.sign(&digest).unwrap();
71        let recovered = Ecdsa::recover(&signature, &digest).unwrap();
72
73        assert_eq!(public, recovered);
74    }
75
76    proptest! {
77        #[test]
78        fn recover_property_check(seed: u64) {
79            let rng = &mut StdRng::seed_from_u64(seed);
80            let secret = SigningKey::random(rng);
81
82            let mut digest = [0u8; 32];
83            rng.fill_bytes(&mut digest);
84
85            let signer = Signer::from(secret);
86            let public = signer.to_public();
87            let signature = signer.sign(&digest).unwrap();
88            let recovered = Ecdsa::recover(&signature, &digest).unwrap();
89
90            assert_eq!(public, recovered);
91        }
92    }
93}