BigInt Arithmetic
BigInt provides 256-bit and 512-bit unsigned integer arithmetic in the VM. Unlike Field elements (which use modular arithmetic over BN254), BigInts use non-modular arithmetic with explicit overflow/underflow errors. This makes them ideal for operations where you need exact integer semantics at cryptographic widths.
Creating BigInts
Section titled “Creating BigInts”There are three ways to create BigInt values:
Literal syntax
Section titled “Literal syntax”Use the 0i prefix followed by width (256 or 512) and radix (x for hex, d for decimal, b for binary):
let a = 0i256xFF // 256-bit, hexlet b = 0i256d255 // 256-bit, decimallet c = 0i256b11111111 // 256-bit, binarylet d = 0i512x1234ABCD // 512-bit, hexAll three representations of 255 are equal:
assert(0i256xFF == 0i256d255)assert(0i256d255 == 0i256b11111111)Constructor functions
Section titled “Constructor functions”bigint256() and bigint512() construct from integers or strings:
let a = bigint256(42) // from integerlet b = bigint256("0xFF") // from hex stringlet c = bigint256("12345") // from decimal stringlet d = bigint512(0) // 512-bit zeroType checking
Section titled “Type checking”Use typeof() to inspect BigInt values:
let x = 0i256d42print(typeof(x)) // "BigInt256"
let y = 0i512d42print(typeof(y)) // "BigInt512"Arithmetic
Section titled “Arithmetic”Standard operators work between BigInts of the same width:
let a = 0i256d100let b = 0i256d200
print(a + b) // BigInt256(0x12c) (300)print(b - a) // BigInt256(0x64) (100)print(a * b) // BigInt256(0x4e20) (20000)print(b / a) // BigInt256(0x2) (2)print(b % a) // BigInt256(0x0) (0)Exponentiation uses ^ with an integer exponent:
let base = 0i256d2let result = base ^ 128print(result) // 2^128 as a 256-bit integerOverflow and Underflow
Section titled “Overflow and Underflow”Unlike Field elements, BigInt arithmetic does not wrap around. Operations that exceed the range produce runtime errors:
// This will error: BigInt overflowlet max = bit_not(bigint256(0)) // all bits set = 2^256 - 1let boom = max + bigint256(1) // ERROR: BigIntOverflow// This will error: BigInt underflowlet zero = bigint256(0)let boom = zero - bigint256(1) // ERROR: BigIntUnderflowThis makes bugs visible immediately rather than producing silently wrong results.
Type Safety
Section titled “Type Safety”BigInt enforces strict type boundaries. You cannot mix BigInt with Int, Field, or a different BigInt width:
// All of these are runtime errors:// bigint256(1) + 1 -- BigInt + Int// bigint256(1) + 0p1 -- BigInt + Field// bigint256(1) + bigint512(1) -- width mismatchThis prevents accidental arithmetic between incompatible types.
Bitwise Operations
Section titled “Bitwise Operations”BigInt supports full bitwise manipulation through native functions:
let a = 0i256xFFlet b = 0i256x0F
print(bit_and(a, b)) // 0x0Fprint(bit_or(a, b)) // 0xFFprint(bit_xor(a, b)) // 0xF0print(bit_not(b)) // all bits flippedShifts
Section titled “Shifts”let one = bigint256(1)
// Shift left: multiply by powers of 2let shifted = bit_shl(one, 128) // 2^128
// Shift right: divide by powers of 2let back = bit_shr(shifted, 128) // back to 1assert(back == one)Left shifts error if any set bits are shifted out (overflow protection). Right shifts discard shifted-out bits.
Bit Decomposition
Section titled “Bit Decomposition”Convert between BigInts and individual bits with to_bits() and from_bits():
let val = bigint256(42)let bits = to_bits(val)
// bits is a list of 256 integers (0 or 1), LSB-firstprint(len(bits)) // 256print(bits[0]) // 0 (least significant bit)print(bits[1]) // 1print(bits[2]) // 0print(bits[3]) // 1print(bits[4]) // 0print(bits[5]) // 1// 42 = 0b101010, LSB-first = [0, 1, 0, 1, 0, 1, 0, 0, ...]
// Reconstruct from bitslet reconstructed = from_bits(bits, 256)assert(reconstructed == val)The second argument to from_bits() is the target width (256 or 512).
Example: Bit Counting
Section titled “Example: Bit Counting”Count the number of set bits in a BigInt:
fn popcount(x) { let bits = to_bits(x) mut count = 0 for b in bits { count = count + b } return count}
assert(popcount(bigint256(0)) == 0)assert(popcount(bigint256(1)) == 1)assert(popcount(bigint256(255)) == 8)assert(popcount(bigint256("0xFFFF")) == 16)
print("popcount(0xFF) =", popcount(bigint256(255))) // 8Example: XOR Swap
Section titled “Example: XOR Swap”Swap two values using XOR without a temporary variable:
mut a = 0i256d42mut b = 0i256d99
a = bit_xor(a, b)b = bit_xor(b, a)a = bit_xor(a, b)
assert(a == 0i256d99)assert(b == 0i256d42)print("After XOR swap: a =", a, "b =", b)Example: Power of Two Check
Section titled “Example: Power of Two Check”Check whether a BigInt is a power of two using the bit trick n & (n - 1) == 0:
fn is_power_of_two(n) { if n == bigint256(0) { return false } let prev = n - bigint256(1) return bit_and(n, prev) == bigint256(0)}
assert(is_power_of_two(bigint256(1)) == true)assert(is_power_of_two(bigint256(256)) == true)assert(is_power_of_two(bit_shl(bigint256(1), 128)) == true)assert(is_power_of_two(bigint256(3)) == false)assert(is_power_of_two(bigint256(100)) == false)
print("2^128 is power of 2:", is_power_of_two(bit_shl(bigint256(1), 128)))BigInt vs Field
Section titled “BigInt vs Field”| BigInt | Field | |
|---|---|---|
| Arithmetic | Non-modular (overflow errors) | Modular (wraps around BN254 prime) |
| Widths | 256-bit, 512-bit | 254-bit (BN254 scalar field) |
| Circuit support | VM only | Both VM and circuits |
| Use case | Exact integer arithmetic at crypto widths | ZK circuit values, Poseidon hashing |
| Division | Integer division (truncates) | Modular inverse |
| Negation | Error (unsigned) | Additive inverse in field |
Choose BigInt when you need exact integer semantics. Choose Field when you need modular arithmetic or circuit compatibility.