Skip to main content

radix_common/crypto/ed25519/
private_key.rs

1use super::Ed25519Signature;
2use crate::internal_prelude::*;
3use core::pin::Pin;
4use ed25519_dalek::{Signer, SigningKey};
5use zeroize::Zeroize;
6
7// Pin<Box<>> assures the memory location of secret key is fixed preventing
8// accidental leaks due to movement.
9// SigningKey is wrapped in Option because it does not implement Zeroize,
10// but it implements ZeroizeOnDrop on drop, so we can trigger zeroize on drop
11// by assigning `None` value.
12pub struct Ed25519PrivateKey(Pin<Box<Option<SigningKey>>>);
13
14// SigningKey consists of
15// - SecretKey (sensitive) - implements Zeroize and it is zeroed when SigningKey is dropped
16// - VerifyingKey (not sensitive) - does not implement Zeroize
17// Because of the `VerifyingKey` we cannot simply derive Zeroize for Ed25519PrivateKey.
18impl Zeroize for Ed25519PrivateKey {
19    fn zeroize(&mut self) {
20        *self.0 = None;
21    }
22}
23
24impl Ed25519PrivateKey {
25    pub const LENGTH: usize = 32;
26
27    fn signing_key(&self) -> &SigningKey {
28        let option = &*self.0;
29        option
30            .as_ref()
31            .expect("Cannot access signing key after zeroizing")
32    }
33
34    pub fn public_key(&self) -> Ed25519PublicKey {
35        Ed25519PublicKey(self.signing_key().verifying_key().to_bytes())
36    }
37
38    pub fn sign(&self, msg: impl AsRef<[u8]>) -> Ed25519Signature {
39        // SHA512 is used here
40
41        Ed25519Signature(self.signing_key().sign(msg.as_ref()).to_bytes())
42    }
43
44    pub fn to_bytes(&self) -> Vec<u8> {
45        self.signing_key().to_bytes().to_vec()
46    }
47
48    #[allow(clippy::result_unit_err)]
49    pub fn from_bytes(slice: &[u8]) -> Result<Self, ()> {
50        if slice.len() != Ed25519PrivateKey::LENGTH {
51            return Err(());
52        }
53
54        let signing_key = SigningKey::try_from(slice).map_err(|_| ())?;
55
56        Ok(Self(Box::pin(Some(signing_key))))
57    }
58
59    #[allow(clippy::result_unit_err)]
60    pub fn from_u64(n: u64) -> Result<Self, ()> {
61        let mut bytes = [0u8; Ed25519PrivateKey::LENGTH];
62        bytes[Ed25519PrivateKey::LENGTH - 8..Ed25519PrivateKey::LENGTH]
63            .copy_from_slice(&n.to_be_bytes());
64
65        Ok(Self(Box::pin(Some(SigningKey::from_bytes(&bytes)))))
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use sbor::rust::str::FromStr;
73
74    #[test]
75    fn sign_and_verify() {
76        let test_sk = "0000000000000000000000000000000000000000000000000000000000000001";
77        let test_pk = "4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29";
78        let test_message_hash = hash("Test");
79        let test_signature = "cf0ca64435609b85ab170da339d415bbac87d678dfd505969be20adc6b5971f4ee4b4620c602bcbc34fd347596546675099d696265f4a42a16df343da1af980e";
80        let sk = Ed25519PrivateKey::from_bytes(&hex::decode(test_sk).unwrap()).unwrap();
81        let pk = Ed25519PublicKey::from_str(test_pk).unwrap();
82        let sig = Ed25519Signature::from_str(test_signature).unwrap();
83
84        assert_eq!(sk.public_key(), pk);
85        assert_eq!(sk.sign(test_message_hash), sig);
86        assert!(verify_ed25519(test_message_hash, &pk, &sig));
87    }
88
89    fn find_slice_in_memory(ptr: *const u8, slice: &[u8]) -> Option<usize> {
90        // Get a raw pointer to the value
91        // let ptr = val as *const Option<SigningKey> as *const u8;
92
93        // Get the size of the type
94        let size = mem::size_of::<Option<SigningKey>>();
95
96        unsafe {
97            for i in 0..size - slice.len() + 1 {
98                let memory_slice: &[u8] = core::slice::from_raw_parts(ptr.add(i), slice.len());
99                if memory_slice == slice {
100                    return Some(i);
101                }
102            }
103        }
104        None
105    }
106
107    #[test]
108    fn verify_zeroize() {
109        let bytes = "4fd3fb62d6b7a4749f75d56d06b0aea1ec2c2a6986d2bfa975d7891585590fea";
110        let key_bytes = hex::decode(bytes).unwrap();
111        let mut secret_key = Ed25519PrivateKey::from_bytes(&key_bytes).unwrap();
112
113        let secret_key_inner_ptr = &*secret_key.0 as *const Option<SigningKey> as *const u8;
114
115        let key_offset =
116            find_slice_in_memory(secret_key_inner_ptr, &key_bytes).expect("Key bytes not found");
117
118        secret_key.zeroize();
119
120        let zero_bytes = [0u8; 32];
121
122        let memory_slice_after_zeroize = unsafe {
123            core::slice::from_raw_parts(secret_key_inner_ptr.add(key_offset), zero_bytes.len())
124        };
125
126        assert_eq!(memory_slice_after_zeroize, zero_bytes,);
127    }
128
129    #[test]
130    fn verify_zeroize_on_drop() {
131        let bytes = "4fd3fb62d6b7a4749f75d56d06b0aea1ec2c2a6986d2bfa975d7891585590fea";
132        let key_bytes = hex::decode(bytes).unwrap();
133        let secret_key = Ed25519PrivateKey::from_bytes(&key_bytes).unwrap();
134
135        let secret_key_inner_ptr = &*secret_key.0 as *const Option<SigningKey> as *const u8;
136
137        let key_offset =
138            find_slice_in_memory(secret_key_inner_ptr, &key_bytes).expect("Key bytes not found");
139
140        drop(secret_key);
141
142        let zero_bytes = [0u8; 32];
143        let memory_slice_after_zeroize = unsafe {
144            core::slice::from_raw_parts(secret_key_inner_ptr.add(key_offset), zero_bytes.len())
145        };
146
147        assert_eq!(memory_slice_after_zeroize, zero_bytes,);
148    }
149}