Merge rust-bitcoin/rust-bitcoin#2277: Implement `CompressedPublicKey`
a92d49fe33
Implement `CompressedPublicKey` (Martin Habovstiak) Pull request description: P2WPKH requires keys to be compressed which introduces error handling even in cases when it's statically known that a key is compressed. To avoid it, this change introduces `CompressedPublicKey` which is similar to `PublicKey` except it's statically known to be compressed. This also changes relevant code to use `CompressedPublicKey` instead of `PublicKey`. ACKs for top commit: tcharding: ACKa92d49fe33
apoelstra: ACKa92d49fe33
Tree-SHA512: ff5ff8f0cf81035f042dd8fdd52a0801f0488aea56f3cdd840663abaf7ac1d25a0339cd8d1b00f1f92878c5bd55881bc1740424683cde0c28539b546f171ed4b
This commit is contained in:
commit
3d6151b9e1
|
@ -43,8 +43,8 @@ fn main() -> ! {
|
||||||
let secp = Secp256k1::preallocated_new(&mut buf_ful).unwrap();
|
let secp = Secp256k1::preallocated_new(&mut buf_ful).unwrap();
|
||||||
|
|
||||||
// Derive address
|
// Derive address
|
||||||
let pubkey = pk.public_key(&secp);
|
let pubkey = pk.public_key(&secp).try_into().unwrap();
|
||||||
let address = Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap();
|
let address = Address::p2wpkh(&pubkey, Network::Bitcoin);
|
||||||
hprintln!("Address: {}", address).unwrap();
|
hprintln!("Address: {}", address).unwrap();
|
||||||
|
|
||||||
assert_eq!(address.to_string(), "bc1qpx9t9pzzl4qsydmhyt6ctrxxjd4ep549np9993".to_string());
|
assert_eq!(address.to_string(), "bc1qpx9t9pzzl4qsydmhyt6ctrxxjd4ep549np9993".to_string());
|
||||||
|
|
|
@ -8,7 +8,7 @@ use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv, Xpub};
|
||||||
use bitcoin::hex::FromHex;
|
use bitcoin::hex::FromHex;
|
||||||
use bitcoin::secp256k1::ffi::types::AlignedType;
|
use bitcoin::secp256k1::ffi::types::AlignedType;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
use bitcoin::PublicKey;
|
use bitcoin::CompressedPublicKey;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// This example derives root xprv from a 32-byte seed,
|
// This example derives root xprv from a 32-byte seed,
|
||||||
|
@ -53,6 +53,6 @@ fn main() {
|
||||||
// manually creating indexes this time
|
// manually creating indexes this time
|
||||||
let zero = ChildNumber::from_normal_idx(0).unwrap();
|
let zero = ChildNumber::from_normal_idx(0).unwrap();
|
||||||
let public_key = xpub.derive_pub(&secp, &[zero, zero]).unwrap().public_key;
|
let public_key = xpub.derive_pub(&secp, &[zero, zero]).unwrap().public_key;
|
||||||
let address = Address::p2wpkh(&PublicKey::new(public_key), network).unwrap();
|
let address = Address::p2wpkh(&CompressedPublicKey(public_key), network);
|
||||||
println!("First receiving address: {}", address);
|
println!("First receiving address: {}", address);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ use bitcoin::locktime::absolute;
|
||||||
use bitcoin::psbt::{self, Input, Psbt, PsbtSighashType};
|
use bitcoin::psbt::{self, Input, Psbt, PsbtSighashType};
|
||||||
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
|
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
transaction, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, Transaction,
|
transaction, Address, Amount, Network, OutPoint, CompressedPublicKey, ScriptBuf, Sequence, Transaction,
|
||||||
TxIn, TxOut, Witness,
|
TxIn, TxOut, Witness,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ impl WatchOnly {
|
||||||
let mut input = Input { witness_utxo: Some(previous_output()), ..Default::default() };
|
let mut input = Input { witness_utxo: Some(previous_output()), ..Default::default() };
|
||||||
|
|
||||||
let pk = self.input_xpub.to_pub();
|
let pk = self.input_xpub.to_pub();
|
||||||
let wpkh = pk.wpubkey_hash().expect("a compressed pubkey");
|
let wpkh = pk.wpubkey_hash();
|
||||||
|
|
||||||
let redeem_script = ScriptBuf::new_p2wpkh(&wpkh);
|
let redeem_script = ScriptBuf::new_p2wpkh(&wpkh);
|
||||||
input.redeem_script = Some(redeem_script);
|
input.redeem_script = Some(redeem_script);
|
||||||
|
@ -209,7 +209,7 @@ impl WatchOnly {
|
||||||
let fingerprint = self.master_fingerprint;
|
let fingerprint = self.master_fingerprint;
|
||||||
let path = input_derivation_path()?;
|
let path = input_derivation_path()?;
|
||||||
let mut map = BTreeMap::new();
|
let mut map = BTreeMap::new();
|
||||||
map.insert(pk.inner, (fingerprint, path));
|
map.insert(pk.0, (fingerprint, path));
|
||||||
input.bip32_derivation = map;
|
input.bip32_derivation = map;
|
||||||
|
|
||||||
let ty = PsbtSighashType::from_str("SIGHASH_ALL")?;
|
let ty = PsbtSighashType::from_str("SIGHASH_ALL")?;
|
||||||
|
@ -251,12 +251,12 @@ impl WatchOnly {
|
||||||
fn change_address<C: Verification>(
|
fn change_address<C: Verification>(
|
||||||
&self,
|
&self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
) -> Result<(PublicKey, Address, DerivationPath)> {
|
) -> Result<(CompressedPublicKey, Address, DerivationPath)> {
|
||||||
let path = [ChildNumber::from_normal_idx(1)?, ChildNumber::from_normal_idx(0)?];
|
let path = [ChildNumber::from_normal_idx(1)?, ChildNumber::from_normal_idx(0)?];
|
||||||
let derived = self.account_0_xpub.derive_pub(secp, &path)?;
|
let derived = self.account_0_xpub.derive_pub(secp, &path)?;
|
||||||
|
|
||||||
let pk = derived.to_pub();
|
let pk = derived.to_pub();
|
||||||
let addr = Address::p2wpkh(&pk, NETWORK)?;
|
let addr = Address::p2wpkh(&pk, NETWORK);
|
||||||
let path = path.into_derivation_path()?;
|
let path = path.into_derivation_path()?;
|
||||||
|
|
||||||
Ok((pk, addr, path))
|
Ok((pk, addr, path))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
use bitcoin::{consensus, ecdsa, sighash, Amount, PublicKey, Script, ScriptBuf, Transaction};
|
use bitcoin::{consensus, ecdsa, sighash, Amount, CompressedPublicKey, Script, ScriptBuf, Transaction};
|
||||||
use hex_lit::hex;
|
use hex_lit::hex;
|
||||||
|
|
||||||
//These are real blockchain transactions examples of computing sighash for:
|
//These are real blockchain transactions examples of computing sighash for:
|
||||||
|
@ -35,8 +35,8 @@ fn compute_sighash_p2wpkh(raw_tx: &[u8], inp_idx: usize, value: u64) {
|
||||||
|
|
||||||
//BIP-143: "The item 5 : For P2WPKH witness program, the scriptCode is 0x1976a914{20-byte-pubkey-hash}88ac"
|
//BIP-143: "The item 5 : For P2WPKH witness program, the scriptCode is 0x1976a914{20-byte-pubkey-hash}88ac"
|
||||||
//this is nothing but a standard P2PKH script OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG:
|
//this is nothing but a standard P2PKH script OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG:
|
||||||
let pk = PublicKey::from_slice(pk_bytes).expect("failed to parse pubkey");
|
let pk = CompressedPublicKey::from_slice(pk_bytes).expect("failed to parse pubkey");
|
||||||
let wpkh = pk.wpubkey_hash().expect("compressed key");
|
let wpkh = pk.wpubkey_hash();
|
||||||
println!("Script pubkey hash: {:x}", wpkh);
|
println!("Script pubkey hash: {:x}", wpkh);
|
||||||
let spk = ScriptBuf::new_p2wpkh(&wpkh);
|
let spk = ScriptBuf::new_p2wpkh(&wpkh);
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ fn compute_sighash_p2wpkh(raw_tx: &[u8], inp_idx: usize, value: u64) {
|
||||||
let msg = secp256k1::Message::from_digest(sighash.to_byte_array());
|
let msg = secp256k1::Message::from_digest(sighash.to_byte_array());
|
||||||
println!("Message is {:x}", msg);
|
println!("Message is {:x}", msg);
|
||||||
let secp = secp256k1::Secp256k1::verification_only();
|
let secp = secp256k1::Secp256k1::verification_only();
|
||||||
secp.verify_ecdsa(&msg, &sig.sig, &pk.inner).unwrap();
|
pk.verify(&secp, &msg, &sig).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes sighash for a legacy multisig transaction input that spends either a p2sh or a p2ms output.
|
/// Computes sighash for a legacy multisig transaction input that spends either a p2sh or a p2ms output.
|
||||||
|
|
|
@ -45,7 +45,7 @@ use crate::blockdata::constants::{
|
||||||
use crate::blockdata::script::witness_program::WitnessProgram;
|
use crate::blockdata::script::witness_program::WitnessProgram;
|
||||||
use crate::blockdata::script::witness_version::WitnessVersion;
|
use crate::blockdata::script::witness_version::WitnessVersion;
|
||||||
use crate::blockdata::script::{self, PushBytesBuf, Script, ScriptBuf, ScriptHash};
|
use crate::blockdata::script::{self, PushBytesBuf, Script, ScriptBuf, ScriptHash};
|
||||||
use crate::crypto::key::{self, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey};
|
use crate::crypto::key::{PubkeyHash, PublicKey, CompressedPublicKey, TweakedPublicKey, UntweakedPublicKey};
|
||||||
use crate::network::Network;
|
use crate::network::Network;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::taproot::TapNodeHash;
|
use crate::taproot::TapNodeHash;
|
||||||
|
@ -441,23 +441,23 @@ impl Address {
|
||||||
///
|
///
|
||||||
/// This is the native segwit address type for an output redeemable with a single signature.
|
/// This is the native segwit address type for an output redeemable with a single signature.
|
||||||
pub fn p2wpkh(
|
pub fn p2wpkh(
|
||||||
pk: &PublicKey,
|
pk: &CompressedPublicKey,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Address, key::UncompressedPubkeyError> {
|
) -> Self {
|
||||||
let program = WitnessProgram::p2wpkh(pk)?;
|
let program = WitnessProgram::p2wpkh(pk);
|
||||||
Ok(Address::from_witness_program(program, network))
|
Address::from_witness_program(program, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a pay to script address that embeds a witness pay to public key.
|
/// Creates a pay to script address that embeds a witness pay to public key.
|
||||||
///
|
///
|
||||||
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients.
|
||||||
pub fn p2shwpkh(
|
pub fn p2shwpkh(
|
||||||
pk: &PublicKey,
|
pk: &CompressedPublicKey,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Address, key::UncompressedPubkeyError> {
|
) -> Self {
|
||||||
let builder = script::Builder::new().push_int(0).push_slice(pk.wpubkey_hash()?);
|
let builder = script::Builder::new().push_int(0).push_slice(pk.wpubkey_hash());
|
||||||
let script_hash = builder.as_script().script_hash();
|
let script_hash = builder.as_script().script_hash();
|
||||||
Ok(Address::p2sh_from_hash(script_hash, network))
|
Address::p2sh_from_hash(script_hash, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a witness pay to script hash address.
|
/// Creates a witness pay to script hash address.
|
||||||
|
@ -838,7 +838,7 @@ mod tests {
|
||||||
use secp256k1::XOnlyPublicKey;
|
use secp256k1::XOnlyPublicKey;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::crypto::key::{PublicKey, UncompressedPubkeyError};
|
use crate::crypto::key::PublicKey;
|
||||||
use crate::network::Network::{Bitcoin, Testnet};
|
use crate::network::Network::{Bitcoin, Testnet};
|
||||||
|
|
||||||
fn roundtrips(addr: &Address, network: Network) {
|
fn roundtrips(addr: &Address, network: Network) {
|
||||||
|
@ -926,17 +926,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_p2wpkh() {
|
fn test_p2wpkh() {
|
||||||
// stolen from Bitcoin transaction: b3c8c2b6cfc335abbcb2c7823a8453f55d64b2b5125a9a61e8737230cdb8ce20
|
// stolen from Bitcoin transaction: b3c8c2b6cfc335abbcb2c7823a8453f55d64b2b5125a9a61e8737230cdb8ce20
|
||||||
let mut key = "033bc8c83c52df5712229a2f72206d90192366c36428cb0c12b6af98324d97bfbc"
|
let key = "033bc8c83c52df5712229a2f72206d90192366c36428cb0c12b6af98324d97bfbc"
|
||||||
.parse::<PublicKey>()
|
.parse::<CompressedPublicKey>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let addr = Address::p2wpkh(&key, Bitcoin).unwrap();
|
let addr = Address::p2wpkh(&key, Bitcoin);
|
||||||
assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
|
assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
|
||||||
roundtrips(&addr, Bitcoin);
|
roundtrips(&addr, Bitcoin);
|
||||||
|
|
||||||
// Test uncompressed pubkey
|
|
||||||
key.compressed = false;
|
|
||||||
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -955,17 +951,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_p2shwpkh() {
|
fn test_p2shwpkh() {
|
||||||
// stolen from Bitcoin transaction: ad3fd9c6b52e752ba21425435ff3dd361d6ac271531fc1d2144843a9f550ad01
|
// stolen from Bitcoin transaction: ad3fd9c6b52e752ba21425435ff3dd361d6ac271531fc1d2144843a9f550ad01
|
||||||
let mut key = "026c468be64d22761c30cd2f12cbc7de255d592d7904b1bab07236897cc4c2e766"
|
let key = "026c468be64d22761c30cd2f12cbc7de255d592d7904b1bab07236897cc4c2e766"
|
||||||
.parse::<PublicKey>()
|
.parse::<CompressedPublicKey>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let addr = Address::p2shwpkh(&key, Bitcoin).unwrap();
|
let addr = Address::p2shwpkh(&key, Bitcoin);
|
||||||
assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
|
assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
||||||
roundtrips(&addr, Bitcoin);
|
roundtrips(&addr, Bitcoin);
|
||||||
|
|
||||||
// Test uncompressed pubkey
|
|
||||||
key.compressed = false;
|
|
||||||
assert_eq!(Address::p2wpkh(&key, Bitcoin).unwrap_err(), UncompressedPubkeyError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -19,7 +19,7 @@ use secp256k1::{self, Secp256k1, XOnlyPublicKey};
|
||||||
use serde;
|
use serde;
|
||||||
|
|
||||||
use crate::base58;
|
use crate::base58;
|
||||||
use crate::crypto::key::{self, Keypair, PrivateKey, PublicKey};
|
use crate::crypto::key::{self, Keypair, PrivateKey, CompressedPublicKey};
|
||||||
use crate::internal_macros::impl_bytes_newtype;
|
use crate::internal_macros::impl_bytes_newtype;
|
||||||
use crate::network::Network;
|
use crate::network::Network;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -708,7 +708,7 @@ impl Xpub {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs ECDSA compressed public key matching internal public key representation.
|
/// Constructs ECDSA compressed public key matching internal public key representation.
|
||||||
pub fn to_pub(self) -> PublicKey { PublicKey { compressed: true, inner: self.public_key } }
|
pub fn to_pub(self) -> CompressedPublicKey { CompressedPublicKey(self.public_key) }
|
||||||
|
|
||||||
/// Constructs BIP340 x-only public key for BIP-340 signatures and Taproot use matching
|
/// Constructs BIP340 x-only public key for BIP-340 signatures and Taproot use matching
|
||||||
/// the internal public key representation.
|
/// the internal public key representation.
|
||||||
|
|
|
@ -14,7 +14,7 @@ use secp256k1::{Secp256k1, Verification};
|
||||||
|
|
||||||
use crate::blockdata::script::witness_version::WitnessVersion;
|
use crate::blockdata::script::witness_version::WitnessVersion;
|
||||||
use crate::blockdata::script::{PushBytes, PushBytesBuf, PushBytesErrorReport, Script};
|
use crate::blockdata::script::{PushBytes, PushBytesBuf, PushBytesErrorReport, Script};
|
||||||
use crate::crypto::key::{self, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
|
use crate::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
|
||||||
use crate::taproot::TapNodeHash;
|
use crate::taproot::TapNodeHash;
|
||||||
|
|
||||||
/// The segregated witness program.
|
/// The segregated witness program.
|
||||||
|
@ -67,10 +67,9 @@ impl WitnessProgram {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [`WitnessProgram`] from `pk` for a P2WPKH output.
|
/// Creates a [`WitnessProgram`] from `pk` for a P2WPKH output.
|
||||||
pub fn p2wpkh(pk: &PublicKey) -> Result<Self, key::UncompressedPubkeyError> {
|
pub fn p2wpkh(pk: &CompressedPublicKey) -> Self {
|
||||||
let hash = pk.wpubkey_hash()?;
|
let hash = pk.wpubkey_hash();
|
||||||
let program = WitnessProgram::new_p2wpkh(hash.to_byte_array());
|
WitnessProgram::new_p2wpkh(hash.to_byte_array())
|
||||||
Ok(program)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [`WitnessProgram`] from `script` for a P2WSH output.
|
/// Creates a [`WitnessProgram`] from `script` for a P2WSH output.
|
||||||
|
|
|
@ -262,6 +262,129 @@ impl From<&PublicKey> for PubkeyHash {
|
||||||
fn from(key: &PublicKey) -> PubkeyHash { key.pubkey_hash() }
|
fn from(key: &PublicKey) -> PubkeyHash { key.pubkey_hash() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An always-compressed Bitcoin ECDSA public key
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct CompressedPublicKey(pub secp256k1::PublicKey);
|
||||||
|
|
||||||
|
impl CompressedPublicKey {
|
||||||
|
/// Returns bitcoin 160-bit hash of the public key
|
||||||
|
pub fn pubkey_hash(&self) -> PubkeyHash { PubkeyHash::hash(&self.to_bytes()) }
|
||||||
|
|
||||||
|
/// Returns bitcoin 160-bit hash of the public key for witness program
|
||||||
|
pub fn wpubkey_hash(&self) -> WPubkeyHash {
|
||||||
|
WPubkeyHash::from_byte_array(hash160::Hash::hash(&self.to_bytes()).to_byte_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the public key into a writer
|
||||||
|
pub fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
|
||||||
|
writer.write_all(&self.to_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the public key from a reader
|
||||||
|
///
|
||||||
|
/// This internally reads the first byte before reading the rest, so
|
||||||
|
/// use of a `BufReader` is recommended.
|
||||||
|
pub fn read_from<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, io::Error> {
|
||||||
|
let mut bytes = [0; 33];
|
||||||
|
|
||||||
|
reader.read_exact(&mut bytes)?;
|
||||||
|
Self::from_slice(&bytes).map_err(|e| {
|
||||||
|
// Need a static string for no-std io
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
let reason = e;
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
let reason = "secp256k1 error";
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, reason)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the public key.
|
||||||
|
///
|
||||||
|
/// As the type name suggests, the key is serialzied in compressed format.
|
||||||
|
///
|
||||||
|
/// Note that this can be used as a sort key to get BIP67-compliant sorting.
|
||||||
|
/// That's why this type doesn't have the `to_sort_key` method - it would duplicate this one.
|
||||||
|
pub fn to_bytes(&self) -> [u8; 33] {
|
||||||
|
self.0.serialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a public key from a slice
|
||||||
|
pub fn from_slice(data: &[u8]) -> Result<Self, secp256k1::Error> {
|
||||||
|
secp256k1::PublicKey::from_slice(data).map(CompressedPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the public key as supposed to be used with this secret
|
||||||
|
pub fn from_private_key<C: secp256k1::Signing>(
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
sk: &PrivateKey,
|
||||||
|
) -> Result<Self, UncompressedPubkeyError> {
|
||||||
|
sk.public_key(secp).try_into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that `sig` is a valid ECDSA signature for `msg` using this public key.
|
||||||
|
pub fn verify<C: secp256k1::Verification>(
|
||||||
|
&self,
|
||||||
|
secp: &Secp256k1<C>,
|
||||||
|
msg: &secp256k1::Message,
|
||||||
|
sig: &ecdsa::Signature,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(secp.verify_ecdsa(msg, &sig.sig, &self.0)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CompressedPublicKey {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::LowerHex::fmt(&self.to_bytes().as_hex(), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for CompressedPublicKey {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
CompressedPublicKey::from_slice(&<[u8; 33]>::from_hex(s)?).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<PublicKey> for CompressedPublicKey {
|
||||||
|
type Error = UncompressedPubkeyError;
|
||||||
|
|
||||||
|
fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
|
||||||
|
if value.compressed {
|
||||||
|
Ok(CompressedPublicKey(value.inner))
|
||||||
|
} else {
|
||||||
|
Err(UncompressedPubkeyError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CompressedPublicKey> for PublicKey {
|
||||||
|
fn from(value: CompressedPublicKey) -> Self {
|
||||||
|
PublicKey::new(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CompressedPublicKey> for XOnlyPublicKey {
|
||||||
|
fn from(pk: CompressedPublicKey) -> Self { pk.0.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CompressedPublicKey> for PubkeyHash {
|
||||||
|
fn from(key: CompressedPublicKey) -> Self { key.pubkey_hash() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&CompressedPublicKey> for PubkeyHash {
|
||||||
|
fn from(key: &CompressedPublicKey) -> Self { key.pubkey_hash() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CompressedPublicKey> for WPubkeyHash {
|
||||||
|
fn from(key: CompressedPublicKey) -> Self { key.wpubkey_hash() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&CompressedPublicKey> for WPubkeyHash {
|
||||||
|
fn from(key: &CompressedPublicKey) -> Self { key.wpubkey_hash() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A Bitcoin ECDSA private key
|
/// A Bitcoin ECDSA private key
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct PrivateKey {
|
pub struct PrivateKey {
|
||||||
|
@ -485,6 +608,71 @@ impl<'de> serde::Deserialize<'de> for PublicKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl serde::Serialize for CompressedPublicKey {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
if s.is_human_readable() {
|
||||||
|
s.collect_str(self)
|
||||||
|
} else {
|
||||||
|
s.serialize_bytes(&self.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl<'de> serde::Deserialize<'de> for CompressedPublicKey {
|
||||||
|
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||||
|
if d.is_human_readable() {
|
||||||
|
struct HexVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for HexVisitor {
|
||||||
|
type Value = CompressedPublicKey;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
formatter.write_str("a 66 digits long ASCII hex string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
if let Ok(hex) = core::str::from_utf8(v) {
|
||||||
|
CompressedPublicKey::from_str(hex).map_err(E::custom)
|
||||||
|
} else {
|
||||||
|
Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
CompressedPublicKey::from_str(v).map_err(E::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.deserialize_str(HexVisitor)
|
||||||
|
} else {
|
||||||
|
struct BytesVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for BytesVisitor {
|
||||||
|
type Value = CompressedPublicKey;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
formatter.write_str("a bytestring")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
CompressedPublicKey::from_slice(v).map_err(E::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.deserialize_bytes(BytesVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Untweaked BIP-340 X-coord-only public key
|
/// Untweaked BIP-340 X-coord-only public key
|
||||||
pub type UntweakedPublicKey = XOnlyPublicKey;
|
pub type UntweakedPublicKey = XOnlyPublicKey;
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ pub use crate::{
|
||||||
blockdata::witness::{self, Witness},
|
blockdata::witness::{self, Witness},
|
||||||
consensus::encode::VarInt,
|
consensus::encode::VarInt,
|
||||||
crypto::ecdsa,
|
crypto::ecdsa,
|
||||||
crypto::key::{self, PrivateKey, PubkeyHash, PublicKey, WPubkeyHash, XOnlyPublicKey},
|
crypto::key::{self, PrivateKey, PubkeyHash, PublicKey, CompressedPublicKey, WPubkeyHash, XOnlyPublicKey},
|
||||||
crypto::sighash::{self, LegacySighash, SegwitV0Sighash, TapSighash, TapSighashTag},
|
crypto::sighash::{self, LegacySighash, SegwitV0Sighash, TapSighash, TapSighashTag},
|
||||||
merkle_tree::MerkleBlock,
|
merkle_tree::MerkleBlock,
|
||||||
network::Network,
|
network::Network,
|
||||||
|
|
|
@ -238,22 +238,25 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(signature.to_base64(), signature.to_string());
|
assert_eq!(signature.to_base64(), signature.to_string());
|
||||||
let signature2 = super::MessageSignature::from_str(&signature.to_string()).unwrap();
|
let signature2 = super::MessageSignature::from_str(&signature.to_string()).unwrap();
|
||||||
let pubkey = signature2.recover_pubkey(&secp, msg_hash).unwrap();
|
let pubkey = signature2.recover_pubkey(&secp, msg_hash)
|
||||||
assert!(pubkey.compressed);
|
.unwrap()
|
||||||
assert_eq!(pubkey.inner, secp256k1::PublicKey::from_secret_key(&secp, &privkey));
|
.try_into()
|
||||||
|
.expect("compressed was set to true");
|
||||||
|
|
||||||
let p2pkh = Address::p2pkh(pubkey, Network::Bitcoin);
|
let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin);
|
||||||
assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
|
|
||||||
let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin).unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
signature2.is_signed_by_address(&secp, &p2wpkh, msg_hash),
|
signature2.is_signed_by_address(&secp, &p2wpkh, msg_hash),
|
||||||
Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2wpkh))
|
Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2wpkh))
|
||||||
);
|
);
|
||||||
let p2shwpkh = Address::p2shwpkh(&pubkey, Network::Bitcoin).unwrap();
|
let p2shwpkh = Address::p2shwpkh(&pubkey, Network::Bitcoin);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash),
|
signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash),
|
||||||
Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2sh))
|
Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2sh))
|
||||||
);
|
);
|
||||||
|
let p2pkh = Address::p2pkh(pubkey, Network::Bitcoin);
|
||||||
|
assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
|
||||||
|
|
||||||
|
assert_eq!(pubkey.0, secp256k1::PublicKey::from_secret_key(&secp, &privkey));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue