bip322_simple/
lib.rs

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    //Create script sig
28    let mut script_sig = Vec::new();
29    script_sig.extend(hex::decode("0020").unwrap());
30    script_sig.extend(result);
31    //Tx ins
32    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    //Tx outs
43    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    //Tx ins
58    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    //Tx outs
66    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
148//All in one functions
149pub 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, // no tweak for script spend
176    };
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}