Fix bug in redc: use montgomery mul for now. Add NIST P256 curve

This commit is contained in:
Mamy André-Ratsimbazafy 2020-02-16 18:59:10 +01:00
parent 7740bfbae4
commit c3d458e31b
No known key found for this signature in database
GPG Key ID: 7B88AD1FE79492E1
7 changed files with 175 additions and 51 deletions

View File

@ -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"
# ############################################################
#

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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
# ############################################################
#

View File

@ -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)

View File

@ -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()