diff --git a/constantine/config/curves.nim b/constantine/config/curves.nim index 226d099..8715de6 100644 --- a/constantine/config/curves.nim +++ b/constantine/config/curves.nim @@ -52,6 +52,9 @@ when not defined(testingCurves): bitsize: 381 modulus: "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" # Equation: y^2 = x^3 + 4 + curve P256: # secp256r1 / NIST P-256 + bitsize: 256 + modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" else: # Fake curve for testing field arithmetic declareCurves: @@ -64,6 +67,9 @@ else: curve Mersenne127: bitsize: 127 modulus: "0x7fffffffffffffffffffffffffffffff" # 2^127 - 1 + curve P256: # secp256r1 / NIST P-256 + bitsize: 256 + modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" # ############################################################ # diff --git a/constantine/io/io_fields.nim b/constantine/io/io_fields.nim index e1eb7ec..4cf669f 100644 --- a/constantine/io/io_fields.nim +++ b/constantine/io/io_fields.nim @@ -11,6 +11,10 @@ import ../config/curves, ../math/[bigints_checked, finite_fields] +# No exceptions allowed +{.push raises: [].} +{.push inline.} + # ############################################################ # # Parsing from canonical inputs to internal representation @@ -22,7 +26,7 @@ func fromUint*(dst: var Fq, ## Parse a regular unsigned integer ## and store it into a BigInt of size `bits` let raw = (type dst.mres).fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian) - dst.mres.unsafeMontyResidue(raw, Fq.C.Mod.mres, Fq.C.getR2modP(), Fq.C.getNegInvModWord()) + dst.fromBig(raw) func serializeRawUint*(dst: var openarray[byte], src: Fq, diff --git a/constantine/math/bigints_checked.nim b/constantine/math/bigints_checked.nim index 91b072e..19f70d1 100644 --- a/constantine/math/bigints_checked.nim +++ b/constantine/math/bigints_checked.nim @@ -65,6 +65,8 @@ template view*(a: var BigInt): BigIntViewMut = BigIntViewMut(cast[BigIntView](a.addr)) debug: + import strutils + func `==`*(a, b: BigInt): CTBool[Word] = ## Returns true if 2 big ints are equal var accum: Word @@ -78,10 +80,10 @@ debug: result.add "](bitLength: " result.add $a.bitLength result.add ", limbs: [" - result.add $BaseType(a.limbs[0]) + result.add $BaseType(a.limbs[0]) & " (0x" & toHex(BaseType(a.limbs[0])) & ')' for i in 1 ..< a.limbs.len: result.add ", " - result.add $BaseType(a.limbs[i]) + result.add $BaseType(a.limbs[i]) & " (0x" & toHex(BaseType(a.limbs[i])) & ')' result.add "])" # No exceptions allowed @@ -123,7 +125,7 @@ func reduce*[aBits, mBits](r: var BigInt[mBits], a: BigInt[aBits], M: BigInt[mBi # pass a pointer+length to a fixed session of the BSS. reduce(r.view, a.view, M.view) -func unsafeMontyResidue*[mBits](mres: var BigInt[mBits], a, N, r2modN: BigInt[mBits], negInvModWord: static BaseType) = +func montyResidue*[mBits](mres: var BigInt[mBits], a, N, r2modN: BigInt[mBits], negInvModWord: static BaseType) = ## Convert a BigInt from its natural representation ## to the Montgomery n-residue form ## @@ -134,7 +136,7 @@ func unsafeMontyResidue*[mBits](mres: var BigInt[mBits], a, N, r2modN: BigInt[mB ## Nesting Montgomery form is possible by applying this function twice. montyResidue(mres.view, a.view, N.view, r2modN.view, Word(negInvModWord)) -func unsafeRedc*[mBits](mres: var BigInt[mBits], N: BigInt[mBits], negInvModWord: static BaseType) = +func redc*[mBits](r: var BigInt[mBits], a, N: BigInt[mBits], negInvModWord: static BaseType) = ## Convert a BigInt from its Montgomery n-residue form ## to the natural representation ## @@ -142,7 +144,12 @@ func unsafeRedc*[mBits](mres: var BigInt[mBits], N: BigInt[mBits], negInvModWord ## ## Caller must take care of properly switching between ## the natural and montgomery domain. - redc(mres.view, N.view, Word(negInvModWord)) + let one = block: + var one: BigInt[mBits] + one.setInternalBitLength() + one.limbs[0] = Word(1) + one + redc(r.view, a.view, one.view, N.view, Word(negInvModWord)) func montyMul*[mBits](r: var BigInt[mBits], a, b, M: BigInt[mBits], negInvModWord: static BaseType) = ## Compute r <- a*b (mod M) in the Montgomery domain diff --git a/constantine/math/bigints_raw.nim b/constantine/math/bigints_raw.nim index aa8b8d0..1adaff8 100644 --- a/constantine/math/bigints_raw.nim +++ b/constantine/math/bigints_raw.nim @@ -448,7 +448,7 @@ func montyMul*( # Then substract M discard r.sub(M, r_hi.isNonZero() or not r.sub(M, CtFalse)) -func redc*(a: BigIntViewMut, N: BigIntViewConst, negInvModWord: Word) = +func redc*(r: BigIntViewMut, a: BigIntViewAny, one, N: BigIntViewConst, negInvModWord: Word) {.inline.} = ## Transform a bigint ``a`` from it's Montgomery N-residue representation (mod N) ## to the regular natural representation (mod N) ## @@ -471,25 +471,12 @@ func redc*(a: BigIntViewMut, N: BigIntViewConst, negInvModWord: Word) = checkOddModulus(N) checkMatchingBitlengths(a, N) - let nLen = N.numLimbs() - for i in 0 ..< nLen: - - let z0 = wordMul(a[0], negInvModWord) - var carry = DoubleWord(0) - - for j in 0 ..< nLen: - let z = DoubleWord(a[i]) + unsafeExtPrecMul(z0, N[i]) + carry - carry = z shr WordBitSize - if j != 0: - a[j] = Word(z) and MaxWord - - a[^1] = Word(carry) - -# TODO: benchmark Montgomery Multiplication vs Shift-Left (after constant-time division) + # TODO: This is a Montgomery multiplication by 1 and can be specialized + montyMul(r, a, one, N, negInvModWord) func montyResidue*( r: BigIntViewMut, a: BigIntViewAny, - N, r2modN: BigIntViewConst, negInvModWord: Word) = + N, r2modN: BigIntViewConst, negInvModWord: Word) {.inline.} = ## Transform a bigint ``a`` from it's natural representation (mod N) ## to a the Montgomery n-residue representation ## @@ -512,27 +499,3 @@ func montyResidue*( checkMatchingBitlengths(a, N) montyMul(r, a, r2ModN, N, negInvModWord) - -func montyResidue*(a: BigIntViewMut, N: BigIntViewConst) = - ## Transform a bigint ``a`` from it's natural representation (mod N) - ## to a the Montgomery n-residue representation - ## - ## Modular shift - based - ## - ## with W = N.numLimbs() - ## and R = (2^WordBitSize)^W - ## - ## Does "a * R (mod N)" - ## - ## `a`: The source BigInt in the natural representation. `a` in [0, N) range - ## `N`: The field modulus. N must be odd. - ## - ## Important: `a` is overwritten - # Reference: https://eprint.iacr.org/2017/1057.pdf - checkValidModulus(N) - checkOddModulus(N) - checkMatchingBitlengths(a, N) - - let nLen = N.numLimbs() - for i in countdown(nLen, 1): - a.shlAddMod(Zero, N) diff --git a/constantine/math/finite_fields.nim b/constantine/math/finite_fields.nim index 08a1efc..76ff58d 100644 --- a/constantine/math/finite_fields.nim +++ b/constantine/math/finite_fields.nim @@ -57,12 +57,17 @@ debug: func fromBig*[C: static Curve](T: type Fq[C], src: BigInt): Fq[C] {.noInit.} = ## Convert a BigInt to its Montgomery form - result.mres.unsafeMontyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord()) + result.mres.montyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord()) + +func fromBig*[C: static Curve](dst: var Fq[C], src: BigInt) {.noInit.} = + ## Convert a BigInt to its Montgomery form + dst.mres.montyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord()) func toBig*(src: Fq): auto {.noInit.} = ## Convert a finite-field element to a BigInt in natral representation - result = src.mres - result.unsafeRedC(Fq.C.Mod.mres, Fq.C.getNegInvModWord()) + var r {.noInit.}: typeof(src.mres) + r.redc(src.mres, Fq.C.Mod.mres, Fq.C.getNegInvModWord()) + return r # ############################################################ # diff --git a/constantine/math/precomputed.nim b/constantine/math/precomputed.nim index 32b527f..e6c7e28 100644 --- a/constantine/math/precomputed.nim +++ b/constantine/math/precomputed.nim @@ -161,7 +161,9 @@ func r2mod*(M: BigInt): BigInt = const w = M.limbs.len msb = M.bits-1 - WordBitSize * (w - 1) + start = (w-1)*WordBitSize + msb + stop = 2*WordBitSize*w result.limbs[^1] = Word(1 shl msb) # C0 = 2^(wn-1), the power of 2 immediatly less than the modulus - for _ in (w-1)*WordBitSize + msb ..< w * WordBitSize * 2: + for _ in start ..< stop: result.doubleMod(M) diff --git a/tests/test_finite_fields.nim b/tests/test_finite_fields.nim index 00ae70c..681c075 100644 --- a/tests/test_finite_fields.nim +++ b/tests/test_finite_fields.nim @@ -164,4 +164,141 @@ proc main() = # Check equality when converting back to natural domain 9'u64 == cast[uint64](r_bytes) + test "Addition mod 2^61 - 1": + block: + var x, y, z: Fq[Mersenne61] + + x.fromUint(80'u64) + y.fromUint(10'u64) + z.fromUint(90'u64) + + x += y + + var x_bytes: array[8, byte] + x_bytes.serializeRawUint(x, cpuEndian) + let new_x = cast[uint64](x_bytes) + + check: + # Check equality in the Montgomery domain + bool(z == x) + # Check equality when converting back to natural domain + new_x == 90'u64 + + block: + var x, y, z: Fq[Mersenne61] + + x.fromUint(1'u64 shl 61 - 2) + y.fromUint(1'u32) + z.fromUint(0'u32) + + x += y + + var x_bytes: array[8, byte] + x_bytes.serializeRawUint(x, cpuEndian) + let new_x = cast[uint64](x_bytes) + + check: + # Check equality in the Montgomery domain + bool(z == x) + # Check equality when converting back to natural domain + new_x == 0'u64 + + block: + var x, y, z: Fq[Mersenne61] + + x.fromUint(1'u64 shl 61 - 2) + y.fromUint(2'u64) + z.fromUint(1'u64) + + x += y + + var x_bytes: array[8, byte] + x_bytes.serializeRawUint(x, cpuEndian) + let new_x = cast[uint64](x_bytes) + + check: + # Check equality in the Montgomery domain + bool(z == x) + # Check equality when converting back to natural domain + new_x == 1'u64 + + test "Substraction mod 2^61 - 1": + block: + var x, y, z: Fq[Mersenne61] + + x.fromUint(80'u64) + y.fromUint(10'u64) + z.fromUint(70'u64) + + x -= y + + var x_bytes: array[8, byte] + x_bytes.serializeRawUint(x, cpuEndian) + let new_x = cast[uint64](x_bytes) + + check: + # Check equality in the Montgomery domain + bool(z == x) + # Check equality when converting back to natural domain + new_x == 70'u64 + + block: + var x, y, z: Fq[Mersenne61] + + x.fromUint(0'u64) + y.fromUint(1'u64) + z.fromUint(1'u64 shl 61 - 2) + + x -= y + + var x_bytes: array[8, byte] + x_bytes.serializeRawUint(x, cpuEndian) + let new_x = cast[uint64](x_bytes) + + check: + # Check equality in the Montgomery domain + bool(z == x) + # Check equality when converting back to natural domain + new_x == 1'u64 shl 61 - 2 + + test "Multiplication mod 101": + block: + var x, y, z: Fq[Mersenne61] + + x.fromUint(10'u32) + y.fromUint(10'u32) + z.fromUint(100'u32) + + let r = x * y + + var r_bytes: array[8, byte] + r_bytes.serializeRawUint(r, cpuEndian) + let new_r = cast[uint64](r_bytes) + + check: + # Check equality in the Montgomery domain + bool(z == r) + # Check equality when converting back to natural domain + cast[uint64](r_bytes) == 100'u64 + + block: + var x, y, z: Fq[Mersenne61] + + x.fromUint(1'u32 shl 31) + y.fromUint(1'u32 shl 31) + z.fromUint(2'u32) + + let r = x * y + + var r_bytes: array[8, byte] + r_bytes.serializeRawUint(r, cpuEndian) + let new_r = cast[uint64](r_bytes) + + check: + # Check equality in the Montgomery domain + bool(z == r) + # Check equality when converting back to natural domain + new_r == 2'u64 + + main()