thru_base/
tn_tools.rs

1use crate::tn_public_address;
2use crate::txn_lib::TnPubkey;
3use crate::{tn_public_address::tn_pubkey_to_address_string, tn_signature_encoding};
4use anyhow::Result;
5use ed25519_dalek::SigningKey;
6use hex;
7use rand::TryRngCore;
8use rand::rngs::OsRng;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11
12use thiserror::Error;
13
14pub fn gen_key() -> Result<[u8; 32]> {
15    let mut private_key = [0u8; 32];
16    let mut rng = OsRng;
17    rng.try_fill_bytes(&mut private_key).unwrap();
18    Ok(private_key)
19}
20
21#[derive(Debug, Clone)]
22pub struct KeyPair {
23    pub name: String,
24    pub private_key: [u8; 32],
25    pub public_key: TnPubkey,
26    pub address_string: Pubkey,
27}
28
29impl KeyPair {
30    pub fn generate(name: &str) -> Result<Self> {
31        // Generate new key
32        let mut private_key = [0u8; 32];
33        let mut rng = OsRng;
34        rng.try_fill_bytes(&mut private_key)?;
35        // Derive public key
36        let signing_key = SigningKey::from_bytes(&private_key);
37        let verifying_key = signing_key.verifying_key();
38        let public_key = verifying_key.to_bytes();
39
40        // Generate proper ta... address string using thru-base utilities
41        let address_string = Pubkey::from_bytes(&public_key);
42
43        Ok(Self {
44            name: name.to_string(),
45            private_key,
46            public_key,
47            address_string,
48        })
49    }
50
51    pub fn from_hex_private_key<P: AsRef<[u8]>>(name: &str, hex_private_key: P) -> Result<Self> {
52        // Convert hex string to 32-byte array
53        let private_key_bytes = hex::decode(hex_private_key)
54            .map_err(|e| anyhow::anyhow!("Failed to decode hex private key: {}", e))?;
55
56        if private_key_bytes.len() != 32 {
57            return Err(anyhow::anyhow!(
58                "Private key must be 32 bytes, got {}",
59                private_key_bytes.len()
60            ));
61        }
62
63        let mut private_key = [0u8; 32];
64        private_key.copy_from_slice(&private_key_bytes);
65
66        // Derive public key
67        let signing_key = SigningKey::from_bytes(&private_key);
68        let verifying_key = signing_key.verifying_key();
69        let public_key = verifying_key.to_bytes();
70
71        // Generate proper ta... address string using thru-base utilities
72        let address_string = Pubkey::from_bytes(&public_key);
73
74        Ok(Self {
75            name: name.to_string(),
76            private_key,
77            public_key,
78            address_string,
79        })
80    }
81
82    pub fn public_key_hex(&self) -> String {
83        hex::encode(self.public_key)
84    }
85    pub fn public_key_str(&self) -> String {
86        tn_pubkey_to_address_string(&self.public_key)
87    }
88}
89
90/// A public key on the blockchain
91///
92/// Public keys in Thru are encoded as 46-character strings starting with "ta"
93/// using a custom base64-url encoding with checksum validation.
94#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
95pub struct Pubkey(String);
96
97impl Pubkey {
98    /// Create a new public key from a string
99    ///
100    /// The string must be a valid 46-character Thru address starting with "ta"
101    pub fn new(key: String) -> Result<Self> {
102        // Basic validation
103        if key.is_empty() {
104            return Err(ValidationError::InvalidPubkey("empty pubkey".to_string()).into());
105        }
106
107        // Validate format: should be 46 characters starting with "ta"
108        if key.len() != 46 {
109            return Err(ValidationError::InvalidPubkey(format!(
110                "invalid pubkey length: expected 46, got {}",
111                key.len()
112            ))
113            .into());
114        }
115
116        if !key.starts_with("ta") {
117            return Err(
118                ValidationError::InvalidPubkey("pubkey must start with 'ta'".to_string()).into(),
119            );
120        }
121
122        // Validate by attempting to decode
123        let mut decoded = [0u8; 32];
124        match tn_public_address::tn_public_address_decode(&mut decoded, key.as_bytes()) {
125            Ok(()) => Ok(Self(key)),
126            Err(code) => Err(ValidationError::InvalidPubkey(format!(
127                "invalid pubkey format: decode error {}",
128                code
129            ))
130            .into()),
131        }
132    }
133
134    /// Get the public key as a string
135    pub fn as_str(&self) -> &str {
136        &self.0
137    }
138
139    /// Convert to bytes (decode the address)
140    pub fn to_bytes(&self) -> Result<[u8; 32]> {
141        let mut bytes = [0u8; 32];
142        match tn_public_address::tn_public_address_decode(&mut bytes, self.0.as_bytes()) {
143            Ok(()) => Ok(bytes),
144            Err(code) => Err(ValidationError::InvalidPubkey(format!(
145                "failed to decode pubkey: error {}",
146                code
147            ))
148            .into()),
149        }
150    }
151
152    /// Create a Pubkey from raw bytes
153    pub fn from_bytes(bytes: &[u8; 32]) -> Self {
154        let address = tn_public_address::tn_pubkey_to_address_string(bytes);
155        // This should never fail since we're encoding from valid bytes
156        Self(address)
157    }
158}
159
160impl fmt::Display for Pubkey {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        write!(f, "{}", self.0)
163    }
164}
165
166/// A transaction signature
167///
168/// Signatures in Thru are encoded as 90-character strings starting with "ts"
169/// using a custom base64-url encoding with checksum validation.
170#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
171pub struct Signature(String);
172
173impl Signature {
174    /// Create a new signature from a string
175    ///
176    /// The string must be a valid 90-character Thru signature starting with "ts"
177    pub fn new(sig: String) -> Result<Self> {
178        // Basic validation
179        if sig.is_empty() {
180            return Err(ValidationError::InvalidSignature("empty signature".to_string()).into());
181        }
182
183        // Validate format: should be 90 characters starting with "ts"
184        if sig.len() != 90 {
185            return Err(ValidationError::InvalidSignature(format!(
186                "invalid signature length: expected 90, got {}",
187                sig.len()
188            ))
189            .into());
190        }
191
192        if !sig.starts_with("ts") {
193            return Err(ValidationError::InvalidSignature(
194                "signature must start with 'ts'".to_string(),
195            )
196            .into());
197        }
198
199        // Validate by attempting to decode
200        let mut decoded = [0u8; 64];
201        match tn_signature_encoding::tn_signature_decode(&mut decoded, sig.as_bytes()) {
202            Ok(()) => Ok(Self(sig)),
203            Err(code) => Err(ValidationError::InvalidSignature(format!(
204                "invalid signature format: decode error {}",
205                code
206            ))
207            .into()),
208        }
209    }
210
211    /// Get the signature as a string
212    pub fn as_str(&self) -> &str {
213        &self.0
214    }
215
216    /// Convert to bytes (decode the signature)
217    pub fn to_bytes(&self) -> Result<[u8; 64]> {
218        let mut bytes = [0u8; 64];
219        match tn_signature_encoding::tn_signature_decode(&mut bytes, self.0.as_bytes()) {
220            Ok(()) => Ok(bytes),
221            Err(code) => Err(ValidationError::InvalidSignature(format!(
222                "failed to decode signature: error {}",
223                code
224            ))
225            .into()),
226        }
227    }
228
229    /// Create a Signature from raw bytes
230    pub fn from_bytes(bytes: &[u8; 64]) -> Self {
231        let signature = tn_signature_encoding::tn_signature_to_string(bytes);
232        // This should never fail since we're encoding from valid bytes
233        Self(signature)
234    }
235}
236
237impl fmt::Display for Signature {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        write!(f, "{}", self.0)
240    }
241}
242
243/// Validation errors for input data
244#[derive(Error, Debug)]
245pub enum ValidationError {
246    /// Invalid public key format
247    #[error("Invalid public key: {0}")]
248    InvalidPubkey(String),
249
250    /// Invalid signature format
251    #[error("Invalid signature: {0}")]
252    InvalidSignature(String),
253}
254
255#[cfg(test)]
256mod tests {
257
258    use super::*;
259
260    #[test]
261    fn test_pubkey_validation() {
262        // Create a valid pubkey using the encoding function
263        let bytes = [1u8; 32];
264        let valid_pubkey = tn_public_address::tn_pubkey_to_address_string(&bytes);
265        assert!(Pubkey::new(valid_pubkey.to_string()).is_ok());
266
267        // Empty pubkey
268        assert!(Pubkey::new("".to_string()).is_err());
269
270        // Wrong length
271        assert!(Pubkey::new("ta111".to_string()).is_err());
272
273        // Wrong prefix
274        assert!(Pubkey::new("tb1111111111111111111111111111111111111111111".to_string()).is_err());
275
276        // Invalid characters (not base64-url)
277        assert!(Pubkey::new("ta!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!".to_string()).is_err());
278    }
279
280    #[test]
281    fn test_signature_validation() {
282        // Create a valid signature using the encoding function
283        let bytes = [1u8; 64];
284        let valid_signature = tn_signature_encoding::tn_signature_to_string(&bytes);
285        assert!(Signature::new(valid_signature.to_string()).is_ok());
286
287        // Empty signature
288        assert!(Signature::new("".to_string()).is_err());
289
290        // Wrong length
291        assert!(Signature::new("ts111".to_string()).is_err());
292
293        // Wrong prefix
294        assert!(Signature::new("ta111111111111111111111111111111111111111111111111111111111111111111111111111111111111".to_string()).is_err());
295
296        // Invalid characters (not base64-url)
297        assert!(Signature::new("ts!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!".to_string()).is_err());
298    }
299
300    #[test]
301    fn test_pubkey_roundtrip() {
302        // Test encoding and decoding
303        let bytes = [1u8; 32];
304        let pubkey = Pubkey::from_bytes(&bytes);
305        let decoded_bytes = pubkey.to_bytes().unwrap();
306        assert_eq!(bytes, decoded_bytes);
307    }
308
309    #[test]
310    fn test_signature_roundtrip() {
311        // Test encoding and decoding
312        let bytes = [1u8; 64];
313        let signature = Signature::from_bytes(&bytes);
314        let decoded_bytes = signature.to_bytes().unwrap();
315        assert_eq!(bytes, decoded_bytes);
316    }
317}