Skip to content

Commit

Permalink
feat: bigint pow and from_octets
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-jerry-ye committed Aug 30, 2024
1 parent 41bae5e commit 577d98c
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 0 deletions.
103 changes: 103 additions & 0 deletions builtin/bigint.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -769,3 +769,106 @@ fn max[T : Compare](a : T, b : T) -> T {
b
}
}

/// Compute the power of a BigInt to another BigInt.
///
/// Optionally takes a modulus BigInt.
///
/// The exponent must be non-negative, and modulus must be positive.
pub fn pow(self : BigInt, exp : BigInt, ~modulus : BigInt = one) -> BigInt {
if exp.sign == Negative {
abort("negative exponent")
}
if modulus.is_zero() || modulus.sign == Negative {
abort("modulus non-positive")
}
let mut result = 1N
let mut base = self
let mut exp = exp
while exp > 0N {
if exp % 2N == 1N {
result = result * base % modulus
}
base = base * base % modulus
exp = exp / 2N
}
result
}

/// Convert an octet string to a BigInt.
///
/// The input is treated as a big-endian octet array.
///
/// Example: `from_octets(b"\xab\xcd\xef") == from_hex("abcdef")`
///
/// The input must not be empty, unless signum is zero
pub fn BigInt::from_octets(input : Bytes, ~signum : Int = 1) -> BigInt {
let len = input.length() // number of bytes
if signum == 0 {
return zero
} else if signum < 0 {
return -from_octets(input)
}
if len == 0 {
abort("empty octet string")
}
let div = len * 8 / radix_bit_len
let mod = len * 8 % radix_bit_len // number of bits in the first limb
let limbs_len = if mod == 0 { div } else { div + 1 }
let limbs = FixedArray::make(limbs_len, 0U)
// head at most significant limb
for i = 0; i < mod / 8; i = i + 1 {
limbs[limbs_len - 1] = limbs[limbs_len - 1]
.lsl(8)
.lor(input[i].to_int().to_uint())
}
let byte_per_limb = radix_bit_len / 8
// tail
for i = 0; i < div; i = i + 1 {
for j = 0; j < byte_per_limb; j = j + 1 {
let bytes_idx = len - byte_per_limb - i * byte_per_limb + j
limbs[i] = limbs[i].lsl(8).lor(input[bytes_idx].to_int().to_uint())
}
}
if limbs[limbs_len - 1] == 0 {
{ limbs, sign: Positive, len: max(1, limbs_len - 1) }
} else {
{ limbs, sign: Positive, len: limbs_len }
}
}

/// Convert a BigInt to an octet string.
///
/// The output is a big-endian octet array.
///
/// The output may be padded with leading zeros to meet the length requirement
/// if and only if the actual length is less than the expected.
///
/// The value should be non-negative and the padding should be positive.
///
/// Example:
/// - `to_octets(from_hex("abcdef"), length=3) == b"\xab\xcd\xef"`
/// - `to_octets(from_hex("1abcdef"), length=3) == b"\x01\xab\xcd\xef"`
pub fn BigInt::to_octets(self : BigInt, ~length? : Int) -> Bytes {
let length = match length {
None => 1
Some(l) => if l <= 0 { abort("negative length") } else { l }
}
if self.is_zero() {
return Bytes::new(max(1, length))
}
if self.sign == Negative {
abort("negative BigInt")
}
let head_bits = 32 - self.limbs[self.len - 1].to_int().clz()
let tail_len = self.len - 1
let len = (head_bits + 7) / 8 + tail_len * (radix_bit_len / 8)
let len = max(length, len)
let result = Bytes::new(len)
for i = 0; i < len && i / 4 < self.len; i = i + 1 {
result[len - 1 - i] = (self.limbs[i / 4].shr(i % 4 * 8) & 0xffU)
.to_int()
.to_byte()
}
result
}
216 changes: 216 additions & 0 deletions builtin/bigint_wbtest.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,192 @@ test "to_hex" {
)
}

