Skip to main content

freezeout_core/
crypto.rs

1// Copyright (C) 2025 Vince Vasta
2// SPDX-License-Identifier: Apache-2.0
3
4//! Cryptographic types for signing messages.
5use anyhow::{Result, bail};
6use bip39::Mnemonic;
7use blake2::{Blake2s, Digest, digest, digest::typenum::ToInt};
8use ed25519_dalek::{Signer, Verifier};
9use rand::{CryptoRng, RngCore, SeedableRng, rngs::StdRng};
10use serde::{Deserialize, Serialize};
11use std::fmt;
12use zeroize::Zeroizing;
13
14const ENTROPY_LEN: usize = 16;
15type Entropy = [u8; ENTROPY_LEN];
16
17/// A key for signing messages.
18pub struct SigningKey {
19    key: ed25519_dalek::SigningKey,
20    entropy: Zeroizing<Entropy>,
21}
22
23/// The hasher used for signatures.
24type SigHasher = Blake2s<digest::consts::U32>;
25
26impl Default for SigningKey {
27    fn default() -> Self {
28        let mut rng = StdRng::from_os_rng();
29        Self::from_crypto_rng(&mut rng)
30    }
31}
32
33impl SigningKey {
34    /// Create a signing key from a mnemonic phrase.
35    pub fn from_phrase<S>(phrase: S) -> Result<Self>
36    where
37        S: AsRef<str>,
38    {
39        let mnemonic = Mnemonic::from_phrase(phrase.as_ref(), Default::default())?;
40        if mnemonic.entropy().len() != ENTROPY_LEN {
41            bail!("Invalid passphrase length");
42        }
43
44        let mut entropy = Entropy::default();
45        entropy.copy_from_slice(mnemonic.entropy());
46        Ok(Self::from_entropy(entropy))
47    }
48
49    /// Sign a message.
50    pub fn sign<T>(&self, msg: &T) -> Signature
51    where
52        T: Serialize,
53    {
54        let mut hasher = SigHasher::new();
55        bincode::serialize_into(&mut hasher, msg).expect("should serialize to hasher");
56        Signature(self.key.sign(&hasher.finalize()))
57    }
58
59    /// Get the secret key phrase.
60    pub fn phrase(&self) -> String {
61        // This should never fail as we control the entropy size.
62        Mnemonic::from_entropy(self.entropy.as_ref(), Default::default())
63            .unwrap()
64            .phrase()
65            .to_string()
66    }
67
68    /// Get the signature verifying key.
69    pub fn verifying_key(&self) -> VerifyingKey {
70        VerifyingKey(self.key.verifying_key())
71    }
72
73    fn from_crypto_rng<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
74        let mut entropy = Entropy::default();
75        rng.fill_bytes(&mut entropy);
76        Self::from_entropy(entropy)
77    }
78
79    fn from_entropy(entropy: Entropy) -> Self {
80        // Hash 128 bits entropy to 256 bits SigningKey.
81        let key_hash = SigHasher::digest(entropy);
82        let key = ed25519_dalek::SigningKey::from_bytes(&key_hash.into());
83        let entropy = Zeroizing::new(entropy);
84        Self { key, entropy }
85    }
86}
87
88impl fmt::Debug for SigningKey {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(
91            f,
92            "SigningKey({})",
93            bs58::encode(self.key.as_bytes()).into_string()
94        )
95    }
96}
97
98/// Message signature.
99#[derive(Clone, Copy, Serialize, Deserialize)]
100pub struct Signature(ed25519_dalek::Signature);
101
102impl fmt::Debug for Signature {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(
105            f,
106            "Signature({})",
107            bs58::encode(&self.0.to_bytes()).into_string()
108        )
109    }
110}
111
112/// Key for signature verification.
113#[derive(Clone, Copy, Serialize, Deserialize)]
114pub struct VerifyingKey(ed25519_dalek::VerifyingKey);
115
116impl VerifyingKey {
117    /// Verifies a message signature.
118    pub fn verify<T>(&self, msg: &T, signature: &Signature) -> bool
119    where
120        T: Serialize,
121    {
122        let mut hasher = SigHasher::new();
123        bincode::serialize_into(&mut hasher, msg).expect("should serialize to hasher");
124        self.0.verify(&hasher.finalize(), &signature.0).is_ok()
125    }
126
127    /// Returns the [PeerId] for this key.
128    pub fn peer_id(&self) -> PeerId {
129        let mut hasher = Blake2s::<digest::consts::U16>::new();
130        hasher.update(self.0.as_bytes());
131        PeerId(hasher.finalize().into())
132    }
133}
134
135impl fmt::Debug for VerifyingKey {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        write!(
138            f,
139            "VerifyingKey({})",
140            bs58::encode(self.0.as_bytes()).into_string()
141        )
142    }
143}
144
145/// A message sender identifier derived from a signature verifying key.
146#[derive(Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
147pub struct PeerId([u8; digest::consts::U16::INT]);
148
149impl PeerId {
150    /// The hex digits for this peer id.
151    pub fn digits(&self) -> String {
152        self.0
153            .iter()
154            .fold(String::with_capacity(32), |mut output, b| {
155                output.push_str(&format!("{b:02X}"));
156                output
157            })
158    }
159}
160
161impl fmt::Debug for PeerId {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        write!(f, "PeerId({})", self.digits())
164    }
165}
166
167impl fmt::Display for PeerId {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        write!(f, "{}", self.digits())
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn keypair_phrase() {
179        let sk = SigningKey::default();
180        let from_phrase = SigningKey::from_phrase(sk.phrase()).unwrap();
181        assert_eq!(sk.key, from_phrase.key);
182    }
183
184    #[test]
185    fn sign() {
186        #[derive(Serialize)]
187        struct Point {
188            x: f32,
189            y: f32,
190        }
191
192        let msg = Point { x: 10.2, y: 4.3 };
193
194        let sk = SigningKey::default();
195        let sig = sk.sign(&msg);
196
197        // Signed message
198        let vk = sk.verifying_key();
199        assert!(vk.verify(&msg, &sig));
200
201        // Invalid message
202        let msg = Point { x: 10.2001, y: 4.3 };
203        assert!(!vk.verify(&msg, &sig));
204    }
205}