use crate::descriptor::Descriptor;
use crate::error::DescriptorError;
use rustywallet_keys::public_key::{PublicKey, PublicKeyFormat};
use sha2::{Sha256, Digest};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScriptPubkey {
pub script: Vec<u8>,
pub script_type: ScriptType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScriptType {
P2pk,
P2pkh,
P2sh,
P2wpkh,
P2wsh,
P2tr,
}
impl ScriptPubkey {
pub fn p2pk(pubkey: &PublicKey) -> Self {
let pk_bytes = hex::decode(pubkey.to_hex(PublicKeyFormat::Compressed)).unwrap();
let mut script = Vec::with_capacity(pk_bytes.len() + 2);
script.push(pk_bytes.len() as u8); script.extend_from_slice(&pk_bytes);
script.push(0xac);
Self {
script,
script_type: ScriptType::P2pk,
}
}
pub fn p2pkh(pubkey: &PublicKey) -> Self {
let pk_bytes = hex::decode(pubkey.to_hex(PublicKeyFormat::Compressed)).unwrap();
let pubkey_hash = hash160(&pk_bytes);
let mut script = Vec::with_capacity(25);
script.push(0x76); script.push(0xa9); script.push(0x14); script.extend_from_slice(&pubkey_hash);
script.push(0x88); script.push(0xac);
Self {
script,
script_type: ScriptType::P2pkh,
}
}
pub fn p2sh(redeem_script: &[u8]) -> Self {
let script_hash = hash160(redeem_script);
let mut script = Vec::with_capacity(23);
script.push(0xa9); script.push(0x14); script.extend_from_slice(&script_hash);
script.push(0x87);
Self {
script,
script_type: ScriptType::P2sh,
}
}
pub fn p2wpkh(pubkey: &PublicKey) -> Self {
let pk_bytes = hex::decode(pubkey.to_hex(PublicKeyFormat::Compressed)).unwrap();
let pubkey_hash = hash160(&pk_bytes);
let mut script = Vec::with_capacity(22);
script.push(0x00); script.push(0x14); script.extend_from_slice(&pubkey_hash);
Self {
script,
script_type: ScriptType::P2wpkh,
}
}
pub fn p2wsh(witness_script: &[u8]) -> Self {
let script_hash = sha256(witness_script);
let mut script = Vec::with_capacity(34);
script.push(0x00); script.push(0x20); script.extend_from_slice(&script_hash);
Self {
script,
script_type: ScriptType::P2wsh,
}
}
pub fn p2tr(output_key: &[u8; 32]) -> Self {
let mut script = Vec::with_capacity(34);
script.push(0x51); script.push(0x20); script.extend_from_slice(output_key);
Self {
script,
script_type: ScriptType::P2tr,
}
}
pub fn multisig(threshold: usize, pubkeys: &[PublicKey]) -> Result<Vec<u8>, DescriptorError> {
if threshold == 0 || threshold > pubkeys.len() || pubkeys.len() > 16 {
return Err(DescriptorError::InvalidThreshold {
k: threshold,
n: pubkeys.len(),
});
}
let mut script = Vec::new();
script.push(0x50 + threshold as u8);
for pk in pubkeys {
let pk_bytes = hex::decode(pk.to_hex(PublicKeyFormat::Compressed)).unwrap();
script.push(pk_bytes.len() as u8);
script.extend_from_slice(&pk_bytes);
}
script.push(0x50 + pubkeys.len() as u8);
script.push(0xae);
Ok(script)
}
pub fn as_bytes(&self) -> &[u8] {
&self.script
}
pub fn script_type(&self) -> ScriptType {
self.script_type
}
pub fn is_witness(&self) -> bool {
matches!(
self.script_type,
ScriptType::P2wpkh | ScriptType::P2wsh | ScriptType::P2tr
)
}
}
pub fn generate_script_pubkey(
descriptor: &Descriptor,
index: u32,
) -> Result<ScriptPubkey, DescriptorError> {
match descriptor {
Descriptor::Pk(key) => {
let pubkey = key.derive_public_key(index)?;
Ok(ScriptPubkey::p2pk(&pubkey))
}
Descriptor::Pkh(key) => {
let pubkey = key.derive_public_key(index)?;
Ok(ScriptPubkey::p2pkh(&pubkey))
}
Descriptor::Wpkh(key) => {
let pubkey = key.derive_public_key(index)?;
Ok(ScriptPubkey::p2wpkh(&pubkey))
}
Descriptor::Sh(inner) => {
let inner_script = generate_redeem_script(inner, index)?;
Ok(ScriptPubkey::p2sh(&inner_script))
}
Descriptor::Wsh(inner) => {
let witness_script = generate_witness_script(inner, index)?;
Ok(ScriptPubkey::p2wsh(&witness_script))
}
Descriptor::Tr(key) => {
let pubkey = key.derive_public_key(index)?;
let pk_hex = pubkey.to_hex(PublicKeyFormat::Compressed);
let pk_bytes = hex::decode(&pk_hex).unwrap();
let mut xonly = [0u8; 32];
xonly.copy_from_slice(&pk_bytes[1..33]); Ok(ScriptPubkey::p2tr(&xonly))
}
Descriptor::Multi { threshold, keys } => {
let pubkeys: Result<Vec<_>, _> = keys
.iter()
.map(|k| k.derive_public_key(index))
.collect();
let pubkeys = pubkeys?;
let script = ScriptPubkey::multisig(*threshold, &pubkeys)?;
Ok(ScriptPubkey::p2sh(&script))
}
Descriptor::SortedMulti { threshold, keys } => {
let mut pubkeys: Vec<_> = keys
.iter()
.map(|k| k.derive_public_key(index))
.collect::<Result<Vec<_>, _>>()?;
pubkeys.sort_by(|a, b| {
let a_hex = a.to_hex(PublicKeyFormat::Compressed);
let b_hex = b.to_hex(PublicKeyFormat::Compressed);
a_hex.cmp(&b_hex)
});
let script = ScriptPubkey::multisig(*threshold, &pubkeys)?;
Ok(ScriptPubkey::p2sh(&script))
}
}
}
fn generate_redeem_script(descriptor: &Descriptor, index: u32) -> Result<Vec<u8>, DescriptorError> {
match descriptor {
Descriptor::Wpkh(key) => {
let pubkey = key.derive_public_key(index)?;
let script = ScriptPubkey::p2wpkh(&pubkey);
Ok(script.script)
}
Descriptor::Wsh(inner) => {
let witness_script = generate_witness_script(inner, index)?;
let script = ScriptPubkey::p2wsh(&witness_script);
Ok(script.script)
}
Descriptor::Multi { threshold, keys } => {
let pubkeys: Result<Vec<_>, _> = keys
.iter()
.map(|k| k.derive_public_key(index))
.collect();
ScriptPubkey::multisig(*threshold, &pubkeys?)
}
Descriptor::SortedMulti { threshold, keys } => {
let mut pubkeys: Vec<_> = keys
.iter()
.map(|k| k.derive_public_key(index))
.collect::<Result<Vec<_>, _>>()?;
pubkeys.sort_by(|a, b| {
let a_hex = a.to_hex(PublicKeyFormat::Compressed);
let b_hex = b.to_hex(PublicKeyFormat::Compressed);
a_hex.cmp(&b_hex)
});
ScriptPubkey::multisig(*threshold, &pubkeys)
}
_ => Err(DescriptorError::ScriptError(format!(
"Cannot create redeem script for {}",
descriptor.descriptor_type()
))),
}
}
fn generate_witness_script(descriptor: &Descriptor, index: u32) -> Result<Vec<u8>, DescriptorError> {
match descriptor {
Descriptor::Multi { threshold, keys } => {
let pubkeys: Result<Vec<_>, _> = keys
.iter()
.map(|k| k.derive_public_key(index))
.collect();
ScriptPubkey::multisig(*threshold, &pubkeys?)
}
Descriptor::SortedMulti { threshold, keys } => {
let mut pubkeys: Vec<_> = keys
.iter()
.map(|k| k.derive_public_key(index))
.collect::<Result<Vec<_>, _>>()?;
pubkeys.sort_by(|a, b| {
let a_hex = a.to_hex(PublicKeyFormat::Compressed);
let b_hex = b.to_hex(PublicKeyFormat::Compressed);
a_hex.cmp(&b_hex)
});
ScriptPubkey::multisig(*threshold, &pubkeys)
}
_ => Err(DescriptorError::ScriptError(format!(
"Cannot create witness script for {}",
descriptor.descriptor_type()
))),
}
}
fn hash160(data: &[u8]) -> [u8; 20] {
use ripemd::Ripemd160;
let sha = Sha256::digest(data);
let ripemd = Ripemd160::digest(sha);
let mut result = [0u8; 20];
result.copy_from_slice(&ripemd);
result
}
fn sha256(data: &[u8]) -> [u8; 32] {
let hash = Sha256::digest(data);
let mut result = [0u8; 32];
result.copy_from_slice(&hash);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_p2pkh_script() {
let pubkey_hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
let pubkey = PublicKey::from_hex(pubkey_hex).unwrap();
let script = ScriptPubkey::p2pkh(&pubkey);
assert_eq!(script.script_type, ScriptType::P2pkh);
assert_eq!(script.script.len(), 25);
assert_eq!(script.script[0], 0x76); assert_eq!(script.script[1], 0xa9); }
#[test]
fn test_p2wpkh_script() {
let pubkey_hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
let pubkey = PublicKey::from_hex(pubkey_hex).unwrap();
let script = ScriptPubkey::p2wpkh(&pubkey);
assert_eq!(script.script_type, ScriptType::P2wpkh);
assert_eq!(script.script.len(), 22);
assert_eq!(script.script[0], 0x00); assert_eq!(script.script[1], 0x14); }
#[test]
fn test_p2tr_script() {
let output_key = [0x42u8; 32];
let script = ScriptPubkey::p2tr(&output_key);
assert_eq!(script.script_type, ScriptType::P2tr);
assert_eq!(script.script.len(), 34);
assert_eq!(script.script[0], 0x51); assert_eq!(script.script[1], 0x20); }
#[test]
fn test_multisig_script() {
let pk1_hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
let pk2_hex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
let pk1 = PublicKey::from_hex(pk1_hex).unwrap();
let pk2 = PublicKey::from_hex(pk2_hex).unwrap();
let script = ScriptPubkey::multisig(2, &[pk1, pk2]).unwrap();
assert_eq!(script[0], 0x52); assert_eq!(*script.last().unwrap(), 0xae); }
}