1use bitcoin::hashes::sha256;
10use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
11use bitcoin::taproot::TaprootBuilder;
12use bitcoin::ScriptBuf;
13use serde::{Deserialize, Serialize};
14use std::fmt;
15
16use crate::bitcoin::error::BitcoinError;
17
18#[derive(Debug)]
20pub enum TaprootError {
21 KeyError(String),
23 ScriptError(String),
25 TaprootError(String),
27 BuilderError(String),
29 BitcoinError(String),
31 Secp256k1Error(secp256k1::Error),
33 HexError(hex::FromHexError),
35 ValidationError(String),
37}
38
39impl fmt::Display for TaprootError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Self::KeyError(e) => write!(f, "Key error: {e}"),
43 Self::ScriptError(e) => write!(f, "Script error: {e}"),
44 Self::TaprootError(e) => write!(f, "Taproot error: {e}"),
45 Self::BuilderError(e) => write!(f, "Builder error: {e}"),
46 Self::BitcoinError(e) => write!(f, "Bitcoin error: {e}"),
47 Self::Secp256k1Error(e) => write!(f, "Secp256k1 error: {e}"),
48 Self::HexError(e) => write!(f, "Hex error: {e}"),
49 Self::ValidationError(e) => write!(f, "Validation error: {e}"),
50 }
51 }
52}
53
54impl std::error::Error for TaprootError {}
55
56impl From<bitcoin::secp256k1::Error> for TaprootError {
57 fn from(e: bitcoin::secp256k1::Error) -> Self {
58 Self::Secp256k1Error(e)
59 }
60}
61
62impl From<hex::FromHexError> for TaprootError {
63 fn from(e: hex::FromHexError) -> Self {
64 Self::HexError(e)
65 }
66}
67
68impl From<BitcoinError> for TaprootError {
69 fn from(e: BitcoinError) -> Self {
70 Self::BitcoinError(e.to_string())
71 }
72}
73
74impl From<bitcoin::taproot::TaprootError> for TaprootError {
75 fn from(e: bitcoin::taproot::TaprootError) -> Self {
76 Self::TaprootError(e.to_string())
77 }
78}
79
80impl From<bitcoin::taproot::IncompleteBuilderError> for TaprootError {
81 fn from(e: bitcoin::taproot::IncompleteBuilderError) -> Self {
82 Self::BuilderError(e.to_string())
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct TaprootAsset {
89 pub asset_id: [u8; 32],
91 pub name: String,
93 pub supply: u64,
95 pub precision: u8,
97 pub metadata: String,
99 pub issued: bool,
101 pub leaves: Vec<ScriptBuf>,
103 pub num_leaves: u32,
105 pub issuer_pubkey: [u8; 32],
107}
108
109impl TaprootAsset {
110 pub fn new(
112 name: &str,
113 supply: u64,
114 precision: u8,
115 metadata: &str,
116 issuer_secret_key: &[u8],
117 ) -> Result<Self, TaprootError> {
118 if name.is_empty() || name.len() > 32 {
120 return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
121 "Asset name must be 1-32 characters".to_string(),
122 ));
123 }
124
125 if supply == 0 {
126 return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
127 "Supply must be greater than 0".to_string(),
128 ));
129 }
130
131 if precision > 8 {
132 return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
133 "Precision must be 0-8".to_string(),
134 ));
135 }
136
137 if metadata.len() > 1024 {
138 return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
139 "Metadata too large (max 1024 bytes)".to_string(),
140 ));
141 }
142
143 let asset_id = generate_asset_id(name, supply, precision, metadata)?;
145
146 let mut builder = bitcoin::blockdata::script::Builder::new();
148 builder = builder.push_opcode(bitcoin::opcodes::all::OP_RETURN);
149 builder = push_bytes_to_script(builder, &asset_id);
150
151 let leaves = vec![builder.into_script()];
153
154 let secp = Secp256k1::new();
156 let secret_key = SecretKey::from_slice(issuer_secret_key)
157 .map_err(|e| crate::bitcoin::taproot::TaprootError::KeyError(e.to_string()))?;
158 let (x_only_pubkey, _) = secret_key.public_key(&secp).x_only_public_key();
159
160 Ok(Self {
161 asset_id,
162 name: name.to_string(),
163 supply,
164 precision,
165 metadata: metadata.to_string(),
166 issued: true,
167 leaves,
168 num_leaves: 1,
169 issuer_pubkey: x_only_pubkey.serialize(),
170 })
171 }
172
173 pub fn create_asset_script(&self) -> Result<ScriptBuf, TaprootError> {
182 use bitcoin::blockdata::opcodes;
183 use bitcoin::blockdata::script::Builder;
184
185 let mut builder = Builder::new();
186
187 builder = builder.push_opcode(opcodes::all::OP_RETURN);
189
190 builder = push_bytes_to_script(builder, &self.asset_id);
192
193 let name_bytes = self.name.as_bytes();
195 let name_slice = if name_bytes.len() > 32 {
196 &name_bytes[..32]
197 } else {
198 name_bytes
199 };
200 builder = push_bytes_to_script(builder, name_slice);
201
202 let supply_bytes = self.supply.to_le_bytes();
204 builder = push_bytes_to_script(builder, &supply_bytes);
205
206 builder = builder.push_int(self.precision as i64);
208
209 Ok(builder.into_script())
210 }
211}
212
213pub fn generate_keypair() -> Result<(SecretKey, bitcoin::key::XOnlyPublicKey), TaprootError> {
221 let secp = Secp256k1::new();
222 let mut rng = rand::thread_rng();
223 let (secret_key, _) = secp.generate_keypair(&mut rng);
224
225 let x_only = secret_key.x_only_public_key(&secp);
227 Ok((secret_key, x_only.0))
228}
229
230fn generate_asset_id(
241 name: &str,
242 supply: u64,
243 precision: u8,
244 metadata: &str,
245) -> Result<[u8; 32], TaprootError> {
246 use bitcoin::hashes::{Hash, HashEngine};
247
248 let mut engine = sha256::Hash::engine();
249 engine.input(name.as_bytes());
250 engine.input(&supply.to_le_bytes());
251 engine.input(&[precision]);
252 engine.input(metadata.as_bytes());
253
254 let hash = sha256::Hash::from_engine(engine);
255 Ok(hash.to_byte_array())
256}
257
258fn push_bytes_to_script(
260 mut builder: bitcoin::blockdata::script::Builder,
261 data: &[u8],
262) -> bitcoin::blockdata::script::Builder {
263 for &byte in data {
264 builder = builder.push_int(byte as i64);
265 }
266 builder
267}
268
269pub fn create_asset(
304 name: &str,
305 supply: u64,
306 precision: u8,
307 metadata: &str,
308 issuer_secret_key: &[u8],
309) -> Result<TaprootAsset, BitcoinError> {
310 if name.is_empty() || name.len() > 32 {
312 return Err(BitcoinError::InvalidScript(
313 "Asset name must be 1-32 characters".to_string(),
314 ));
315 }
316
317 if supply == 0 {
318 return Err(BitcoinError::InvalidScript(
319 "Asset supply must be greater than 0".to_string(),
320 ));
321 }
322
323 if precision > 8 {
324 return Err(BitcoinError::InvalidScript(
325 "Precision must be between 0 and 8".to_string(),
326 ));
327 }
328
329 if metadata.len() > 1024 {
330 return Err(BitcoinError::InvalidScript(
331 "Metadata exceeds maximum length of 1024 bytes".to_string(),
332 ));
333 }
334
335 if issuer_secret_key.len() != 32 {
336 return Err(BitcoinError::InvalidPrivateKey);
337 }
338
339 TaprootAsset::new(name, supply, precision, metadata, issuer_secret_key)
341 .map_err(|e| BitcoinError::TaprootError(e.to_string()))
342}
343
344pub fn issue_asset(asset: &TaprootAsset, issuer_secret_key: &[u8]) -> Result<String, TaprootError> {
359 let secp = Secp256k1::new();
361
362 let secret_key = secp256k1::SecretKey::from_slice(issuer_secret_key)
364 .map_err(|e| TaprootError::KeyError(e.to_string()))?;
365
366 let (x_only_pubkey, _) = secret_key.public_key(&secp).x_only_public_key();
368
369 let asset_script = asset.create_asset_script()?;
371
372 let mut builder = TaprootBuilder::new();
374
375 builder = builder
377 .add_leaf(1, asset_script.clone())
378 .map_err(|e| TaprootError::BuilderError(format!("Failed to add leaf: {e}")))?;
379
380 let taproot_spend_info = builder
382 .finalize(&secp, x_only_pubkey)
383 .map_err(|e| TaprootError::TaprootError(format!("Failed to finalize builder: {e:?}")))?;
384
385 let output_key = taproot_spend_info.output_key();
387 let output_script = bitcoin::ScriptBuf::new_p2tr(&secp, output_key.into(), None);
388
389 let script_bytes = output_script.as_bytes();
391 if script_bytes.len() != 34 || !script_bytes.starts_with(&[0x51, 0x20]) {
392 return Err(TaprootError::BuilderError(
393 "Output script does not match tr(KEY,{SILENT_LEAF}) pattern".to_string(),
394 ));
395 }
396
397 Ok(hex::encode(output_script.as_bytes()))
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use bitcoin::secp256k1::rand::rngs::OsRng;
404 use bitcoin::secp256k1::Secp256k1;
405
406 #[test]
407 fn test_generate_keypair() {
408 let result = generate_keypair();
409 assert!(result.is_ok());
410
411 let (secret_key, public_key) = result.unwrap();
412
413 let secp = Secp256k1::new();
415 let derived_pubkey = secret_key.x_only_public_key(&secp).0;
416 assert_eq!(public_key, derived_pubkey);
417 }
418
419 #[test]
420 fn test_generate_asset_id() {
421 let asset_id1 = generate_asset_id("TEST", 1000, 8, "metadata").unwrap();
422 let asset_id2 = generate_asset_id("TEST", 1000, 8, "metadata").unwrap();
423 let asset_id3 = generate_asset_id("TEST", 1001, 8, "metadata").unwrap();
424
425 assert_eq!(asset_id1, asset_id2);
427
428 assert_ne!(asset_id1, asset_id3);
430
431 assert_eq!(asset_id1.len(), 32);
433 }
434
435 #[test]
436 fn test_taproot_asset_creation() {
437 let secp = Secp256k1::new();
438 let mut rng = OsRng;
439 let (secret_key, _) = secp.generate_keypair(&mut rng);
440
441 let asset = TaprootAsset::new(
442 "TESTCOIN",
443 1000000,
444 8,
445 "Test asset metadata",
446 &secret_key[..],
447 );
448
449 assert!(asset.is_ok());
450 let asset = asset.unwrap();
451
452 assert_eq!(asset.name, "TESTCOIN");
453 assert_eq!(asset.supply, 1000000);
454 assert_eq!(asset.precision, 8);
455 assert_eq!(asset.metadata, "Test asset metadata");
456 assert!(asset.issued);
457 assert_eq!(asset.num_leaves, 1);
458 assert_eq!(asset.leaves.len(), 1);
459 }
460
461 #[test]
462 fn test_asset_validation() {
463 let secp = Secp256k1::new();
464 let mut rng = OsRng;
465 let (secret_key, _) = secp.generate_keypair(&mut rng);
466
467 let result = TaprootAsset::new("", 1000, 8, "metadata", &secret_key[..]);
469 assert!(result.is_err());
470
471 let long_name = "a".repeat(33);
473 let result = TaprootAsset::new(&long_name, 1000, 8, "metadata", &secret_key[..]);
474 assert!(result.is_err());
475
476 let result = TaprootAsset::new("TEST", 0, 8, "metadata", &secret_key[..]);
478 assert!(result.is_err());
479
480 let result = TaprootAsset::new("TEST", 1000, 9, "metadata", &secret_key[..]);
482 assert!(result.is_err());
483
484 let large_metadata = "a".repeat(1025);
486 let result = TaprootAsset::new("TEST", 1000, 8, &large_metadata, &secret_key[..]);
487 assert!(result.is_err());
488 }
489
490 #[test]
491 fn test_create_asset_script() {
492 let secp = Secp256k1::new();
493 let mut rng = OsRng;
494 let (secret_key, _) = secp.generate_keypair(&mut rng);
495
496 let asset = TaprootAsset::new("TEST", 1000, 8, "metadata", &secret_key[..]).unwrap();
497
498 let script = asset.create_asset_script();
499 assert!(script.is_ok());
500
501 let script = script.unwrap();
502 assert!(!script.is_empty());
503
504 let script_bytes = script.as_bytes();
506 assert_eq!(script_bytes[0], 0x6a); }
508
509 #[test]
510 fn test_create_asset_function() {
511 let secp = Secp256k1::new();
512 let mut rng = OsRng;
513 let (secret_key, _) = secp.generate_keypair(&mut rng);
514
515 let result = create_asset("TESTCOIN", 1000000, 8, "Test metadata", &secret_key[..]);
516
517 assert!(result.is_ok());
518 let asset = result.unwrap();
519
520 assert_eq!(asset.name, "TESTCOIN");
521 assert_eq!(asset.supply, 1000000);
522 assert_eq!(asset.precision, 8);
523 assert_eq!(asset.metadata, "Test metadata");
524 assert!(asset.issued);
525 assert_eq!(asset.num_leaves, 1);
526 assert_eq!(asset.leaves.len(), 1);
527 }
528}