kobe_svm/
standard_wallet.rs1use alloc::string::String;
7
8use ed25519_dalek::{SigningKey, VerifyingKey};
9use zeroize::{Zeroize, Zeroizing};
10
11use crate::Error;
12
13#[derive(Debug)]
18pub struct StandardWallet {
19 signing_key: SigningKey,
21}
22
23impl StandardWallet {
24 #[cfg(feature = "rand")]
33 pub fn generate() -> Result<Self, Error> {
34 let mut bytes = Zeroizing::new([0u8; 32]);
35 getrandom::fill(&mut *bytes)
36 .map_err(|_| Error::Derivation("system random number generator unavailable".into()))?;
37 let signing_key = SigningKey::from_bytes(&bytes);
38 Ok(Self { signing_key })
39 }
40
41 #[must_use]
43 pub fn from_bytes(bytes: &[u8; 32]) -> Self {
44 let signing_key = SigningKey::from_bytes(bytes);
45 Self { signing_key }
46 }
47
48 pub fn from_hex(hex_key: &str) -> Result<Self, Error> {
54 let mut bytes = hex::decode(hex_key).map_err(|_| Error::InvalidHex)?;
55
56 if bytes.len() != 32 {
57 bytes.zeroize();
58 return Err(Error::Derivation(alloc::format!(
59 "expected 32 bytes, got {}",
60 bytes.len()
61 )));
62 }
63
64 let mut key_bytes = Zeroizing::new([0u8; 32]);
65 key_bytes.copy_from_slice(&bytes);
66 bytes.zeroize();
67 Ok(Self::from_bytes(&key_bytes))
68 }
69
70 #[inline]
72 #[must_use]
73 pub fn address(&self) -> String {
74 let verifying_key: VerifyingKey = self.signing_key.verifying_key();
75 bs58::encode(verifying_key.as_bytes()).into_string()
76 }
77
78 #[inline]
80 #[must_use]
81 pub fn secret_bytes(&self) -> Zeroizing<[u8; 32]> {
82 Zeroizing::new(*self.signing_key.as_bytes())
83 }
84
85 #[inline]
87 #[must_use]
88 pub fn secret_hex(&self) -> Zeroizing<String> {
89 Zeroizing::new(hex::encode(self.signing_key.as_bytes()))
90 }
91
92 #[inline]
97 #[must_use]
98 pub fn keypair_base58(&self) -> Zeroizing<String> {
99 let verifying_key: VerifyingKey = self.signing_key.verifying_key();
100 let mut keypair_bytes = [0u8; 64];
101 keypair_bytes[..32].copy_from_slice(self.signing_key.as_bytes());
102 keypair_bytes[32..].copy_from_slice(verifying_key.as_bytes());
103 let encoded = bs58::encode(&keypair_bytes).into_string();
104 keypair_bytes.fill(0);
106 Zeroizing::new(encoded)
107 }
108
109 #[inline]
111 #[must_use]
112 pub fn pubkey_hex(&self) -> String {
113 let verifying_key: VerifyingKey = self.signing_key.verifying_key();
114 hex::encode(verifying_key.as_bytes())
115 }
116}
117
118#[cfg(test)]
119#[allow(clippy::unwrap_used)]
120mod tests {
121 use super::*;
122
123 #[cfg(feature = "rand")]
124 #[test]
125 fn test_generate() {
126 let wallet = StandardWallet::generate().unwrap();
127 let address = wallet.address();
128
129 assert!(address.len() >= 32 && address.len() <= 44);
131 }
132
133 #[test]
134 fn test_from_bytes() {
135 let key = [1u8; 32];
136 let wallet = StandardWallet::from_bytes(&key);
137 let address = wallet.address();
138
139 assert!(address.len() >= 32 && address.len() <= 44);
140 }
141
142 #[test]
143 fn test_from_hex() {
144 let hex_key = "0101010101010101010101010101010101010101010101010101010101010101";
145 let wallet = StandardWallet::from_hex(hex_key).unwrap();
146 let address = wallet.address();
147
148 assert!(address.len() >= 32 && address.len() <= 44);
149 }
150
151 #[test]
152 fn test_deterministic() {
153 let key = [42u8; 32];
154 let wallet1 = StandardWallet::from_bytes(&key);
155 let wallet2 = StandardWallet::from_bytes(&key);
156
157 assert_eq!(wallet1.address(), wallet2.address());
158 }
159}