use crate::config::MultisigConfig;
pub mod opcodes {
pub const OP_0: u8 = 0x00;
pub const OP_1: u8 = 0x51;
pub const OP_16: u8 = 0x60;
pub const OP_CHECKMULTISIG: u8 = 0xae;
pub const OP_HASH160: u8 = 0xa9;
pub const OP_EQUAL: u8 = 0x87;
}
pub fn build_multisig_script(config: &MultisigConfig) -> Vec<u8> {
let mut script = Vec::new();
script.push(number_to_opcode(config.threshold()));
for pubkey in config.public_keys() {
script.push(33); script.extend_from_slice(pubkey);
}
script.push(number_to_opcode(config.total()));
script.push(opcodes::OP_CHECKMULTISIG);
script
}
pub fn build_p2sh_script_pubkey(script_hash: &[u8; 20]) -> Vec<u8> {
let mut script = Vec::with_capacity(23);
script.push(opcodes::OP_HASH160);
script.push(20); script.extend_from_slice(script_hash);
script.push(opcodes::OP_EQUAL);
script
}
pub fn build_p2wsh_script_pubkey(script_hash: &[u8; 32]) -> Vec<u8> {
let mut script = Vec::with_capacity(34);
script.push(opcodes::OP_0);
script.push(32); script.extend_from_slice(script_hash);
script
}
pub fn build_p2sh_p2wsh_redeem_script(witness_script_hash: &[u8; 32]) -> Vec<u8> {
build_p2wsh_script_pubkey(witness_script_hash)
}
fn number_to_opcode(n: u8) -> u8 {
if n == 0 {
opcodes::OP_0
} else if n <= 16 {
opcodes::OP_1 + n - 1
} else {
panic!("Number too large for opcode: {}", n);
}
}
pub fn parse_multisig_script(script: &[u8]) -> Option<(u8, u8, Vec<[u8; 33]>)> {
if script.len() < 3 {
return None;
}
let m = opcode_to_number(script[0])?;
if m == 0 || m > 15 {
return None;
}
let mut pos = 1;
let mut pubkeys = Vec::new();
while pos < script.len() {
let byte = script[pos];
if (opcodes::OP_1..=opcodes::OP_16).contains(&byte) {
break;
}
if byte != 33 {
return None;
}
if pos + 34 > script.len() {
return None;
}
let mut pubkey = [0u8; 33];
pubkey.copy_from_slice(&script[pos + 1..pos + 34]);
pubkeys.push(pubkey);
pos += 34;
}
if pos >= script.len() {
return None;
}
let n = opcode_to_number(script[pos])?;
if n as usize != pubkeys.len() {
return None;
}
if pos + 1 >= script.len() || script[pos + 1] != opcodes::OP_CHECKMULTISIG {
return None;
}
Some((m, n, pubkeys))
}
fn opcode_to_number(opcode: u8) -> Option<u8> {
if opcode == opcodes::OP_0 {
Some(0)
} else if (opcodes::OP_1..=opcodes::OP_16).contains(&opcode) {
Some(opcode - opcodes::OP_1 + 1)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::MultisigConfig;
fn make_pubkey(seed: u8) -> [u8; 33] {
let mut key = [seed; 33];
key[0] = 0x02;
key
}
#[test]
fn test_build_2_of_3_script() {
let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
let config = MultisigConfig::new(2, keys).unwrap();
let script = build_multisig_script(&config);
assert_eq!(script.len(), 105);
assert_eq!(script[0], opcodes::OP_1 + 1); assert_eq!(script[script.len() - 2], opcodes::OP_1 + 2); assert_eq!(script[script.len() - 1], opcodes::OP_CHECKMULTISIG);
}
#[test]
fn test_parse_multisig_script() {
let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
let config = MultisigConfig::new(2, keys.clone()).unwrap();
let script = build_multisig_script(&config);
let (m, n, parsed_keys) = parse_multisig_script(&script).unwrap();
assert_eq!(m, 2);
assert_eq!(n, 3);
assert_eq!(parsed_keys.len(), 3);
}
#[test]
fn test_p2sh_script_pubkey() {
let hash = [0xab; 20];
let script = build_p2sh_script_pubkey(&hash);
assert_eq!(script.len(), 23);
assert_eq!(script[0], opcodes::OP_HASH160);
assert_eq!(script[1], 20);
assert_eq!(script[22], opcodes::OP_EQUAL);
}
#[test]
fn test_p2wsh_script_pubkey() {
let hash = [0xcd; 32];
let script = build_p2wsh_script_pubkey(&hash);
assert_eq!(script.len(), 34);
assert_eq!(script[0], opcodes::OP_0);
assert_eq!(script[1], 32);
}
}