diff --git a/constantine.nim b/constantine.nim index 1ce2a27..914581b 100644 --- a/constantine.nim +++ b/constantine.nim @@ -5,3 +5,6 @@ # * 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. + +# TODO +# export public proc diff --git a/constantine/bigints.nim b/constantine/bigints.nim index bc39a4e..9bcb5a0 100644 --- a/constantine/bigints.nim +++ b/constantine/bigints.nim @@ -37,7 +37,8 @@ # So the least significant limb is limb[0] # This is independent from the base type endianness. -import ./primitives, ./config +import ./primitives +from ./private/primitives_internal import unsafeDiv2n1n, unsafeExtendedPrecMul type Word* = Ct[uint64] type BaseType* = uint64 # Exported type for conversion in "normal integers" @@ -45,6 +46,15 @@ type BaseType* = uint64 # Exported type for conversion in "normal integers" const WordBitSize* = sizeof(Word) * 8 - 1 ## Limbs are 63-bit by default +const + Zero* = Word(0) + One* = Word(1) + MaxWord* = (not Zero) shr 1 + ## This represents 0x7F_FF_FF_FF__FF_FF_FF_FF + ## also 0b0111...1111 + ## This biggest representable number in our limbs. + ## i.e. The most significant bit is never set at the end of each function + func wordsRequired(bits: int): int {.compileTime.}= (bits + WordBitSize - 1) div WordBitSize @@ -73,11 +83,59 @@ type # "Limb-endianess" is little-endian (least significant limb at BigInt.limbs[0]) limbs*: array[bits.wordsRequired, Word] -const MaxWord* = (not Ct[uint64](0)) shr 1 - ## This represents 0x7F_FF_FF_FF__FF_FF_FF_FF - ## also 0b0111...1111 - ## This biggest representable number in our limbs. - ## i.e. The most significant bit is never set at the end of each function +# No exceptions allowed +# TODO: can we use compile-time "Natural" instead of "int" in that case? +{.push raises: [].} + +# ############################################################ +# +# Internal +# +# ############################################################ + +func copyLimbs*[dstBits, srcBits]( + dst: var BigInt[dstBits], dstStart: static int, + src: BigInt[srcBits], srcStart: static int, + numLimbs: static int) {.inline.}= + ## Copy `numLimbs` from src into dst + ## If `dst` buffer is larger than `numLimbs` buffer + ## the extra space will be zero-ed out + ## + ## Limbs ordering is little-endian. limb 0 is the least significant/ + ## + ## This should work at both compile-time and runtime. + ## + ## `numLimbs` must be less or equal the limbs of the `dst` and `src` buffers + ## This is checked at compile-time and has no runtime impact + + static: + doAssert numLimbs >= 0, "`numLimbs` must be greater or equal zero" + + doAssert numLimbs + srcStart <= src.limbs.len, + "The number of limbs to copy (" & $numLimbs & + ") must be less or equal to the number of limbs in the `src` buffer (" & + $src.limbs.len & " for " & $srcBits & " bits)" + + doAssert numLimbs + dstStart <= dst.limbs.len, + "The number of limbs to copy (" & $numLimbs & + ") must be less or equal to the number of limbs in the `dst` buffer (" & + $dst.limbs.len & " for " & $dstBits & " bits)" + + # TODO: do we need a copyMem / memcpy specialization for runtime + # or use dst.limbs[0.. M.limbs[i] + ) + + # Fix quotient, the true quotient is either q-1, q or q+1 + # + # if carry < q or carry == q and over_p we must do "a -= p" + # if carry > hi (negative result) we must do "a += p" + + let neg = carry < hi + let tooBig = not neg and (over_p or (carry < hi)) + + discard a.add(M, ctl = neg) + discard a.sub(M, ctl = tooBig) + return + +func reduce*[aBits, mBits](r: var BigInt[mBits], a: BigInt[aBits], M: BigInt[mBits]) = + ## Reduce `a` modulo `M` and store the result in `r` + ## + ## The modulus `M` **must** use `mBits` bits. + ## + ## CT: Depends only on the length of the modulus `M` + + # Note: for all cryptographic intents and purposes the modulus is known at compile-time + # but we don't want to inline it as it would increase codesize, better have Nim + # pass a pointer+length to a fixed session of the BSS. + + assert not M.limbs[^1].isZero.bool, "The modulus must use all declared bits" + + when aBits < mBits: + # if a uses less bits than the modulus, + # it is guaranteed < modulus. + # This relies on the precondition that the modulus uses all declared bits + copyLimbs(r, 0, a, 0, a.limbs.len) + r.setZero(a.limbs.len, r.limbs.len-1) + else: + # a length i at least equal to the modulus. + # we can copy modulus.limbs-1 words + # and modular shift-left-add the rest + const aOffset = a.limbs.len - M.limbs.len + copyLimbs(r, 0, a, aOffset, M.limbs.len - 1) + r.limbs[^1] = Zero + for i in countdown(aOffset, 0): + r.shlAddMod(a.limbs[i], M) diff --git a/constantine/io.nim b/constantine/io.nim index 9a511ec..36c87a4 100644 --- a/constantine/io.nim +++ b/constantine/io.nim @@ -34,7 +34,7 @@ func fromRawUintLE( var dst_idx = 0 - acc = Word(0) + acc = Zero acc_len = 0 for src_idx in 0 ..< src.len: diff --git a/tests/generators/README.md b/tests/generators/README.md new file mode 100644 index 0000000..72d0315 --- /dev/null +++ b/tests/generators/README.md @@ -0,0 +1,7 @@ +# Test vectors generators + +Generators for complex tests. + +The generators can be written in any language +and should be from industrial grade libraries (GMP, OpenSSL, ...) +or cryptography standards (IETF specs, ...) diff --git a/tests/test_bigints.nim b/tests/test_bigints.nim index e698169..f698c38 100644 --- a/tests/test_bigints.nim +++ b/tests/test_bigints.nim @@ -6,7 +6,7 @@ # * 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 unittest, random, +import unittest, random, strutils, ../constantine/[io, bigints, primitives] suite "isZero": @@ -139,3 +139,67 @@ suite "Arithmetic operations - Addition": 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": + let a = BigInt[32].fromUint(100'u32) + let m = BigInt[8].fromUint(13'u8) + + var r: BigInt[8] + r.reduce(a, m) + check: + bool(r == BigInt[8].fromUint(100'u8 mod 13)) + + test "2^64 mod 3": + let a = BigInt[65].fromHex("0x1_00000000_00000000") + let m = BigInt[8].fromUint(3'u8) + + var r: BigInt[8] + r.reduce(a, m) + check: + bool(r == BigInt[8].fromUint(1'u8)) + + test "1234567891234567890 mod 10": + let a = BigInt[64].fromUint(1234567891234567890'u64) + let m = BigInt[8].fromUint(10'u8) + + var r: BigInt[8] + r.reduce(a, m) + check: + bool(r == BigInt[8].fromUint(0'u8)) + +suite "Modular operations - small modulus - Stint specific failures highlighted by property-based testing": + # Vectors taken from Stint - https://github.com/status-im/nim-stint + # We need to use hex for the modulus as we can't construct BigInt with bits < 64 from an uint64 + test "Modulo: 65696211516342324 mod 174261910798982": + let u = 65696211516342324'u64 + let v = "0x9e7d834a8286" # 174261910798982'u64 + + let a = BigInt[64].fromUint(u) + let m = BigInt[48].fromHex(v) + + var r: BigInt[48] + r.reduce(a, m) + # Copy the result in a conveniently sized buffer + var rr: BigInt[64] + copyLimbs(rr, 0, r, 0, r.limbs.len) + + check: + bool(rr == BigInt[64].fromUint(u mod v.fromHex[:uint64])) + + test "Modulo: 15080397990160655 mod 600432699691": + let u = 15080397990160655'u64 + let v = "0x8bcc93e92b" # 600432699691'u64 + + let a = BigInt[64].fromUint(u) + let m = BigInt[40].fromHex(v) + + var r: BigInt[40] + r.reduce(a, m) + # Copy the result in a conveniently sized buffer + var rr: BigInt[64] + copyLimbs(rr, 0, r, 0, r.limbs.len) + + check: + bool(rr == BigInt[64].fromUint(u mod v.fromHex[:uint64]))