Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Biguint division #159

Merged
merged 29 commits into from
Oct 5, 2023
Merged
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4cbdd67
Division draft
fkrause98 Oct 2, 2023
19e5a7a
Non working draft
fkrause98 Oct 2, 2023
483154a
Fix compile errors
fkrause98 Oct 2, 2023
4b8f1a6
Use proper pointers for quotient and remainder
fkrause98 Oct 2, 2023
8fc9dc3
Add fix note, some more changes
fkrause98 Oct 2, 2023
d8137b2
Add comment
fkrause98 Oct 2, 2023
30d814b
Implement `big_uint_bit_size`
jpcenteno Oct 3, 2023
ea90420
Increase pointer to prevent it from steping over console_log
jpcenteno Oct 3, 2023
3e20a9a
WIP divrem
jpcenteno Oct 3, 2023
116b067
Fix loop and zero initializer
jpcenteno Oct 3, 2023
0d8a7de
Push test cases
jpcenteno Oct 3, 2023
6f8401a
Add other test case
jpcenteno Oct 3, 2023
795ca23
Add docs and tests for `big_uint_inplace_or_1`
jpcenteno Oct 4, 2023
6d1d64c
Fix bug related to bit shifting
jpcenteno Oct 4, 2023
91981a5
Fix borrow return in big uint sub function
IAvecilla Oct 4, 2023
7f137e7
Delete playground file used for debugging
IAvecilla Oct 4, 2023
35644c3
Merge branch 'modexp_reimplementation' into biguint-division
IAvecilla Oct 4, 2023
6b62ff1
Fix sub with borrow function
IAvecilla Oct 4, 2023
9d0c789
Add playground again to check more big integer division tests
IAvecilla Oct 4, 2023
21c3b30
Remove playground used for testing
IAvecilla Oct 5, 2023
1a56e10
Write documentation for new shift functions
IAvecilla Oct 5, 2023
8409032
Improve naming and documentation for new helper functions
IAvecilla Oct 5, 2023
a19ba3f
Rename bigUIntOrWith1 to bigUintInPlaceOrWith1
fkrause98 Oct 5, 2023
9dc24f6
Add tmp buffer parameters to bigUIntDivRem. Improve docs.
jpcenteno Oct 5, 2023
a88020d
Simplify subLimbsWithBorrow
jpcenteno Oct 5, 2023
a840fb5
Remove `mul` call from `bigUIntInPlaceOrWith1`
jpcenteno Oct 5, 2023
26bea8a
Remove multiplications from copyBigUint
jpcenteno Oct 5, 2023
5ad30e7
Optimize bigUIntBitSize loop
jpcenteno Oct 5, 2023
249471d
Simplify zeroWithLimbSizeAt
jpcenteno Oct 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 199 additions & 2 deletions precompiles/Modexp.yul
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,42 @@ object "ModExp" {
function LIMB_SIZE_IN_BITS() -> limbSize {
limbSize := 0x100
}

// HELPER FUNCTIONS

/// @notice Stores a one in big unsigned integer form in memory.
/// @param nLimbs The number of limbs needed to represent the operand.
/// @param toAddress The pointer to the MSB of the destination.
function oneWithLimbSizeAt(nLimbs, toAddress) {
let pointerToOne := toAddress
mstore(pointerToOne, 0x1)
for { let i := sub(nLimbs, 1) } gt(i, 0) { i := sub(i, 1) } {
let offset := add(mul(i, 32), pointerToOne)
mstore(offset, 0x0)
}
}

/// @notice Stores a zero in big unsigned integer form in memory.
/// @param nLimbs The number of limbs needed to represent the operand.
/// @param toAddress The pointer to the MSB of the destination.
function zeroWithLimbSizeAt(nLimbs, toAddress) {
for { let i := 0 } lt(i, nLimbs) { i := add(i, 1) } {
let offset := mul(i, 32)
mstore(add(toAddress, offset), 0)
}
}
jpcenteno marked this conversation as resolved.
Show resolved Hide resolved
jpcenteno marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Copy a big unsigned integer from one memory location to another.
/// @param nLimbs The number of limbs needed to represent the operand.
/// @param fromAddress The pointer to the MSB of the number to copy.
/// @param toAddress The pointer to the MSB of the destination.
function copyBigUint(nLimbs, fromAddress, toAddress) {
for { let i := 0 } lt(i, nLimbs) { i := add(i, 1) } {
let fromOffset := add(mul(i, 32), fromAddress)
let toOffset := add(mul(i, 32), toAddress)
mstore(toOffset, mload(fromOffset))
}
}
jpcenteno marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Computes an addition and checks for overflow.
/// @param augend The value to add to.
/// @param addend The value to add.
Expand Down Expand Up @@ -346,6 +379,21 @@ object "ModExp" {
mstore(add(limbPointer, anOffset), aValue)
}

/// @notice Computes the difference between two 256 bit number and keeps
/// account of the borrow bit
/// in lshPointer and rhsPointer.
/// @dev Reference: https://github.com/lambdaclass/lambdaworks/blob/main/math/src/unsigned_integer/element.rs#L785
/// @param leftLimb The left side of the difference (i.e. the a in a - b).
/// @param rightLimb The right side of the difference (i.e. the b in a - b).
/// @return subtractionResult i.e. the c in c = a - b.
/// @return returnBorrow If there was any borrow on the subtraction, is returned as 1.
function subLimbsWithBorrow(leftLimb, rightLimb, limbBorrow) -> subtractionResult, returnBorrow {
let rightPlusBorrow := add(rightLimb, limbBorrow)
subtractionResult := sub(leftLimb, rightPlusBorrow)
if gt(subtractionResult, leftLimb) {
returnBorrow := 1
}
jpcenteno marked this conversation as resolved.
Show resolved Hide resolved
}
/// @notice Computes the BigUint subtraction between the number stored
/// in minuendPtr and subtrahendPtr.
/// @dev Reference: https://github.com/lambdaclass/lambdaworks/blob/main/math/src/unsigned_integer/element.rs#L795
Expand All @@ -357,12 +405,13 @@ object "ModExp" {
let minuendCurrentLimb
let subtrahendCurrentLimb
let differenceCurrentLimb
borrow := 0
let limbOffset := 0
for { let i := nLimbs } gt(i, 0) { i := sub(i, 1) } {
limbOffset := mul(sub(i,1), 32)
let minuendCurrentLimb := getLimbValueAtOffset(minuendPtr, limbOffset)
let subtrahendCurrentLimb := getLimbValueAtOffset(subtrahendPtr, limbOffset)
let differenceCurrentLimb, borrow := overflowingSubWithBorrow(minuendCurrentLimb, subtrahendCurrentLimb, borrow)
differenceCurrentLimb, borrow := overflowingSubWithBorrow(minuendCurrentLimb, subtrahendCurrentLimb, borrow)
storeLimbValueAtOffset(differencePtr, limbOffset, differenceCurrentLimb)
}
}
Expand Down Expand Up @@ -402,6 +451,154 @@ object "ModExp" {
}
}

