|
| 1 | +use std::collections::BTreeMap; |
| 2 | +use std::str::FromStr; |
| 3 | + |
| 4 | +use actual_base64 as base64; |
| 5 | +use bitcoin::absolute::Height; |
| 6 | +use bitcoin::blockdata::locktime::absolute; |
| 7 | +use bitcoin::key::TapTweak; |
| 8 | +use bitcoin::psbt::{self, Psbt}; |
| 9 | +use bitcoin::sighash::SighashCache; |
| 10 | +use bitcoin::{taproot, PrivateKey, ScriptBuf}; |
| 11 | +use miniscript::bitcoin::consensus::encode::deserialize; |
| 12 | +use miniscript::bitcoin::hashes::hex::FromHex; |
| 13 | +use miniscript::bitcoin::{ |
| 14 | + self, secp256k1, Address, Network, OutPoint, Sequence, Transaction, TxIn, TxOut, |
| 15 | +}; |
| 16 | +use miniscript::plan::Assets; |
| 17 | +use miniscript::psbt::{PsbtExt, PsbtInputExt}; |
| 18 | +use miniscript::{Descriptor, DescriptorPublicKey}; |
| 19 | +use secp256k1::Secp256k1; |
| 20 | + |
| 21 | +fn main() { |
| 22 | + // Defining the descriptor keys required. |
| 23 | + let secp256k1 = secp256k1::Secp256k1::new(); |
| 24 | + let keys = vec![ |
| 25 | + "036a7ae441409bd40af1b8efba7dbd34b822b9a72566eff10b889b8de13659e343", |
| 26 | + "03b6c8a1a901edf3c5f1cb0e3ffe1f20393435a5d467f435e2858c9ab43d3ca78c", |
| 27 | + "03500a2b48b0f66c8183cc0d6645ab21cc19c7fad8a33ff04d41c3ece54b0bc1c5", |
| 28 | + "033ad2d191da4f39512adbaac320cae1f12f298386a4e9d43fd98dec7cf5db2ac9", |
| 29 | + "023fc33527afab09fa97135f2180bcd22ce637b1d2fbcb2db748b1f2c33f45b2b4", |
| 30 | + ]; |
| 31 | + |
| 32 | + // Defining the taproot descriptor |
| 33 | + let s = format!( |
| 34 | + "tr({},{{pkh({}),{{multi_a(1,{},{}),and_v(v:pk({}),after(10))}}}})", |
| 35 | + keys[0], keys[1], keys[2], keys[3], keys[4] |
| 36 | + ); |
| 37 | + |
| 38 | + let bridge_descriptor = Descriptor::from_str(&s).expect("parse descriptor string"); |
| 39 | + assert!(bridge_descriptor.sanity_check().is_ok()); |
| 40 | + |
| 41 | + println!( |
| 42 | + "Bridge pubkey script: {}", |
| 43 | + bridge_descriptor.script_pubkey() |
| 44 | + ); |
| 45 | + println!( |
| 46 | + "Bridge address: {}", |
| 47 | + bridge_descriptor.address(Network::Regtest).unwrap() |
| 48 | + ); |
| 49 | + |
| 50 | + let master_private_key_str = "KxQqtbUnMugSEbKHG3saknvVYux1cgFjFqWzMfwnFhLm8QrGq26v"; |
| 51 | + let master_private_key = |
| 52 | + PrivateKey::from_str(master_private_key_str).expect("Can't create private key"); |
| 53 | + println!( |
| 54 | + "Master public key: {}", |
| 55 | + master_private_key.public_key(&secp256k1) |
| 56 | + ); |
| 57 | + |
| 58 | + let backup1_private_key_str = "Kwb9oFfPNt6D3Fa9DCF5emRvLyJ3UUvCHnVxp4xf7bWDxWmeVdeH"; |
| 59 | + let backup1_private = |
| 60 | + PrivateKey::from_str(backup1_private_key_str).expect("Can't create private key"); |
| 61 | + |
| 62 | + println!( |
| 63 | + "Backup1 public key: {}", |
| 64 | + backup1_private.public_key(&secp256k1) |
| 65 | + ); |
| 66 | + |
| 67 | + let backup2_private_key_str = "cPJFWUKk8sdL7pcDKrmNiWUyqgovimmhaaZ8WwsByDaJ45qLREkh"; |
| 68 | + let backup2_private = |
| 69 | + PrivateKey::from_str(backup2_private_key_str).expect("Can't create private key"); |
| 70 | + |
| 71 | + println!( |
| 72 | + "Backup2 public key: {}", |
| 73 | + backup2_private.public_key(&secp256k1) |
| 74 | + ); |
| 75 | + |
| 76 | + let backup3_private_key_str = "cT5cH9UVm81W5QAf5KABXb23RKNSMbMzMx85y6R2mF42L94YwKX6"; |
| 77 | + let _backup3_private = |
| 78 | + PrivateKey::from_str(backup3_private_key_str).expect("Can't create private key"); |
| 79 | + |
| 80 | + println!( |
| 81 | + "Backup3 public key: {}", |
| 82 | + _backup3_private.public_key(&secp256k1) |
| 83 | + ); |
| 84 | + |
| 85 | + // Create a spending transaction |
| 86 | + let spend_tx = Transaction { |
| 87 | + version: 2, |
| 88 | + lock_time: absolute::LockTime::Blocks(Height::ZERO), |
| 89 | + input: vec![], |
| 90 | + output: vec![], |
| 91 | + }; |
| 92 | + |
| 93 | + // Creating a PSBT Object |
| 94 | + let mut psbt = Psbt { |
| 95 | + unsigned_tx: spend_tx, |
| 96 | + unknown: BTreeMap::new(), |
| 97 | + proprietary: BTreeMap::new(), |
| 98 | + xpub: BTreeMap::new(), |
| 99 | + version: 0, |
| 100 | + inputs: vec![], |
| 101 | + outputs: vec![], |
| 102 | + }; |
| 103 | + |
| 104 | + let hex_tx = "020000000001018ff27041f3d738f5f84fd5ee62f1c5b36afebfb15f6da0c9d1382ddd0eaaa23c0000000000feffffff02b3884703010000001600142ca3b4e53f17991582d47b15a053b3201891df5200e1f5050000000022512061763f4288d086c0347c4e3c387ce22ab9372cecada6c326e77efd57e9a5ea460247304402207b820860a9d425833f729775880b0ed59dd12b64b9a3d1ab677e27e4d6b370700220576003163f8420fe0b9dc8df726cff22cbc191104a2d4ae4f9dfedb087fcec72012103817e1da42a7701df4db94db8576f0e3605f3ab3701608b7e56f92321e4d8999100000000"; |
| 105 | + let depo_tx: Transaction = deserialize(&Vec::<u8>::from_hex(hex_tx).unwrap()).unwrap(); |
| 106 | + |
| 107 | + let receiver = Address::from_str("bcrt1qsdks5za4t6sevaph6tz9ddfjzvhkdkxe9tfrcy").unwrap(); |
| 108 | + |
| 109 | + let amount = 100000000; |
| 110 | + |
| 111 | + let (outpoint, witness_utxo) = get_vout(&depo_tx, bridge_descriptor.script_pubkey()); |
| 112 | + |
| 113 | + // Defining the Transaction Input |
| 114 | + let mut txin = TxIn::default(); |
| 115 | + txin.previous_output = outpoint; |
| 116 | + txin.sequence = Sequence::from_height(26); //Sequence::MAX; // |
| 117 | + psbt.unsigned_tx.input.push(txin); |
| 118 | + |
| 119 | + // Defining the Transaction Output |
| 120 | + psbt.unsigned_tx.output.push(TxOut { |
| 121 | + script_pubkey: receiver.payload.script_pubkey(), |
| 122 | + value: amount / 5 - 500, |
| 123 | + }); |
| 124 | + |
| 125 | + psbt.unsigned_tx.output.push(TxOut { |
| 126 | + script_pubkey: bridge_descriptor.script_pubkey(), |
| 127 | + value: amount * 4 / 5, |
| 128 | + }); |
| 129 | + |
| 130 | + // Plan the Transaction using available assets |
| 131 | + let mut assets = Assets::new(); |
| 132 | + assets = assets.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); // Master Key for Key Spend Path |
| 133 | + assets = assets.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); // Script Spend Path |
| 134 | + |
| 135 | + // Obtain the result of the plan based on provided assets |
| 136 | + let result = bridge_descriptor.clone().get_plan(&assets); |
| 137 | + |
| 138 | + // Creating PSBT Input |
| 139 | + let mut input = psbt::Input::default(); |
| 140 | + result.unwrap().update_psbt_input(&mut input); |
| 141 | + input |
| 142 | + .update_with_descriptor_unchecked(&bridge_descriptor) |
| 143 | + .unwrap(); |
| 144 | + input.witness_utxo = Some(witness_utxo.clone()); |
| 145 | + |
| 146 | + // Push the PSBT Input and declare an PSBT Output Structure |
| 147 | + psbt.inputs.push(input); |
| 148 | + psbt.outputs.push(psbt::Output::default()); |
| 149 | + |
| 150 | + // Use private keys to sign |
| 151 | + let sk1 = master_private_key.inner; |
| 152 | + let sk2 = backup1_private.inner; |
| 153 | + |
| 154 | + // In the following example we have signed the descriptor with master key |
| 155 | + // which will allow the transaction to be key spend type. |
| 156 | + // Any other key apart from master key is part of script policies and it |
| 157 | + // will sign for script spend path if it satisfies. |
| 158 | + sign_taproot_psbt(&sk1, &mut psbt, &secp256k1); // Key Spend |
| 159 | + sign_taproot_psbt(&sk2, &mut psbt, &secp256k1); // Script Spend |
| 160 | + |
| 161 | + // Serializing and finalizing the PSBT Transaction |
| 162 | + let serialized = psbt.serialize(); |
| 163 | + println!("{}", base64::encode(&serialized)); |
| 164 | + psbt.finalize_mut(&secp256k1).unwrap(); |
| 165 | + |
| 166 | + let tx = psbt.extract_tx(); |
| 167 | + println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); |
| 168 | +} |
| 169 | + |
| 170 | +// Siging the Taproot PSBT Transaction |
| 171 | +fn sign_taproot_psbt( |
| 172 | + secret_key: &secp256k1::SecretKey, |
| 173 | + psbt: &mut psbt::Psbt, |
| 174 | + secp256k1: &Secp256k1<secp256k1::All>, |
| 175 | +) { |
| 176 | + // Creating signing entitites required |
| 177 | + let hash_ty = bitcoin::sighash::TapSighashType::Default; |
| 178 | + let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); |
| 179 | + |
| 180 | + // Defining Keypair for given private key |
| 181 | + let keypair = secp256k1::KeyPair::from_seckey_slice(&secp256k1, secret_key.as_ref()).unwrap(); |
| 182 | + |
| 183 | + // Checking if leaf hash exist or not. |
| 184 | + // For Key Spend -> Leaf Hash is None |
| 185 | + // For Script Spend -> Leaf Hash is Some(_) |
| 186 | + // Convert this leaf_hash tree to a full map. |
| 187 | + let (leaf_hashes, (_, _)) = &psbt.inputs[0].tap_key_origins[&keypair.x_only_public_key().0]; |
| 188 | + let leaf_hash = if !leaf_hashes.is_empty() { |
| 189 | + Some(leaf_hashes[0]) |
| 190 | + } else { |
| 191 | + None |
| 192 | + }; |
| 193 | + |
| 194 | + let keypair = match leaf_hash { |
| 195 | + None => keypair |
| 196 | + .tap_tweak(&secp256k1, psbt.inputs[0].tap_merkle_root) |
| 197 | + .to_inner(), // tweak for key spend |
| 198 | + Some(_) => keypair, // no tweak for script spend |
| 199 | + }; |
| 200 | + |
| 201 | + // Construct the message to input for schnorr signature |
| 202 | + let msg = psbt |
| 203 | + .sighash_msg(0, &mut sighash_cache, leaf_hash) |
| 204 | + .unwrap() |
| 205 | + .to_secp_msg(); |
| 206 | + let sig = secp256k1.sign_schnorr(&msg, &keypair); |
| 207 | + let (pk, _parity) = keypair.x_only_public_key(); |
| 208 | + assert!(secp256k1.verify_schnorr(&sig, &msg, &pk).is_ok()); |
| 209 | + |
| 210 | + // Create final signature with corresponding hash type |
| 211 | + let final_signature1 = taproot::Signature { hash_ty, sig }; |
| 212 | + |
| 213 | + if let Some(lh) = leaf_hash { |
| 214 | + // Script Spend |
| 215 | + psbt.inputs[0] |
| 216 | + .tap_script_sigs |
| 217 | + .insert((pk, lh), final_signature1); |
| 218 | + } else { |
| 219 | + // Key Spend |
| 220 | + psbt.inputs[0].tap_key_sig = Some(final_signature1); |
| 221 | + println!("{:#?}", psbt); |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +// Find the Outpoint by spk |
| 226 | +fn get_vout(tx: &Transaction, spk: ScriptBuf) -> (OutPoint, TxOut) { |
| 227 | + for (i, txout) in tx.clone().output.into_iter().enumerate() { |
| 228 | + if spk == txout.script_pubkey { |
| 229 | + return (OutPoint::new(tx.txid(), i as u32), txout); |
| 230 | + } |
| 231 | + } |
| 232 | + panic!("Only call get vout on functions which have the expected outpoint"); |
| 233 | +} |
0 commit comments