1pub mod hashing;
2pub mod wallet;
3
4use base64::{engine::general_purpose, Engine};
5use bitcoin::{
6 absolute, ecdsa,
7 hashes::Hash,
8 key::TapTweak,
9 psbt::{self, PartiallySignedTransaction, SignError},
10 secp256k1::{self, Message, Secp256k1, Signing, Verification, XOnlyPublicKey},
11 sighash::{self, EcdsaSighashType, SighashCache, TapSighash, TapSighashType},
12 taproot::TapLeafHash,
13 OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness,
14};
15use wallet::Wallet;
16const UTXO: &str = "0000000000000000000000000000000000000000000000000000000000000000";
17const TAG: &str = "BIP0322-signed-message";
18
19fn create_to_spend(message: &str, wallet: &Wallet) -> Txid {
20 let tag_hash = hashing::hash_sha256(&TAG.as_bytes().to_vec());
21 let mut concat_for_result = Vec::new();
22 concat_for_result.extend(tag_hash.clone());
23 concat_for_result.extend(tag_hash);
24 concat_for_result.extend(message.as_bytes().to_vec());
25 let result = hashing::hash_sha256(&concat_for_result);
26
27 let mut script_sig = Vec::new();
29 script_sig.extend(hex::decode("0020").unwrap());
30 script_sig.extend(result);
31 let ins = vec![TxIn {
33 previous_output: OutPoint {
34 txid: UTXO.parse().unwrap(),
35 vout: 0xFFFFFFFF,
36 },
37 script_sig: ScriptBuf::from_bytes(script_sig),
38 sequence: Sequence(0),
39 witness: Witness::new(),
40 }];
41
42 let outs = vec![TxOut {
44 value: 0,
45 script_pubkey: wallet.desc.script_pubkey(),
46 }];
47
48 let tx = Transaction {
49 version: 0,
50 lock_time: absolute::LockTime::ZERO,
51 input: ins,
52 output: outs,
53 };
54 tx.txid()
55}
56fn create_to_sign_empty(txid: Txid, wallet: &Wallet) -> PartiallySignedTransaction {
57 let ins = vec![TxIn {
59 previous_output: OutPoint { txid, vout: 0 },
60 script_sig: ScriptBuf::new(),
61 sequence: Sequence(0),
62 witness: Witness::new(),
63 }];
64
65 let outs = vec![TxOut {
67 value: 0,
68 script_pubkey: ScriptBuf::from_bytes(hex::decode("6a").unwrap()),
69 }];
70
71 let tx = Transaction {
72 version: 0,
73 lock_time: absolute::LockTime::ZERO,
74 input: ins,
75 output: outs,
76 };
77 let mut psbt = PartiallySignedTransaction::from_unsigned_tx(tx).unwrap();
78 psbt.inputs[0].witness_utxo = Some(TxOut {
79 value: 0,
80 script_pubkey: wallet.desc.script_pubkey(),
81 });
82 psbt
83}
84fn get_base64_signature<C: Signing>(
85 to_sign_empty: PartiallySignedTransaction,
86 wallet: &Wallet,
87 secp: &Secp256k1<C>,
88) -> String {
89 let redeem_script = ScriptBuf::new_v0_p2wpkh(&wallet.pubkey.wpubkey_hash().unwrap());
90 let script_code = ScriptBuf::p2wpkh_script_code(&redeem_script)
91 .ok_or(SignError::NotWpkh)
92 .unwrap();
93 let binding = to_sign_empty.unsigned_tx;
94 let mut cache = SighashCache::new(&binding);
95 let message = cache
96 .segwit_signature_hash(0, &script_code, 0, EcdsaSighashType::All)
97 .unwrap();
98 let message = Message::from_slice(message.as_ref()).unwrap();
99 let signature = secp.sign_ecdsa(&message, &wallet.private_key.inner);
100 let sig = ecdsa::Signature {
101 sig: signature,
102 hash_ty: EcdsaSighashType::All,
103 };
104 let witness = vec![sig.to_vec(), wallet.pubkey.to_bytes()];
105
106 let result: Vec<u8> = witness_to_vec(witness);
107 general_purpose::STANDARD.encode(result)
108}
109fn get_base64_signature_taproot<C: Signing + Verification>(
110 to_sign_empty: &mut PartiallySignedTransaction,
111 wallet: &Wallet,
112 secp: &Secp256k1<C>,
113) -> String {
114 let x_only_pubkey = XOnlyPublicKey::from_slice(&wallet.pubkey.to_bytes()[1..]).unwrap();
115 to_sign_empty.inputs[0].tap_internal_key = Some(x_only_pubkey);
116 let binding = to_sign_empty.unsigned_tx.clone();
117 let cache = SighashCache::new(&binding)
118 .taproot_signature_hash(
119 0,
120 &sighash::Prevouts::All(&[TxOut {
121 value: 0,
122 script_pubkey: wallet.desc.script_pubkey(),
123 }]),
124 None,
125 None,
126 TapSighashType::Default,
127 )
128 .unwrap();
129
130 sign_psbt_taproot(
131 &wallet.private_key.inner,
132 None,
133 &mut to_sign_empty.inputs[0],
134 cache,
135 secp,
136 )
137}
138fn witness_to_vec(witness: Vec<Vec<u8>>) -> Vec<u8> {
139 let mut ret_val: Vec<u8> = Vec::new();
140 ret_val.push(witness.len() as u8);
141 for item in witness {
142 ret_val.push(item.len() as u8);
143 ret_val.extend(item);
144 }
145 ret_val
146}
147
148pub fn simple_signature_with_wif_segwit(message: &str, wif: &str) -> String {
150 let secp = Secp256k1::new();
151 let wallet = Wallet::new(wif, wallet::WalletType::NativeSegwit, &secp);
152 let txid = create_to_spend(message, &wallet);
153 let to_sign = create_to_sign_empty(txid, &wallet);
154 get_base64_signature(to_sign, &wallet, &secp)
155}
156pub fn simple_signature_with_wif_taproot(message: &str, wif: &str) -> String {
157 let secp = Secp256k1::new();
158 let wallet = Wallet::new(wif, wallet::WalletType::Taproot, &secp);
159 let txid = create_to_spend(message, &wallet);
160 let mut to_sign = create_to_sign_empty(txid, &wallet);
161 get_base64_signature_taproot(&mut to_sign, &wallet, &secp)
162}
163fn sign_psbt_taproot<C: Signing + Verification>(
164 secret_key: &secp256k1::SecretKey,
165 leaf_hash: Option<TapLeafHash>,
166 psbt_input: &mut psbt::Input,
167 hash: TapSighash,
168 secp: &Secp256k1<C>,
169) -> String {
170 let keypair = secp256k1::KeyPair::from_seckey_slice(secp, secret_key.as_ref()).unwrap();
171 let keypair = match leaf_hash {
172 None => keypair
173 .tap_tweak(secp, psbt_input.tap_merkle_root)
174 .to_inner(),
175 Some(_) => keypair, };
177 let sig = secp.sign_schnorr_no_aux_rand(
178 &Message::from_slice(hash.as_byte_array()).unwrap(),
179 &keypair,
180 );
181 let witness = vec![sig.as_ref().to_vec()];
182
183 let result: Vec<u8> = witness_to_vec(witness);
184 general_purpose::STANDARD.encode(result)
185}
186#[cfg(feature = "ffi")]
187mod ffi {
188
189 use std::ffi::{CStr, CString};
190
191 use libc::c_char;
192
193 use crate::{simple_signature_with_wif_segwit, simple_signature_with_wif_taproot};
194
195 #[no_mangle]
196 pub extern "C" fn signature_with_wif_segwit(
197 message: *const c_char,
198 wif: *const c_char,
199 ) -> *const c_char {
200 let message_c_str = unsafe { CStr::from_ptr(message) };
201 let wif_c_str = unsafe { CStr::from_ptr(wif) };
202
203 let ret_val = simple_signature_with_wif_segwit(
204 message_c_str.to_str().unwrap(),
205 wif_c_str.to_str().unwrap(),
206 );
207 let ret_val_c_string = CString::new(ret_val).unwrap();
208 ret_val_c_string.into_raw()
209 }
210
211 #[no_mangle]
212 pub extern "C" fn signature_with_wif_taproot(
213 message: *const c_char,
214 wif: *const c_char,
215 ) -> *const c_char {
216 let message_c_str = unsafe { CStr::from_ptr(message) };
217 let wif_c_str = unsafe { CStr::from_ptr(wif) };
218
219 let ret_val = simple_signature_with_wif_taproot(
220 message_c_str.to_str().unwrap(),
221 wif_c_str.to_str().unwrap(),
222 );
223 let ret_val_c_string = CString::new(ret_val).unwrap();
224 ret_val_c_string.into_raw()
225 }
226}
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_create_to_spend() {
233 let secp = Secp256k1::new();
234 let wallet = Wallet::new(
235 "L3gn3CheHVnEJHApMjb6BuKdc45LzqChEebLMQaMh3V7cMh6qsaM",
236 wallet::WalletType::NativeSegwit,
237 &secp,
238 );
239 assert_eq!(
240 create_to_spend("test", &wallet).to_string(),
241 "b5cb848389f3ee72a8984560f6ae19f4a8dec1cd2d7e799d62f3e38ff121271c"
242 );
243 }
244
245 #[test]
246 fn test_base64_encoded_signature() {
247 let secp = Secp256k1::new();
248 let wallet = Wallet::new(
249 "L3gn3CheHVnEJHApMjb6BuKdc45LzqChEebLMQaMh3V7cMh6qsaM",
250 wallet::WalletType::NativeSegwit,
251 &secp,
252 );
253 let txid = create_to_spend("test", &wallet);
254 let to_sign = create_to_sign_empty(txid, &wallet);
255 let signature = get_base64_signature(to_sign, &wallet, &secp);
256 assert_eq!(signature, "AkcwRAIgcS8lDfTl7UAytHbZI9BT74uTYqIuQHHUxlFOGGmT5Q8CIAclpi1G295lXeeRfDXdUWfdlkWdhv0S8XFP8rNFfvnDASEDviPnXh+H71VQrKuWCm2FYhSGV9TPO4XJTPhu3fwhhPM=")
257 }
258 #[test]
259 fn test_taproot_signature() {
260 let secp = Secp256k1::new();
261 let wallet = Wallet::new(
262 "L4F5BYm82Bck6VEY64EbqQkoBXqkegq9X9yc6iLTV3cyJoqUasnY",
263 wallet::WalletType::Taproot,
264 &secp,
265 );
266 let txid = create_to_spend(
267 "Sign this message to log in to https://www.subber.xyz // 200323342",
268 &wallet,
269 );
270 let mut to_sign = create_to_sign_empty(txid, &wallet);
271 let signature = get_base64_signature_taproot(&mut to_sign, &wallet, &secp);
272 assert_eq!(signature, "AUBxfbxG6dgW18nia1pfYVPB/OtzRImvqu5O2AvHwRmjmvRN5/bWbDDlMMfGlqJdRbqwUsxVAS/FfvbLJDE7MQFL")
273 }
274 #[test]
275 fn test_simple_sig_segwit() {
276 assert_eq!(simple_signature_with_wif_segwit("test", "L3gn3CheHVnEJHApMjb6BuKdc45LzqChEebLMQaMh3V7cMh6qsaM"), "AkcwRAIgcS8lDfTl7UAytHbZI9BT74uTYqIuQHHUxlFOGGmT5Q8CIAclpi1G295lXeeRfDXdUWfdlkWdhv0S8XFP8rNFfvnDASEDviPnXh+H71VQrKuWCm2FYhSGV9TPO4XJTPhu3fwhhPM=")
277 }
278 #[test]
279 fn test_simple_sig_taproot() {
280 assert_eq!(simple_signature_with_wif_taproot("Sign this message to log in to https://www.subber.xyz // 200323342", "L4F5BYm82Bck6VEY64EbqQkoBXqkegq9X9yc6iLTV3cyJoqUasnY"), "AUBxfbxG6dgW18nia1pfYVPB/OtzRImvqu5O2AvHwRmjmvRN5/bWbDDlMMfGlqJdRbqwUsxVAS/FfvbLJDE7MQFL")
281 }
282}