Skip to content

Commit 9934172

Browse files
author
Diane Huxley
authored
Full Bip322 signatures (#5)
1 parent d1313aa commit 9934172

File tree

3 files changed

+305
-179
lines changed

3 files changed

+305
-179
lines changed

src/lib.rs

+78-179
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
use {
2-
base64::{engine::general_purpose, Engine},
32
bitcoin::{
4-
absolute::LockTime,
5-
blockdata::script,
6-
consensus::{Decodable, Encodable},
7-
key::{Keypair, TapTweak},
8-
opcodes,
9-
psbt::Psbt,
10-
script::PushBytes,
11-
secp256k1::{self, schnorr::Signature, Message, Secp256k1, XOnlyPublicKey},
12-
sighash::{self, SighashCache, TapSighashType},
13-
transaction::Version,
14-
Address, Amount, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction,
15-
TxIn, TxOut, Witness,
3+
absolute::LockTime, blockdata::script, opcodes, psbt::Psbt, script::PushBytes,
4+
secp256k1::Secp256k1, transaction::Version, Address, Amount, Network, OutPoint, PrivateKey,
5+
PublicKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness,
166
},
177
bitcoin_hashes::{sha256, Hash},
18-
std::{io::Cursor, str},
8+
};
9+
10+
mod sign;
11+
mod verify;
12+
13+
pub use {
14+
sign::{full_sign, simple_sign},
15+
verify::{full_verify, simple_verify},
1916
};
2017

2118
pub struct Wallet {
@@ -57,7 +54,7 @@ fn message_hash(message: &str) -> Vec<u8> {
5754
.to_vec()
5855
}
5956

60-
fn to_spend(address: &Address, message: &str) -> Transaction {
57+
fn create_to_spend(address: &Address, message: &str) -> Transaction {
6158
Transaction {
6259
version: Version(0),
6360
lock_time: LockTime::ZERO,
@@ -82,153 +79,36 @@ fn to_spend(address: &Address, message: &str) -> Transaction {
8279
}
8380
}
8481

85-
fn to_sign(to_spend_tx: Transaction) -> Transaction {
86-
Transaction {
82+
fn create_to_sign(to_spend: &Transaction) -> Psbt {
83+
let inputs = vec![TxIn {
84+
previous_output: OutPoint {
85+
txid: to_spend.txid(),
86+
vout: 0,
87+
},
88+
script_sig: ScriptBuf::new(),
89+
sequence: Sequence(0),
90+
witness: Witness::new(),
91+
}];
92+
93+
let to_sign = Transaction {
8794
version: Version(0),
8895
lock_time: LockTime::ZERO,
89-
input: vec![TxIn {
90-
previous_output: OutPoint {
91-
txid: to_spend_tx.txid(),
92-
vout: 0,
93-
},
94-
script_sig: ScriptBuf::new(),
95-
sequence: Sequence(0),
96-
witness: Witness::new(),
97-
}],
96+
input: inputs,
9897
output: vec![TxOut {
9998
value: Amount::from_sat(0),
10099
script_pubkey: script::Builder::new()
101100
.push_opcode(opcodes::all::OP_RETURN)
102101
.into_script(),
103102
}],
104-
}
105-
}
103+
};
106104

107-
// #[allow(unused)]
108-
fn to_sign_psbt(
109-
to_spend_tx: Transaction,
110-
to_sign_tx: Transaction,
111-
) -> Result<Psbt, bitcoin::psbt::Error> {
112-
let mut psbt = Psbt::from_unsigned_tx(to_sign_tx)?;
105+
let mut psbt = Psbt::from_unsigned_tx(to_sign).unwrap();
113106
psbt.inputs[0].witness_utxo = Some(TxOut {
114107
value: Amount::from_sat(0),
115-
script_pubkey: to_spend_tx.output[0].script_pubkey.clone(),
108+
script_pubkey: to_spend.output[0].script_pubkey.clone(),
116109
});
117110

118-
Ok(psbt)
119-
}
120-
121-
pub fn sign(address: &Address, message: &str, wallet: &Wallet) -> String {
122-
let to_spend_tx = to_spend(address, message);
123-
let to_sign_tx = to_sign(to_spend_tx.clone());
124-
let mut psbt = to_sign_psbt(to_spend_tx.clone(), to_sign_tx).unwrap();
125-
126-
let secp = Secp256k1::new();
127-
let private_key = wallet.private_key;
128-
let key_pair = Keypair::from_secret_key(&secp, &private_key.inner);
129-
let (x_only_public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair);
130-
131-
psbt.inputs[0].tap_internal_key = Some(x_only_public_key);
132-
133-
let sighash_type = TapSighashType::All;
134-
135-
let mut sighash_cache = SighashCache::new(psbt.unsigned_tx.clone());
136-
137-
let sighash = sighash_cache
138-
.taproot_key_spend_signature_hash(
139-
0,
140-
&sighash::Prevouts::All(&[TxOut {
141-
value: Amount::from_sat(0),
142-
script_pubkey: to_spend_tx.output[0].clone().script_pubkey,
143-
}]),
144-
sighash_type,
145-
)
146-
.expect("signature hash should compute");
147-
148-
let key_pair = key_pair
149-
.tap_tweak(&secp, psbt.inputs[0].tap_merkle_root)
150-
.to_inner();
151-
152-
let sig = secp.sign_schnorr_no_aux_rand(
153-
&secp256k1::Message::from_digest_slice(sighash.as_ref())
154-
.expect("should be cryptographically secure hash"),
155-
&key_pair,
156-
);
157-
158-
let witness = sighash_cache
159-
.witness_mut(0)
160-
.expect("getting mutable witness reference should work");
161-
162-
witness.push(
163-
bitcoin::taproot::Signature {
164-
sig,
165-
hash_ty: sighash_type,
166-
}
167-
.to_vec(),
168-
);
169-
170-
let mut buffer = Vec::new();
171-
witness.consensus_encode(&mut buffer).unwrap();
172-
173-
general_purpose::STANDARD.encode(buffer)
174-
}
175-
176-
pub fn verify(address: &Address, message: &str, signature: &str) -> bool {
177-
let to_spend_tx = to_spend(address, message);
178-
let to_sign_tx = to_sign(to_spend_tx.clone());
179-
180-
let mut cursor = Cursor::new(general_purpose::STANDARD.decode(signature).unwrap());
181-
182-
let witness = match Witness::consensus_decode_from_finite_reader(&mut cursor) {
183-
Ok(witness) => witness,
184-
Err(_) => return false,
185-
};
186-
187-
let encoded_signature = &witness.to_vec()[0];
188-
189-
let (signature, sighash_type) = if encoded_signature.len() == 65 {
190-
(
191-
Signature::from_slice(&encoded_signature.as_slice()[..64]).unwrap(),
192-
TapSighashType::from_consensus_u8(encoded_signature[64]).unwrap(),
193-
)
194-
} else if encoded_signature.len() == 64 {
195-
(
196-
Signature::from_slice(encoded_signature.as_slice()).unwrap(),
197-
TapSighashType::Default,
198-
)
199-
} else {
200-
return false;
201-
};
202-
203-
let pub_key =
204-
if let bitcoin::address::Payload::WitnessProgram(witness_program) = address.payload() {
205-
if witness_program.version().to_num() == 1 && witness_program.program().len() == 32 {
206-
XOnlyPublicKey::from_slice(witness_program.program().as_bytes()).unwrap()
207-
} else {
208-
return false;
209-
}
210-
} else {
211-
return false;
212-
};
213-
214-
let mut sighash_cache = SighashCache::new(to_sign_tx);
215-
216-
let sighash = sighash_cache
217-
.taproot_key_spend_signature_hash(
218-
0,
219-
&sighash::Prevouts::All(&[TxOut {
220-
value: Amount::from_sat(0),
221-
script_pubkey: to_spend_tx.output[0].clone().script_pubkey,
222-
}]),
223-
sighash_type,
224-
)
225-
.expect("signature hash should compute");
226-
227-
let message = Message::from_digest_slice(sighash.as_ref()).unwrap();
228-
229-
Secp256k1::verification_only()
230-
.verify_schnorr(&signature, &message, &pub_key)
231-
.is_ok()
111+
psbt
232112
}
233113

234114
#[cfg(test)]
@@ -259,7 +139,7 @@ mod tests {
259139
#[test]
260140
fn to_spend_txids_correct() {
261141
assert_eq!(
262-
to_spend(
142+
create_to_spend(
263143
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
264144
""
265145
)
@@ -269,7 +149,7 @@ mod tests {
269149
);
270150

271151
assert_eq!(
272-
to_spend(
152+
create_to_spend(
273153
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
274154
"Hello World"
275155
)
@@ -281,47 +161,51 @@ mod tests {
281161

282162
#[test]
283163
fn to_sign_txids_correct() {
164+
let to_spend = create_to_spend(
165+
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
166+
"",
167+
);
168+
let to_sign = create_to_sign(&to_spend);
284169
assert_eq!(
285-
to_sign(to_spend(
286-
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
287-
""
288-
))
289-
.txid()
290-
.to_string(),
170+
to_sign.unsigned_tx.txid().to_string(),
291171
"1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6"
292172
);
293173

174+
let to_spend = create_to_spend(
175+
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
176+
"Hello World",
177+
);
178+
let to_sign = create_to_sign(&to_spend);
294179
assert_eq!(
295-
to_sign(to_spend(
296-
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
297-
"Hello World"
298-
))
299-
.txid()
300-
.to_string(),
180+
to_sign.unsigned_tx.txid().to_string(),
301181
"88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf"
302182
);
303183
}
304184

305185
#[test]
306-
fn verify_and_falsify_taproot() {
307-
assert!(verify(
308-
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
309-
"Hello World",
310-
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
311-
),);
186+
fn simple_verify_and_falsify_taproot() {
187+
assert!(
188+
simple_verify(
189+
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
190+
"Hello World",
191+
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
192+
)
193+
);
312194

313-
assert!(!verify(
314-
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
315-
"Hello World -- This should fail",
316-
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
317-
),);
195+
assert!(
196+
!simple_verify(
197+
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
198+
"Hello World -- This should fail",
199+
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
200+
)
201+
);
318202
}
319203

320204
#[test]
321-
fn sign_taproot() {
205+
fn simple_sign_taproot() {
322206
let wallet = Wallet::new(WIF_PRIVATE_KEY);
323207

324-
let signature = sign(
208+
let signature = simple_sign(
325209
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
326210
"Hello World",
327211
&wallet,
@@ -334,13 +218,28 @@ mod tests {
334218
}
335219

336220
#[test]
337-
fn roundtrip_taproot() {
221+
fn roundtrip_taproot_simple() {
222+
let wallet = Wallet::new(WIF_PRIVATE_KEY);
223+
224+
assert!(simple_verify(
225+
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
226+
"Hello World",
227+
&simple_sign(
228+
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
229+
"Hello World",
230+
&wallet
231+
)
232+
));
233+
}
234+
235+
#[test]
236+
fn roundtrip_taproot_full() {
338237
let wallet = Wallet::new(WIF_PRIVATE_KEY);
339238

340-
assert!(verify(
239+
assert!(full_verify(
341240
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
342241
"Hello World",
343-
&sign(
242+
&full_sign(
344243
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
345244
"Hello World",
346245
&wallet

0 commit comments

Comments
 (0)