valence-crypto-utils 0.1.2

A collection of crypto utilities for the Valence ecosystem.
Documentation
use alloc::vec::Vec;
use serde_json::Value;
use sha2::{Digest as _, Sha256};

use crate::{Ecdsa, Signer};

impl Signer {
    /// Signs a JSON using rfc-8785 canonical format.
    pub fn sign_json(&self, json: &[u8]) -> anyhow::Result<Vec<u8>> {
        let json: Value = serde_json::from_slice(json)?;
        let canonical = serde_json_canonicalizer::to_string(&json)?;
        let digest = Sha256::digest(canonical.as_bytes());

        self.sign(&digest)
    }

    /// Signs a JSON using rfc-8785 canonical format prepending the provided id.
    pub fn sign_json_with_id(&self, id: &[u8], json: &[u8]) -> anyhow::Result<Vec<u8>> {
        let json: Value = serde_json::from_slice(json)?;
        let canonical = serde_json_canonicalizer::to_string(&json)?;

        let mut hasher = Sha256::new();

        hasher.update(id);
        hasher.update(canonical);

        let digest = hasher.finalize();

        self.sign(&digest)
    }
}

impl Ecdsa {
    /// Recovers the public identity from a signature for the provided JSON.
    pub fn recover_from_json(sig: &[u8], json: &[u8]) -> anyhow::Result<Vec<u8>> {
        let json: Value = serde_json::from_slice(json)?;
        let canonical = serde_json_canonicalizer::to_string(&json)?;
        let digest = Sha256::digest(canonical.as_bytes());

        Self::recover(sig, &digest)
    }

    /// Recovers the public identity from a signature for the provided JSON.
    pub fn recover_from_json_with_id(
        sig: &[u8],
        id: &[u8],
        json: &[u8],
    ) -> anyhow::Result<Vec<u8>> {
        let json: Value = serde_json::from_slice(json)?;
        let canonical = serde_json_canonicalizer::to_string(&json)?;

        let mut hasher = Sha256::new();

        hasher.update(id);
        hasher.update(canonical);

        let digest = hasher.finalize();

        Self::recover(sig, &digest)
    }
}

#[test]
fn recover_from_request_works() {
    let secret = "b0cb16ba60d3b770eb5e001efaa8f5b94270f4e8e858f673f5e896db11d21698";
    let request = serde_json::json!({
        "b": "foo",
        "a": 42,
    });
    let requests = [
        serde_json::to_string(&request).unwrap(),
        serde_json::to_string_pretty(&request).unwrap(),
        r#"{"b":          "foo", "a": 42   }"#.into(),
    ];

    let secret = const_hex::decode(secret).unwrap();
    let signer = crate::Signer::from_secret(&secret).unwrap();
    let public = signer.to_public();

    for r in requests {
        let signature = signer.sign_json(r.as_bytes()).unwrap();
        let recovered = Ecdsa::recover_from_json(&signature, r.as_bytes()).unwrap();

        assert_eq!(public, recovered);
    }
}

#[test]
fn recover_from_request_with_id_works() {
    let secret = "b0cb16ba60d3b770eb5e001efaa8f5b94270f4e8e858f673f5e896db11d21698";
    let request = serde_json::json!({
        "b": "foo",
        "a": 42,
    });
    let requests = [
        serde_json::to_string(&request).unwrap(),
        serde_json::to_string_pretty(&request).unwrap(),
        r#"{"b":          "foo", "a": 42   }"#.into(),
    ];

    let secret = const_hex::decode(secret).unwrap();
    let signer = crate::Signer::from_secret(&secret).unwrap();
    let public = signer.to_public();

    for r in requests {
        let id = b"id";
        let signature = signer.sign_json_with_id(id, r.as_bytes()).unwrap();
        let recovered = Ecdsa::recover_from_json_with_id(&signature, id, r.as_bytes()).unwrap();

        assert_eq!(public, recovered);
    }
}