test "from_octets" {
// Check zero
let a = from_octets(b"\x00")
check_len!(a)
assert_eq!(a, 0)
let a = from_octets(b"\x00", signum=-1)
check_len!(a)
assert_eq!(a, 0)
let a = from_octets(b"\x00", signum=0)
check_len!(a)
assert_eq!(a, 0)

// Test positive number
let a = from_octets(b"\x01")
check_len!(a)
assert_eq!(a, 0x01)

// Test negative number
let a = from_octets(b"\x0F", signum=-1)
check_len!(a)
assert_eq!(a, -0x0F)
let a = from_octets(b"\x0a", signum=-1)
check_len!(a)
assert_eq!(a, -0x0A)

// Test large positive number
let a = from_octets(b"\x11\x22\x10\xF4\x7D\xE9\x81\x15")
check_len!(a)
assert_eq!(a, 0x112210F47DE98115)
assert_eq!(a, 1234567890123456789)

// Test very large positive number
let a = from_octets(
b"\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78",
)
check_len!(a)
assert_eq!(
a, 0x123456789012345678123456789012345678123456789012345678123456789012345678,
)
assert_eq!(
a, 35365207917649046390549507392234216535182073572857507984542859337680634154115797374584,
)
let a = from_octets(
b"\x11\xE3\x44\x4D\xC1\xF3\x5F\x05\x7A\xD2\xCB\xC2\x79\x17\x37\x46\x81\x40\xA4\x26\xFA\xC3\xCB\xA7\xAF\x8C\x92\xA8\xF3\x4E",
)
check_len!(a)
assert_eq!(a, 0x11E3444DC1F35F057AD2CBC2791737468140A426FAC3CBA7AF8C92A8F34E)
assert_eq!(
a, 123456789012345678123456789012345678123456789012345678123456789012345678,
)
let a = from_octets(
b"\x08\x05\x14\x6F\x2F\x58\x58\x09\x62\xA0\xA2\xE6\x13\x4B\xC7\x5E\x25\xAD\x97\xAE\x3D\x09\xCD\x34\xBA\x4F\x62\x9A\xB8\x91\x1F\x3F\x5C\xB8\x57\x3A\x62\xED\xD1\x6B\x0D\x46\x77\x5A\x41\x5F\x54\x5A\x75\x51\x8D\xA3\x43\x99\x14\xD9\xCA\xA2\x64\x49\x06\x7D\x85\xE7\x04\xE8\xFC\xF9\xB2\x91\x82\x48\x5B\x41\xF9\x52\x61\x6B\xAC\xDF\xDD\xF5\x2B\x41\x3B\x52\x4D\x0E\xB7\x43\xE8\x26\x4A\x9C\x6A\xE3\x2D\x12\xC3\xD2\x0C\x5B\x81\x18\x90\x60\xF4\xAC\x5D\x21\x67\x13\xD5\x03\xA6\x96\x44\xEA\x8E\x4E\xA2\x20\xA7\x20\xC4\x1F\x6B\x3D\x18\xBE\xD6\x5B\x42\x38\x31\x8E\x6B\x0A\x41\xD8\x54\x0D\x75\x68\x65\xEC\x92\xDF\x40\xE8\xD3\x65\xA2\x30\xF1\x7D\xF1\xD0\xA4\x40\xBC\x6A\x55\x7C\xD4\x6D\x00\xB1\x0D\x74\xC0\xE7\x55\x00\xB2\xAD\xB3\xA0\x33\x62\x23\xF9\x28\x5B\x78\xCD\x04\xF4\x85\xE4\x17\xE1\xDB\x56\x2B\x9E\xFC\xF7\x94\x33\x20\x9E\x6D\x6E\x2F\x43\xA4\x84\xE4\x71\xDE\x4F\x1F\x5A\xE3\x8E\x08\xE7\xDA\xEB\x64\x4C\x2C\x0A\x22\x69\x7D\xD6\xD2\x9B\xE0\xB4\x0F\xF5\x0D\xB5\x75\xFE\xF0\x2F\xA5\x52\x59\x53\xC7\xC1\x98\xB4\xA3\x60\x0B\xA8\xCE\x1F\x91\x78\x52\xA4\xB9\x57\x15\x11\x89\xF0\x9D\xCD\xFC\xB7\x99\x63\xE7\xD8\x50\x12\x78\x58\xA9\x78\x55\xB9\x48\x70\xAC\xCB\xE8\x20\x3E\x73\xFE\x79\x79\x1E\xE6\xEA\x1B\x12\x82\xA0\xCE\xAC\x54\xD6\xF6\xB7\xCD\x6C\x7B\x8D\x80\x92\xE9\x49\xFF\x0A\x84",
)
check_len!(a)
assert_eq!(
a, 0x805146F2F58580962A0A2E6134BC75E25AD97AE3D09CD34BA4F629AB8911F3F5CB8573A62EDD16B0D46775A415F545A75518DA3439914D9CAA26449067D85E704E8FCF9B29182485B41F952616BACDFDDF52B413B524D0EB743E8264A9C6AE32D12C3D20C5B81189060F4AC5D216713D503A69644EA8E4EA220A720C41F6B3D18BED65B4238318E6B0A41D8540D756865EC92DF40E8D365A230F17DF1D0A440BC6A557CD46D00B10D74C0E75500B2ADB3A0336223F9285B78CD04F485E417E1DB562B9EFCF79433209E6D6E2F43A484E471DE4F1F5AE38E08E7DAEB644C2C0A22697DD6D29BE0B40FF50DB575FEF02FA5525953C7C198B4A3600BA8CE1F917852A4B957151189F09DCDFCB79963E7D850127858A97855B94870ACCBE8203E73FE79791EE6EA1B1282A0CEAC54D6F6B7CD6C7B8D8092E949FF0A84,
)
assert_eq!(
a, 12345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812345678901234567812,
)

// Test very large negative number
let a = from_octets(
b"\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78",
signum=-1,
)
check_len!(a)
assert_eq!(
a, -0x123456789012345678123456789012345678123456789012345678123456789012345678,
)
assert_eq!(
a, -35365207917649046390549507392234216535182073572857507984542859337680634154115797374584,
)
let a = from_octets(
b"\x00\xd2\x68\x67\xc5\x72\x75\x22\xf2\xca\xe8\x27\x3d\x05\x6f\xee\x02\x3a\xca\xb0\x9a\x23\x43\xcb\xc0\x1b\x29\xbf\x29\x95\xaa\x34\x02\x78\x7b\xd6\x71\x95\x47\x85\x44\xc2\x00\x73\x28\xec\x92\x41\xd1\x5e\x15\xce\x74\xdd\x0f\x23\x7c\x5e\xf2\x43\x0a\xf7\xe2\x89\xb0\x00\xe6\xe1\xf4\x8c\x84\x7b\x1f\x9e\x28\x0b\x3d\xc8\xdb\xcc\xe1\xa6\xdf\x92\x6e\xa0\xbd\x96\xec\x0d\x72\x19\xc0\x16\xd8\x0e\xf0\xb4\x3a\x14\x82\xab\x38\x6c\xe8\xc9\xef\xc6\xa3\x04\xef\x15\xcf\x8c\x89\xea\x6d\x3e\x05\xdc\x5f\x39\xd7\xdb\xcf\x78\xad\x00\x86\x51\x29\x76\x35\x96\x8f\xbc\x86\x9a\x18\x5c\xa9\x34\xef\xfc\x94\x08\x3a\xdd\xed\xe6\x59\xe9\xa0\x07\xcd\xa2\x1a\x17\x51\xf8\x3c\x70\xc8\xbc\x43\x65\x1e\x78\x28\x6d\x23\xc0\x43\x5f\xf4\x9f\x5b\xb1\x3f\x81\x7d\xf0\xfd\x8c\x37\x3f\xd5\x58\x55\xf9\x20\x5d\xdf\x74\x95\x89\xe1\x57\xbc\xc0\x35\x82\xea\x7b\xb1\x33\x9f\x47\x69\x93\xd0\x84\x48\x31\x88\xea\x47\x67\x9a\xfa\x25\x29\x37\xc4\x2c\xff\x96\x72\xcc\x6a\x68\x56\x0f\xa0\x18\x48\xff\x65\x68\xf3\xfd\x41\x74\x67\x1b\x2f\xef\x76\x54\xbf\xcb\xe5\x7f\x24\xb6\x38\xc7",
)
check_len!(a)
let a = from_octets(b"\x00\x00\x00\x00")
check_len!(a)
assert_eq!(a, 0)
}

