use crate::primitives::error::PrimitivesError;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub enum SmallLimbs {
Inline { data: [u64; 4], len: u8 },
Heap(Vec<u64>),
}
impl SmallLimbs {
fn zero() -> Self {
SmallLimbs::Inline {
data: [0; 4],
len: 0,
}
}
fn from_limbs(limbs: &[u64]) -> Self {
let stripped = strip_leading_zeros(limbs);
if stripped.len() <= 4 {
let mut data = [0u64; 4];
for (i, &v) in stripped.iter().enumerate() {
data[i] = v;
}
SmallLimbs::Inline {
data,
len: stripped.len() as u8,
}
} else {
SmallLimbs::Heap(stripped.to_vec())
}
}
fn as_slice(&self) -> &[u64] {
match self {
SmallLimbs::Inline { data, len } => &data[..*len as usize],
SmallLimbs::Heap(v) => v.as_slice(),
}
}
fn len(&self) -> usize {
match self {
SmallLimbs::Inline { len, .. } => *len as usize,
SmallLimbs::Heap(v) => v.len(),
}
}
fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_inline(&self) -> bool {
matches!(self, SmallLimbs::Inline { .. })
}
fn to_vec(&self) -> Vec<u64> {
self.as_slice().to_vec()
}
fn get(&self, index: usize) -> u64 {
let s = self.as_slice();
if index < s.len() {
s[index]
} else {
0
}
}
}
fn strip_leading_zeros(limbs: &[u64]) -> &[u64] {
let mut end = limbs.len();
while end > 0 && limbs[end - 1] == 0 {
end -= 1;
}
&limbs[..end]
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endian {
Big,
Little,
}
#[derive(Clone, Debug)]
pub struct BigNumber {
limbs: SmallLimbs,
negative: bool,
pub red: Option<Arc<ReductionContext>>,
}
pub use crate::primitives::reduction_context::ReductionContext;
impl BigNumber {
pub fn zero() -> Self {
BigNumber {
limbs: SmallLimbs::zero(),
negative: false,
red: None,
}
}
pub fn from_number(n: i64) -> Self {
if n == 0 {
return Self::zero();
}
let negative = n < 0;
let mag = (n as i128).unsigned_abs() as u64;
BigNumber {
limbs: SmallLimbs::from_limbs(&[mag]),
negative,
red: None,
}
}
pub fn one() -> Self {
Self::from_number(1)
}
pub fn from_hex(hex: &str) -> Result<Self, PrimitivesError> {
if hex.is_empty() {
return Ok(Self::zero());
}
let (hex_str, negative) = if let Some(rest) = hex.strip_prefix('-') {
(rest, true)
} else {
(hex, false)
};
if hex_str.is_empty() {
return Ok(Self::zero());
}
let hex_str = hex_str.trim_start_matches('0');
if hex_str.is_empty() {
return Ok(Self::zero());
}
for c in hex_str.chars() {
if !c.is_ascii_hexdigit() {
return Err(PrimitivesError::InvalidHex(format!(
"invalid hex character: {}",
c
)));
}
}
let mut limbs = Vec::new();
let bytes = hex_str.as_bytes();
let mut pos = bytes.len();
while pos > 0 {
let start = pos.saturating_sub(16);
let chunk = &hex_str[start..pos];
let val = u64::from_str_radix(chunk, 16).map_err(|e| {
PrimitivesError::InvalidHex(format!("failed to parse hex chunk: {}", e))
})?;
limbs.push(val);
pos = start;
}
Ok(BigNumber {
limbs: SmallLimbs::from_limbs(&limbs),
negative: negative && !limbs.is_empty(),
red: None,
})
}
pub fn from_bytes(bytes: &[u8], endian: Endian) -> Self {
if bytes.is_empty() {
return Self::zero();
}
let be_bytes: Vec<u8> = match endian {
Endian::Big => bytes.to_vec(),
Endian::Little => {
let mut v = bytes.to_vec();
v.reverse();
v
}
};
let start = be_bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(be_bytes.len());
if start == be_bytes.len() {
return Self::zero();
}
let be_bytes = &be_bytes[start..];
let mut limbs = Vec::new();
let mut pos = be_bytes.len();
while pos > 0 {
let chunk_start = pos.saturating_sub(8);
let chunk = &be_bytes[chunk_start..pos];
let mut val: u64 = 0;
for &b in chunk {
val = (val << 8) | (b as u64);
}
limbs.push(val);
pos = chunk_start;
}
BigNumber {
limbs: SmallLimbs::from_limbs(&limbs),
negative: false,
red: None,
}
}
pub fn from_array(bytes: &[u8], endian: Endian) -> Self {
Self::from_bytes(bytes, endian)
}
pub fn to_hex(&self) -> String {
if self.is_zero() {
return "0".to_string();
}
let prefix = if self.negative { "-" } else { "" };
let limbs = self.limbs.as_slice();
let mut hex = String::new();
for (i, &limb) in limbs.iter().rev().enumerate() {
if i == 0 {
hex.push_str(&format!("{:x}", limb));
} else {
hex.push_str(&format!("{:016x}", limb));
}
}
format!("{}{}", prefix, hex)
}
pub fn to_array(&self, endian: Endian, length: Option<usize>) -> Vec<u8> {
if self.is_zero() {
let len = length.unwrap_or(0);
return vec![0u8; len];
}
let mut be_bytes = Vec::new();
let limbs = self.limbs.as_slice();
for &limb in limbs.iter().rev() {
be_bytes.extend_from_slice(&limb.to_be_bytes());
}
let start = be_bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(be_bytes.len());
let be_bytes = &be_bytes[start..];
let actual_len = be_bytes.len();
let target_len = length.unwrap_or(actual_len);
let mut result = vec![0u8; target_len];
let copy_len = actual_len.min(target_len);
let offset = target_len.saturating_sub(actual_len);
result[offset..offset + copy_len].copy_from_slice(&be_bytes[..copy_len]);
match endian {
Endian::Big => result,
Endian::Little => {
result.reverse();
result
}
}
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_array(Endian::Big, None)
}
#[allow(clippy::should_implement_trait)]
pub fn cmp(&self, other: &BigNumber) -> i32 {
if self.negative && !other.negative {
if self.is_zero() && other.is_zero() {
return 0;
}
return -1;
}
if !self.negative && other.negative {
if self.is_zero() && other.is_zero() {
return 0;
}
return 1;
}
let mag_cmp = self.ucmp(other);
if self.negative {
-mag_cmp } else {
mag_cmp
}
}
pub fn ucmp(&self, other: &BigNumber) -> i32 {
let a = self.limbs.as_slice();
let b = other.limbs.as_slice();
if a.len() != b.len() {
return if a.len() < b.len() { -1 } else { 1 };
}
for i in (0..a.len()).rev() {
if a[i] < b[i] {
return -1;
}
if a[i] > b[i] {
return 1;
}
}
0
}
pub fn cmpn(&self, n: i64) -> i32 {
let other = BigNumber::from_number(n);
self.cmp(&other)
}
pub fn gtn(&self, n: i64) -> bool {
self.cmpn(n) > 0
}
pub fn ltn(&self, n: i64) -> bool {
self.cmpn(n) < 0
}
pub fn eqn(&self, n: i64) -> bool {
self.cmpn(n) == 0
}
pub fn is_zero(&self) -> bool {
self.limbs.is_empty()
}
pub fn is_odd(&self) -> bool {
if self.is_zero() {
return false;
}
(self.limbs.get(0) & 1) == 1
}
pub fn is_even(&self) -> bool {
!self.is_odd()
}
pub fn is_negative(&self) -> bool {
self.negative && !self.is_zero()
}
pub fn is_neg(&self) -> bool {
self.is_negative()
}
pub fn is_one(&self) -> bool {
!self.negative && self.limbs.len() == 1 && self.limbs.get(0) == 1
}
pub fn bit_length(&self) -> usize {
if self.is_zero() {
return 0;
}
let limbs = self.limbs.as_slice();
let top_limb = limbs[limbs.len() - 1];
let top_bits = 64 - top_limb.leading_zeros() as usize;
(limbs.len() - 1) * 64 + top_bits
}
pub fn byte_length(&self) -> usize {
if self.is_zero() {
return 0;
}
self.bit_length().div_ceil(8)
}
pub fn andln(&self, mask: u32) -> u32 {
if self.is_zero() || mask == 0 {
return 0;
}
let low = self.limbs.get(0);
let m = if mask >= 32 {
u32::MAX
} else {
(1u32 << mask) - 1
};
(low as u32) & m
}
pub fn testn(&self, bit: usize) -> bool {
if self.is_zero() {
return false;
}
let limb_index = bit / 64;
let bit_index = bit % 64;
let limb = self.limbs.get(limb_index);
(limb >> bit_index) & 1 == 1
}
pub fn zero_bits(&self) -> usize {
if self.is_zero() {
return 0;
}
let limbs = self.limbs.as_slice();
let mut count = 0;
for &limb in limbs {
if limb == 0 {
count += 64;
} else {
count += limb.trailing_zeros() as usize;
break;
}
}
count
}
pub fn is_inline(&self) -> bool {
self.limbs.is_inline()
}
pub fn get_limbs(&self) -> &[u64] {
self.limbs.as_slice()
}
pub fn from_raw_limbs(limbs: &[u64]) -> BigNumber {
BigNumber {
limbs: SmallLimbs::from_limbs(limbs),
negative: false,
red: None,
}
}
pub fn neg(&self) -> BigNumber {
let mut r = self.clone();
if !r.is_zero() {
r.negative = !r.negative;
}
r
}
pub fn ineg(&mut self) -> &mut Self {
if !self.is_zero() {
self.negative = !self.negative;
}
self
}
pub fn abs(&self) -> BigNumber {
let mut r = self.clone();
r.negative = false;
r
}
pub fn to_number(&self) -> Option<i64> {
if self.is_zero() {
return Some(0);
}
if self.limbs.len() > 1 {
return None;
}
let val = self.limbs.get(0);
if self.negative {
if val > (i64::MAX as u64) + 1 {
None
} else {
Some(-(val as i64))
}
} else if val > i64::MAX as u64 {
None
} else {
Some(val as i64)
}
}
pub fn to_script_num(&self) -> Vec<u8> {
if self.is_zero() {
return Vec::new();
}
let abs_val = self.abs();
let be_bytes = abs_val.to_array(Endian::Big, None);
let mut le_bytes: Vec<u8> = be_bytes.into_iter().rev().collect();
if let Some(&last) = le_bytes.last() {
if last & 0x80 != 0 {
if self.negative {
le_bytes.push(0x80);
} else {
le_bytes.push(0x00);
}
} else if self.negative {
let len = le_bytes.len();
le_bytes[len - 1] |= 0x80;
}
}
le_bytes
}
pub fn from_script_num(
bytes: &[u8],
require_minimal: bool,
max_len: Option<usize>,
) -> Result<BigNumber, PrimitivesError> {
if let Some(max) = max_len {
if bytes.len() > max {
return Err(PrimitivesError::InvalidLength(format!(
"script number too long: {} > {}",
bytes.len(),
max
)));
}
}
if bytes.is_empty() {
return Ok(BigNumber::zero());
}
if require_minimal {
let last = bytes[bytes.len() - 1];
if last == 0x80 && bytes[..bytes.len() - 1].iter().all(|&b| b == 0) {
return Err(PrimitivesError::InvalidFormat(
"non-minimal script number: negative zero".to_string(),
));
}
if bytes.len() > 1 {
let last = bytes[bytes.len() - 1];
let second_last = bytes[bytes.len() - 2];
if (last == 0x00 || last == 0x80) && (second_last & 0x80 == 0) {
return Err(PrimitivesError::InvalidFormat(
"non-minimal script number encoding".to_string(),
));
}
}
}
let last = bytes[bytes.len() - 1];
let is_negative = last & 0x80 != 0;
let mut le_bytes = bytes.to_vec();
let len = le_bytes.len();
le_bytes[len - 1] &= 0x7f;
le_bytes.reverse();
let start = le_bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(le_bytes.len());
if start == le_bytes.len() {
return Ok(BigNumber::zero());
}
let mut result = BigNumber::from_bytes(&le_bytes[start..], Endian::Big);
result.negative = is_negative;
Ok(result)
}
pub fn strip(&mut self) -> &mut Self {
let limbs_vec = self.limbs.to_vec();
self.limbs = SmallLimbs::from_limbs(&limbs_vec);
if self.is_zero() {
self.negative = false;
}
self
}
}
impl std::fmt::Display for BigNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl PartialEq for BigNumber {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == 0
}
}
impl Eq for BigNumber {}
impl PartialOrd for BigNumber {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(std::cmp::Ord::cmp(self, other))
}
}
impl Ord for BigNumber {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.cmp_ord(other)
}
}
impl BigNumber {
fn cmp_ord(&self, other: &Self) -> std::cmp::Ordering {
match BigNumber::cmp(self, other) {
-1 => std::cmp::Ordering::Less,
0 => std::cmp::Ordering::Equal,
_ => std::cmp::Ordering::Greater,
}
}
}
impl BigNumber {
pub fn add(&self, other: &BigNumber) -> BigNumber {
if self.negative != other.negative {
if self.negative {
let a_abs = self.abs();
return other.sub_unsigned(&a_abs, other.ucmp(&a_abs));
} else {
let b_abs = other.abs();
return self.sub_unsigned(&b_abs, self.ucmp(&b_abs));
}
}
let result_limbs = add_magnitudes(self.limbs.as_slice(), other.limbs.as_slice());
BigNumber {
limbs: SmallLimbs::from_limbs(&result_limbs),
negative: self.negative, red: None,
}
}
pub fn iadd(&mut self, other: &BigNumber) -> &mut Self {
let result = self.add(other);
self.limbs = result.limbs;
self.negative = result.negative;
self
}
pub fn addn(&self, n: i64) -> BigNumber {
let other = BigNumber::from_number(n);
self.add(&other)
}
pub fn iaddn(&mut self, n: i64) -> &mut Self {
let other = BigNumber::from_number(n);
let result = self.add(&other);
self.limbs = result.limbs;
self.negative = result.negative;
self
}
pub fn sub(&self, other: &BigNumber) -> BigNumber {
let neg_other = other.neg();
self.add(&neg_other)
}
pub fn isub(&mut self, other: &BigNumber) -> &mut Self {
let result = self.sub(other);
self.limbs = result.limbs;
self.negative = result.negative;
self
}
pub fn subn(&self, n: i64) -> BigNumber {
let other = BigNumber::from_number(n);
self.sub(&other)
}
pub fn isubn(&mut self, n: i64) -> &mut Self {
let other = BigNumber::from_number(n);
let result = self.sub(&other);
self.limbs = result.limbs;
self.negative = result.negative;
self
}
fn sub_unsigned(&self, other: &BigNumber, cmp_result: i32) -> BigNumber {
if cmp_result == 0 {
return BigNumber::zero();
}
let (larger, smaller, neg) = if cmp_result > 0 {
(self.limbs.as_slice(), other.limbs.as_slice(), self.negative)
} else {
(
other.limbs.as_slice(),
self.limbs.as_slice(),
!self.negative,
)
};
let result_limbs = sub_magnitudes(larger, smaller);
let mut result = BigNumber {
limbs: SmallLimbs::from_limbs(&result_limbs),
negative: neg,
red: None,
};
if result.is_zero() {
result.negative = false;
}
result
}
pub fn mul(&self, other: &BigNumber) -> BigNumber {
if self.is_zero() || other.is_zero() {
return BigNumber::zero();
}
let result_limbs = mul_magnitudes(self.limbs.as_slice(), other.limbs.as_slice());
let negative = self.negative != other.negative;
BigNumber {
limbs: SmallLimbs::from_limbs(&result_limbs),
negative,
red: None,
}
}
pub fn imul(&mut self, other: &BigNumber) -> &mut Self {
let result = self.mul(other);
self.limbs = result.limbs;
self.negative = result.negative;
self.red = None;
self
}
pub fn imuln(&mut self, n: i64) -> &mut Self {
let other = BigNumber::from_number(n);
let result = self.mul(&other);
self.limbs = result.limbs;
self.negative = result.negative;
self
}
pub fn muln(&self, n: i64) -> BigNumber {
let mut r = self.clone();
r.imuln(n);
r
}
pub fn sqr(&self) -> BigNumber {
let limbs = self.limbs.as_slice();
if limbs.len() == 4 {
let a4: [u64; 4] = [limbs[0], limbs[1], limbs[2], limbs[3]];
let result_limbs = sqr_4x4(&a4);
return BigNumber {
limbs: SmallLimbs::from_limbs(&result_limbs),
negative: false, red: None,
};
}
self.mul(self)
}
pub fn isqr(&mut self) -> &mut Self {
let sq = self.sqr();
self.limbs = sq.limbs;
self.negative = sq.negative;
self.red = None;
self
}
pub fn pow(&self, exp: &BigNumber) -> BigNumber {
if exp.is_zero() {
return BigNumber::one();
}
if self.is_zero() {
return BigNumber::zero();
}
let mut result = BigNumber::one();
let mut base = self.abs();
let base_neg = self.is_neg();
let exp_odd = exp.is_odd();
let mut e = exp.clone();
while !e.is_zero() {
if e.is_odd() {
result = result.mul(&base);
}
base = base.sqr();
e.iushrn(1);
}
if base_neg && exp_odd {
result.negative = true;
}
result
}
pub fn div_mod(&self, other: &BigNumber) -> Result<(BigNumber, BigNumber), PrimitivesError> {
if other.is_zero() {
return Err(PrimitivesError::DivisionByZero);
}
if self.is_zero() {
return Ok((BigNumber::zero(), BigNumber::zero()));
}
let cmp = self.ucmp(other);
if cmp < 0 {
return Ok((BigNumber::zero(), self.abs()));
}
if cmp == 0 {
let mut q = BigNumber::one();
q.negative = self.negative != other.negative;
if q.negative && q.is_zero() {
q.negative = false;
}
return Ok((q, BigNumber::zero()));
}
let (q, r) = div_mod_unsigned(self.limbs.as_slice(), other.limbs.as_slice());
let mut quotient = BigNumber {
limbs: SmallLimbs::from_limbs(&q),
negative: self.negative != other.negative,
red: None,
};
let remainder = BigNumber {
limbs: SmallLimbs::from_limbs(&r),
negative: false,
red: None,
};
if quotient.is_zero() {
quotient.negative = false;
}
Ok((quotient, remainder))
}
pub fn div(&self, other: &BigNumber) -> Result<BigNumber, PrimitivesError> {
let (q, _) = self.div_mod(other)?;
Ok(q)
}
pub fn umod(&self, other: &BigNumber) -> Result<BigNumber, PrimitivesError> {
let (_, mut r) = self.div_mod(other)?;
if self.negative && !r.is_zero() {
r = other.abs().sub(&r);
}
r.negative = false;
Ok(r)
}
pub fn r#mod(&self, other: &BigNumber) -> Result<BigNumber, PrimitivesError> {
self.umod(other)
}
pub fn ushln(&self, bits: usize) -> BigNumber {
if self.is_zero() || bits == 0 {
return self.clone();
}
let limb_shift = bits / 64;
let bit_shift = bits % 64;
let old_limbs = self.limbs.as_slice();
let new_len = old_limbs.len() + limb_shift + if bit_shift > 0 { 1 } else { 0 };
let mut new_limbs = vec![0u64; new_len];
if bit_shift == 0 {
for (i, &v) in old_limbs.iter().enumerate() {
new_limbs[i + limb_shift] = v;
}
} else {
let mut carry = 0u64;
for (i, &v) in old_limbs.iter().enumerate() {
new_limbs[i + limb_shift] = (v << bit_shift) | carry;
carry = v >> (64 - bit_shift);
}
if carry > 0 {
new_limbs[old_limbs.len() + limb_shift] = carry;
}
}
BigNumber {
limbs: SmallLimbs::from_limbs(&new_limbs),
negative: self.negative,
red: None,
}
}
pub fn iushln(&mut self, bits: usize) -> &mut Self {
let result = self.ushln(bits);
self.limbs = result.limbs;
self
}
pub fn ushrn(&self, bits: usize) -> BigNumber {
if self.is_zero() || bits == 0 {
return self.clone();
}
let limb_shift = bits / 64;
let bit_shift = bits % 64;
let old_limbs = self.limbs.as_slice();
if limb_shift >= old_limbs.len() {
return BigNumber::zero();
}
let new_len = old_limbs.len() - limb_shift;
let mut new_limbs = vec![0u64; new_len];
if bit_shift == 0 {
new_limbs[..new_len].copy_from_slice(&old_limbs[limb_shift..(new_len + limb_shift)]);
} else {
for i in 0..new_len {
let current = old_limbs[i + limb_shift] >> bit_shift;
let next = if i + limb_shift + 1 < old_limbs.len() {
old_limbs[i + limb_shift + 1] << (64 - bit_shift)
} else {
0
};
new_limbs[i] = current | next;
}
}
let mut result = BigNumber {
limbs: SmallLimbs::from_limbs(&new_limbs),
negative: self.negative,
red: None,
};
if result.is_zero() {
result.negative = false;
}
result
}
pub fn iushrn(&mut self, bits: usize) -> &mut Self {
let result = self.ushrn(bits);
self.limbs = result.limbs;
self.negative = result.negative;
self
}
pub fn iushrn_with_remainder(&mut self, bits: usize) -> BigNumber {
let remainder = self.maskn(bits);
self.iushrn(bits);
remainder
}
pub fn maskn(&self, n: usize) -> BigNumber {
if self.is_zero() || n == 0 {
return BigNumber::zero();
}
if n >= self.bit_length() {
return self.clone();
}
let limb_count = n.div_ceil(64);
let bit_remainder = n % 64;
let old_limbs = self.limbs.as_slice();
let mut new_limbs = vec![0u64; limb_count];
let copy_len = limb_count.min(old_limbs.len());
new_limbs[..copy_len].copy_from_slice(&old_limbs[..copy_len]);
if bit_remainder > 0 && limb_count > 0 {
new_limbs[limb_count - 1] &= (1u64 << bit_remainder) - 1;
}
BigNumber {
limbs: SmallLimbs::from_limbs(&new_limbs),
negative: false,
red: None,
}
}
pub fn imaskn(&mut self, n: usize) -> &mut Self {
let result = self.maskn(n);
self.limbs = result.limbs;
self.negative = false;
self
}
pub fn gcd(&self, other: &BigNumber) -> BigNumber {
let mut a = self.abs();
let mut b = other.abs();
if a.is_zero() {
return b;
}
if b.is_zero() {
return a;
}
let shift_a = a.zero_bits();
let shift_b = b.zero_bits();
let shift = shift_a.min(shift_b);
a.iushrn(shift_a);
b.iushrn(shift_b);
loop {
let c = a.ucmp(&b);
if c == 0 {
return a.ushln(shift);
}
if c < 0 {
std::mem::swap(&mut a, &mut b);
}
a = a.sub(&b);
a.iushrn(a.zero_bits());
}
}
pub fn egcd(&self, other: &BigNumber) -> (BigNumber, BigNumber, BigNumber) {
let a = self.abs();
let b = other.abs();
if a.is_zero() {
return (b, BigNumber::zero(), BigNumber::one());
}
if b.is_zero() {
return (a, BigNumber::one(), BigNumber::zero());
}
let mut old_r = a;
let mut r = b;
let mut old_s = BigNumber::one();
let mut s = BigNumber::zero();
let mut old_t = BigNumber::zero();
let mut t = BigNumber::one();
while !r.is_zero() {
let q = old_r.div(&r).unwrap_or_else(|_| BigNumber::zero());
let new_r = old_r.sub(&q.mul(&r));
old_r = r;
r = new_r;
let new_s = old_s.sub(&q.mul(&s));
old_s = s;
s = new_s;
let new_t = old_t.sub(&q.mul(&t));
old_t = t;
t = new_t;
}
(old_r, old_s, old_t)
}
pub fn invm(&self, m: &BigNumber) -> Result<BigNumber, PrimitivesError> {
if m.is_zero() || m.is_one() {
return Err(PrimitivesError::ArithmeticError(
"modulus must be greater than 1".to_string(),
));
}
let a = self.umod(m)?;
if a.is_zero() {
return Err(PrimitivesError::ArithmeticError(
"no inverse for zero".to_string(),
));
}
let (gcd, x, _) = a.egcd(m);
if !gcd.is_one() {
return Err(PrimitivesError::ArithmeticError(
"no modular inverse exists (gcd != 1)".to_string(),
));
}
let result = x.umod(m)?;
Ok(result)
}
pub fn _invmp(&self, m: &BigNumber) -> Result<BigNumber, PrimitivesError> {
self.invm(m)
}
pub fn to_red(&self, ctx: Arc<ReductionContext>) -> BigNumber {
let reduced = ctx.convert_to(self);
let mut r = reduced;
r.red = Some(ctx);
r
}
pub fn force_red(&self, ctx: Arc<ReductionContext>) -> BigNumber {
let mut r = self.clone();
r.red = Some(ctx);
r
}
pub fn from_red(&self) -> BigNumber {
let mut r = self.clone();
r.red = None;
r
}
pub fn red_add(&self, other: &BigNumber) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_add requires reduction context".to_string())
})?;
Ok(ctx.add(self, other))
}
pub fn red_iadd(&mut self, other: &BigNumber) -> Result<&mut Self, PrimitivesError> {
let ctx = self
.red
.as_ref()
.ok_or_else(|| {
PrimitivesError::ArithmeticError("red_iadd requires reduction context".to_string())
})?
.clone();
let result = ctx.add(self, other);
self.limbs = result.limbs;
self.negative = result.negative;
Ok(self)
}
pub fn red_sub(&self, other: &BigNumber) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_sub requires reduction context".to_string())
})?;
Ok(ctx.sub(self, other))
}
pub fn red_mul(&self, other: &BigNumber) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_mul requires reduction context".to_string())
})?;
Ok(ctx.mul(self, other))
}
pub fn red_sqr(&self) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_sqr requires reduction context".to_string())
})?;
Ok(ctx.sqr(self))
}
pub fn red_neg(&self) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_neg requires reduction context".to_string())
})?;
Ok(ctx.neg(self))
}
pub fn red_invm(&self) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_invm requires reduction context".to_string())
})?;
Ok(ctx.invm(self))
}
pub fn red_pow(&self, exp: &BigNumber) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_pow requires reduction context".to_string())
})?;
Ok(ctx.pow(self, exp))
}
pub fn red_sqrt(&self) -> Result<BigNumber, PrimitivesError> {
let ctx = self.red.as_ref().ok_or_else(|| {
PrimitivesError::ArithmeticError("red_sqrt requires reduction context".to_string())
})?;
Ok(ctx.sqrt(self))
}
pub fn copy_from(&mut self, src: &BigNumber) {
self.limbs = src.limbs.clone();
self.negative = src.negative;
self.red = src.red.clone();
}
pub fn move_to(dest: &mut BigNumber, src: &BigNumber) {
dest.copy_from(src);
}
pub fn setn(&mut self, bit: usize, val: bool) -> &mut Self {
let limb_index = bit / 64;
let bit_index = bit % 64;
let mut limbs_vec = self.limbs.to_vec();
while limbs_vec.len() <= limb_index {
limbs_vec.push(0);
}
if val {
limbs_vec[limb_index] |= 1u64 << bit_index;
} else {
limbs_vec[limb_index] &= !(1u64 << bit_index);
}
self.limbs = SmallLimbs::from_limbs(&limbs_vec);
self
}
pub fn notn(&self, width: usize) -> BigNumber {
let mut r = self.clone();
r.inotn(width);
r
}
pub fn inotn(&mut self, width: usize) -> &mut Self {
if width == 0 {
self.limbs = SmallLimbs::zero();
self.negative = false;
return self;
}
let full_limbs = width / 64;
let remaining_bits = width % 64;
let total_limbs = full_limbs + if remaining_bits > 0 { 1 } else { 0 };
let mut limbs_vec = self.limbs.to_vec();
while limbs_vec.len() < total_limbs {
limbs_vec.push(0);
}
for limb in limbs_vec.iter_mut().take(full_limbs) {
*limb = !*limb;
}
if remaining_bits > 0 {
let mask = (1u64 << remaining_bits) - 1;
limbs_vec[full_limbs] = (!limbs_vec[full_limbs]) & mask;
}
for limb in limbs_vec.iter_mut().skip(total_limbs) {
*limb = 0;
}
self.limbs = SmallLimbs::from_limbs(&limbs_vec);
self.negative = false;
self
}
pub fn expand(&mut self, _size: usize) -> &mut Self {
self
}
pub fn to_twos(&self, width: usize) -> BigNumber {
if !self.is_neg() {
return self.maskn(width);
}
let mut pow_val = BigNumber::one();
pow_val.iushln(width);
pow_val.iadd(self);
pow_val.maskn(width)
}
pub fn from_twos(&self, width: usize) -> BigNumber {
if width == 0 {
return self.clone();
}
if !self.testn(width - 1) {
return self.clone();
}
let mut pow_val = BigNumber::one();
pow_val.iushln(width);
self.sub(&pow_val)
}
}
fn add_magnitudes(a: &[u64], b: &[u64]) -> Vec<u64> {
let max_len = a.len().max(b.len());
let mut result = Vec::with_capacity(max_len + 1);
let mut carry: u64 = 0;
for i in 0..max_len {
let av = if i < a.len() { a[i] } else { 0 };
let bv = if i < b.len() { b[i] } else { 0 };
let (sum1, c1) = av.overflowing_add(bv);
let (sum2, c2) = sum1.overflowing_add(carry);
result.push(sum2);
carry = (c1 as u64) + (c2 as u64);
}
if carry > 0 {
result.push(carry);
}
result
}
fn sub_magnitudes(a: &[u64], b: &[u64]) -> Vec<u64> {
let mut result = Vec::with_capacity(a.len());
let mut borrow: u64 = 0;
for i in 0..a.len() {
let av = a[i];
let bv = if i < b.len() { b[i] } else { 0 };
let (diff1, c1) = av.overflowing_sub(bv);
let (diff2, c2) = diff1.overflowing_sub(borrow);
result.push(diff2);
borrow = (c1 as u64) + (c2 as u64);
}
result
}
#[inline(always)]
fn mul_2x2(a: &[u64; 2], b: &[u64; 2]) -> [u64; 4] {
let mut result = [0u64; 4];
for i in 0..2 {
let mut carry: u128 = 0;
for j in 0..2 {
let prod = (a[i] as u128) * (b[j] as u128) + (result[i + j] as u128) + carry;
result[i + j] = prod as u64;
carry = prod >> 64;
}
result[i + 2] = carry as u64;
}
result
}
#[inline]
pub(crate) fn mul_4x4(a: &[u64; 4], b: &[u64; 4]) -> [u64; 8] {
let a_lo = [a[0], a[1]];
let a_hi = [a[2], a[3]];
let b_lo = [b[0], b[1]];
let b_hi = [b[2], b[3]];
let z0 = mul_2x2(&a_lo, &b_lo); let z2 = mul_2x2(&a_hi, &b_hi);
let a_sum_0 = (a_lo[0] as u128) + (a_hi[0] as u128);
let a_sum_1 = (a_lo[1] as u128) + (a_hi[1] as u128) + (a_sum_0 >> 64);
let a_sum_2 = a_sum_1 >> 64;
let a_sum = [a_sum_0 as u64, a_sum_1 as u64, a_sum_2 as u64];
let b_sum_0 = (b_lo[0] as u128) + (b_hi[0] as u128);
let b_sum_1 = (b_lo[1] as u128) + (b_hi[1] as u128) + (b_sum_0 >> 64);
let b_sum_2 = b_sum_1 >> 64;
let b_sum = [b_sum_0 as u64, b_sum_1 as u64, b_sum_2 as u64];
let mut z1_full = [0u64; 6];
for i in 0..3 {
let mut carry: u128 = 0;
for j in 0..3 {
let prod = (a_sum[i] as u128) * (b_sum[j] as u128) + (z1_full[i + j] as u128) + carry;
z1_full[i + j] = prod as u64;
carry = prod >> 64;
}
z1_full[i + 3] = carry as u64;
}
let mut borrow: u128 = 0;
for i in 0..4 {
let a_val = z1_full[i] as u128;
let b_val = (z0[i] as u128) + borrow;
if a_val >= b_val {
z1_full[i] = (a_val - b_val) as u64;
borrow = 0;
} else {
z1_full[i] = (a_val + (1u128 << 64) - b_val) as u64;
borrow = 1;
}
}
for item in z1_full.iter_mut().skip(4) {
let a_val = *item as u128;
if a_val >= borrow {
*item = (a_val - borrow) as u64;
borrow = 0;
} else {
*item = (a_val + (1u128 << 64) - borrow) as u64;
borrow = 1;
}
}
borrow = 0;
for i in 0..4 {
let a_val = z1_full[i] as u128;
let b_val = (z2[i] as u128) + borrow;
if a_val >= b_val {
z1_full[i] = (a_val - b_val) as u64;
borrow = 0;
} else {
z1_full[i] = (a_val + (1u128 << 64) - b_val) as u64;
borrow = 1;
}
}
for item in z1_full.iter_mut().skip(4) {
let a_val = *item as u128;
if a_val >= borrow {
*item = (a_val - borrow) as u64;
borrow = 0;
} else {
*item = (a_val + (1u128 << 64) - borrow) as u64;
borrow = 1;
}
}
let mut result = [0u64; 8];
result[0] = z0[0];
result[1] = z0[1];
result[2] = z0[2];
result[3] = z0[3];
let mut carry: u128 = 0;
for i in 0..6 {
let sum = (result[i + 2] as u128) + (z1_full[i] as u128) + carry;
result[i + 2] = sum as u64;
carry = sum >> 64;
}
carry = 0;
for i in 0..4 {
let sum = (result[i + 4] as u128) + (z2[i] as u128) + carry;
result[i + 4] = sum as u64;
carry = sum >> 64;
}
result
}
#[inline]
fn sqr_4x4(a: &[u64; 4]) -> [u64; 8] {
mul_4x4(a, a)
}
fn mul_magnitudes(a: &[u64], b: &[u64]) -> Vec<u64> {
if a.is_empty() || b.is_empty() {
return vec![];
}
if a.len() == 4 && b.len() == 4 {
let a4: [u64; 4] = [a[0], a[1], a[2], a[3]];
let b4: [u64; 4] = [b[0], b[1], b[2], b[3]];
return mul_4x4(&a4, &b4).to_vec();
}
let n = a.len().max(b.len());
if n <= 32 {
return schoolbook_mul(a, b);
}
karatsuba_mul(a, b)
}
fn schoolbook_mul(a: &[u64], b: &[u64]) -> Vec<u64> {
let mut result = vec![0u64; a.len() + b.len()];
for i in 0..a.len() {
let mut carry: u128 = 0;
for j in 0..b.len() {
let prod = (a[i] as u128) * (b[j] as u128) + (result[i + j] as u128) + carry;
result[i + j] = prod as u64;
carry = prod >> 64;
}
if carry > 0 {
result[i + b.len()] += carry as u64;
}
}
result
}
fn add_at(a: &mut [u64], b: &[u64], offset: usize) {
let mut carry: u64 = 0;
for (i, &b_val) in b.iter().enumerate() {
let pos = offset + i;
let (sum1, c1) = a[pos].overflowing_add(b_val);
let (sum2, c2) = sum1.overflowing_add(carry);
a[pos] = sum2;
carry = (c1 as u64) + (c2 as u64);
}
let mut pos = offset + b.len();
while carry > 0 && pos < a.len() {
let (sum, c) = a[pos].overflowing_add(carry);
a[pos] = sum;
carry = c as u64;
pos += 1;
}
}
#[allow(dead_code)]
fn sub_at(a: &mut [u64], b: &[u64], offset: usize) {
let mut borrow: u64 = 0;
for (i, &b_val) in b.iter().enumerate() {
let pos = offset + i;
let (diff1, b1) = a[pos].overflowing_sub(b_val);
let (diff2, b2) = diff1.overflowing_sub(borrow);
a[pos] = diff2;
borrow = (b1 as u64) + (b2 as u64);
}
let mut pos = offset + b.len();
while borrow > 0 && pos < a.len() {
let (diff, b1) = a[pos].overflowing_sub(borrow);
a[pos] = diff;
borrow = b1 as u64;
pos += 1;
}
}
fn karatsuba_mul(a: &[u64], b: &[u64]) -> Vec<u64> {
let n = a.len().max(b.len());
if n <= 32 {
return schoolbook_mul(a, b);
}
let half = n / 2;
let (a0, a1) = if a.len() <= half {
(a, &[][..])
} else {
(&a[..half], &a[half..])
};
let (b0, b1) = if b.len() <= half {
(b, &[][..])
} else {
(&b[..half], &b[half..])
};
let z0 = karatsuba_mul(a0, b0);
let z2 = if a1.is_empty() || b1.is_empty() {
vec![]
} else {
karatsuba_mul(a1, b1)
};
let sum_a = limb_add(a0, a1);
let sum_b = limb_add(b0, b1);
let z1_full = karatsuba_mul(&sum_a, &sum_b);
let mut z1 = z1_full;
limb_sub_inplace(&mut z1, &z0);
limb_sub_inplace(&mut z1, &z2);
let result_len = a.len() + b.len();
let mut result = vec![0u64; result_len];
add_at(&mut result, &z0, 0);
add_at(&mut result, &z1, half);
if !z2.is_empty() {
add_at(&mut result, &z2, 2 * half);
}
result
}
fn limb_add(a: &[u64], b: &[u64]) -> Vec<u64> {
let max_len = a.len().max(b.len());
let mut result = vec![0u64; max_len + 1];
let mut carry: u64 = 0;
for i in 0..max_len {
let av = if i < a.len() { a[i] } else { 0 };
let bv = if i < b.len() { b[i] } else { 0 };
let (s1, c1) = av.overflowing_add(bv);
let (s2, c2) = s1.overflowing_add(carry);
result[i] = s2;
carry = (c1 as u64) + (c2 as u64);
}
if carry > 0 {
result[max_len] = carry;
} else {
result.pop();
}
result
}
fn limb_sub_inplace(a: &mut [u64], b: &[u64]) {
let mut borrow: u64 = 0;
for i in 0..b.len() {
if i >= a.len() {
break;
}
let (d1, b1) = a[i].overflowing_sub(b[i]);
let (d2, b2) = d1.overflowing_sub(borrow);
a[i] = d2;
borrow = (b1 as u64) + (b2 as u64);
}
let mut pos = b.len();
while borrow > 0 && pos < a.len() {
let (d, b1) = a[pos].overflowing_sub(borrow);
a[pos] = d;
borrow = b1 as u64;
pos += 1;
}
}
fn div_mod_unsigned(numerator: &[u64], denominator: &[u64]) -> (Vec<u64>, Vec<u64>) {
let n_bits = {
let mut bits = 0;
for (i, &limb) in numerator.iter().enumerate() {
if limb != 0 {
bits = i * 64 + (64 - limb.leading_zeros() as usize);
}
}
bits
};
let d_bits = {
let mut bits = 0;
for (i, &limb) in denominator.iter().enumerate() {
if limb != 0 {
bits = i * 64 + (64 - limb.leading_zeros() as usize);
}
}
bits
};
if d_bits == 0 {
return (vec![], vec![]);
}
if n_bits < d_bits {
return (vec![], numerator.to_vec());
}
if denominator.len() == 1 && denominator[0] != 0 {
let d = denominator[0] as u128;
let mut quotient = vec![0u64; numerator.len()];
let mut remainder: u128 = 0;
for i in (0..numerator.len()).rev() {
let cur = (remainder << 64) | (numerator[i] as u128);
quotient[i] = (cur / d) as u64;
remainder = cur % d;
}
return (quotient, vec![remainder as u64]);
}
let shift = n_bits - d_bits;
let mut remainder = numerator.to_vec();
let q_limbs = shift / 64 + 1;
let mut quotient = vec![0u64; q_limbs];
for i in (0..=shift).rev() {
let limb_shift = i / 64;
let bit_shift = i % 64;
let can_sub = can_subtract_shifted(&remainder, denominator, limb_shift, bit_shift);
if can_sub {
quotient[i / 64] |= 1u64 << (i % 64);
subtract_shifted(&mut remainder, denominator, limb_shift, bit_shift);
}
}
(quotient, remainder)
}
fn can_subtract_shifted(a: &[u64], b: &[u64], limb_shift: usize, bit_shift: usize) -> bool {
let shifted_len = b.len() + limb_shift + 1;
for i in (0..shifted_len.max(a.len())).rev() {
let a_val = if i < a.len() { a[i] } else { 0 };
let b_val = get_shifted_limb(b, limb_shift, bit_shift, i);
if a_val > b_val {
return true;
}
if a_val < b_val {
return false;
}
}
true }
fn get_shifted_limb(b: &[u64], limb_shift: usize, bit_shift: usize, i: usize) -> u64 {
if i < limb_shift {
return 0;
}
let src_idx = i - limb_shift;
if bit_shift == 0 {
if src_idx < b.len() {
b[src_idx]
} else {
0
}
} else {
let lo = if src_idx < b.len() {
b[src_idx] << bit_shift
} else {
0
};
let hi = if src_idx > 0 && (src_idx - 1) < b.len() {
b[src_idx - 1] >> (64 - bit_shift)
} else {
0
};
lo | hi
}
}
fn subtract_shifted(a: &mut Vec<u64>, b: &[u64], limb_shift: usize, bit_shift: usize) {
let shifted_len = b.len() + limb_shift + 1;
while a.len() < shifted_len {
a.push(0);
}
let mut borrow: u64 = 0;
for (i, limb) in a.iter_mut().enumerate() {
let b_val = get_shifted_limb(b, limb_shift, bit_shift, i);
let (diff1, c1) = limb.overflowing_sub(b_val);
let (diff2, c2) = diff1.overflowing_sub(borrow);
*limb = diff2;
borrow = (c1 as u64) + (c2 as u64);
}
while a.len() > 1 && a[a.len() - 1] == 0 {
a.pop();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(serde::Deserialize)]
#[allow(dead_code)]
struct ConversionVector {
hex: String,
#[serde(default)]
value: Option<i64>,
#[serde(default)]
bytes_be: Option<Vec<u8>>,
#[serde(default)]
bytes_le: Option<Vec<u8>>,
#[serde(default)]
bytes_be_32: Option<Vec<u8>>,
bit_length: usize,
byte_length: usize,
is_zero: bool,
is_odd: bool,
#[serde(default)]
note: Option<String>,
}
#[derive(serde::Deserialize)]
struct ArithmeticVector {
op: String,
#[serde(default)]
a: Option<String>,
#[serde(default)]
b: Option<String>,
#[serde(default)]
m: Option<String>,
expected: String,
#[serde(default)]
note: Option<String>,
}
#[derive(serde::Deserialize)]
struct TestVectors {
conversions: Vec<ConversionVector>,
arithmetic: Vec<ArithmeticVector>,
}
fn load_test_vectors() -> TestVectors {
let json = include_str!("../../test-vectors/big_number.json");
serde_json::from_str(json).expect("failed to parse big_number.json")
}
#[test]
fn test_from_hex_zero() {
let n = BigNumber::from_hex("0").unwrap();
assert!(n.is_zero());
assert!(!n.is_negative());
assert_eq!(n.to_hex(), "0");
}
#[test]
fn test_from_hex_ff() {
let n = BigNumber::from_hex("ff").unwrap();
assert!(!n.is_zero());
assert_eq!(n.to_hex(), "ff");
assert_eq!(n.to_number(), Some(255));
}
#[test]
fn test_from_hex_secp256k1_p() {
let hex = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f";
let n = BigNumber::from_hex(hex).unwrap();
assert!(!n.is_zero());
assert!(n.is_odd());
assert_eq!(n.bit_length(), 256);
assert_eq!(n.to_hex(), hex);
assert!(n.is_inline());
}
#[test]
fn test_from_number() {
let z = BigNumber::from_number(0);
assert!(z.is_zero());
let neg = BigNumber::from_number(-1);
assert!(neg.is_negative());
assert_eq!(neg.to_number(), Some(-1));
let pos = BigNumber::from_number(42);
assert_eq!(pos.to_number(), Some(42));
}
#[test]
fn test_hex_roundtrip_vectors() {
let vectors = load_test_vectors();
for v in &vectors.conversions {
let n = BigNumber::from_hex(&v.hex).unwrap();
let hex_out = n.to_hex();
let expected = v.hex.trim_start_matches('0');
let expected = if expected.is_empty() { "0" } else { expected };
assert_eq!(hex_out, expected, "hex roundtrip failed for {:?}", v.note);
}
}
#[test]
fn test_properties_from_vectors() {
let vectors = load_test_vectors();
for v in &vectors.conversions {
let n = BigNumber::from_hex(&v.hex).unwrap();
assert_eq!(
n.bit_length(),
v.bit_length,
"bit_length mismatch for {:?}",
v.note
);
assert_eq!(
n.byte_length(),
v.byte_length,
"byte_length mismatch for {:?}",
v.note
);
assert_eq!(n.is_zero(), v.is_zero, "is_zero mismatch for {:?}", v.note);
assert_eq!(n.is_odd(), v.is_odd, "is_odd mismatch for {:?}", v.note);
}
}
#[test]
fn test_to_array_big_endian_32() {
let hex = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
let n = BigNumber::from_hex(hex).unwrap();
let arr = n.to_array(Endian::Big, Some(32));
assert_eq!(arr.len(), 32);
assert!(arr.iter().all(|&b| b == 255));
}
#[test]
fn test_to_array_little_endian_minimal() {
let n = BigNumber::from_hex("ff").unwrap();
let arr = n.to_array(Endian::Little, None);
assert_eq!(arr, vec![255]);
}
#[test]
fn test_to_array_big_endian_padded() {
let n = BigNumber::from_hex("ff").unwrap();
let arr = n.to_array(Endian::Big, Some(4));
assert_eq!(arr, vec![0, 0, 0, 255]);
}
#[test]
fn test_to_array_vectors() {
let vectors = load_test_vectors();
for v in &vectors.conversions {
let n = BigNumber::from_hex(&v.hex).unwrap();
if let Some(ref be) = v.bytes_be {
let arr = n.to_array(Endian::Big, None);
assert_eq!(arr, *be, "to_array(Big) mismatch for {:?}", v.note);
}
if let Some(ref le) = v.bytes_le {
let arr = n.to_array(Endian::Little, None);
assert_eq!(arr, *le, "to_array(Little) mismatch for {:?}", v.note);
}
if let Some(ref be32) = v.bytes_be_32 {
let arr = n.to_array(Endian::Big, Some(32));
assert_eq!(arr, *be32, "to_array(Big, 32) mismatch for {:?}", v.note);
}
}
}
#[test]
fn test_from_bytes_roundtrip() {
let hex = "deadbeef";
let n = BigNumber::from_hex(hex).unwrap();
let bytes = n.to_array(Endian::Big, None);
let n2 = BigNumber::from_bytes(&bytes, Endian::Big);
assert_eq!(n2.to_hex(), hex);
let le_bytes = n.to_array(Endian::Little, None);
let n3 = BigNumber::from_bytes(&le_bytes, Endian::Little);
assert_eq!(n3.to_hex(), hex);
}
#[test]
fn test_cmp() {
let a = BigNumber::from_number(5);
let b = BigNumber::from_number(3);
assert_eq!(BigNumber::cmp(&a, &b), 1);
assert_eq!(BigNumber::cmp(&b, &a), -1);
assert_eq!(BigNumber::cmp(&a, &a), 0);
let neg = BigNumber::from_number(-1);
assert_eq!(BigNumber::cmp(&neg, &a), -1);
assert_eq!(BigNumber::cmp(&a, &neg), 1);
}
#[test]
fn test_is_odd() {
assert!(BigNumber::from_number(1).is_odd());
assert!(!BigNumber::from_number(2).is_odd());
assert!(!BigNumber::from_number(0).is_odd());
}
#[test]
fn test_bit_length() {
assert_eq!(BigNumber::from_number(0).bit_length(), 0);
assert_eq!(BigNumber::from_number(1).bit_length(), 1);
assert_eq!(BigNumber::from_number(255).bit_length(), 8);
assert_eq!(BigNumber::from_number(256).bit_length(), 9);
}
#[test]
fn test_byte_length() {
assert_eq!(BigNumber::from_number(0).byte_length(), 0);
assert_eq!(BigNumber::from_number(255).byte_length(), 1);
assert_eq!(BigNumber::from_number(256).byte_length(), 2);
}
#[test]
fn test_clone_independence() {
let a = BigNumber::from_number(42);
let mut b = a.clone();
b.iadd(&BigNumber::from_number(1));
assert_eq!(a.to_number(), Some(42));
assert_eq!(b.to_number(), Some(43));
}
#[test]
fn test_small_limbs_inline() {
let n =
BigNumber::from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
.unwrap();
assert!(n.is_inline());
let big = BigNumber::from_hex(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
)
.unwrap();
assert!(!big.is_inline());
}
#[test]
fn test_andln() {
let n = BigNumber::from_hex("ff").unwrap();
assert_eq!(n.andln(4), 0xf); assert_eq!(n.andln(1), 1); assert_eq!(n.andln(8), 0xff);
let z = BigNumber::zero();
assert_eq!(z.andln(4), 0);
}
#[test]
fn test_from_array_endians() {
let be_bytes = vec![0xde, 0xad, 0xbe, 0xef];
let n_be = BigNumber::from_array(&be_bytes, Endian::Big);
assert_eq!(n_be.to_hex(), "deadbeef");
let le_bytes = vec![0xef, 0xbe, 0xad, 0xde];
let n_le = BigNumber::from_array(&le_bytes, Endian::Little);
assert_eq!(n_le.to_hex(), "deadbeef");
}
#[test]
fn test_zero_to_array() {
let z = BigNumber::zero();
let arr = z.to_array(Endian::Big, Some(32));
assert_eq!(arr.len(), 32);
assert!(arr.iter().all(|&b| b == 0));
let arr_none = z.to_array(Endian::Big, None);
assert_eq!(arr_none.len(), 0);
}
#[test]
fn test_testn() {
let n = BigNumber::from_number(5); assert!(n.testn(0)); assert!(!n.testn(1)); assert!(n.testn(2)); assert!(!n.testn(3)); }
#[test]
fn test_neg_and_abs() {
let n = BigNumber::from_number(42);
let neg = n.neg();
assert!(neg.is_negative());
assert_eq!(neg.to_number(), Some(-42));
let abs_val = neg.abs();
assert!(!abs_val.is_negative());
assert_eq!(abs_val.to_number(), Some(42));
}
#[test]
fn test_add_basic() {
assert!(BigNumber::zero().add(&BigNumber::zero()).is_zero());
assert_eq!(
BigNumber::from_number(1)
.add(&BigNumber::from_number(1))
.to_number(),
Some(2)
);
let ff = BigNumber::from_hex("ff").unwrap();
let one = BigNumber::from_number(1);
assert_eq!(ff.add(&one).to_hex(), "100");
}
#[test]
fn test_add_256bit_overflow() {
let max256 =
BigNumber::from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
.unwrap();
let one = BigNumber::from_number(1);
let result = max256.add(&one);
assert_eq!(result.bit_length(), 257);
assert!(!result.is_inline()); }
#[test]
fn test_add_negative_positive() {
let neg5 = BigNumber::from_number(-5);
let pos3 = BigNumber::from_number(3);
assert_eq!(neg5.add(&pos3).to_number(), Some(-2));
assert_eq!(pos3.add(&neg5).to_number(), Some(-2));
}
#[test]
fn test_sub_basic() {
assert_eq!(
BigNumber::from_number(5)
.sub(&BigNumber::from_number(3))
.to_number(),
Some(2)
);
assert_eq!(
BigNumber::from_number(3)
.sub(&BigNumber::from_number(5))
.to_number(),
Some(-2)
);
assert_eq!(
BigNumber::from_number(0)
.sub(&BigNumber::from_number(1))
.to_number(),
Some(-1)
);
}
#[test]
fn test_mul_basic() {
assert_eq!(
BigNumber::from_number(2)
.mul(&BigNumber::from_number(3))
.to_number(),
Some(6)
);
assert!(BigNumber::from_number(0)
.mul(&BigNumber::from_number(100))
.is_zero());
}
#[test]
fn test_mul_large() {
let ff = BigNumber::from_hex("ff").unwrap();
let result = ff.mul(&ff);
assert_eq!(result.to_hex(), "fe01"); }
#[test]
fn test_sqr() {
let n = BigNumber::from_number(5);
assert_eq!(n.sqr().to_number(), Some(25));
assert_eq!(n.sqr().to_hex(), n.mul(&n).to_hex());
}
#[test]
fn test_div_basic() {
assert_eq!(
BigNumber::from_number(10)
.div(&BigNumber::from_number(3))
.unwrap()
.to_number(),
Some(3)
);
assert_eq!(
BigNumber::from_number(10)
.div(&BigNumber::from_number(2))
.unwrap()
.to_number(),
Some(5)
);
}
#[test]
fn test_div_by_zero() {
let result = BigNumber::from_number(10).div(&BigNumber::zero());
assert!(result.is_err());
}
#[test]
fn test_umod_basic() {
assert_eq!(
BigNumber::from_number(10)
.umod(&BigNumber::from_number(3))
.unwrap()
.to_number(),
Some(1)
);
assert_eq!(
BigNumber::from_hex("ff")
.unwrap()
.umod(&BigNumber::from_number(16))
.unwrap()
.to_number(),
Some(15)
);
}
#[test]
fn test_ushln() {
let n = BigNumber::from_number(1);
assert_eq!(n.ushln(1).to_number(), Some(2));
let shifted = n.ushln(64);
assert_eq!(shifted.bit_length(), 65);
}
#[test]
fn test_ushrn() {
let n = BigNumber::from_number(256);
assert_eq!(n.ushrn(1).to_number(), Some(128));
let big = BigNumber::from_number(1).ushln(64);
assert_eq!(big.ushrn(64).to_number(), Some(1));
}
#[test]
fn test_iushrn_in_place() {
let mut n = BigNumber::from_number(8);
n.iushrn(1);
assert_eq!(n.to_number(), Some(4));
n.iushrn(1);
assert_eq!(n.to_number(), Some(2));
n.iushrn(1);
assert_eq!(n.to_number(), Some(1));
}
#[test]
fn test_maskn() {
let n = BigNumber::from_hex("ff").unwrap();
assert_eq!(n.maskn(4).to_number(), Some(15)); assert_eq!(n.maskn(8).to_number(), Some(255)); }
#[test]
fn test_gcd() {
assert_eq!(
BigNumber::from_number(12)
.gcd(&BigNumber::from_number(8))
.to_number(),
Some(4)
);
assert_eq!(
BigNumber::from_number(17)
.gcd(&BigNumber::from_number(13))
.to_number(),
Some(1)
);
}
#[test]
fn test_egcd() {
let a = BigNumber::from_number(12);
let b = BigNumber::from_number(8);
let (gcd, x, y) = a.egcd(&b);
assert_eq!(gcd.to_number(), Some(4));
let check = a.mul(&x).add(&b.mul(&y));
assert_eq!(check.to_number(), gcd.to_number());
}
#[test]
fn test_invm_small() {
let a = BigNumber::from_number(3);
let m = BigNumber::from_number(11);
let inv = a.invm(&m).unwrap();
assert_eq!(inv.to_number(), Some(4));
let check = a.mul(&inv).umod(&m).unwrap();
assert_eq!(check.to_number(), Some(1));
}
#[test]
fn test_invm_secp256k1() {
let a = BigNumber::from_number(7);
let m =
BigNumber::from_hex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
.unwrap();
let inv = a.invm(&m).unwrap();
let check = a.mul(&inv).umod(&m).unwrap();
assert!(check.is_one(), "7 * 7^-1 mod n should be 1");
}
#[test]
fn test_arithmetic_vectors() {
let vectors = load_test_vectors();
for v in &vectors.arithmetic {
match v.op.as_str() {
"add" => {
let a = BigNumber::from_hex(v.a.as_ref().unwrap()).unwrap();
let b = BigNumber::from_hex(v.b.as_ref().unwrap()).unwrap();
let result = a.add(&b);
assert_eq!(
result.to_hex(),
v.expected,
"add({}, {}) failed",
v.a.as_ref().unwrap(),
v.b.as_ref().unwrap()
);
}
"sub" => {
let a = BigNumber::from_hex(v.a.as_ref().unwrap()).unwrap();
let b = BigNumber::from_hex(v.b.as_ref().unwrap()).unwrap();
let result = a.sub(&b);
assert_eq!(
result.to_hex(),
v.expected,
"sub({}, {}) failed",
v.a.as_ref().unwrap(),
v.b.as_ref().unwrap()
);
}
"mul" => {
let a = BigNumber::from_hex(v.a.as_ref().unwrap()).unwrap();
let b = BigNumber::from_hex(v.b.as_ref().unwrap()).unwrap();
let result = a.mul(&b);
assert_eq!(
result.to_hex(),
v.expected,
"mul({}, {}) failed",
v.a.as_ref().unwrap(),
v.b.as_ref().unwrap()
);
}
"div" => {
let a = BigNumber::from_hex(v.a.as_ref().unwrap()).unwrap();
let b = BigNumber::from_hex(v.b.as_ref().unwrap()).unwrap();
let result = a.div(&b).unwrap();
assert_eq!(
result.to_hex(),
v.expected,
"div({}, {}) failed",
v.a.as_ref().unwrap(),
v.b.as_ref().unwrap()
);
}
"mod" => {
let a = BigNumber::from_hex(v.a.as_ref().unwrap()).unwrap();
let b = BigNumber::from_hex(v.b.as_ref().unwrap()).unwrap();
let result = a.umod(&b).unwrap();
assert_eq!(
result.to_hex(),
v.expected,
"mod({}, {}) failed",
v.a.as_ref().unwrap(),
v.b.as_ref().unwrap()
);
}
"gcd" => {
let a = BigNumber::from_hex(v.a.as_ref().unwrap()).unwrap();
let b = BigNumber::from_hex(v.b.as_ref().unwrap()).unwrap();
let result = a.gcd(&b);
assert_eq!(
result.to_hex(),
v.expected,
"gcd({}, {}) failed",
v.a.as_ref().unwrap(),
v.b.as_ref().unwrap()
);
}
"invm" => {
let a = BigNumber::from_hex(v.a.as_ref().unwrap()).unwrap();
let m = BigNumber::from_hex(v.m.as_ref().unwrap()).unwrap();
let result = a.invm(&m).unwrap();
assert_eq!(
result.to_hex(),
v.expected,
"invm({}, {}) failed: {:?}",
v.a.as_ref().unwrap(),
v.m.as_ref().unwrap(),
v.note
);
let check = a.mul(&result).umod(&m).unwrap();
assert!(check.is_one(), "invm verification failed for {:?}", v.note);
}
_ => {} }
}
}
#[test]
fn test_to_red_from_red() {
let ctx =
crate::primitives::reduction_context::ReductionContext::new(BigNumber::from_number(7));
let a = BigNumber::from_number(10);
let a_red = a.to_red(ctx.clone());
assert!(a_red.red.is_some());
assert_eq!(a_red.to_number(), Some(3));
let a_back = a_red.from_red();
assert!(a_back.red.is_none());
assert_eq!(a_back.to_number(), Some(3));
}
#[test]
fn test_pow() {
let base = BigNumber::from_number(2);
let exp = BigNumber::from_number(10);
assert_eq!(base.pow(&exp).to_number(), Some(1024));
assert!(BigNumber::from_number(42).pow(&BigNumber::zero()).is_one());
}
#[test]
fn test_negative_arithmetic() {
let neg3 = BigNumber::from_number(-3);
let neg4 = BigNumber::from_number(-4);
assert_eq!(neg3.mul(&neg4).to_number(), Some(12));
let pos4 = BigNumber::from_number(4);
assert_eq!(neg3.mul(&pos4).to_number(), Some(-12));
}
#[test]
fn test_large_values_heap() {
let a =
BigNumber::from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
.unwrap();
let result = a.mul(&a);
assert!(!result.is_inline()); assert!(result.bit_length() > 256);
}
#[test]
fn test_script_num_zero() {
let zero = BigNumber::zero();
let encoded = zero.to_script_num();
assert!(encoded.is_empty(), "zero should encode to empty vec");
let decoded = BigNumber::from_script_num(&encoded, true, None).unwrap();
assert!(decoded.is_zero());
}
#[test]
fn test_script_num_positive_small() {
let one = BigNumber::from_number(1);
assert_eq!(one.to_script_num(), vec![0x01]);
let rt = BigNumber::from_script_num(&[0x01], false, None).unwrap();
assert_eq!(rt.to_number(), Some(1));
let n = BigNumber::from_number(127);
assert_eq!(n.to_script_num(), vec![0x7f]);
let rt = BigNumber::from_script_num(&[0x7f], false, None).unwrap();
assert_eq!(rt.to_number(), Some(127));
}
#[test]
fn test_script_num_128() {
let n = BigNumber::from_number(128);
assert_eq!(n.to_script_num(), vec![0x80, 0x00]);
let rt = BigNumber::from_script_num(&[0x80, 0x00], false, None).unwrap();
assert_eq!(rt.to_number(), Some(128));
}
#[test]
fn test_script_num_255() {
let n = BigNumber::from_number(255);
assert_eq!(n.to_script_num(), vec![0xff, 0x00]);
let rt = BigNumber::from_script_num(&[0xff, 0x00], false, None).unwrap();
assert_eq!(rt.to_number(), Some(255));
}
#[test]
fn test_script_num_256() {
let n = BigNumber::from_number(256);
assert_eq!(n.to_script_num(), vec![0x00, 0x01]);
let rt = BigNumber::from_script_num(&[0x00, 0x01], false, None).unwrap();
assert_eq!(rt.to_number(), Some(256));
}
#[test]
fn test_script_num_negative() {
let n = BigNumber::from_number(-1);
assert_eq!(n.to_script_num(), vec![0x81]);
let rt = BigNumber::from_script_num(&[0x81], false, None).unwrap();
assert_eq!(rt.to_number(), Some(-1));
let n = BigNumber::from_number(-128);
assert_eq!(n.to_script_num(), vec![0x80, 0x80]);
let rt = BigNumber::from_script_num(&[0x80, 0x80], false, None).unwrap();
assert_eq!(rt.to_number(), Some(-128));
let n = BigNumber::from_number(-256);
assert_eq!(n.to_script_num(), vec![0x00, 0x81]);
let rt = BigNumber::from_script_num(&[0x00, 0x81], false, None).unwrap();
assert_eq!(rt.to_number(), Some(-256));
}
#[test]
fn test_script_num_roundtrip_comprehensive() {
let values: Vec<i64> = vec![
0, 1, -1, 2, -2, 127, -127, 128, -128, 255, -255, 256, -256, 1000, -1000, 32767,
-32767, 32768, -32768, 65535, -65535, 65536, -65536,
];
for val in values {
let bn = BigNumber::from_number(val);
let encoded = bn.to_script_num();
let decoded = BigNumber::from_script_num(&encoded, false, None).unwrap();
assert_eq!(
decoded.to_number(),
Some(val),
"round-trip failed for {}",
val
);
}
}
#[test]
fn test_script_num_minimal_encoding_check() {
let result = BigNumber::from_script_num(&[0x00, 0x00], true, None);
assert!(result.is_err(), "non-minimal encoding should be rejected");
let result = BigNumber::from_script_num(&[0x80], true, None);
assert!(
result.is_err(),
"negative zero should be rejected as non-minimal"
);
let result = BigNumber::from_script_num(&[0x01, 0x00], true, None);
assert!(
result.is_err(),
"unnecessary zero extension should be rejected"
);
}
#[test]
fn test_script_num_max_len() {
let result = BigNumber::from_script_num(&[0x01, 0x02, 0x03, 0x04, 0x05], true, Some(4));
assert!(result.is_err(), "should reject inputs exceeding max_len");
}
}