Implement p2pk_public_key + tests

For some applications, such as block explorers, it's useful to be able
to obtain the public key used in case of P2PK. This is considered
general enough for addition into bitcoin library,
so this change adds it.

The public key is returned only when it's valid and the script is P2PK.
To avoid duplicating the logic checking whether a script is P2PK the
logic is moved to a new p2pk_pubkey_bytes() method which returns raw
bytes and is then called from both is_p2pk() and p2pk_public_key()
This commit is contained in:
connormullett 2022-11-24 00:13:39 -05:00
parent e16b9bdb20
commit 25f781bef3
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