diff --git a/constantine/bigints.nim b/constantine/bigints.nim index 5823869..2af2d8f 100644 --- a/constantine/bigints.nim +++ b/constantine/bigints.nim @@ -40,8 +40,8 @@ import ./primitives from ./private/primitives_internal import unsafeDiv2n1n, unsafeExtendedPrecMul -type Word* = Ct[uint64] -type BaseType* = uint64 # Exported type for conversion in "normal integers" +type Word* = Ct[uint32] +type BaseType* = uint32 # Exported type for conversion in "normal integers" const WordBitSize* = sizeof(Word) * 8 - 1 ## Limbs are 63-bit by default @@ -217,16 +217,16 @@ func shlAddMod[bits](a: var BigInt[bits], c: Word, M: BigInt[bits]) = const R = bits and WordBitSize # R = bits mod 64 when R == 0: # If the number of bits is a multiple of 64 - let a1 = a.limbs[^2] # let a0 = a.limbs[^1] # moveMem(a.limbs[1].addr, a.limbs[0].addr, (len-1) * Word.sizeof) # we can just shift words a.limbs[0] = c # and replace the first one by c + let a1 = a.limbs[^1] let m0 = M.limbs[^1] else: # Need to deal with partial word shifts at the edge. - let a1 = ((a.limbs[^2] shl (WordBitSize-R)) or (a.limbs[^3] shr R)) and MaxWord let a0 = ((a.limbs[^1] shl (WordBitSize-R)) or (a.limbs[^2] shr R)) and MaxWord moveMem(a.limbs[1].addr, a.limbs[0].addr, (len-1) * Word.sizeof) a.limbs[0] = c + let a1 = ((a.limbs[^1] shl (WordBitSize-R)) or (a.limbs[^2] shr R)) and MaxWord let m0 = ((M.limbs[^1] shl (WordBitSize-R)) or (M.limbs[^2] shr R)) and MaxWord # m0 has its high bit set. (a0, a1)/p0 fits in a limb. @@ -256,8 +256,8 @@ func shlAddMod[bits](a: var BigInt[bits], c: Word, M: BigInt[bits]) = block: # q*p var qp_hi: Word unsafeExtendedPrecMul(qp_hi, qp_lo, q, M.limbs[i]) # q * p - assert qp_lo.isMsbSet.not.bool - assert carry.isMsbSet.not.bool + # assert qp_lo.isMsbSet.not.bool + # assert carry.isMsbSet.not.bool qp_lo += carry # Add carry from previous limb let qp_carry = qp_lo.isMsbSet carry = mux(qp_carry, qp_hi + One, qp_hi) # New carry diff --git a/constantine/private/primitives_internal.nim b/constantine/private/primitives_internal.nim index dd1efe8..70f38b9 100644 --- a/constantine/private/primitives_internal.nim +++ b/constantine/private/primitives_internal.nim @@ -57,6 +57,12 @@ func unsafeExtendedPrecMul*(hi, lo: var Ct[uint64], a, b: Ct[uint64]) {.inline.} else: asm_x86_64_extMul(T(hi), T(lo), T(a), T(b)) +func unsafeExtendedPrecMul*(hi, lo: var Ct[uint32], a, b: Ct[uint32]) {.inline.}= + ## Extended precision multiplication uint32 * uint32 --> uint32 + let extMul = uint64(a) * uint64(b) + hi = (Ct[uint32])(extMul shr 32) + lo = (Ct[uint32])(extMul and 31) + func asm_x86_64_div2n1n(q, r: var uint64, n_hi, n_lo, d: uint64) {.inline.}= ## Division uint128 by uint64 ## Warning ⚠️ : @@ -118,6 +124,20 @@ func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}= else: asm_x86_64_div2n1n(T(q), T(r), T(n_hi), T(n_lo), T(d)) +func unsafeDiv2n1n*(q, r: var Ct[uint32], n_hi, n_lo, d: Ct[uint32]) {.inline.}= + ## Division uint64 by uint32 + ## Warning ⚠️ : + ## - if n_hi == d, quotient does not fit in an uint32 + ## - if n_hi > d result is undefined + ## + ## To avoid issues, n_hi, n_lo, d should be normalized. + ## i.e. shifted (== multiplied by the same power of 2) + ## so that the most significant bit in d is set. + let dividend = (uint64(n_hi) shl 32) or uint64(n_lo) + let divisor = uint64(d) + q = (Ct[uint32])(dividend div divisor) + r = (Ct[uint32])(dividend mod divisor) + when isMainModule: block: # Multiplication var hi, lo: uint64 diff --git a/tests/test_bigints.nim b/tests/test_bigints.nim index b590890..517b796 100644 --- a/tests/test_bigints.nim +++ b/tests/test_bigints.nim @@ -124,22 +124,6 @@ suite "Arithmetic operations - Addition": bool(a == c) not bool(carry) - block: - var a = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF") - let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") - let carry = a.add(b, ctrue(Word)) - - # BigInt[128] takes 3 Words as a BigInt Word is 63-bit - var ab: array[3*sizeof(Word), byte] - ab.dumpRawUint(a, littleEndian) - - # Given that it uses 3 words, we actually can store 2^128 in BigInt[128] - var c: array[3*sizeof(Word), byte] - c[16] = 1 - check: - c == ab - not bool(carry) # carry can only happen within limbs - suite "Modular operations - small modulus": # Vectors taken from Stint - https://github.com/status-im/nim-stint test "100 mod 13": diff --git a/tests/test_bigints_multimod.nim b/tests/test_bigints_multimod.nim new file mode 100644 index 0000000..c1ab968 --- /dev/null +++ b/tests/test_bigints_multimod.nim @@ -0,0 +1,38 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + # Standard library + unittest, random, strutils, + # Third-party + ../constantine/[io, bigints, primitives] + +suite "Bigints - Multiprecision modulo": + test "bitsize 237 mod bitsize 192": + let a = BigInt[237].fromHex("0x123456789012345678901234567890123456789012345678901234567890") + let m = BigInt[192].fromHex("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB") + + let expected = BigInt[192].fromHex("0x34567890123456789012345678901234567886f8091a3087") + + var r: BigInt[192] + r.reduce(a, m) + + check: + bool(r == expected) + + test "bitsize 365 mod bitsize 258": + let a = BigInt[365].fromHex("0x6c8ae85a6cab4bc530b91177e3f399894ff1fe335b6b3fcdc577ea4f8d754bbe71a6353e8609a4769ec8c56727a") + let m = BigInt[258].fromHex("0x2cadadfa2bb7d7141ad9728d6955ddb68a8b81ecb6a7610575bf4d6f562b09f0d") + + let expected = BigInt[258].fromHex("0xb7c8844f534bf298645dc118384e975245c1a44ba4b0bca8a04c9db0c9035b9") + + var r: BigInt[258] + r.reduce(a, m) + + check: + bool(r == expected) diff --git a/tests/test_io.nim b/tests/test_io.nim index ae5368d..b687422 100644 --- a/tests/test_io.nim +++ b/tests/test_io.nim @@ -23,15 +23,6 @@ suite "IO": T(big.limbs[0]) == 0 T(big.limbs[1]) == 0 - block: # 2^63 is properly represented on 2 limbs - let x = 1'u64 shl 63 - let x_bytes = cast[array[8, byte]](x) - let big = BigInt[64].fromRawUint(x_bytes, cpuEndian) - - check: - T(big.limbs[0]) == 0 - T(big.limbs[1]) == 1 - test "Parsing and dumping round-trip on uint64": block: # "Little-endian" - 2^63