Skip to content

Commit

Permalink
Full Bip322 signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
diehuxx committed Jul 24, 2024
1 parent 0cd876d commit 9410397
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 161 deletions.
209 changes: 48 additions & 161 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
use {
base64::{engine::general_purpose, Engine},
bitcoin::{
absolute::LockTime,
blockdata::script,
consensus::{Decodable, Encodable},
key::{Keypair, TapTweak},
opcodes,
psbt::Psbt,
script::PushBytes,
secp256k1::{self, schnorr::Signature, Message, Secp256k1, XOnlyPublicKey},
sighash::{self, SighashCache, TapSighashType},
transaction::Version,
Address, Amount, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence,
Transaction, TxIn, TxOut, Witness,
absolute::LockTime, blockdata::script, opcodes, script::PushBytes, secp256k1::Secp256k1,
transaction::Version, Address, Amount, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf,
Sequence, Transaction, TxIn, TxOut, Witness,
},
bitcoin_hashes::{sha256, Hash},
std::{io::Cursor, str},
};

mod sign;
pub use sign::{full_sign, simple_sign};

mod verifier;
pub use verifier::{full_verify, simple_verify};

pub struct Wallet {
pub btc_address: Address,
pub descriptor: miniscript::Descriptor<PublicKey>,
Expand Down Expand Up @@ -82,7 +77,7 @@ fn to_spend(address: &Address, message: &str) -> Transaction {
}
}