// @notice Computes the bit size of an unsigned integer.
// @dev Return value boundary: `0 <= bitSize <= 256`
// @param number An unsigned integer value.
// @return bitSize Number of bits required to represent `number`.
function UIntBitSize(number) -> bitSize {
// Increment bitSize until there are no significant bits left.
bitSize := 0
for { let shift_me := number } lt(0, shift_me) { shift_me := shr(1, shift_me) } {
bitSize := add(bitSize, 1)
}
}

/// @notice Computes the bit size of a big unsigned integer.
/// @param basePtr Base pointer for a big unsigned integer.
/// @param nLimbs The number of limbs needed to represent the operand.
/// @return bitSize Number of bits of the big unsigned integer.
function bigUIntBitSize(basePtr, nLimbs) -> bitSize {
bitSize := shl(8, nLimbs)

// Iterate until finding the most significant limb or reach the end of the limbs.
let limb := 0
for { let i := 0 } and(lt(i, nLimbs), iszero(limb)) { i := add(i, 1) } {
bitSize := sub(bitSize, 256) // Decrement one limb worth of bits.
let ptr_i := add(basePtr, shl(5, i)) // = basePtr + i * 32 bytes
limb := mload(ptr_i)
}

// At this point, `limb == limbs[i - 1]`. Where `i` equals the
// last value it took.

// At this point, `bitSize` equals the amount of bits in the
// limbs following the most significant limb.

bitSize := add(bitSize, UIntBitSize(limb))
}
jpcenteno marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Performs in-place `x | 1` operation.
/// @dev This function will mutate the memory space `mem[basePtr...(basePtr + nLimbs * 32)]`
/// @dev It consumes constant time, aka `O(1)`.
/// @param basePtr Base pointer for a big unsigned integer.
/// @param nLimbs Number of 32 Byte limbs composing the big unsigned integer.
function bigUIntInPlaceOrWith1(basePtr, nLimbs) {
let offset := mul(sub(nLimbs, 1), 32)
jpcenteno marked this conversation as resolved.
Show resolved Hide resolved
let limbPtr := add(basePtr, offset)
let limb := mload(limbPtr)
mstore(limbPtr, or(limb, 0x1))
}

