use bitcoin::{CompressedPublicKey, Network};
use super::*;
pub fn verify_simple_encoded(address: &str, message: &str, signature: &str) -> Result<()> {
let address = Address::from_str(address).map_err(|_| error::AddressParse { address }.build())?;
let mut cursor = bitcoin::io::Cursor::new(
general_purpose::STANDARD
.decode(signature)
.context(error::SignatureDecode { signature })?,
);
let witness = Witness::consensus_decode_from_finite_reader(&mut cursor)
.map_err(|_| error::WitnessMalformed)
.unwrap();
verify_simple(&address.assume_checked(), message.as_bytes(), witness)
}
pub fn verify_full_encoded(address: &str, message: &str, to_sign: &str) -> Result<()> {
let address = Address::from_str(address).map_err(|_| error::AddressParse { address }.build())?;
let mut cursor = bitcoin::io::Cursor::new(
general_purpose::STANDARD
.decode(to_sign)
.map_err(|_| error::TransactionBase64Decode {
transaction: to_sign,
})
.unwrap(),
);
let to_sign = Transaction::consensus_decode_from_finite_reader(&mut cursor)
.map_err(|_| error::TransactionConsensusDecode {
transaction: to_sign,
})
.unwrap();
verify_full(&address.assume_checked(), message.as_bytes(), to_sign)
}
pub fn verify_simple(address: &Address, message: &[u8], signature: Witness) -> Result<()> {
verify_full(
address,
message,
create_to_sign(&create_to_spend(address, message)?, Some(signature))?
.extract_tx()
.map_err(|_| error::TransactionExtract)
.unwrap(),
)
}
pub fn verify_full_witness(
pubkey: &PublicKey,
address: &Address,
message: &[u8],
witness: Witness,
) -> Result<()> {
let to_spend = create_to_spend(address, message)?;
let to_sign = create_to_sign(&to_spend, Some(witness))?;
verify_address_pubkey(address, pubkey).and_then(|_| {
verify_full(
address,
message,
to_sign
.extract_tx()
.map_err(|_| error::TransactionExtract)
.unwrap(),
)
})
}
fn verify_address_pubkey(address: &Address, pubkey: &PublicKey) -> Result<()> {
match address.to_address_data() {
AddressData::Segwit { witness_program }
if witness_program.version().to_num() == 1 && witness_program.program().len() == 32 =>
{
let network = if address.to_string().starts_with("bc1") {
Network::Bitcoin } else {
Network::Testnet
};
verify_p2tr_address(address, pubkey, network)
}
AddressData::Segwit { witness_program }
if witness_program.version().to_num() == 0 && witness_program.program().len() == 20 =>
{
let network = if address.to_string().starts_with("bc1") {
Network::Bitcoin } else {
Network::Testnet
};
verify_p2wpkh_address(address, pubkey, network)
}
_ => panic!("Unsupported address"),
}
}
fn verify_p2tr_address(address: &Address, pubkey: &PublicKey, network: Network) -> Result<()> {
let internal_key = XOnlyPublicKey::from_slice(&pubkey.to_bytes()[1..]).unwrap();
let secp = Secp256k1::verification_only();
let generated_address = Address::p2tr(&secp, internal_key, None, network);
if generated_address.to_string() != address.to_string() {
return Err(Error::PublicKeyMismatch);
}
Ok(())
}
fn verify_p2wpkh_address(address: &Address, pubkey: &PublicKey, network: Network) -> Result<()> {
let generated_address =
Address::p2wpkh(&CompressedPublicKey::try_from(*pubkey).unwrap(), network);
if generated_address.to_string() != address.to_string() {
return Err(Error::PublicKeyMismatch);
}
Ok(())
}
fn verify_full(address: &Address, message: &[u8], to_sign: Transaction) -> Result<()> {
match address.to_address_data() {
AddressData::Segwit { witness_program }
if witness_program.version().to_num() == 1 && witness_program.program().len() == 32 =>
{
let pub_key = XOnlyPublicKey::from_slice(witness_program.program().as_bytes())
.map_err(|_| Error::InvalidPublicKey)?;
verify_full_p2tr(address, message, to_sign, pub_key)
}
AddressData::Segwit { witness_program }
if witness_program.version().to_num() == 0 && witness_program.program().len() == 20 =>
{
let pub_key =
PublicKey::from_slice(&to_sign.input[0].witness[1]).map_err(|_| Error::InvalidPublicKey)?;
verify_full_p2wpkh(address, message, to_sign, pub_key, false)
}
AddressData::P2sh { script_hash: _ } => {
let pub_key =
PublicKey::from_slice(&to_sign.input[0].witness[1]).map_err(|_| Error::InvalidPublicKey)?;
verify_full_p2wpkh(address, message, to_sign, pub_key, true)
}
_ => Err(Error::UnsupportedAddress {
address: address.to_string(),
}),
}
}
fn verify_full_p2wpkh(
address: &Address,
message: &[u8],
to_sign: Transaction,
pub_key: PublicKey,
is_p2sh: bool,
) -> Result<()> {
let to_spend = create_to_spend(address, message)?;
let to_sign = create_to_sign(&to_spend, Some(to_sign.input[0].witness.clone()))?;
let to_spend_outpoint = OutPoint {
txid: to_spend.compute_txid(),
vout: 0,
};
if to_spend_outpoint != to_sign.unsigned_tx.input[0].previous_output {
return Err(Error::ToSignInvalid);
}
let witness = if let Some(witness) = to_sign.inputs[0].final_script_witness.clone() {
witness
} else {
return Err(Error::WitnessEmpty);
};
if witness.len() != 2 {
return Err(Error::InvalidWitness);
}
let encoded_signature = witness.to_vec()[0].clone();
let witness_pub_key = &witness.to_vec()[1];
if &pub_key.to_bytes() != witness_pub_key {
return Err(Error::PublicKeyMismatch);
}
let signature_length = encoded_signature.len();
let (signature, sighash_type) = match signature_length {
71 | 72 => (
bitcoin::secp256k1::ecdsa::Signature::from_der(
&encoded_signature.as_slice()[..signature_length - 1],
)
.map_err(|_| error::SignatureInvalid)
.unwrap(),
EcdsaSighashType::from_consensus(encoded_signature[signature_length - 1] as u32),
),
_ => {
return Err(Error::SignatureLength {
length: encoded_signature.len(),
encoded_signature,
})
}
};
if !(sighash_type == EcdsaSighashType::All) {
return Err(Error::SigHashTypeUnsupported {
sighash_type: sighash_type.to_string(),
});
}
let mut sighash_cache = SighashCache::new(to_sign.unsigned_tx);
let sighash = sighash_cache
.p2wpkh_signature_hash(
0,
&if is_p2sh {
ScriptBuf::new_p2wpkh(&pub_key.wpubkey_hash().unwrap())
} else {
to_spend.output[0].script_pubkey.clone()
},
to_spend.output[0].value,
sighash_type,
)
.expect("signature hash should compute");
let message =
Message::from_digest_slice(sighash.as_ref()).expect("should be cryptographically secure hash");
Secp256k1::verification_only()
.verify_ecdsa(&message, &signature, &pub_key.inner)
.map_err(|_| error::SignatureInvalid.build())
}
fn verify_full_p2tr(
address: &Address,
message: &[u8],
to_sign: Transaction,
pub_key: XOnlyPublicKey,
) -> Result<()> {
let to_spend = create_to_spend(address, message)?;
let to_sign = create_to_sign(&to_spend, Some(to_sign.input[0].witness.clone()))?;
let to_spend_outpoint = OutPoint {
txid: to_spend.compute_txid(),
vout: 0,
};
if to_spend_outpoint != to_sign.unsigned_tx.input[0].previous_output {
return Err(Error::ToSignInvalid);
}
let witness = if let Some(witness) = to_sign.inputs[0].final_script_witness.clone() {
witness
} else {
return Err(Error::WitnessEmpty);
};
let encoded_signature = witness.to_vec()[0].clone();
let (signature, sighash_type) = match encoded_signature.len() {
65 => (
Signature::from_slice(&encoded_signature.as_slice()[..64])
.map_err(|_| error::SignatureInvalid)
.unwrap(),
TapSighashType::from_consensus_u8(encoded_signature[64])
.map_err(|_| error::SigHashTypeInvalid)
.unwrap(),
),
64 => (
Signature::from_slice(encoded_signature.as_slice())
.map_err(|_| error::SignatureInvalid)
.unwrap(),
TapSighashType::Default,
),
_ => {
return Err(Error::SignatureLength {
length: encoded_signature.len(),
encoded_signature,
})
}
};
if !(sighash_type == TapSighashType::All || sighash_type == TapSighashType::Default) {
return Err(Error::SigHashTypeUnsupported {
sighash_type: sighash_type.to_string(),
});
}
let mut sighash_cache = SighashCache::new(to_sign.unsigned_tx);
let sighash = sighash_cache
.taproot_key_spend_signature_hash(
0,
&sighash::Prevouts::All(&[TxOut {
value: Amount::from_sat(0),
script_pubkey: to_spend.output[0].clone().script_pubkey,
}]),
sighash_type,
)
.expect("signature hash should compute");
let message =
Message::from_digest_slice(sighash.as_ref()).expect("should be cryptographically secure hash");
Secp256k1::verification_only()
.verify_schnorr(&signature, &message, &pub_key)
.map_err(|_| error::SignatureInvalid.build())
}