From 25f781bef36aaf1e092fea0acf5ec51d90f95dd2 Mon Sep 17 00:00:00 2001 From: connormullett Date: Thu, 24 Nov 2022 00:13:39 -0500 Subject: [PATCH] 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() --- bitcoin/src/blockdata/script.rs | 174 ++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 11 deletions(-) diff --git a/bitcoin/src/blockdata/script.rs b/bitcoin/src/blockdata/script.rs index 2768b303..adebb5f0 100644 --- a/bitcoin/src/blockdata/script.rs +++ b/bitcoin/src/blockdata/script.rs @@ -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::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