fn to_sign(to_spend_tx: Transaction) -> Transaction {
fn blank_to_sign(to_spend_tx: Transaction) -> Transaction {
Transaction {
version: Version(0),
lock_time: LockTime::ZERO,
Expand All @@ -104,133 +99,6 @@ fn to_sign(to_spend_tx: Transaction) -> Transaction {
}
}

// #[allow(unused)]
fn to_sign_psbt(
to_spend_tx: Transaction,
to_sign_tx: Transaction,
) -> Result<Psbt, bitcoin::psbt::Error> {
let mut psbt = Psbt::from_unsigned_tx(to_sign_tx)?;
psbt.inputs[0].witness_utxo = Some(TxOut {
value: Amount::from_sat(0),
script_pubkey: to_spend_tx.output[0].script_pubkey.clone(),
});

Ok(psbt)
}

pub fn sign(address: &Address, message: &str, wallet: &Wallet) -> String {
let to_spend_tx = to_spend(address, message);
let to_sign_tx = to_sign(to_spend_tx.clone());
let mut psbt = to_sign_psbt(to_spend_tx.clone(), to_sign_tx).unwrap();

let secp = Secp256k1::new();
let private_key = wallet.private_key;
let key_pair = Keypair::from_secret_key(&secp, &private_key.inner);
let (x_only_public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair);

psbt.inputs[0].tap_internal_key = Some(x_only_public_key);

let sighash_type = TapSighashType::All;

let mut sighash_cache = SighashCache::new(psbt.unsigned_tx.clone());

let sighash = sighash_cache
.taproot_key_spend_signature_hash(
0,
&sighash::Prevouts::All(&[TxOut {
value: Amount::from_sat(0),
script_pubkey: to_spend_tx.output[0].clone().script_pubkey,
}]),
sighash_type,
)
.expect("signature hash should compute");

let key_pair = key_pair
.tap_tweak(&secp, psbt.inputs[0].tap_merkle_root)
.to_inner();

let sig = secp.sign_schnorr_no_aux_rand(
&secp256k1::Message::from_digest_slice(sighash.as_ref())
.expect("should be cryptographically secure hash"),
&key_pair,
);

let witness = sighash_cache
.witness_mut(0)
.expect("getting mutable witness reference should work");

witness.push(
bitcoin::taproot::Signature {
sig,
hash_ty: sighash_type,
}
.to_vec(),
);

let mut buffer = Vec::new();
witness.consensus_encode(&mut buffer).unwrap();

general_purpose::STANDARD.encode(buffer)
}

pub fn verify(address: &Address, message: &str, signature: &str) -> bool {
let to_spend_tx = to_spend(address, message);
let to_sign_tx = to_sign(to_spend_tx.clone());

let mut cursor = Cursor::new(general_purpose::STANDARD.decode(signature).unwrap());

let witness = match Witness::consensus_decode_from_finite_reader(&mut cursor) {
Ok(witness) => witness,
Err(_) => return false,
};

let encoded_signature = &witness.to_vec()[0];

let (signature, sighash_type) = if encoded_signature.len() == 65 {
(
Signature::from_slice(&encoded_signature.as_slice()[..64]).unwrap(),
TapSighashType::from_consensus_u8(encoded_signature[64]).unwrap(),
)
} else if encoded_signature.len() == 64 {
(
Signature::from_slice(encoded_signature.as_slice()).unwrap(),
TapSighashType::Default,
)
} else {
return false;
};

let pub_key =
if let bitcoin::address::Payload::WitnessProgram(witness_program) = address.payload() {
if witness_program.version().to_num() == 1 && witness_program.program().len() == 32 {
XOnlyPublicKey::from_slice(witness_program.program().as_bytes()).unwrap()
} else {
return false;
}
} else {
return false;
};

let mut sighash_cache = SighashCache::new(to_sign_tx);

let sighash = sighash_cache
.taproot_key_spend_signature_hash(
0,
&sighash::Prevouts::All(&[TxOut {
value: Amount::from_sat(0),
script_pubkey: to_spend_tx.output[0].clone().script_pubkey,
}]),
sighash_type,
)
.expect("signature hash should compute");

let message = Message::from_digest_slice(sighash.as_ref()).unwrap();

Secp256k1::verification_only()
.verify_schnorr(&signature, &message, &pub_key)
.is_ok()
}

#[cfg(test)]
mod tests {
use {super::*, std::str::FromStr};
Expand Down Expand Up @@ -282,7 +150,7 @@ mod tests {
#[test]
fn to_sign_txids_correct() {
assert_eq!(
to_sign(to_spend(
blank_to_sign(to_spend(
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
""
))
Expand All @@ -292,7 +160,7 @@ mod tests {
);

assert_eq!(
to_sign(to_spend(
blank_to_sign(to_spend(
&Address::from_str(SEGWIT_ADDRESS).unwrap().assume_checked(),
"Hello World"
))
Expand All @@ -304,43 +172,62 @@ mod tests {

#[test]
fn verify_and_falsify_taproot() {
assert!(verify(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World",
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
),);
assert!(
simple_verify(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World",
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
)
);

assert!(!verify(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World -- This should fail",
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
),);
assert!(
!simple_verify(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World -- This should fail",
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
)
);
}

#[test]
fn sign_taproot() {
let wallet = Wallet::new(WIF_PRIVATE_KEY);

let signature = sign(
let signature = simple_sign(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World",
&wallet,
);

assert_eq!(
signature,
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
);
signature,
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="
);
}

#[test]
fn roundtrip_taproot_simple() {
let wallet = Wallet::new(WIF_PRIVATE_KEY);

assert!(simple_verify(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World",
&simple_sign(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World",
&wallet
)
));
}

#[test]
fn roundtrip_taproot() {
fn roundtrip_taproot_full() {
let wallet = Wallet::new(WIF_PRIVATE_KEY);

assert!(verify(
assert!(full_verify(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World",
&sign(
&full_sign(
&Address::from_str(TAPROOT_ADDRESS).unwrap().assume_checked(),
"Hello World",
&wallet
Expand Down
100 changes: 100 additions & 0 deletions src/sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use {
crate::{blank_to_sign, to_spend, Wallet},
base64::{engine::general_purpose, Engine},
bitcoin::{
consensus::Encodable,
key::{Keypair, TapTweak},
psbt::Psbt,
secp256k1::{self, Secp256k1, XOnlyPublicKey},
sighash::{self, SighashCache, TapSighashType},
Address, Amount, PrivateKey, Transaction, TxOut, Witness,
},
};

fn to_sign_psbt(
to_spend_tx: Transaction,
to_sign_tx: Transaction,
) -> Result<Psbt, bitcoin::psbt::Error> {
let mut psbt = Psbt::from_unsigned_tx(to_sign_tx)?;
psbt.inputs[0].witness_utxo = Some(TxOut {
value: Amount::from_sat(0),
script_pubkey: to_spend_tx.output[0].script_pubkey.clone(),
});

Ok(psbt)
}

fn sig_witness(to_spend_tx: Transaction, to_sign: Psbt, private_key: PrivateKey) -> Witness {
let mut to_sign = to_sign.clone();

let secp = Secp256k1::new();
let private_key = private_key;
let key_pair = Keypair::from_secret_key(&secp, &private_key.inner);
let (x_only_public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair);
to_sign.inputs[0].tap_internal_key = Some(x_only_public_key);

let sighash_type = TapSighashType::All;

let mut sighash_cache = SighashCache::new(to_sign.unsigned_tx.clone());

let sighash = sighash_cache
.taproot_key_spend_signature_hash(
0,
&sighash::Prevouts::All(&[TxOut {
value: Amount::from_sat(0),
script_pubkey: to_spend_tx.output[0].clone().script_pubkey,
}]),
sighash_type,
)
.expect("signature hash should compute");

let key_pair = key_pair
.tap_tweak(&secp, to_sign.inputs[0].tap_merkle_root)
.to_inner();

let sig = secp.sign_schnorr_no_aux_rand(
&secp256k1::Message::from_digest_slice(sighash.as_ref())
.expect("should be cryptographically secure hash"),
&key_pair,
);

let witness = sighash_cache
.witness_mut(0)
.expect("getting mutable witness reference should work");

witness.push(
bitcoin::taproot::Signature {
sig,
hash_ty: sighash_type,
}
.to_vec(),
);

witness.to_owned()
}

pub fn simple_sign(address: &Address, message: &str, wallet: &Wallet) -> String {
let to_spend_tx = to_spend(address, message);
let to_sign_tx = blank_to_sign(to_spend_tx.clone());
let psbt = to_sign_psbt(to_spend_tx.clone(), to_sign_tx).unwrap();

let witness = sig_witness(to_spend_tx, psbt, wallet.private_key);

let mut buffer = Vec::new();
witness.consensus_encode(&mut buffer).unwrap();

general_purpose::STANDARD.encode(buffer)
}

pub fn full_sign(address: &Address, message: &str, wallet: &Wallet) -> String {
let to_spend_tx = to_spend(address, message);
let mut to_sign_tx = blank_to_sign(to_spend_tx.clone());
let psbt = to_sign_psbt(to_spend_tx.clone(), to_sign_tx.clone()).unwrap();

to_sign_tx.input[0].witness = sig_witness(to_spend_tx, psbt, wallet.private_key);

let mut buffer = Vec::new();
to_sign_tx.consensus_encode(&mut buffer).unwrap();

general_purpose::STANDARD.encode(buffer)
}
Loading

0 comments on commit 9410397

Please # to comment.