test "to_octets" {
// Check zero
let a = 0x00N
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x00"
,
)

// Test positive number
let a = 0x0FN
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x0f"
,
)

// Test positive number with leading zero
let a = 0x10N
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x10"
,
)

// Test large positive number
let a = 0x01234567890123456789N
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x01\x23\x45\x67\x89\x01\x23\x45\x67\x89"
,
)

// Check padding
let a = 0x100000N
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x10\x00\x00"
,
)

// Test very large positive number
let a = 0x11E3444DC1F35F057AD2CBC2791737468140A426FAC3CBA7AF8C92A8F34EN
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x11\xe3\x44\x4d\xc1\xf3\x5f\x05\x7a\xd2\xcb\xc2\x79\x17\x37\x46\x81\x40\xa4\x26\xfa\xc3\xcb\xa7\xaf\x8c\x92\xa8\xf3\x4e"
,
)
let a = 0x123456789012345678123456789012345678123456789012345678123456789012345678N
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78\x12\x34\x56\x78\x90\x12\x34\x56\x78"
,
)
let a = 0x805146F2F58580962A0A2E6134BC75E25AD97AE3D09CD34BA4F629AB8911F3F5CB8573A62EDD16B0D46775A415F545A75518DA3439914D9CAA26449067D85E704E8FCF9B29182485B41F952616BACDFDDF52B413B524D0EB743E8264A9C6AE32D12C3D20C5B81189060F4AC5D216713D503A69644EA8E4EA220A720C41F6B3D18BED65B4238318E6B0A41D8540D756865EC92DF40E8D365A230F17DF1D0A440BC6A557CD46D00B10D74C0E75500B2ADB3A0336223F9285B78CD04F485E417E1DB562B9EFCF79433209E6D6E2F43A484E471DE4F1F5AE38E08E7DAEB644C2C0A22697DD6D29BE0B40FF50DB575FEF02FA5525953C7C198B4A3600BA8CE1F917852A4B957151189F09DCDFCB79963E7D850127858A97855B94870ACCBE8203E73FE79791EE6EA1B1282A0CEAC54D6F6B7CD6C7B8D8092E949FF0A84N
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x08\x05\x14\x6f\x2f\x58\x58\x09\x62\xa0\xa2\xe6\x13\x4b\xc7\x5e\x25\xad\x97\xae\x3d\x09\xcd\x34\xba\x4f\x62\x9a\xb8\x91\x1f\x3f\x5c\xb8\x57\x3a\x62\xed\xd1\x6b\x0d\x46\x77\x5a\x41\x5f\x54\x5a\x75\x51\x8d\xa3\x43\x99\x14\xd9\xca\xa2\x64\x49\x06\x7d\x85\xe7\x04\xe8\xfc\xf9\xb2\x91\x82\x48\x5b\x41\xf9\x52\x61\x6b\xac\xdf\xdd\xf5\x2b\x41\x3b\x52\x4d\x0e\xb7\x43\xe8\x26\x4a\x9c\x6a\xe3\x2d\x12\xc3\xd2\x0c\x5b\x81\x18\x90\x60\xf4\xac\x5d\x21\x67\x13\xd5\x03\xa6\x96\x44\xea\x8e\x4e\xa2\x20\xa7\x20\xc4\x1f\x6b\x3d\x18\xbe\xd6\x5b\x42\x38\x31\x8e\x6b\x0a\x41\xd8\x54\x0d\x75\x68\x65\xec\x92\xdf\x40\xe8\xd3\x65\xa2\x30\xf1\x7d\xf1\xd0\xa4\x40\xbc\x6a\x55\x7c\xd4\x6d\x00\xb1\x0d\x74\xc0\xe7\x55\x00\xb2\xad\xb3\xa0\x33\x62\x23\xf9\x28\x5b\x78\xcd\x04\xf4\x85\xe4\x17\xe1\xdb\x56\x2b\x9e\xfc\xf7\x94\x33\x20\x9e\x6d\x6e\x2f\x43\xa4\x84\xe4\x71\xde\x4f\x1f\x5a\xe3\x8e\x08\xe7\xda\xeb\x64\x4c\x2c\x0a\x22\x69\x7d\xd6\xd2\x9b\xe0\xb4\x0f\xf5\x0d\xb5\x75\xfe\xf0\x2f\xa5\x52\x59\x53\xc7\xc1\x98\xb4\xa3\x60\x0b\xa8\xce\x1f\x91\x78\x52\xa4\xb9\x57\x15\x11\x89\xf0\x9d\xcd\xfc\xb7\x99\x63\xe7\xd8\x50\x12\x78\x58\xa9\x78\x55\xb9\x48\x70\xac\xcb\xe8\x20\x3e\x73\xfe\x79\x79\x1e\xe6\xea\x1b\x12\x82\xa0\xce\xac\x54\xd6\xf6\xb7\xcd\x6c\x7b\x8d\x80\x92\xe9\x49\xff\x0a\x84"
,
)
}

