1use bsv_rs::primitives::PrivateKey;
7use bsv_rs::wallet::{Counterparty, KeyDeriverApi, ProtoWallet};
8use serde::{Deserialize, Serialize};
9
10use crate::error::{Error, Result};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct UnlockingScriptTemplate {
20 pub derivation_prefix: String,
22 pub derivation_suffix: String,
24 pub script_type: ScriptType,
26 pub satoshis: u64,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum ScriptType {
33 P2PKH,
35 P2PK,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct SignerInput {
45 pub vin: u32,
46 pub source_txid: String,
47 pub source_vout: u32,
48 pub satoshis: u64,
49 pub source_locking_script: Option<Vec<u8>>,
50 pub unlocking_script: Option<Vec<u8>>,
51 pub derivation_prefix: Option<String>,
52 pub derivation_suffix: Option<String>,
53 pub sender_identity_key: Option<String>,
54}
55
56#[derive(Debug)]
73pub struct WalletSigner {
74 #[allow(dead_code)]
76 root_key: Option<PrivateKey>,
77}
78
79impl WalletSigner {
80 pub fn new(root_key: Option<PrivateKey>) -> Self {
86 Self { root_key }
87 }
88
89 pub fn sign_transaction(
109 &self,
110 unsigned_tx: &[u8],
111 inputs: &[SignerInput],
112 proto_wallet: &ProtoWallet,
113 ) -> Result<Vec<u8>> {
114 let mut tx_data = unsigned_tx.to_vec();
116
117 for (vin, input) in inputs.iter().enumerate() {
119 if input.unlocking_script.is_some() {
121 continue;
122 }
123
124 let derivation_prefix = input.derivation_prefix.as_ref().ok_or_else(|| {
126 Error::ValidationError(format!(
127 "Input {} requires signing but has no derivation_prefix",
128 vin
129 ))
130 })?;
131
132 let derivation_suffix = input.derivation_suffix.as_ref().ok_or_else(|| {
133 Error::ValidationError(format!(
134 "Input {} requires signing but has no derivation_suffix",
135 vin
136 ))
137 })?;
138
139 let locking_script = input.source_locking_script.as_ref().ok_or_else(|| {
141 Error::ValidationError(format!(
142 "Input {} requires signing but has no source_locking_script",
143 vin
144 ))
145 })?;
146
147 let counterparty = if let Some(ref sender_key) = input.sender_identity_key {
149 let pubkey = bsv_rs::primitives::PublicKey::from_hex(sender_key)
151 .map_err(|e| Error::ValidationError(format!("Invalid sender key: {}", e)))?;
152 Counterparty::Other(pubkey)
153 } else {
154 Counterparty::Self_
156 };
157
158 use bsv_rs::wallet::{Protocol, SecurityLevel};
165
166 let brc29_protocol = Protocol::new(SecurityLevel::Counterparty, "3241645161d8");
167 let key_id = format!("{} {}", derivation_prefix, derivation_suffix);
168
169 tracing::debug!(
170 derivation_prefix = %derivation_prefix,
171 derivation_suffix = %derivation_suffix,
172 key_id = %key_id,
173 "Deriving key for input using BRC-29 protocol"
174 );
175
176 let signing_key = proto_wallet
177 .key_deriver()
178 .derive_private_key(&brc29_protocol, &key_id, &counterparty)
179 .map_err(|e| Error::TransactionError(format!("Key derivation failed: {}", e)))?;
180
181 let sighash = compute_sighash(&tx_data, vin as u32, locking_script, input.satoshis)?;
184
185 let signature = signing_key
187 .sign(&sighash)
188 .map_err(|e| Error::TransactionError(format!("Signing failed: {}", e)))?;
189
190 let pubkey = signing_key.public_key();
192
193 let pubkey_compressed = pubkey.to_compressed();
195 let pubkey_hash = hash160(&pubkey_compressed);
196 validate_p2pkh_pubkey_match(&pubkey_hash, locking_script)?;
197
198 let unlocking_script =
200 build_unlocking_script(locking_script, &signature.to_der(), &pubkey_compressed)?;
201
202 tx_data = insert_unlocking_script(&tx_data, vin as u32, &unlocking_script)?;
204 }
205
206 for (vin, input) in inputs.iter().enumerate() {
209 if let Some(ref script) = input.unlocking_script {
210 tx_data = insert_unlocking_script(&tx_data, vin as u32, script)?;
211 }
212 }
213
214 Ok(tx_data)
215 }
216
217 pub fn sign_input(
233 &self,
234 tx_data: &[u8],
235 input_index: u32,
236 input: &SignerInput,
237 proto_wallet: &ProtoWallet,
238 ) -> Result<Vec<u8>> {
239 let derivation_prefix = input.derivation_prefix.as_ref().ok_or_else(|| {
240 Error::ValidationError(format!(
241 "Input {} requires signing but has no derivation_prefix",
242 input_index
243 ))
244 })?;
245
246 let derivation_suffix = input.derivation_suffix.as_ref().ok_or_else(|| {
247 Error::ValidationError(format!(
248 "Input {} requires signing but has no derivation_suffix",
249 input_index
250 ))
251 })?;
252
253 let locking_script = input.source_locking_script.as_ref().ok_or_else(|| {
254 Error::ValidationError(format!(
255 "Input {} requires signing but has no source_locking_script",
256 input_index
257 ))
258 })?;
259
260 let counterparty = if let Some(ref sender_key) = input.sender_identity_key {
261 let pubkey = bsv_rs::primitives::PublicKey::from_hex(sender_key)
262 .map_err(|e| Error::ValidationError(format!("Invalid sender key: {}", e)))?;
263 Counterparty::Other(pubkey)
264 } else {
265 Counterparty::Self_
266 };
267
268 use bsv_rs::wallet::{Protocol, SecurityLevel};
272
273 let brc29_protocol = Protocol::new(SecurityLevel::Counterparty, "3241645161d8");
274 let key_id = format!("{} {}", derivation_prefix, derivation_suffix);
275
276 let signing_key = proto_wallet
277 .key_deriver()
278 .derive_private_key(&brc29_protocol, &key_id, &counterparty)
279 .map_err(|e| Error::TransactionError(format!("Key derivation failed: {}", e)))?;
280
281 let sighash = compute_sighash(tx_data, input_index, locking_script, input.satoshis)?;
282
283 let signature = signing_key
284 .sign(&sighash)
285 .map_err(|e| Error::TransactionError(format!("Signing failed: {}", e)))?;
286
287 let pubkey = signing_key.public_key();
288
289 let pubkey_compressed = pubkey.to_compressed();
291 let pubkey_hash = hash160(&pubkey_compressed);
292 validate_p2pkh_pubkey_match(&pubkey_hash, locking_script)?;
293
294 build_unlocking_script(locking_script, &signature.to_der(), &pubkey_compressed)
295 }
296
297 pub fn create_unlock_template(
309 &self,
310 prefix: &str,
311 suffix: &str,
312 script_type: ScriptType,
313 satoshis: u64,
314 ) -> UnlockingScriptTemplate {
315 UnlockingScriptTemplate {
316 derivation_prefix: prefix.to_string(),
317 derivation_suffix: suffix.to_string(),
318 script_type,
319 satoshis,
320 }
321 }
322
323 #[allow(clippy::ptr_arg)]
339 pub fn apply_templates(
340 &self,
341 raw_tx: &mut Vec<u8>,
342 templates: &[(usize, UnlockingScriptTemplate)],
343 proto_wallet: &ProtoWallet,
344 ) -> Result<Vec<u8>> {
345 tracing::debug!("Applying {} unlocking script templates", templates.len());
349
350 let mut tx_data = raw_tx.clone();
351
352 for (input_index, template) in templates {
353 tracing::debug!(
354 input_index = %input_index,
355 derivation_prefix = %template.derivation_prefix,
356 derivation_suffix = %template.derivation_suffix,
357 script_type = ?template.script_type,
358 "Applying template for input"
359 );
360
361 use bsv_rs::wallet::{Protocol, SecurityLevel};
363
364 let brc29_protocol = Protocol::new(SecurityLevel::Counterparty, "3241645161d8");
365 let key_id = format!(
366 "{} {}",
367 template.derivation_prefix, template.derivation_suffix
368 );
369
370 let signing_key = proto_wallet
371 .key_deriver()
372 .derive_private_key(&brc29_protocol, &key_id, &Counterparty::Self_)
373 .map_err(|e| {
374 Error::TransactionError(format!("Template key derivation failed: {}", e))
375 })?;
376
377 let pubkey = signing_key.public_key();
378 let pubkey_bytes = pubkey.to_compressed();
379
380 let locking_script = match template.script_type {
382 ScriptType::P2PKH => {
383 let pubkey_hash = hash160(&pubkey_bytes);
385 let mut script = vec![0x76, 0xa9, 0x14];
386 script.extend_from_slice(&pubkey_hash);
387 script.extend_from_slice(&[0x88, 0xac]);
388 script
389 }
390 ScriptType::P2PK => {
391 let mut script = vec![pubkey_bytes.len() as u8];
393 script.extend_from_slice(&pubkey_bytes);
394 script.push(0xac);
395 script
396 }
397 };
398
399 let sighash = compute_sighash(
400 &tx_data,
401 *input_index as u32,
402 &locking_script,
403 template.satoshis,
404 )?;
405
406 let signature = signing_key
407 .sign(&sighash)
408 .map_err(|e| Error::TransactionError(format!("Template signing failed: {}", e)))?;
409
410 let unlocking_script =
411 build_unlocking_script(&locking_script, &signature.to_der(), &pubkey_bytes)?;
412
413 tx_data = insert_unlocking_script(&tx_data, *input_index as u32, &unlocking_script)?;
414 }
415
416 Ok(tx_data)
417 }
418}
419
420fn compute_sighash(
429 tx_data: &[u8],
430 input_index: u32,
431 locking_script: &[u8],
432 satoshis: u64,
433) -> Result<[u8; 32]> {
434 let (version, inputs, outputs, locktime) = parse_transaction(tx_data)?;
436
437 let mut prevouts_data = Vec::new();
439 for input in &inputs {
440 prevouts_data.extend_from_slice(&input.txid);
441 prevouts_data.extend_from_slice(&input.vout.to_le_bytes());
442 }
443 let hash_prevouts = double_sha256(&prevouts_data);
444
445 let mut sequence_data = Vec::new();
447 for input in &inputs {
448 sequence_data.extend_from_slice(&input.sequence.to_le_bytes());
449 }
450 let hash_sequence = double_sha256(&sequence_data);
451
452 let mut outputs_data = Vec::new();
454 for output in &outputs {
455 outputs_data.extend_from_slice(&output.satoshis.to_le_bytes());
456 write_varint(&mut outputs_data, output.script.len() as u64);
457 outputs_data.extend_from_slice(&output.script);
458 }
459 let hash_outputs = double_sha256(&outputs_data);
460
461 let mut preimage = Vec::new();
463
464 preimage.extend_from_slice(&version.to_le_bytes());
466
467 preimage.extend_from_slice(&hash_prevouts);
469
470 preimage.extend_from_slice(&hash_sequence);
472
473 let input = &inputs[input_index as usize];
475 preimage.extend_from_slice(&input.txid);
476 preimage.extend_from_slice(&input.vout.to_le_bytes());
477
478 write_varint(&mut preimage, locking_script.len() as u64);
480 preimage.extend_from_slice(locking_script);
481
482 preimage.extend_from_slice(&satoshis.to_le_bytes());
484
485 preimage.extend_from_slice(&input.sequence.to_le_bytes());
487
488 preimage.extend_from_slice(&hash_outputs);
490
491 preimage.extend_from_slice(&locktime.to_le_bytes());
493
494 preimage.extend_from_slice(&0x41u32.to_le_bytes());
496
497 Ok(double_sha256(&preimage))
499}
500
501fn parse_transaction(tx_data: &[u8]) -> Result<(u32, Vec<TxInput>, Vec<TxOutput>, u32)> {
503 let mut offset = 0;
504
505 if tx_data.len() < 4 {
507 return Err(Error::TransactionError("Transaction too short".to_string()));
508 }
509 let version = u32::from_le_bytes([
510 tx_data[offset],
511 tx_data[offset + 1],
512 tx_data[offset + 2],
513 tx_data[offset + 3],
514 ]);
515 offset += 4;
516
517 let (input_count, bytes_read) = read_varint(&tx_data[offset..])?;
519 offset += bytes_read;
520
521 let mut inputs = Vec::with_capacity(input_count as usize);
523 for _ in 0..input_count {
524 if offset + 32 > tx_data.len() {
526 return Err(Error::TransactionError(
527 "Unexpected end of transaction data".to_string(),
528 ));
529 }
530 let mut txid = [0u8; 32];
531 txid.copy_from_slice(&tx_data[offset..offset + 32]);
532 offset += 32;
533
534 if offset + 4 > tx_data.len() {
536 return Err(Error::TransactionError(
537 "Unexpected end of transaction data".to_string(),
538 ));
539 }
540 let vout = u32::from_le_bytes([
541 tx_data[offset],
542 tx_data[offset + 1],
543 tx_data[offset + 2],
544 tx_data[offset + 3],
545 ]);
546 offset += 4;
547
548 let (script_len, bytes_read) = read_varint(&tx_data[offset..])?;
550 offset += bytes_read;
551
552 if offset + script_len as usize > tx_data.len() {
553 return Err(Error::TransactionError(
554 "Unexpected end of transaction data".to_string(),
555 ));
556 }
557 let script = tx_data[offset..offset + script_len as usize].to_vec();
558 offset += script_len as usize;
559
560 if offset + 4 > tx_data.len() {
562 return Err(Error::TransactionError(
563 "Unexpected end of transaction data".to_string(),
564 ));
565 }
566 let sequence = u32::from_le_bytes([
567 tx_data[offset],
568 tx_data[offset + 1],
569 tx_data[offset + 2],
570 tx_data[offset + 3],
571 ]);
572 offset += 4;
573
574 inputs.push(TxInput {
575 txid,
576 vout,
577 script,
578 sequence,
579 });
580 }
581
582 let (output_count, bytes_read) = read_varint(&tx_data[offset..])?;
584 offset += bytes_read;
585
586 let mut outputs = Vec::with_capacity(output_count as usize);
588 for _ in 0..output_count {
589 if offset + 8 > tx_data.len() {
591 return Err(Error::TransactionError(
592 "Unexpected end of transaction data".to_string(),
593 ));
594 }
595 let satoshis = u64::from_le_bytes([
596 tx_data[offset],
597 tx_data[offset + 1],
598 tx_data[offset + 2],
599 tx_data[offset + 3],
600 tx_data[offset + 4],
601 tx_data[offset + 5],
602 tx_data[offset + 6],
603 tx_data[offset + 7],
604 ]);
605 offset += 8;
606
607 let (script_len, bytes_read) = read_varint(&tx_data[offset..])?;
609 offset += bytes_read;
610
611 if offset + script_len as usize > tx_data.len() {
612 return Err(Error::TransactionError(
613 "Unexpected end of transaction data".to_string(),
614 ));
615 }
616 let script = tx_data[offset..offset + script_len as usize].to_vec();
617 offset += script_len as usize;
618
619 outputs.push(TxOutput { satoshis, script });
620 }
621
622 if offset + 4 > tx_data.len() {
624 return Err(Error::TransactionError(
625 "Unexpected end of transaction data".to_string(),
626 ));
627 }
628 let locktime = u32::from_le_bytes([
629 tx_data[offset],
630 tx_data[offset + 1],
631 tx_data[offset + 2],
632 tx_data[offset + 3],
633 ]);
634
635 Ok((version, inputs, outputs, locktime))
636}
637
638struct TxInput {
640 txid: [u8; 32],
641 vout: u32,
642 script: Vec<u8>,
643 sequence: u32,
644}
645
646struct TxOutput {
648 satoshis: u64,
649 script: Vec<u8>,
650}
651
652fn read_varint(data: &[u8]) -> Result<(u64, usize)> {
654 if data.is_empty() {
655 return Err(Error::TransactionError("Empty varint".to_string()));
656 }
657
658 let first = data[0];
659 if first < 0xfd {
660 Ok((first as u64, 1))
661 } else if first == 0xfd {
662 if data.len() < 3 {
663 return Err(Error::TransactionError("Truncated varint".to_string()));
664 }
665 let val = u16::from_le_bytes([data[1], data[2]]) as u64;
666 Ok((val, 3))
667 } else if first == 0xfe {
668 if data.len() < 5 {
669 return Err(Error::TransactionError("Truncated varint".to_string()));
670 }
671 let val = u32::from_le_bytes([data[1], data[2], data[3], data[4]]) as u64;
672 Ok((val, 5))
673 } else {
674 if data.len() < 9 {
675 return Err(Error::TransactionError("Truncated varint".to_string()));
676 }
677 let val = u64::from_le_bytes([
678 data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
679 ]);
680 Ok((val, 9))
681 }
682}
683
684fn double_sha256(data: &[u8]) -> [u8; 32] {
686 use sha2::{Digest, Sha256};
687
688 let hash1 = Sha256::digest(data);
689 let hash2 = Sha256::digest(hash1);
690 let mut result = [0u8; 32];
691 result.copy_from_slice(&hash2);
692 result
693}
694
695fn hash160(data: &[u8]) -> [u8; 20] {
699 use sha2::{Digest, Sha256};
700
701 let sha256_hash = Sha256::digest(data);
702
703 use ripemd::Ripemd160;
704 let ripemd_hash = <Ripemd160 as ripemd::Digest>::digest(sha256_hash);
705 let mut result = [0u8; 20];
706 result.copy_from_slice(&ripemd_hash);
707 result
708}
709
710fn validate_p2pkh_pubkey_match(pubkey_hash160: &[u8; 20], locking_script: &[u8]) -> Result<()> {
717 if locking_script.len() == 25
718 && locking_script[0] == 0x76
719 && locking_script[1] == 0xa9
720 && locking_script[2] == 0x14
721 && locking_script[23] == 0x88
722 && locking_script[24] == 0xac
723 {
724 let script_hash160 = &locking_script[3..23];
725 if pubkey_hash160 != script_hash160 {
726 return Err(Error::SigningError(format!(
727 "Derived pubkey hash160 {} does not match locking script hash160 {}",
728 hex::encode(pubkey_hash160),
729 hex::encode(script_hash160)
730 )));
731 }
732 }
733 Ok(())
734}
735
736fn build_unlocking_script(
738 locking_script: &[u8],
739 signature: &[u8],
740 pubkey: &[u8],
741) -> Result<Vec<u8>> {
742 if locking_script.len() == 25
746 && locking_script[0] == 0x76 && locking_script[1] == 0xa9 && locking_script[2] == 0x14 && locking_script[23] == 0x88 && locking_script[24] == 0xac
751 {
753 let mut unlocking = Vec::new();
755
756 let sig_with_hashtype: Vec<u8> = signature
758 .iter()
759 .copied()
760 .chain(std::iter::once(0x41)) .collect();
762
763 unlocking.push(sig_with_hashtype.len() as u8);
765 unlocking.extend_from_slice(&sig_with_hashtype);
766
767 unlocking.push(pubkey.len() as u8);
769 unlocking.extend_from_slice(pubkey);
770
771 return Ok(unlocking);
772 }
773
774 if locking_script.len() >= 35
777 && (locking_script[0] == 33 || locking_script[0] == 65)
778 && locking_script[locking_script.len() - 1] == 0xac
779 {
780 let mut unlocking = Vec::new();
782
783 let sig_with_hashtype: Vec<u8> = signature
784 .iter()
785 .copied()
786 .chain(std::iter::once(0x41))
787 .collect();
788
789 unlocking.push(sig_with_hashtype.len() as u8);
790 unlocking.extend_from_slice(&sig_with_hashtype);
791
792 return Ok(unlocking);
793 }
794
795 Err(Error::TransactionError(format!(
797 "Unknown locking script type: {}",
798 hex::encode(locking_script)
799 )))
800}
801
802fn insert_unlocking_script(
804 tx_data: &[u8],
805 input_index: u32,
806 unlocking_script: &[u8],
807) -> Result<Vec<u8>> {
808 let (version, inputs, outputs, locktime) = parse_transaction(tx_data)?;
810
811 let mut result = Vec::new();
813
814 result.extend_from_slice(&version.to_le_bytes());
816
817 write_varint(&mut result, inputs.len() as u64);
819
820 for (i, input) in inputs.iter().enumerate() {
822 result.extend_from_slice(&input.txid);
824
825 result.extend_from_slice(&input.vout.to_le_bytes());
827
828 let script = if i == input_index as usize {
830 unlocking_script
831 } else {
832 &input.script
833 };
834
835 write_varint(&mut result, script.len() as u64);
837 result.extend_from_slice(script);
838
839 result.extend_from_slice(&input.sequence.to_le_bytes());
841 }
842
843 write_varint(&mut result, outputs.len() as u64);
845
846 for output in &outputs {
848 result.extend_from_slice(&output.satoshis.to_le_bytes());
850
851 write_varint(&mut result, output.script.len() as u64);
853 result.extend_from_slice(&output.script);
854 }
855
856 result.extend_from_slice(&locktime.to_le_bytes());
858
859 Ok(result)
860}
861
862fn write_varint(output: &mut Vec<u8>, value: u64) {
864 if value < 0xfd {
865 output.push(value as u8);
866 } else if value <= 0xffff {
867 output.push(0xfd);
868 output.extend_from_slice(&(value as u16).to_le_bytes());
869 } else if value <= 0xffffffff {
870 output.push(0xfe);
871 output.extend_from_slice(&(value as u32).to_le_bytes());
872 } else {
873 output.push(0xff);
874 output.extend_from_slice(&value.to_le_bytes());
875 }
876}
877
878#[cfg(test)]
883mod tests {
884 use super::*;
885
886 #[test]
887 fn test_read_varint() {
888 assert_eq!(read_varint(&[0x00]).unwrap(), (0, 1));
890 assert_eq!(read_varint(&[0xfc]).unwrap(), (252, 1));
891
892 assert_eq!(read_varint(&[0xfd, 0xfd, 0x00]).unwrap(), (253, 3));
894 assert_eq!(read_varint(&[0xfd, 0xff, 0xff]).unwrap(), (65535, 3));
895
896 assert_eq!(
898 read_varint(&[0xfe, 0x00, 0x00, 0x01, 0x00]).unwrap(),
899 (65536, 5)
900 );
901
902 assert_eq!(
904 read_varint(&[0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]).unwrap(),
905 (4294967296, 9)
906 );
907 }
908
909 #[test]
910 fn test_write_varint() {
911 let mut buf = Vec::new();
912
913 write_varint(&mut buf, 0);
915 assert_eq!(buf, vec![0x00]);
916
917 buf.clear();
918 write_varint(&mut buf, 252);
919 assert_eq!(buf, vec![0xfc]);
920
921 buf.clear();
923 write_varint(&mut buf, 253);
924 assert_eq!(buf, vec![0xfd, 0xfd, 0x00]);
925
926 buf.clear();
927 write_varint(&mut buf, 65535);
928 assert_eq!(buf, vec![0xfd, 0xff, 0xff]);
929 }
930
931 #[test]
932 fn test_double_sha256() {
933 let result = double_sha256(&[]);
935 let expected =
936 hex::decode("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456")
937 .unwrap();
938 assert_eq!(result.to_vec(), expected);
939 }
940
941 #[test]
942 fn test_build_unlocking_script_p2pkh() {
943 let pubkey_hash = [0u8; 20];
945 let mut locking_script = vec![0x76, 0xa9, 0x14];
946 locking_script.extend_from_slice(&pubkey_hash);
947 locking_script.extend_from_slice(&[0x88, 0xac]);
948
949 let signature = vec![0x30, 0x44]; let pubkey = vec![0x02; 33]; let result = build_unlocking_script(&locking_script, &signature, &pubkey).unwrap();
954
955 assert!(!result.is_empty());
957 assert_eq!(result[0], 3); }
960
961 #[test]
962 fn test_wallet_signer_new() {
963 let signer = WalletSigner::new(None);
964 assert!(signer.root_key.is_none());
965
966 let key = PrivateKey::random();
967 let signer = WalletSigner::new(Some(key));
968 assert!(signer.root_key.is_some());
969 }
970
971 #[test]
972 fn test_create_unlock_template() {
973 let signer = WalletSigner::new(None);
974 let template = signer.create_unlock_template("aabbcc", "ddeeff", ScriptType::P2PKH, 5000);
975
976 assert_eq!(template.derivation_prefix, "aabbcc");
977 assert_eq!(template.derivation_suffix, "ddeeff");
978 assert_eq!(template.script_type, ScriptType::P2PKH);
979 assert_eq!(template.satoshis, 5000);
980 }
981
982 #[test]
983 fn test_create_unlock_template_p2pk() {
984 let signer = WalletSigner::new(None);
985 let template = signer.create_unlock_template("1234", "5678", ScriptType::P2PK, 10000);
986
987 assert_eq!(template.derivation_prefix, "1234");
988 assert_eq!(template.derivation_suffix, "5678");
989 assert_eq!(template.script_type, ScriptType::P2PK);
990 assert_eq!(template.satoshis, 10000);
991 }
992
993 #[test]
994 fn test_unlocking_script_template_serialization() {
995 let template = UnlockingScriptTemplate {
996 derivation_prefix: "aabb".to_string(),
997 derivation_suffix: "ccdd".to_string(),
998 script_type: ScriptType::P2PKH,
999 satoshis: 1000,
1000 };
1001
1002 let json = serde_json::to_string(&template).unwrap();
1003 let deserialized: UnlockingScriptTemplate = serde_json::from_str(&json).unwrap();
1004
1005 assert_eq!(deserialized.derivation_prefix, "aabb");
1006 assert_eq!(deserialized.derivation_suffix, "ccdd");
1007 assert_eq!(deserialized.script_type, ScriptType::P2PKH);
1008 }
1009
1010 #[test]
1011 fn test_script_type_equality() {
1012 assert_eq!(ScriptType::P2PKH, ScriptType::P2PKH);
1013 assert_eq!(ScriptType::P2PK, ScriptType::P2PK);
1014 assert_ne!(ScriptType::P2PKH, ScriptType::P2PK);
1015 }
1016
1017 #[test]
1018 fn test_hash160() {
1019 let result = hash160(&[]);
1021 let expected = hex::decode("b472a266d0bd89c13706a4132ccfb16f7c3b9fcb").unwrap();
1024 assert_eq!(result.to_vec(), expected);
1025 }
1026}