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:
    ACK 25f781bef3
  apoelstra:
    ACK 25f781bef3
  Kixunil:
    ACK 25f781bef3

Tree-SHA512: 139f588ca4b6ccc45f8df0b35c19bebae66cb7b07cbad22bbf5d3059118ed54c8e1716d64dc528de59c7c8f4eb684944ce029fbce97a303f385281e990fc3bf0
This commit is contained in:
Andrew Poelstra 2022-12-01 13:34:41 +00:00
commit 4cc4178b0c
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 163 additions and 11 deletions

View File

@ -400,6 +400,30 @@ impl Script {
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.
pub fn new_p2pk(pubkey: &PublicKey) -> Script {
Builder::new()
@ -557,19 +581,12 @@ impl Script {
}
/// 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]
pub fn is_p2pk(&self) -> bool {
match self.len() {
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
}
self.p2pk_pubkey_bytes().is_some()
}
/// 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[..]);
}
#[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]
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