Skip to main content

hashiverse_lib/tools/
signing.rs

1//! # Ed25519 signing and verification helpers
2//!
3//! Thin wrappers over `ed25519-dalek` that accept and return hashiverse's newtype-wrapped
4//! [`Signature`], [`SignatureKey`] and [`VerificationKey`] instead of raw byte arrays.
5//!
6//! Three functions cover the common cases:
7//! - [`sign`] — sign a single slice directly.
8//! - [`sign_multiple`] — Blake3-hash a list of slices then sign the 32-byte digest. Used
9//!   anywhere the "message" is a tuple of fields (post headers, bucket commitments, peer
10//!   announcements) — cheaper and produces a deterministic canonical form regardless of
11//!   field sizes.
12//! - [`verify`] — verify an Ed25519 signature against its signed data.
13//!
14//! Post-quantum fallbacks (Falcon, Dilithium) live in [`crate::tools::keys_post_quantum`]
15//! and are invoked only once the network signals a transition away from Ed25519.
16
17use crate::tools::hashing;
18use crate::tools::types::{Signature, SignatureKey, VerificationKey};
19
20pub fn sign(signature_key: &SignatureKey, data: &[u8]) -> Signature {
21    use ed25519_dalek::Signer;
22    let signature = signature_key.0.sign(data);
23    Signature::from_bytes_exact(signature.into())
24}
25
26pub fn sign_multiple(signature_key: &SignatureKey, datas: &[&[u8]]) -> Signature {
27    let hash = hashing::hash_multiple(datas);
28    sign(signature_key, hash.as_ref())
29}
30
31pub fn verify(verification_key: &VerificationKey, signature: &Signature, data: &[u8]) -> anyhow::Result<()> {
32    use ed25519_dalek::{Verifier};
33    let signature = ed25519_dalek::Signature::from_bytes(signature);
34    let verifying_key = verification_key.0;
35    verifying_key.verify(data, &signature)?;
36    Ok(())
37}
38
39#[cfg(test)]
40mod tests {
41    use crate::tools::keys::Keys;
42    use crate::tools::signing::{sign, sign_multiple, verify};
43    use crate::tools::types::Signature;
44
45    // Regression vectors: Ed25519 signing is deterministic, so a dependency bump (ed25519-dalek,
46    // blake3) that changes output — or stops accepting previously produced signatures — fails here.
47    // Seed matches the regression vectors in `keys.rs`.
48    const REGRESSION_SEED: [u8; 32] = [7u8; 32];
49    const REGRESSION_MESSAGE: &[u8] = b"hashiverse regression message";
50    const REGRESSION_SIGNATURE_HEX: &str = "bbb20840a165224789ddf9e2d5feafe659dbd039ced23737af04cce93076e289c5850b5f73110835fda1c4532677245722734d0740dcd3e5a2c685a0bb894303";
51    const REGRESSION_SIGNATURE_MULTIPLE_HEX: &str = "6dd9dfe34264da8655305fc0b05a44545826458bce43899fc268056321156955da8327cb8b9e2e83293b281bc7fb000d080a2ce79bf613ad2b0882411b990909";
52
53    #[test]
54    fn sign_regression_vector() -> anyhow::Result<()> {
55        let keys = Keys::from_seed(&REGRESSION_SEED, true)?;
56        assert_eq!(sign(&keys.signature_key, REGRESSION_MESSAGE).to_hex_str(), REGRESSION_SIGNATURE_HEX, "ed25519 signature drift");
57        assert_eq!(sign_multiple(&keys.signature_key, &[b"alpha", b"beta", b"gamma"]).to_hex_str(), REGRESSION_SIGNATURE_MULTIPLE_HEX, "ed25519 sign_multiple drift");
58        Ok(())
59    }
60
61    #[test]
62    fn verify_regression_vector() -> anyhow::Result<()> {
63        let keys = Keys::from_seed(&REGRESSION_SEED, true)?;
64        let signature = Signature::from_hex_str(REGRESSION_SIGNATURE_HEX)?;
65        verify(&keys.verification_key, &signature, REGRESSION_MESSAGE)?;
66        assert!(verify(&keys.verification_key, &signature, b"wrong message").is_err());
67
68        let mut tampered_signature_bytes = signature.0;
69        tampered_signature_bytes[0] ^= 0x01;
70        assert!(verify(&keys.verification_key, &Signature::from_bytes_exact(tampered_signature_bytes), REGRESSION_MESSAGE).is_err());
71        Ok(())
72    }
73}