mirror of
https://github.com/logos-storage/constantine.git
synced 2026-01-02 21:23:11 +00:00
* banderwagon curve declaration added * equality for banderwagon implemented * subgroup check added * map_to_field added * feat: banderwagon serialization * fix: imported codecs_status_codes into bls_signature * fix: spec links added in comments * fix: typo in curve declaration * fix: banderwagon subgroup check shifted to subgroups file + map_to_field removed * feat: new equality re-exported * fix: codecs_status_codes imported * fix: equality check removed from banderwagon.nim to twistedEdwards implementation * Update constantine/math/elliptic/ec_twistededwards_affine.nim Co-authored-by: Mamy Ratsimbazafy <mamy_github@numforge.co> * Update constantine/math/elliptic/ec_twistededwards_projective.nim Co-authored-by: Mamy Ratsimbazafy <mamy_github@numforge.co> * adding and doubling tests with minor fixes * feat: banderwagon & bandersnatch generators added * fix: doubling point error for twisted edwards projective * fix: negation of x co-ordinate in spec * fix: negetion of x in serialization * fix: negetion in deserializarion * feat: banderwagon tests * fix: comments added for tests and serialization * Update suggestion constantine/math/config/precompute.nim --------- Co-authored-by: Mamy Ratsimbazafy <mamy_github@numforge.co>
487 lines
16 KiB
Nim
487 lines
16 KiB
Nim
# 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
|
||
../../platforms/abstractions,
|
||
./type_bigint,
|
||
../io/io_bigints
|
||
|
||
{.used.}
|
||
|
||
# Precomputed constants
|
||
# We need alternate code paths for the VM
|
||
# for various reasons
|
||
# ------------------------------------------------------------
|
||
|
||
# ############################################################
|
||
#
|
||
# BigInt primitives
|
||
#
|
||
# ############################################################
|
||
#
|
||
# Those primitives are intended to be compile-time only
|
||
# Those are NOT tagged compile-time, using CTBool seems to confuse the VM
|
||
|
||
# We don't use distinct types here, they confuse the VM
|
||
# Similarly, using addC / subB from primitives confuses the VM
|
||
|
||
# As we choose to use the full 32/64 bits of the integers and there is no carry flag
|
||
# in the compile-time VM we need a portable (and slow) "adc" and "sbb".
|
||
# Hopefully compilation time stays decent.
|
||
|
||
const
|
||
HalfWidth = WordBitWidth shr 1
|
||
HalfBase = (BaseType(1) shl HalfWidth)
|
||
HalfMask = HalfBase - 1
|
||
|
||
func hi(n: BaseType): BaseType =
|
||
result = n shr HalfWidth
|
||
|
||
func lo(n: BaseType): BaseType =
|
||
result = n and HalfMask
|
||
|
||
func split(n: BaseType): tuple[hi, lo: BaseType] =
|
||
result.hi = n.hi
|
||
result.lo = n.lo
|
||
|
||
func merge(hi, lo: BaseType): BaseType =
|
||
(hi shl HalfWidth) or lo
|
||
|
||
func addC(cOut, sum: var BaseType, a, b, cIn: BaseType) =
|
||
# Add with carry, fallback for the Compile-Time VM
|
||
# (CarryOut, Sum) <- a + b + CarryIn
|
||
let (aHi, aLo) = split(a)
|
||
let (bHi, bLo) = split(b)
|
||
let tLo = aLo + bLo + cIn
|
||
let (cLo, rLo) = split(tLo)
|
||
let tHi = aHi + bHi + cLo
|
||
let (cHi, rHi) = split(tHi)
|
||
cOut = cHi
|
||
sum = merge(rHi, rLo)
|
||
|
||
func subB(bOut, diff: var BaseType, a, b, bIn: BaseType) =
|
||
# Substract with borrow, fallback for the Compile-Time VM
|
||
# (BorrowOut, Sum) <- a - b - BorrowIn
|
||
let (aHi, aLo) = split(a)
|
||
let (bHi, bLo) = split(b)
|
||
let tLo = HalfBase + aLo - bLo - bIn
|
||
let (noBorrowLo, rLo) = split(tLo)
|
||
let tHi = HalfBase + aHi - bHi - BaseType(noBorrowLo == 0)
|
||
let (noBorrowHi, rHi) = split(tHi)
|
||
bOut = BaseType(noBorrowHi == 0)
|
||
diff = merge(rHi, rLo)
|
||
|
||
func add(a: var BigInt, w: BaseType): bool =
|
||
## Limbs addition, add a number that fits in a word
|
||
## Returns the carry
|
||
var carry, sum: BaseType
|
||
addC(carry, sum, BaseType(a.limbs[0]), w, carry)
|
||
a.limbs[0] = SecretWord(sum)
|
||
for i in 1 ..< a.limbs.len:
|
||
let ai = BaseType(a.limbs[i])
|
||
addC(carry, sum, ai, 0, carry)
|
||
a.limbs[i] = SecretWord(sum)
|
||
|
||
result = bool(carry)
|
||
|
||
func dbl(a: var BigInt): bool =
|
||
## In-place multiprecision double
|
||
## a -> 2a
|
||
var carry, sum: BaseType
|
||
for i in 0 ..< a.limbs.len:
|
||
let ai = BaseType(a.limbs[i])
|
||
addC(carry, sum, ai, ai, carry)
|
||
a.limbs[i] = SecretWord(sum)
|
||
|
||
result = bool(carry)
|
||
|
||
func sub(a: var BigInt, w: BaseType): bool =
|
||
## Limbs substraction, sub a number that fits in a word
|
||
## Returns the carry
|
||
var borrow, diff: BaseType
|
||
subB(borrow, diff, BaseType(a.limbs[0]), w, borrow)
|
||
a.limbs[0] = SecretWord(diff)
|
||
for i in 1 ..< a.limbs.len:
|
||
let ai = BaseType(a.limbs[i])
|
||
subB(borrow, diff, ai, 0, borrow)
|
||
a.limbs[i] = SecretWord(diff)
|
||
|
||
result = bool(borrow)
|
||
|
||
func mul(hi, lo: var BaseType, u, v: BaseType) =
|
||
## Extended precision multiplication
|
||
## (hi, lo) <- u * v
|
||
var x0, x1, x2, x3: BaseType
|
||
|
||
let
|
||
(uh, ul) = u.split()
|
||
(vh, vl) = v.split()
|
||
|
||
x0 = ul * vl
|
||
x1 = ul * vh
|
||
x2 = uh * vl
|
||
x3 = uh * vh
|
||
|
||
x1 += hi(x0) # This can't carry
|
||
x1 += x2 # but this can
|
||
if x1 < x2: # if carry, add it to x3
|
||
x3 += HalfBase
|
||
|
||
hi = x3 + hi(x1)
|
||
lo = merge(x1, lo(x0))
|
||
|
||
func muladd1(hi, lo: var BaseType, a, b, c: BaseType) {.inline.} =
|
||
## Extended precision multiplication + addition
|
||
## (hi, lo) <- a*b + c
|
||
##
|
||
## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001)
|
||
## so adding any c cannot overflow
|
||
var carry: BaseType
|
||
mul(hi, lo, a, b)
|
||
addC(carry, lo, lo, c, 0)
|
||
addC(carry, hi, hi, 0, carry)
|
||
|
||
func muladd2(hi, lo: var BaseType, a, b, c1, c2: BaseType) {.inline.}=
|
||
## Extended precision multiplication + addition + addition
|
||
## (hi, lo) <- a*b + c1 + c2
|
||
##
|
||
## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001)
|
||
## so adding 0xFFFFFFFFFFFFFFFF leads to (hi: 0xFFFFFFFFFFFFFFFF, lo: 0x0000000000000000)
|
||
## and we have enough space to add again 0xFFFFFFFFFFFFFFFF without overflowing
|
||
var carry1, carry2: BaseType
|
||
|
||
mul(hi, lo, a, b)
|
||
# Carry chain 1
|
||
addC(carry1, lo, lo, c1, 0)
|
||
addC(carry1, hi, hi, 0, carry1)
|
||
# Carry chain 2
|
||
addC(carry2, lo, lo, c2, 0)
|
||
addC(carry2, hi, hi, 0, carry2)
|
||
|
||
func cadd(a: var BigInt, b: BigInt, ctl: bool): bool {.used.} =
|
||
## In-place optional addition
|
||
##
|
||
## It is NOT constant-time and is intended
|
||
## only for compile-time precomputation
|
||
## of non-secret data.
|
||
var carry, sum: BaseType
|
||
for i in 0 ..< a.limbs.len:
|
||
let ai = BaseType(a.limbs[i])
|
||
let bi = BaseType(b.limbs[i])
|
||
addC(carry, sum, ai, bi, carry)
|
||
if ctl:
|
||
a.limbs[i] = SecretWord(sum)
|
||
|
||
result = bool(carry)
|
||
|
||
func csub(a: var BigInt, b: BigInt, ctl: bool): bool =
|
||
## In-place optional substraction
|
||
##
|
||
## It is NOT constant-time and is intended
|
||
## only for compile-time precomputation
|
||
## of non-secret data.
|
||
var borrow, diff: BaseType
|
||
for i in 0 ..< a.limbs.len:
|
||
let ai = BaseType(a.limbs[i])
|
||
let bi = BaseType(b.limbs[i])
|
||
subB(borrow, diff, ai, bi, borrow)
|
||
if ctl:
|
||
a.limbs[i] = SecretWord(diff)
|
||
|
||
result = bool(borrow)
|
||
|
||
func doubleMod(a: var BigInt, M: BigInt) =
|
||
## In-place modular double
|
||
## a -> 2a (mod M)
|
||
##
|
||
## It is NOT constant-time and is intended
|
||
## only for compile-time precomputation
|
||
## of non-secret data.
|
||
var ctl = dbl(a)
|
||
ctl = ctl or not a.csub(M, false)
|
||
discard csub(a, M, ctl)
|
||
|
||
func `<`(a, b: BigInt): bool =
|
||
var diff, borrow: BaseType
|
||
for i in 0 ..< a.limbs.len:
|
||
subB(borrow, diff, BaseType(a.limbs[i]), BaseType(b.limbs[i]), borrow)
|
||
|
||
result = bool borrow
|
||
|
||
func shiftRight*(a: var BigInt, k: int) =
|
||
## Shift right by k.
|
||
##
|
||
## k MUST be less than the base word size (2^32 or 2^64)
|
||
|
||
for i in 0 ..< a.limbs.len-1:
|
||
a.limbs[i] = (a.limbs[i] shr k) or (a.limbs[i+1] shl (WordBitWidth - k))
|
||
a.limbs[a.limbs.len-1] = a.limbs[a.limbs.len-1] shr k
|
||
|
||
# ############################################################
|
||
#
|
||
# Montgomery Magic Constants precomputation
|
||
#
|
||
# ############################################################
|
||
|
||
func checkOdd(a: BaseType) =
|
||
doAssert bool(a and 1), "Internal Error: the modulus must be odd to use the Montgomery representation."
|
||
|
||
func checkOdd(M: BigInt) =
|
||
checkOdd(BaseType M.limbs[0])
|
||
|
||
func checkValidModulus(M: BigInt) =
|
||
const expectedMsb = M.bits-1 - WordBitWidth * (M.limbs.len - 1)
|
||
let msb = log2_vartime(BaseType(M.limbs[M.limbs.len-1]))
|
||
|
||
# This is important for the constant-time explicit modulo operation
|
||
# "reduce" and bigint division.
|
||
doAssert msb == expectedMsb, "Internal Error: the modulus must use all declared bits and only those:\n" &
|
||
" Modulus '" & M.toHex() & "' is declared with " & $M.bits &
|
||
" bits but uses " & $(msb + WordBitWidth * (M.limbs.len - 1)) & " bits."
|
||
|
||
func countSpareBits*(M: BigInt): int =
|
||
## Count the number of extra bits
|
||
## in the modulus M representation.
|
||
##
|
||
## This is used for no-carry operations
|
||
## or lazily reduced operations by allowing
|
||
## output in range:
|
||
## - [0, 2p) if 1 bit is available
|
||
## - [0, 4p) if 2 bits are available
|
||
## - [0, 8p) if 3 bits are available
|
||
## - ...
|
||
checkValidModulus(M)
|
||
let msb = log2_vartime(BaseType(M.limbs[M.limbs.len-1]))
|
||
result = WordBitWidth - 1 - msb.int
|
||
|
||
func invModBitwidth*[T: SomeUnsignedInt](a: T): T =
|
||
# We use BaseType for return value because static distinct type
|
||
# confuses Nim semchecks [UPSTREAM BUG]
|
||
# We don't enforce compile-time evaluation here
|
||
# because static BigInt[bits] also causes semcheck troubles [UPSTREAM BUG]
|
||
|
||
# Modular inverse algorithm:
|
||
# Explanation p11 "Dumas iterations" based on Newton-Raphson:
|
||
# - Cetin Kaya Koc (2017), https://eprint.iacr.org/2017/411
|
||
# - Jean-Guillaume Dumas (2012), https://arxiv.org/pdf/1209.6626v2.pdf
|
||
# - Colin Plumb (1994), http://groups.google.com/groups?selm=1994Apr6.093116.27805%40mnemosyne.cs.du.edu
|
||
# Other sources:
|
||
# - https://crypto.stackexchange.com/questions/47493/how-to-determine-the-multiplicative-inverse-modulo-64-or-other-power-of-two
|
||
# - https://mumble.net/~campbell/2015/01/21/inverse-mod-power-of-two
|
||
# - http://marc-b-reynolds.github.io/math/2017/09/18/ModInverse.html
|
||
|
||
# We are in a special case
|
||
# where m = 2^WordBitWidth.
|
||
# For a and m to be coprimes, a must be odd.
|
||
#
|
||
# We have the following relation
|
||
# ax ≡ 1 (mod 2ᵏ) <=> ax(2 - ax) ≡ 1 (mod 2^(2k))
|
||
# which grows in O(log(log(a)))
|
||
checkOdd(a)
|
||
|
||
let k = log2_vartime(T.sizeof().uint32 * 8)
|
||
result = a # Start from an inverse of M0 modulo 2, M0 is odd and it's own inverse
|
||
for _ in 0 ..< k: # at each iteration we get the inverse mod(2^2k)
|
||
result *= 2 - a * result # x' = x(2 - ax)
|
||
|
||
func negInvModWord*[T: SomeUnsignedInt or SecretWord](a: T): T =
|
||
let t = invModBitwidth(BaseType a)
|
||
{.push hint[ConvFromXtoItselfNotNeeded]: off.}
|
||
return T(-SecretWord(t))
|
||
{.pop.}
|
||
|
||
func negInvModWord*(M: BigInt): BaseType =
|
||
## Returns the Montgomery domain magic constant for the input modulus:
|
||
##
|
||
## µ ≡ -1/M[0] (mod SecretWord)
|
||
##
|
||
## M[0] is the least significant limb of M
|
||
## M must be odd and greater than 2.
|
||
##
|
||
## Assuming 64-bit words:
|
||
##
|
||
## µ ≡ -1/M[0] (mod 2^64)
|
||
checkValidModulus(M)
|
||
return BaseType M.limbs[0].negInvModWord()
|
||
|
||
func r_powmod(n: static int, M: BigInt): BigInt =
|
||
## Returns the Montgomery domain magic constant for the input modulus:
|
||
##
|
||
## R ≡ R (mod M) with R = (2^WordBitWidth)^numWords
|
||
## or
|
||
## R² ≡ R² (mod M) with R = (2^WordBitWidth)^numWords
|
||
##
|
||
## Assuming a field modulus of size 256-bit with 63-bit words, we require 5 words
|
||
## R² ≡ ((2^63)^5)^2 (mod M) = 2^630 (mod M)
|
||
|
||
# Algorithm
|
||
# Bos and Montgomery, Montgomery Arithmetic from a Software Perspective
|
||
# https://eprint.iacr.org/2017/1057.pdf
|
||
#
|
||
# For R = r^n = 2^wn and 2^(wn − 1) ≤ N < 2^wn
|
||
# r^n = 2^63 in on 64-bit and w the number of words
|
||
#
|
||
# 1. C0 = 2^(wn - 1), the power of two immediately less than N
|
||
# 2. for i in 1 ... wn+1
|
||
# Ci = C(i-1) + C(i-1) (mod M)
|
||
#
|
||
# Thus: C(wn+1) ≡ 2^(wn+1) C0 ≡ 2^(wn + 1) 2^(wn - 1) ≡ 2^(2wn) ≡ (2^wn)^2 ≡ R² (mod M)
|
||
|
||
checkOdd(M)
|
||
checkValidModulus(M)
|
||
|
||
const
|
||
w = M.limbs.len
|
||
msb = M.bits-1 - WordBitWidth * (w - 1)
|
||
start = (w-1)*WordBitWidth + msb
|
||
stop = n*WordBitWidth*w
|
||
|
||
result.limbs[M.limbs.len-1] = SecretWord(BaseType(1) shl msb) # C0 = 2^(wn-1), the power of 2 immediatly less than the modulus
|
||
for _ in start ..< stop:
|
||
result.doubleMod(M)
|
||
|
||
func r2mod*(M: BigInt): BigInt =
|
||
## Returns the Montgomery domain magic constant for the input modulus:
|
||
##
|
||
## R² ≡ R² (mod M) with R = (2^WordBitWidth)^numWords
|
||
##
|
||
## Assuming a field modulus of size 256-bit with 63-bit words, we require 5 words
|
||
## R² ≡ ((2^63)^5)^2 (mod M) = 2^630 (mod M)
|
||
r_powmod(2, M)
|
||
|
||
func r3mod*(M: BigInt): BigInt =
|
||
## Returns
|
||
##
|
||
## R³ ≡ R³ (mod M) with R = (2^WordBitWidth)^numWords
|
||
##
|
||
## This is used in hash-to-curve to
|
||
## reduce a double-sized bigint mod M
|
||
## and map it to the Montgomery domain
|
||
## with just redc2x + mulMont
|
||
r_powmod(3, M)
|
||
|
||
func montyOne*(M: BigInt): BigInt =
|
||
## Returns "1 (mod M)" in the Montgomery domain.
|
||
## This is equivalent to R (mod M) in the natural domain
|
||
r_powmod(1, M)
|
||
|
||
func montyPrimeMinus1*(P: BigInt): BigInt =
|
||
## Compute P-1 in the Montgomery domain
|
||
## For use in constant-time sqrt
|
||
result = P
|
||
discard result.csub(P.montyOne(), true)
|
||
|
||
func primePlus1div2*(P: BigInt): BigInt =
|
||
## Compute (P+1)/2, assumes P is odd
|
||
## For use in constant-time modular inversion
|
||
##
|
||
## Warning ⚠️: Result is in the canonical domain (not Montgomery)
|
||
checkOdd(P)
|
||
|
||
# (P+1)/2 = P/2 + 1 if P is odd,
|
||
# this avoids overflowing if the prime uses all bits
|
||
# i.e. in the form (2^64)ʷ - 1 or (2^32)ʷ - 1
|
||
|
||
result = P
|
||
result.shiftRight(1)
|
||
let carry = result.add(1)
|
||
doAssert not carry
|
||
|
||
func primeMinus1div2*(P: BigInt): BigInt =
|
||
## Compute (P-1)/2
|
||
## For use in constant-time modular inversion
|
||
##
|
||
## Warning ⚠️: Result is in the canonical domain (not Montgomery)
|
||
|
||
result = P
|
||
# discard result.sub(1) # right-shifting automatically implies "-1" for odd numbers (which all prime >2 are).
|
||
result.shiftRight(1)
|
||
|
||
func primeMinus3div4_BE*[bits: static int](
|
||
P: BigInt[bits]
|
||
): array[bits.ceilDiv_vartime(8), byte] {.noInit.} =
|
||
## For an input prime `p`, compute (p-3)/4
|
||
## and return the result as a canonical byte array / octet string
|
||
## For use to check if a number is a square (quadratic residue)
|
||
## and if so compute the square root in a fused manner
|
||
##
|
||
# Output size:
|
||
# - (bits + 7) div 8: bits => byte conversion rounded up
|
||
# - (bits + 7 - 2): dividing by 4 means 2 bits is unused
|
||
# => TODO: reduce the output size (to potentially save a byte and corresponding multiplication/squarings)
|
||
|
||
var tmp = P
|
||
discard tmp.sub(3)
|
||
tmp.shiftRight(2)
|
||
|
||
discard result.marshal(tmp, bigEndian)
|
||
|
||
func primeMinus5div8_BE*[bits: static int](
|
||
P: BigInt[bits]
|
||
): array[bits.ceilDiv_vartime(8), byte] {.noInit.} =
|
||
## For an input prime `p`, compute (p-5)/8
|
||
## and return the result as a canonical byte array / octet string
|
||
## For use to check if a number is a square (quadratic residue)
|
||
## and if so compute the square root in a fused manner
|
||
##
|
||
# Output size:
|
||
# - (bits + 7) div 8: bits => byte conversion rounded up
|
||
# - (bits + 7 - 3): dividing by 8 means 3 bits is unused
|
||
# => TODO: reduce the output size (to potentially save a byte and corresponding multiplication/squarings)
|
||
|
||
var tmp = P
|
||
discard tmp.sub(5)
|
||
tmp.shiftRight(3)
|
||
|
||
discard result.marshal(tmp, bigEndian)
|
||
|
||
# ############################################################
|
||
#
|
||
# Compile-time Conversion to Montgomery domain
|
||
#
|
||
# ############################################################
|
||
# This is needed to avoid recursive dependencies
|
||
|
||
func mulMont_precompute(r: var BigInt, a, b, M: BigInt, m0ninv: BaseType) =
|
||
## Montgomery Multiplication using Coarse Grained Operand Scanning (CIOS)
|
||
var t: typeof(M) # zero-init
|
||
const N = t.limbs.len
|
||
var tN: BaseType
|
||
var tNp1: BaseType
|
||
|
||
var tmp: BaseType # Distinct types bug in the VM are a huge pain ...
|
||
|
||
for i in 0 ..< N:
|
||
var A: BaseType
|
||
for j in 0 ..< N:
|
||
muladd2(A, tmp, BaseType(a.limbs[j]), BaseType(b.limbs[i]), BaseType(t.limbs[j]), A)
|
||
t.limbs[j] = SecretWord(tmp)
|
||
addC(tNp1, tN, tN, A, 0)
|
||
|
||
var C, lo: BaseType
|
||
let m = BaseType(t.limbs[0]) * m0ninv
|
||
muladd1(C, lo, m, BaseType(M.limbs[0]), BaseType(t.limbs[0]))
|
||
for j in 1 ..< N:
|
||
muladd2(C, tmp, m, BaseType(M.limbs[j]), BaseType(t.limbs[j]), C)
|
||
t.limbs[j-1] = SecretWord(tmp)
|
||
|
||
var carry: BaseType
|
||
addC(carry, tmp, tN, C, 0)
|
||
t.limbs[N-1] = SecretWord(tmp)
|
||
addC(carry, tN, tNp1, 0, carry)
|
||
|
||
discard t.csub(M, (tN != 0) or not(precompute.`<`(t, M)))
|
||
r = t
|
||
|
||
func montyResidue_precompute*(r: var BigInt, a, M, r2modM: BigInt,
|
||
m0ninv: BaseType) =
|
||
## Transform a bigint ``a`` from it's natural representation (mod N)
|
||
## to a the Montgomery n-residue representation
|
||
## This is intended for compile-time precomputations-only
|
||
mulMont_precompute(r, a, r2ModM, M, m0ninv)
|