Merge rust-bitcoin/rust-bitcoin#1412: Implement `Script::p2pk_public_key(&self) -> Option<PublicKey>`
25f781bef3
Implement p2pk_public_key + tests (connormullett) Pull request description: closes #1408 Adds logic to extract a `PublicKey` (if able) from a p2pk script ACKs for top commit: tcharding: ACK25f781bef3
apoelstra: ACK25f781bef3
Kixunil: ACK25f781bef3
Tree-SHA512: 139f588ca4b6ccc45f8df0b35c19bebae66cb7b07cbad22bbf5d3059118ed54c8e1716d64dc528de59c7c8f4eb684944ce029fbce97a303f385281e990fc3bf0
This commit is contained in:
commit
4cc4178b0c
|
@ -400,6 +400,30 @@ impl Script {
|
||||||
Builder::new()
|
Builder::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the public key if this script is P2PK with a **valid** public key.
|
||||||
|
///
|
||||||
|
/// This may return `None` even when [`is_p2pk()`](Self::is_p2pk) returns true.
|
||||||
|
/// This happens when the public key is invalid (e.g. the point not being on the curve).
|
||||||
|
/// It also implies the script is unspendable.
|
||||||
|
pub fn p2pk_public_key(&self) -> Option<PublicKey> {
|
||||||
|
PublicKey::from_slice(self.p2pk_pubkey_bytes()?).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bytes of the (possibly invalid) public key if this script is P2PK.
|
||||||
|
fn p2pk_pubkey_bytes(&self) -> Option<&[u8]> {
|
||||||
|
match self.len() {
|
||||||
|
67 if self.0[0] == OP_PUSHBYTES_65.to_u8()
|
||||||
|
&& self.0[66] == OP_CHECKSIG.to_u8() => {
|
||||||
|
Some(&self.0[1..66])
|
||||||
|
}
|
||||||
|
35 if self.0[0] == OP_PUSHBYTES_33.to_u8()
|
||||||
|
&& self.0[34] == OP_CHECKSIG.to_u8() => {
|
||||||
|
Some(&self.0[1..34])
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates P2PK-type of scriptPubkey.
|
/// Generates P2PK-type of scriptPubkey.
|
||||||
pub fn new_p2pk(pubkey: &PublicKey) -> Script {
|
pub fn new_p2pk(pubkey: &PublicKey) -> Script {
|
||||||
Builder::new()
|
Builder::new()
|
||||||
|
@ -557,19 +581,12 @@ impl Script {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a P2PK output.
|
/// Checks whether a script pubkey is a P2PK output.
|
||||||
|
///
|
||||||
|
/// You can obtain the public key, if its valid,
|
||||||
|
/// by calling [`p2pk_public_key()`](Self::p2pk_public_key)
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_p2pk(&self) -> bool {
|
pub fn is_p2pk(&self) -> bool {
|
||||||
match self.len() {
|
self.p2pk_pubkey_bytes().is_some()
|
||||||
67 => {
|
|
||||||
self.0[0] == OP_PUSHBYTES_65.to_u8()
|
|
||||||
&& self.0[66] == OP_CHECKSIG.to_u8()
|
|
||||||
}
|
|
||||||
35 => {
|
|
||||||
self.0[0] == OP_PUSHBYTES_33.to_u8()
|
|
||||||
&& self.0[34] == OP_CHECKSIG.to_u8()
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a Segregated Witness (segwit) program.
|
/// Checks whether a script pubkey is a Segregated Witness (segwit) program.
|
||||||
|
@ -1222,6 +1239,141 @@ mod test {
|
||||||
script = script.push_opcode(OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]);
|
script = script.push_opcode(OP_CHECKSIG); comp.push(0xACu8); assert_eq!(&script[..], &comp[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_valid_key_and_valid_script_returns_expected_key() {
|
||||||
|
let key_str = "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3";
|
||||||
|
let key = PublicKey::from_str(key_str).unwrap();
|
||||||
|
let p2pk = Script::builder().push_key(&key).push_opcode(OP_CHECKSIG).into_script();
|
||||||
|
let actual = p2pk.p2pk_pubkey_bytes().unwrap();
|
||||||
|
assert_eq!(actual.to_vec(), key.to_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_no_checksig_returns_none() {
|
||||||
|
let key_str = "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3";
|
||||||
|
let key = PublicKey::from_str(key_str).unwrap();
|
||||||
|
let no_checksig = Script::builder().push_key(&key).into_script();
|
||||||
|
assert_eq!(no_checksig.p2pk_pubkey_bytes(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_emptry_script_returns_none() {
|
||||||
|
let empty_script = Script::builder().into_script();
|
||||||
|
assert!(empty_script.p2pk_pubkey_bytes().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_no_key_returns_none() {
|
||||||
|
// scripts with no key should return None
|
||||||
|
let no_push_bytes = Script::builder().push_opcode(OP_CHECKSIG).into_script();
|
||||||
|
assert!(no_push_bytes.p2pk_pubkey_bytes().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_different_op_code_returns_none() {
|
||||||
|
let key_str = "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3";
|
||||||
|
let key = PublicKey::from_str(key_str).unwrap();
|
||||||
|
let different_op_code = Script::builder().push_key(&key).push_opcode(OP_NOP).into_script();
|
||||||
|
assert!(different_op_code.p2pk_pubkey_bytes().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_incorrect_key_size_returns_none() {
|
||||||
|
// 63 byte key
|
||||||
|
let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1";
|
||||||
|
let invalid_p2pk_script = Script::builder()
|
||||||
|
.push_slice(malformed_key.as_bytes())
|
||||||
|
.push_opcode(OP_CHECKSIG)
|
||||||
|
.into_script();
|
||||||
|
assert!(invalid_p2pk_script.p2pk_pubkey_bytes().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_invalid_key_returns_some() {
|
||||||
|
let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1ux";
|
||||||
|
let invalid_key_script = Script::builder()
|
||||||
|
.push_slice(malformed_key.as_bytes())
|
||||||
|
.push_opcode(OP_CHECKSIG)
|
||||||
|
.into_script();
|
||||||
|
assert!(invalid_key_script.p2pk_pubkey_bytes().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_pubkey_bytes_compressed_key_returns_expected_key() {
|
||||||
|
let compressed_key_str = "0311db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c";
|
||||||
|
let key = PublicKey::from_str(compressed_key_str).unwrap();
|
||||||
|
let p2pk = Script::builder().push_key(&key).push_opcode(OP_CHECKSIG).into_script();
|
||||||
|
let actual = p2pk.p2pk_pubkey_bytes().unwrap();
|
||||||
|
assert_eq!(actual.to_vec(), key.to_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_valid_key_and_valid_script_returns_expected_key() {
|
||||||
|
let key_str = "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3";
|
||||||
|
let key = PublicKey::from_str(key_str).unwrap();
|
||||||
|
let p2pk = Script::builder().push_key(&key).push_opcode(OP_CHECKSIG).into_script();
|
||||||
|
let actual = p2pk.p2pk_public_key().unwrap();
|
||||||
|
assert_eq!(actual, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_no_checksig_returns_none() {
|
||||||
|
let key_str = "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3";
|
||||||
|
let key = PublicKey::from_str(key_str).unwrap();
|
||||||
|
let no_checksig = Script::builder().push_key(&key).into_script();
|
||||||
|
assert_eq!(no_checksig.p2pk_public_key(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_empty_script_returns_none() {
|
||||||
|
let empty_script = Script::builder().into_script();
|
||||||
|
assert!(empty_script.p2pk_public_key().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_no_key_returns_none() {
|
||||||
|
let no_push_bytes = Script::builder().push_opcode(OP_CHECKSIG).into_script();
|
||||||
|
assert!(no_push_bytes.p2pk_public_key().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_different_op_code_returns_none() {
|
||||||
|
let key_str = "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3";
|
||||||
|
let key = PublicKey::from_str(key_str).unwrap();
|
||||||
|
let different_op_code = Script::builder().push_key(&key).push_opcode(OP_NOP).into_script();
|
||||||
|
assert!(different_op_code.p2pk_public_key().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_incorrect_size_returns_none() {
|
||||||
|
let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1";
|
||||||
|
let malformed_key_script = Script::builder()
|
||||||
|
.push_slice(malformed_key.as_bytes())
|
||||||
|
.push_opcode(OP_CHECKSIG)
|
||||||
|
.into_script();
|
||||||
|
assert!(malformed_key_script.p2pk_public_key().is_none());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_invalid_key_returns_none() {
|
||||||
|
let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1ux";
|
||||||
|
let invalid_key_script = Script::builder()
|
||||||
|
.push_slice(malformed_key.as_bytes())
|
||||||
|
.push_opcode(OP_CHECKSIG)
|
||||||
|
.into_script();
|
||||||
|
assert!(invalid_key_script.p2pk_public_key().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn p2pk_public_key_compressed_key_returns_some() {
|
||||||
|
let compressed_key_str = "0311db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c";
|
||||||
|
let key = PublicKey::from_str(compressed_key_str).unwrap();
|
||||||
|
let p2pk = Script::builder().push_key(&key).push_opcode(OP_CHECKSIG).into_script();
|
||||||
|
let actual = p2pk.p2pk_public_key().unwrap();
|
||||||
|
assert_eq!(actual, key);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn script_x_only_key() {
|
fn script_x_only_key() {
|
||||||
// Notice the "20" which prepends the keystr. That 20 is hexidecimal for "32". The Builder automatically adds the 32 opcode
|
// Notice the "20" which prepends the keystr. That 20 is hexidecimal for "32". The Builder automatically adds the 32 opcode
|
||||||
|
|
Loading…
Reference in New Issue