Skip to main content

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    /// Create a Pubkey from a hex string
160    ///
161    /// The hex string can be 64 characters (32 bytes) with optional "0x" prefix
162    pub fn from_hex(hex_str: &str) -> Result<Self> {
163        // Remove "0x" prefix if present
164        let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
165
166        // Validate hex string length (should be 64 characters for 32 bytes)
167        if hex_str.len() != 64 {
168            return Err(ValidationError::InvalidPubkey(format!(
169                "invalid hex pubkey length: expected 64 characters, got {}",
170                hex_str.len()
171            ))
172            .into());
173        }
174
175        // Decode hex to bytes
176        let bytes = hex::decode(hex_str).map_err(|e| {
177            ValidationError::InvalidPubkey(format!("invalid hex pubkey format: {}", e))
178        })?;
179
180        // Ensure we have exactly 32 bytes
181        if bytes.len() != 32 {
182            return Err(ValidationError::InvalidPubkey(format!(
183                "invalid hex pubkey: expected 32 bytes, got {}",
184                bytes.len()
185            ))
186            .into());
187        }
188
189        let mut bytes_array = [0u8; 32];
190        bytes_array.copy_from_slice(&bytes);
191
192        // Convert to Thru address format
193        Ok(Self::from_bytes(&bytes_array))
194    }
195}
196
197impl fmt::Display for Pubkey {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(f, "{}", self.0)
200    }
201}
202
203/// A transaction signature
204///
205/// Signatures in Thru are encoded as 90-character strings starting with "ts"
206/// using a custom base64-url encoding with checksum validation.
207#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
208pub struct Signature(String);
209
210impl Signature {
211    /// Create a new signature from a string
212    ///
213    /// The string must be a valid 90-character Thru signature starting with "ts"
214    pub fn new(sig: String) -> Result<Self> {
215        // Basic validation
216        if sig.is_empty() {
217            return Err(ValidationError::InvalidSignature("empty signature".to_string()).into());
218        }
219
220        // Validate format: should be 90 characters starting with "ts"
221        if sig.len() != 90 {
222            return Err(ValidationError::InvalidSignature(format!(
223                "invalid signature length: expected 90, got {}",
224                sig.len()
225            ))
226            .into());
227        }
228
229        if !sig.starts_with("ts") {
230            return Err(ValidationError::InvalidSignature(
231                "signature must start with 'ts'".to_string(),
232            )
233            .into());
234        }
235
236        // Validate by attempting to decode
237        let mut decoded = [0u8; 64];
238        match tn_signature_encoding::tn_signature_decode(&mut decoded, sig.as_bytes()) {
239            Ok(()) => Ok(Self(sig)),
240            Err(code) => Err(ValidationError::InvalidSignature(format!(
241                "invalid signature format: decode error {}",
242                code
243            ))
244            .into()),
245        }
246    }
247
248    /// Get the signature as a string
249    pub fn as_str(&self) -> &str {
250        &self.0
251    }
252
253    /// Convert to bytes (decode the signature)
254    pub fn to_bytes(&self) -> Result<[u8; 64]> {
255        let mut bytes = [0u8; 64];
256        match tn_signature_encoding::tn_signature_decode(&mut bytes, self.0.as_bytes()) {
257            Ok(()) => Ok(bytes),
258            Err(code) => Err(ValidationError::InvalidSignature(format!(
259                "failed to decode signature: error {}",
260                code
261            ))
262            .into()),
263        }
264    }
265
266    /// Create a Signature from raw bytes
267    pub fn from_bytes(bytes: &[u8; 64]) -> Self {
268        let signature = tn_signature_encoding::tn_signature_to_string(bytes);
269        // This should never fail since we're encoding from valid bytes
270        Self(signature)
271    }
272}
273
274impl fmt::Display for Signature {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        write!(f, "{}", self.0)
277    }
278}
279
280/// Validation errors for input data
281#[derive(Error, Debug)]
282pub enum ValidationError {
283    /// Invalid public key format
284    #[error("Invalid public key: {0}")]
285    InvalidPubkey(String),
286
287    /// Invalid signature format
288    #[error("Invalid signature: {0}")]
289    InvalidSignature(String),
290}
291
292#[cfg(test)]
293mod tests {
294
295    use super::*;
296
297    #[test]
298    fn test_pubkey_validation() {
299        // Create a valid pubkey using the encoding function
300        let bytes = [1u8; 32];
301        let valid_pubkey = tn_public_address::tn_pubkey_to_address_string(&bytes);
302        assert!(Pubkey::new(valid_pubkey.to_string()).is_ok());
303
304        // Empty pubkey
305        assert!(Pubkey::new("".to_string()).is_err());
306
307        // Wrong length
308        assert!(Pubkey::new("ta111".to_string()).is_err());
309
310        // Wrong prefix
311        assert!(Pubkey::new("tb1111111111111111111111111111111111111111111".to_string()).is_err());
312
313        // Invalid characters (not base64-url)
314        assert!(Pubkey::new("ta!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!".to_string()).is_err());
315    }
316
317    #[test]
318    fn test_signature_validation() {
319        // Create a valid signature using the encoding function
320        let bytes = [1u8; 64];
321        let valid_signature = tn_signature_encoding::tn_signature_to_string(&bytes);
322        assert!(Signature::new(valid_signature.to_string()).is_ok());
323
324        // Empty signature
325        assert!(Signature::new("".to_string()).is_err());
326
327        // Wrong length
328        assert!(Signature::new("ts111".to_string()).is_err());
329
330        // Wrong prefix
331        assert!(Signature::new("ta111111111111111111111111111111111111111111111111111111111111111111111111111111111111".to_string()).is_err());
332
333        // Invalid characters (not base64-url)
334        assert!(Signature::new("ts!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!".to_string()).is_err());
335    }
336
337    #[test]
338    fn test_pubkey_roundtrip() {
339        // Test encoding and decoding
340        let bytes = [1u8; 32];
341        let pubkey = Pubkey::from_bytes(&bytes);
342        let decoded_bytes = pubkey.to_bytes().unwrap();
343        assert_eq!(bytes, decoded_bytes);
344    }
345
346    #[test]
347    fn test_signature_roundtrip() {
348        // Test encoding and decoding
349        let bytes = [1u8; 64];
350        let signature = Signature::from_bytes(&bytes);
351        let decoded_bytes = signature.to_bytes().unwrap();
352        assert_eq!(bytes, decoded_bytes);
353    }
354}