1use crate::common_types::types::SecretKey;
2use bitcoin::consensus::{deserialize, Encodable};
3use bitcoin::hashes::Hash;
4use bitcoin::key::TapTweak;
5use bitcoin::secp256k1::Scalar;
6use bitcoin::secp256k1::{PublicKey, Secp256k1};
7use bitcoin::{Address, Network, ScriptBuf, TapTweakHash, Transaction, TxOut, XOnlyPublicKey};
8
9use crate::error::{transaction::TransactionError, validation::ValidationError, SparkSdkError};
10
11pub(crate) fn p2tr_script_from_pubkey(pubkey: &PublicKey, network: Network) -> ScriptBuf {
13 let secp = Secp256k1::new();
14 let xonly = XOnlyPublicKey::from(*pubkey);
15 let address = Address::p2tr(&secp, xonly, None, network);
16 address.script_pubkey()
17}
18
19pub(crate) fn p2tr_address_from_public_key(pubkey: &PublicKey, network: Network) -> Address {
20 let secp = Secp256k1::new();
21 let xonly = XOnlyPublicKey::from(*pubkey);
22
23 Address::p2tr(&secp, xonly, None, network)
24}
25
26pub(crate) fn bitcoin_tx_from_hex(raw_tx_hex: &str) -> Result<Transaction, SparkSdkError> {
28 let tx_bytes = hex::decode(raw_tx_hex).map_err(|e| {
29 SparkSdkError::from(TransactionError::InvalidBitcoinTransaction(format!(
30 "Failed to decode hex: {}",
31 e
32 )))
33 })?;
34 bitcoin_tx_from_bytes(&tx_bytes)
35}
36
37pub(crate) fn serialize_bitcoin_transaction(
38 transaction: &Transaction,
39) -> Result<Vec<u8>, SparkSdkError> {
40 let mut buf = Vec::new();
41 transaction.consensus_encode(&mut buf).map_err(|e| {
42 SparkSdkError::from(TransactionError::InvalidBitcoinTransaction(format!(
43 "Failed to serialize Bitcoin transaction: {}",
44 e
45 )))
46 })?;
47
48 Ok(buf)
49}
50
51pub(crate) fn bitcoin_tx_from_bytes(raw_tx_bytes: &[u8]) -> Result<Transaction, SparkSdkError> {
52 if raw_tx_bytes.is_empty() {
53 return Err(SparkSdkError::from(
54 TransactionError::InvalidBitcoinTransaction(
55 "Cannot deserialize Bitcoin transaction: buffer is empty".to_string(),
56 ),
57 ));
58 }
59
60 let transaction = deserialize(raw_tx_bytes).map_err(|_| {
61 SparkSdkError::from(TransactionError::InvalidBitcoinTransaction(
62 "Invalid transaction".to_string(),
63 ))
64 })?;
65
66 Ok(transaction)
67}
68
69pub(crate) fn sighash_from_tx(
70 tx: &bitcoin::Transaction,
71 input_index: usize,
72 prev_output: &bitcoin::TxOut,
73) -> Result<[u8; 32], SparkSdkError> {
74 let prevouts_arr = [prev_output.clone()];
75 let prev_output_fetcher = bitcoin::sighash::Prevouts::All(&prevouts_arr);
76
77 let sighash = bitcoin::sighash::SighashCache::new(tx)
78 .taproot_key_spend_signature_hash(
79 input_index,
80 &prev_output_fetcher,
81 bitcoin::sighash::TapSighashType::Default,
82 )
83 .map_err(|e| {
84 SparkSdkError::from(ValidationError::InvalidInput {
85 field: format!("Failed to calculate sighash: {}", e),
86 })
87 })?;
88
89 Ok(sighash.to_raw_hash().to_byte_array())
90}
91
92pub(crate) fn sighash_from_tx_new(
93 tx: &Transaction,
94 input_index: usize,
95 prev_output: &TxOut,
96) -> Result<[u8; 32], SparkSdkError> {
97 let prevouts = vec![prev_output.clone(); tx.input.len()];
99
100 let prev_output_fetcher = bitcoin::sighash::Prevouts::All(&prevouts);
102
103 let sighash = bitcoin::sighash::SighashCache::new(tx)
105 .taproot_key_spend_signature_hash(
106 input_index,
107 &prev_output_fetcher,
108 bitcoin::sighash::TapSighashType::Default,
109 )
110 .map_err(|e| {
111 SparkSdkError::from(ValidationError::InvalidInput {
112 field: format!("Failed to calculate sighash: {}", e),
113 })
114 })?;
115
116 Ok(sighash.to_byte_array())
117}
118
119pub(crate) fn compute_taproot_key_no_script(
120 pubkey: bitcoin::secp256k1::PublicKey,
121) -> Result<bitcoin::XOnlyPublicKey, SparkSdkError> {
122 let (x_only_pub, _) = pubkey.x_only_public_key();
123
124 let (tweaked_key, _parity) = x_only_pub.tap_tweak(&bitcoin::secp256k1::Secp256k1::new(), None);
126
127 Ok(tweaked_key.to_inner())
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use bitcoin::absolute::LockTime;
134 use bitcoin::transaction::Version;
135 use bitcoin::Network;
136 use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Witness};
137
138 #[test]
139 fn test_p2tr_address_from_public_key() {
140 let test_vectors = vec![
141 (
142 "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
143 "bc1pmfr3p9j00pfxjh0zmgp99y8zftmd3s5pmedqhyptwy6lm87hf5sspknck9",
144 Network::Bitcoin,
145 ),
146 (
147 "03797dd653040d344fd048c1ad05d4cbcb2178b30c6a0c4276994795f3e833da41",
148 "tb1p8dlmzllfah294ntwatr8j5uuvcj7yg0dete94ck2krrk0ka2c9qqex96hv",
149 Network::Testnet,
150 ),
151 ];
152
153 for (pubkey_hex, expected_addr, network) in test_vectors {
154 let pubkey = PublicKey::from_slice(&hex::decode(pubkey_hex).unwrap()).unwrap();
155 let addr = p2tr_address_from_public_key(&pubkey, network);
156 assert_eq!(&addr.to_string(), expected_addr);
157 }
158 }
159
160 #[test]
161 fn test_tx_from_raw_tx_hex() {
162 let raw_tx_hex = "02000000000102dc552c6c0ef5ed0d8cd64bd1d2d1ffd7cf0ec0b5ad8df2a4c6269b59cffcc696010000000000000000603fbd40e86ee82258c57571c557b89a444aabf5b6a05574e6c6848379febe9a00000000000000000002e86905000000000022512024741d89092c5965f35a63802352fa9c7fae4a23d471b9dceb3379e8ff6b7dd1d054080000000000220020aea091435e74e3c1eba0bd964e67a05f300ace9e73efa66fe54767908f3e68800140f607486d87f59af453d62cffe00b6836d8cca2c89a340fab5fe842b20696908c77fd2f64900feb0cbb1c14da3e02271503fc465fcfb1b043c8187dccdd494558014067dff0f0c321fc8abc28bf555acfdfa5ee889b6909b24bc66cedf05e8cc2750a4d95037c3dc9c24f1e502198bade56fef61a2504809f5b2a60a62afeaf8bf52e00000000";
163 let result_hex = bitcoin_tx_from_hex(raw_tx_hex);
164 assert!(result_hex.is_ok());
165
166 let tx_bytes = hex::decode(raw_tx_hex).unwrap();
167 let result_bytes = bitcoin_tx_from_bytes(&tx_bytes);
168 assert!(result_bytes.is_ok());
169
170 assert_eq!(result_hex.unwrap(), result_bytes.unwrap());
171 }
172
173 #[test]
174 fn test_sighash_from_tx() {
175 let prev_tx_hex = "020000000001010cb9feccc0bdaac30304e469c50b4420c13c43d466e13813fcf42a73defd3f010000000000ffffffff018038010000000000225120d21e50e12ae122b4a5662c09b67cec7449c8182913bc06761e8b65f0fa2242f701400536f9b7542799f98739eeb6c6adaeb12d7bd418771bc5c6847f2abd19297bd466153600af26ccf0accb605c11ad667c842c5713832af4b7b11f1bcebe57745900000000";
176 let prev_tx = bitcoin_tx_from_hex(prev_tx_hex).unwrap();
177
178 let tx = Transaction {
179 version: Version::TWO,
180 lock_time: LockTime::ZERO,
181 input: vec![TxIn {
182 previous_output: OutPoint {
183 txid: prev_tx.compute_txid(),
184 vout: 0,
185 },
186 script_sig: ScriptBuf::new(),
187 sequence: bitcoin::Sequence::MAX,
188 witness: Witness::default(),
189 }],
190 output: vec![TxOut {
191 value: Amount::from_sat(70_000),
192 script_pubkey: prev_tx.output[0].script_pubkey.clone(),
193 }],
194 };
195
196 let sighash = sighash_from_tx(&tx, 0, &prev_tx.output[0]).unwrap();
197 assert_eq!(
198 hex::encode(sighash),
199 "8da5e7aa2b03491d7c2f4359ea4968dd58f69adf9af1a2c6881be0295591c293"
200 );
201 }
202}
203
204#[cfg_attr(feature = "telemetry", tracing::instrument(skip_all))]
212pub fn compute_taproot_key_no_script_from_internal_key(
213 pubkey: &[u8],
214) -> Result<[u8; 33], SparkSdkError> {
215 if pubkey.len() != 32 {
217 return Err(SparkSdkError::from(ValidationError::InvalidInput {
218 field: "Public key must be 32 bytes".to_string(),
219 }));
220 }
221
222 let secp = Secp256k1::new();
224
225 let x_only_key = XOnlyPublicKey::from_slice(pubkey).map_err(|_| {
227 SparkSdkError::from(ValidationError::InvalidInput {
228 field: "Invalid x-only public key".to_string(),
229 })
230 })?;
231
232 let tweak = TapTweakHash::hash(pubkey);
234 let tweak_bytes = tweak.to_byte_array();
235 let tweak_scalar = Scalar::from_be_bytes(tweak_bytes).map_err(|_| {
236 SparkSdkError::from(ValidationError::InvalidInput {
237 field: "Failed to convert tweak to scalar".to_string(),
238 })
239 })?;
240
241 let (tweaked_key, _parity) = x_only_key.add_tweak(&secp, &tweak_scalar).map_err(|_| {
243 SparkSdkError::from(ValidationError::InvalidInput {
244 field: "Failed to tweak public key".to_string(),
245 })
246 })?;
247
248 let taproot_key = PublicKey::from_x_only_public_key(tweaked_key, _parity);
250
251 Ok(taproot_key.serialize())
253}
254
255pub fn parse_secret_key(bytes: &Vec<u8>) -> Result<SecretKey, SparkSdkError> {
256 let secret_key = bitcoin::secp256k1::SecretKey::from_slice(bytes).map_err(|e| {
257 SparkSdkError::from(ValidationError::InvalidArgument {
258 argument: format!("Private key is not valid: {}", e),
259 })
260 })?;
261
262 Ok(secret_key)
263}
264
265pub fn parse_public_key(bytes: &Vec<u8>) -> Result<PublicKey, SparkSdkError> {
266 let public_key = bitcoin::secp256k1::PublicKey::from_slice(bytes).map_err(|e| {
267 SparkSdkError::from(ValidationError::InvalidArgument {
268 argument: format!("Public key is not valid: {}", e),
269 })
270 })?;
271
272 Ok(public_key)
273}