valence-crypto-utils 0.1.2

A collection of crypto utilities for the Valence ecosystem.
Documentation
use alloc::vec::Vec;
use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};

use crate::{Ecdsa, Signer};

impl Signer {
    /// Signs a pre-hashed message.
    pub fn sign(&self, digest: &[u8]) -> anyhow::Result<Vec<u8>> {
        anyhow::ensure!(digest.len() == 32, "unexpected digest length.");

        match self {
            Signer::SecretEccNistP256(secret) => {
                let (sig, recid) = secret
                    .sign_prehash_recoverable(digest)
                    .map_err(|e| anyhow::anyhow!("failed to sign digest: {e}"))?;

                Ok([sig.to_bytes().as_slice(), &[recid.to_byte()]].concat())
            }
        }
    }
}

impl Ecdsa {
    /// Recovers the public identity from the signature and pre-hashed message.
    pub fn recover(sig: &[u8], digest: &[u8]) -> anyhow::Result<Vec<u8>> {
        anyhow::ensure!(digest.len() == 32, "unexpected digest len");
        anyhow::ensure!(sig.len() == 65, "expected signature with recovery id");

        let (sig, recid) = sig
            .split_at_checked(64)
            .ok_or_else(|| anyhow::anyhow!("invalid signature length"))?;

        let recid = recid[0];
        let recid = if recid > 3 {
            recid.saturating_sub(27)
        } else {
            recid
        };

        let sig = Signature::try_from(sig)
            .map_err(|e| anyhow::anyhow!("failed to decode signature: {e}"))?;
        let recid =
            RecoveryId::try_from(recid).map_err(|e| anyhow::anyhow!("invalid recovery id: {e}"))?;

        let vk = VerifyingKey::recover_from_prehash(digest, &sig, recid)
            .map_err(|e| anyhow::anyhow!("failed to recover pk: {e}"))?;

        Ok(vk.to_sec1_bytes().to_vec())
    }
}

#[cfg(test)]
mod tests {
    use k256::ecdsa::SigningKey;
    use proptest::prelude::*;
    use rand::{RngCore as _, SeedableRng as _, rngs::StdRng};

    use super::*;

    #[test]
    fn recover_works() {
        let secret = "b0cb16ba60d3b770eb5e001efaa8f5b94270f4e8e858f673f5e896db11d21698";
        let digest = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";

        let secret = const_hex::decode(secret).unwrap();
        let digest = const_hex::decode(digest).unwrap();

        let signer = Signer::from_secret(&secret).unwrap();
        let public = signer.to_public();
        let signature = signer.sign(&digest).unwrap();
        let recovered = Ecdsa::recover(&signature, &digest).unwrap();

        assert_eq!(public, recovered);
    }

    proptest! {
        #[test]
        fn recover_property_check(seed: u64) {
            let rng = &mut StdRng::seed_from_u64(seed);
            let secret = SigningKey::random(rng);

            let mut digest = [0u8; 32];
            rng.fill_bytes(&mut digest);

            let signer = Signer::from(secret);
            let public = signer.to_public();
            let signature = signer.sign(&digest).unwrap();
            let recovered = Ecdsa::recover(&signature, &digest).unwrap();

            assert_eq!(public, recovered);
        }
    }
}