test "to_octets with padding" {
// corner case of zero
let a = zero
inspect!(
a.to_octets(length=4),
content=
#|b"\x00\x00\x00\x00"
,
)
// some random number
let a = 0x10000N
check_len!(a)
inspect!(
a.to_octets(),
content=
#|b"\x01\x00\x00"
,
)
inspect!(
a.to_octets(length=4),
content=
#|b"\x00\x01\x00\x00"
,
)
}

test "op_add coverage for max(self_len, other_len)" {
let a = BigInt::from_int(123456789)
let b = BigInt::from_int(987654321)
Expand All @@ -607,6 +793,12 @@ test "op_sub coverage for max(self_len, other_len)" {
inspect!(result.len, content="1")
}

test "pow" {
inspect!(4N.pow(13N, modulus=497N), content="445")
inspect!(65N.pow(17, modulus=3233), content="2790")
inspect!(2790N.pow(413, modulus=3233), content="65")
}

test "panic op_div coverage for division by zero" {
let a = BigInt::from_int(123456789)
let b = BigInt::from_int(0)
Expand Down Expand Up @@ -644,3 +836,27 @@ test "panic from_hex coverage for empty string" {
test "panic from_hex coverage for invalid character" {
from_hex("123g56789") |> ignore
}

test "panic pow coverage for negative exponent" {
4N.pow(-13N, modulus=497N) |> ignore
}

test "panic pow coverage for zero modulus" {
4N.pow(13N, modulus=0N) |> ignore
}

test "panic to_octets coverage for negative length" {
123456789N.to_octets(length=-1) |> ignore
}

test "panic to_octets coverage for zero length" {
123456789N.to_octets(length=0) |> ignore
}

test "panic to_octets coverage for negative number than required" {
(-123456789N).to_octets() |> ignore
}

test "panic from_octets coverage for empty octets" {
from_octets(b"") |> ignore
}
3 changes: 3 additions & 0 deletions builtin/builtin.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl BigInt {
from_hex(String) -> Self
from_int(Int) -> Self
from_int64(Int64) -> Self
from_octets(Bytes, ~signum : Int = ..) -> Self
from_string(String) -> Self
is_zero(Self) -> Bool
lsl(Self, Int) -> Self
Expand All @@ -143,9 +144,11 @@ impl BigInt {
op_shl(Self, Int) -> Self
op_shr(Self, Int) -> Self
op_sub(Self, Self) -> Self
pow(Self, Self, ~modulus : Self = ..) -> Self
shl(Self, Int) -> Self
shr(Self, Int) -> Self
to_hex(Self) -> String
to_octets(Self, ~length? : Int) -> Bytes
to_string(Self) -> String
}

Expand Down

0 comments on commit 577d98c

Please sign in to comment.