use crate::primitives::{from_hex, sha256d, to_hex};
use crate::{Error, Result};
pub const BIP276_PREFIX: &str = "bitcoin-script";
pub const NETWORK_MAINNET: u8 = 1;
pub const NETWORK_TESTNET: u8 = 2;
pub fn encode_bip276(network: u8, script_type: u8, script: &[u8]) -> String {
let payload = format!(
"{}:{:02x}{:02x}{}",
BIP276_PREFIX,
network,
script_type,
to_hex(script)
);
let checksum = sha256d(payload.as_bytes());
let checksum_hex = to_hex(&checksum[..4]);
format!("{}{}", payload, checksum_hex)
}
pub fn decode_bip276(encoded: &str) -> Result<(u8, u8, Vec<u8>)> {
let prefix_with_colon = format!("{}:", BIP276_PREFIX);
if !encoded.starts_with(&prefix_with_colon) {
return Err(Error::Bip276Error(format!(
"invalid prefix: expected '{}'",
BIP276_PREFIX
)));
}
let after_prefix = &encoded[prefix_with_colon.len()..];
if after_prefix.len() < 12 {
return Err(Error::Bip276Error("input too short".to_string()));
}
let network_hex = &after_prefix[..2];
let network_bytes = from_hex(network_hex)
.map_err(|_| Error::Bip276Error(format!("invalid network hex: '{}'", network_hex)))?;
let network = network_bytes[0];
let script_type_hex = &after_prefix[2..4];
let script_type_bytes = from_hex(script_type_hex).map_err(|_| {
Error::Bip276Error(format!("invalid script type hex: '{}'", script_type_hex))
})?;
let script_type = script_type_bytes[0];
let data_and_checksum = &after_prefix[4..];
if data_and_checksum.len() < 8 {
return Err(Error::Bip276Error(
"input too short for checksum".to_string(),
));
}
let script_hex = &data_and_checksum[..data_and_checksum.len() - 8];
let provided_checksum = &data_and_checksum[data_and_checksum.len() - 8..];
let script_data = from_hex(script_hex)
.map_err(|_| Error::Bip276Error(format!("invalid script hex: '{}'", script_hex)))?;
let payload = &encoded[..encoded.len() - 8];
let checksum = sha256d(payload.as_bytes());
let expected_checksum = to_hex(&checksum[..4]);
if provided_checksum != expected_checksum {
return Err(Error::Bip276Error("invalid checksum".to_string()));
}
Ok((network, script_type, script_data))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_mainnet() {
let encoded = encode_bip276(NETWORK_MAINNET, 1, b"fake script");
assert_eq!(encoded, "bitcoin-script:010166616b65207363726970746f0cd86a");
}
#[test]
fn test_encode_testnet() {
let encoded = encode_bip276(NETWORK_TESTNET, 1, b"fake script");
assert_eq!(encoded, "bitcoin-script:020166616b65207363726970742577a444");
}
#[test]
fn test_decode_valid() {
let (network, script_type, data) =
decode_bip276("bitcoin-script:010166616b65207363726970746f0cd86a").unwrap();
assert_eq!(network, NETWORK_MAINNET);
assert_eq!(script_type, 1);
assert_eq!(data, b"fake script");
}
#[test]
fn test_roundtrip() {
let original_data = b"hello world script data";
let encoded = encode_bip276(NETWORK_MAINNET, 1, original_data);
let (network, script_type, data) = decode_bip276(&encoded).unwrap();
assert_eq!(network, NETWORK_MAINNET);
assert_eq!(script_type, 1);
assert_eq!(data, original_data);
}
#[test]
fn test_roundtrip_testnet() {
let original_data = b"\x76\xa9\x14";
let encoded = encode_bip276(NETWORK_TESTNET, 2, original_data);
let (network, script_type, data) = decode_bip276(&encoded).unwrap();
assert_eq!(network, NETWORK_TESTNET);
assert_eq!(script_type, 2);
assert_eq!(data, original_data);
}
#[test]
fn test_decode_invalid_prefix() {
let result = decode_bip276("invalid-prefix:010166616b65207363726970746f0cd86a");
assert!(result.is_err());
match result {
Err(Error::Bip276Error(msg)) => {
assert!(msg.contains("invalid prefix"));
}
_ => panic!("expected Bip276Error"),
}
}
#[test]
fn test_decode_invalid_checksum() {
let result = decode_bip276("bitcoin-script:010166616b65207363726970746f0cd8");
assert!(result.is_err());
}
#[test]
fn test_decode_too_short() {
let result = decode_bip276("bitcoin-script:01");
assert!(result.is_err());
}
#[test]
fn test_decode_empty_script() {
let encoded = encode_bip276(NETWORK_MAINNET, 1, b"");
let (network, script_type, data) = decode_bip276(&encoded).unwrap();
assert_eq!(network, NETWORK_MAINNET);
assert_eq!(script_type, 1);
assert!(data.is_empty());
}
#[test]
fn test_roundtrip_various_data() {
let data: Vec<u8> = (0..=255).collect();
let encoded = encode_bip276(NETWORK_MAINNET, 1, &data);
let (network, script_type, decoded) = decode_bip276(&encoded).unwrap();
assert_eq!(network, NETWORK_MAINNET);
assert_eq!(script_type, 1);
assert_eq!(decoded, data);
}
}