diff --git a/bitcoin/src/blockdata/script.rs b/bitcoin/src/blockdata/script.rs index 13bea665..5f9b20cd 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