valence_crypto_utils/
signature.rs1use alloc::vec::Vec;
2use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
3
4use crate::{Ecdsa, Signer};
5
6impl Signer {
7 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 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}