use crate::primitives::BigNumber;
use crate::Result;
pub struct ScriptNum;
impl ScriptNum {
pub fn from_bytes(bytes: &[u8], require_minimal: bool) -> Result<BigNumber> {
if bytes.is_empty() {
return Ok(BigNumber::zero());
}
if require_minimal && !Self::is_minimally_encoded(bytes) {
return Err(crate::error::Error::ScriptExecutionError(
"Non-minimally encoded script number".to_string(),
));
}
let last_byte = bytes[bytes.len() - 1];
let is_negative = (last_byte & 0x80) != 0;
let mut magnitude = bytes.to_vec();
if let Some(last) = magnitude.last_mut() {
*last &= 0x7f;
}
let bn = BigNumber::from_bytes_le(&magnitude);
if is_negative {
Ok(bn.neg())
} else {
Ok(bn)
}
}
pub fn to_bytes(value: &BigNumber) -> Vec<u8> {
if value.is_zero() {
return Vec::new();
}
let is_negative = value.is_negative();
let abs_value = value.abs();
let mut bytes = abs_value.to_bytes_le_min();
if let Some(&last) = bytes.last() {
if (last & 0x80) != 0 {
bytes.push(if is_negative { 0x80 } else { 0x00 });
} else if is_negative {
if let Some(last_mut) = bytes.last_mut() {
*last_mut |= 0x80;
}
}
} else if is_negative {
bytes.push(0x80);
}
bytes
}
pub fn is_minimally_encoded(bytes: &[u8]) -> bool {
if bytes.is_empty() {
return true;
}
let last_byte = bytes[bytes.len() - 1];
if (last_byte & 0x7f) != 0 {
return true;
}
if bytes.len() > 1 && (bytes[bytes.len() - 2] & 0x80) != 0 {
return true;
}
false
}
pub fn cast_to_bool(bytes: &[u8]) -> bool {
if bytes.is_empty() {
return false;
}
for (i, &byte) in bytes.iter().enumerate() {
if byte != 0 {
if i == bytes.len() - 1 && byte == 0x80 {
return false;
}
return true;
}
}
false
}
pub fn minimally_encode(bytes: &[u8]) -> Vec<u8> {
if bytes.is_empty() {
return Vec::new();
}
match Self::from_bytes(bytes, false) {
Ok(bn) => Self::to_bytes(&bn),
Err(_) => bytes.to_vec(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zero() {
assert_eq!(ScriptNum::to_bytes(&BigNumber::zero()), Vec::<u8>::new());
let bn = ScriptNum::from_bytes(&[], true).unwrap();
assert!(bn.is_zero());
}
#[test]
fn test_positive_numbers() {
let bn = BigNumber::from_i64(1);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0x01]);
let bn = BigNumber::from_i64(127);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0x7f]);
let bn = BigNumber::from_i64(128);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0x80, 0x00]);
let bn = BigNumber::from_i64(255);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0xff, 0x00]);
let bn = BigNumber::from_i64(256);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0x00, 0x01]);
}
#[test]
fn test_negative_numbers() {
let bn = BigNumber::from_i64(-1);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0x81]);
let bn = BigNumber::from_i64(-127);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0xff]);
let bn = BigNumber::from_i64(-128);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0x80, 0x80]);
let bn = BigNumber::from_i64(-255);
assert_eq!(ScriptNum::to_bytes(&bn), vec![0xff, 0x80]);
}
#[test]
fn test_roundtrip() {
let test_values = [
0i64, 1, -1, 127, -127, 128, -128, 255, -255, 256, -256, 1000, -1000,
];
for val in test_values {
let bn = BigNumber::from_i64(val);
let bytes = ScriptNum::to_bytes(&bn);
let decoded = ScriptNum::from_bytes(&bytes, true).unwrap();
assert_eq!(bn, decoded, "Roundtrip failed for {}", val);
}
}
#[test]
fn test_minimal_encoding() {
assert!(ScriptNum::is_minimally_encoded(&[]));
assert!(ScriptNum::is_minimally_encoded(&[0x01]));
assert!(ScriptNum::is_minimally_encoded(&[0x7f]));
assert!(ScriptNum::is_minimally_encoded(&[0x80, 0x00]));
assert!(ScriptNum::is_minimally_encoded(&[0x81]));
assert!(!ScriptNum::is_minimally_encoded(&[0x00])); assert!(!ScriptNum::is_minimally_encoded(&[0x80])); assert!(!ScriptNum::is_minimally_encoded(&[0x01, 0x00])); }
#[test]
fn test_cast_to_bool() {
assert!(!ScriptNum::cast_to_bool(&[]));
assert!(!ScriptNum::cast_to_bool(&[0x00]));
assert!(!ScriptNum::cast_to_bool(&[0x00, 0x00]));
assert!(!ScriptNum::cast_to_bool(&[0x80])); assert!(!ScriptNum::cast_to_bool(&[0x00, 0x80]));
assert!(ScriptNum::cast_to_bool(&[0x01]));
assert!(ScriptNum::cast_to_bool(&[0x81])); assert!(ScriptNum::cast_to_bool(&[0x00, 0x01]));
assert!(ScriptNum::cast_to_bool(&[0x7f]));
}
#[test]
fn test_non_minimal_decoding() {
let result = ScriptNum::from_bytes(&[0x00], true);
assert!(result.is_err());
let bn = ScriptNum::from_bytes(&[0x00], false).unwrap();
assert!(bn.is_zero());
}
}