/// @notice Performs one shift to the left for a big unsigned integer (<<).
/// @dev The shift is performed in-place, mutating the memory space of the number.
/// @param numberPtr The pointer to the MSB of the number to shift.
/// @param nLimbs The number of limbs needed to represent the operand.
function bigUIntOneShiftLeft(numberPtr, nLimbs) {
let p := add(numberPtr, shl(5, nLimbs)) // numberPtr + 32 * nLimbs
let carryBit := 0
for { } lt(numberPtr, p) { } {
p := sub(p, 32)
let limb := mload(p)
let msb := shr(255, limb)
limb := or(shl(1, limb), carryBit)
mstore(p, limb)
carryBit := msb
}
}

/// @notice Performs one shift to the right for a big unsigned integer (>>).
/// @dev The shift is performed in-place, mutating the memory space of the number.
/// @param numberPtr The pointer to the MSB of the number to shift.
/// @param nLimbs The number of limbs needed to represent the operand.
function bigUIntOneShiftRight(numberPtr, nLimbs) {
let overflowPtr := add(numberPtr, shl(5, nLimbs))
let carryBit := 0
for { let p := numberPtr } lt(p, overflowPtr) { p := add(p, 32) } {
let limb := mload(p)
let lsb := and(limb, 1)
limb := or(shr(1, limb), carryBit)
carryBit := shl(255, lsb)
mstore(p, limb)
}
}

/// @notice Computes the quotiend and reminder of dividing two big unsigned integers.
/// @dev
/// @dev Temporary buffers:
/// @dev ------------------
/// @dev
/// @dev This function requires two temporary buffers for internal storage:
/// @dev - Both buffers must provide `n_limbs * 32` bytes of writable memory space.
/// @dev - Neither buffer should overlap with each other.
/// @dev - Neither needs to be initialized to any particular value.
/// @dev - Consider the written values as undefined after the function returns.
/// @dev
/// @dev Return values:
/// @dev --------------
/// @dev
/// @dev - resulting `quotient` will be written `mem[base_ptr, base_ptr + 32 * n_limbs)`
/// @dev - resulting `reminder` will be written `mem[base_ptr, base_ptr + 32 * n_limbs)`
/// @dev
/// @param dividend_ptr Base pointer for a big unsigned integer representing the dividend.
/// @param divisor_ptr Base pointer for a big unsigned integer representing the divisor.
/// @param tmp_ptr_1 Base pointer for a contiguous memory space of `n_limbs` for internal usage. Will be overwritten.
/// @param tmp_ptr_2 Base pointer for a contiguous memory space of `n_limbs` for internal usage. Will be overwritten.
/// @param n_limbs Amount of limbs for each big unsigned integer.
/// @param quotient_ptr Base pointer for a big unsigned integer to write the division quotient.
/// @param rem_ptr Base pointer for a big unsigned integer to write the division remainder.
function bigUIntDivRem(dividend_ptr, divisor_ptr, tmp_ptr_1, tmp_ptr_2, n_limbs, quotient_ptr, rem_ptr) {
// Assign meaningful internal names to the temporary buffers passed as parameters. We use abstract names for
// parameters to prevent the leakage of implementation details.
let c_ptr := tmp_ptr_1
let r_ptr := tmp_ptr_2

copyBigUint(n_limbs, dividend_ptr, rem_ptr) // rem = dividend

// Init quotient to 0.
zeroWithLimbSizeAt(n_limbs, quotient_ptr) // quotient = 0

let mb := bigUIntBitSize(divisor_ptr, n_limbs)
let bd := sub(mul(n_limbs, 256), mb)
bigUIntShl(bd, divisor_ptr, n_limbs, c_ptr) // c == divisor << bd

for { } iszero(0) { } {
let borrow := bigUIntSubWithBorrow(rem_ptr, c_ptr, n_limbs, r_ptr)

if iszero(borrow) {
copyBigUint(n_limbs, r_ptr, rem_ptr)
}

copyBigUint(n_limbs, quotient_ptr, r_ptr) // r = quotient
bigUIntInPlaceOrWith1(r_ptr, n_limbs) // r = quotient | 1

if iszero(borrow) {
copyBigUint(n_limbs, r_ptr, quotient_ptr)
}

if iszero(bd) {
break
}

bd := sub(bd, 1)
bigUIntOneShiftRight(c_ptr, n_limbs) // c = c >> 1
bigUIntOneShiftLeft(quotient_ptr, n_limbs) // q[] = q[] << 1
}

if iszero(mb) {
zeroWithLimbSizeAt(n_limbs, quotient_ptr)
}
}

////////////////////////////////////////////////////////////////
// FALLBACK
////////////////////////////////////////////////////////////////
Expand Down