Constant time (#185)

* Implement fully constant-time division closes #2 closes #9

* constant-time hex parsing

* prevent cache timing attacks in toHex() conversion (which is only for test/debug purposes anyway)
This commit is contained in:
Mamy Ratsimbazafy 2022-02-28 09:23:26 +01:00 committed by GitHub
parent ffacf61e8a
commit 26954f905a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 391 additions and 409 deletions

View File

@ -157,7 +157,7 @@ func validate_sig*(signature: Signature): CttBLSStatus =
func serialize_secret_key*(dst: var array[32, byte], secret_key: SecretKey): CttBLSStatus = func serialize_secret_key*(dst: var array[32, byte], secret_key: SecretKey): CttBLSStatus =
## Serialize a secret key ## Serialize a secret key
## Returns cttBLS_Success if successful ## Returns cttBLS_Success if successful
dst.exportRawUint(secret_key.raw, bigEndian) dst.marshal(secret_key.raw, bigEndian)
return cttBLS_Success return cttBLS_Success
func serialize_public_key_compressed*(dst: var array[48, byte], public_key: PublicKey): CttBLSStatus = func serialize_public_key_compressed*(dst: var array[48, byte], public_key: PublicKey): CttBLSStatus =
@ -170,7 +170,7 @@ func serialize_public_key_compressed*(dst: var array[48, byte], public_key: Publ
dst[0] = byte 0b11000000 # Compressed + Infinity dst[0] = byte 0b11000000 # Compressed + Infinity
return cttBLS_Success return cttBLS_Success
dst.exportRawUint(public_key.raw.x, bigEndian) dst.marshal(public_key.raw.x, bigEndian)
# The curve equation has 2 solutions for y² = x³ + 4 with y unknown and x known # The curve equation has 2 solutions for y² = x³ + 4 with y unknown and x known
# The lexicographically largest will have bit 381 set to 1 # The lexicographically largest will have bit 381 set to 1
# (and bit 383 for the compressed representation) # (and bit 383 for the compressed representation)
@ -191,8 +191,8 @@ func serialize_signature_compressed*(dst: var array[96, byte], signature: Signat
dst[0] = byte 0b11000000 # Compressed + Infinity dst[0] = byte 0b11000000 # Compressed + Infinity
return cttBLS_Success return cttBLS_Success
dst.toOpenArray(0, 48-1).exportRawUint(signature.raw.x.c1, bigEndian) dst.toOpenArray(0, 48-1).marshal(signature.raw.x.c1, bigEndian)
dst.toOpenArray(48, 96-1).exportRawUint(signature.raw.x.c0, bigEndian) dst.toOpenArray(48, 96-1).marshal(signature.raw.x.c0, bigEndian)
let isLexicographicallyLargest = let isLexicographicallyLargest =
if signature.raw.y.c1.isZero().bool(): if signature.raw.y.c1.isZero().bool():
@ -208,7 +208,7 @@ func deserialize_secret_key*(dst: var SecretKey, src: array[32, byte]): CttBLSSt
## ##
## This is protected against side-channel unless your key is invalid. ## This is protected against side-channel unless your key is invalid.
## In that case it will like whether it's all zeros or larger than the curve order. ## In that case it will like whether it's all zeros or larger than the curve order.
dst.raw.fromRawUint(src, bigEndian) dst.raw.unmarshal(src, bigEndian)
let status = validate_seckey(dst) let status = validate_seckey(dst)
if status != cttBLS_Success: if status != cttBLS_Success:
dst.raw.setZero() dst.raw.setZero()
@ -240,7 +240,7 @@ func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array
# General case # General case
var t{.noInit.}: matchingBigInt(BLS12_381) var t{.noInit.}: matchingBigInt(BLS12_381)
t.fromRawUint(src, bigEndian) t.unmarshal(src, bigEndian)
t.limbs[^1] = t.limbs[^1] and (MaxWord shr 3) # The first 3 bytes contain metadata to mask out t.limbs[^1] = t.limbs[^1] and (MaxWord shr 3) # The first 3 bytes contain metadata to mask out
if bool(t >= BLS12_381.Mod()): if bool(t >= BLS12_381.Mod()):
@ -294,7 +294,7 @@ func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[
# General case # General case
var t{.noInit.}: matchingBigInt(BLS12_381) var t{.noInit.}: matchingBigInt(BLS12_381)
t.fromRawUint(src.toOpenArray(0, 48-1), bigEndian) t.unmarshal(src.toOpenArray(0, 48-1), bigEndian)
t.limbs[^1] = t.limbs[^1] and (MaxWord shr 3) # The first 3 bytes contain metadata to mask out t.limbs[^1] = t.limbs[^1] and (MaxWord shr 3) # The first 3 bytes contain metadata to mask out
if bool(t >= BLS12_381.Mod()): if bool(t >= BLS12_381.Mod()):
@ -303,7 +303,7 @@ func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[
var x{.noInit.}: Fp2[BLS12_381] var x{.noInit.}: Fp2[BLS12_381]
x.c1.fromBig(t) x.c1.fromBig(t)
t.fromRawUint(src.toOpenArray(48, 96-1), bigEndian) t.unmarshal(src.toOpenArray(48, 96-1), bigEndian)
if bool(t >= BLS12_381.Mod()): if bool(t >= BLS12_381.Mod()):
return cttBLS_CoordinateGreaterOrEqualThanModulus return cttBLS_CoordinateGreaterOrEqualThanModulus

View File

@ -43,7 +43,7 @@ func parseRawUint(
## Return false if the integer is larger than the field modulus. ## Return false if the integer is larger than the field modulus.
## Returns true on success. ## Returns true on success.
var big {.noInit.}: BigInt[254] var big {.noInit.}: BigInt[254]
big.fromRawUint(src, bigEndian) big.unmarshal(src, bigEndian)
if not bool(big < Mod(BN254_Snarks)): if not bool(big < Mod(BN254_Snarks)):
return cttEVM_IntLargerThanModulus return cttEVM_IntLargerThanModulus
@ -136,10 +136,10 @@ func eth_evm_ecadd*(
var aff{.noInit.}: ECP_ShortW_Aff[Fp[BN254_Snarks], G1] var aff{.noInit.}: ECP_ShortW_Aff[Fp[BN254_Snarks], G1]
aff.affine(R) aff.affine(R)
r.toOpenArray(0, 31).exportRawUint( r.toOpenArray(0, 31).marshal(
aff.x, bigEndian aff.x, bigEndian
) )
r.toOpenArray(32, 63).exportRawUint( r.toOpenArray(32, 63).marshal(
aff.y, bigEndian aff.y, bigEndian
) )
@ -185,7 +185,7 @@ func eth_evm_ecmul*(
var smod{.noInit.}: Fr[BN254_Snarks] var smod{.noInit.}: Fr[BN254_Snarks]
var s{.noInit.}: BigInt[256] var s{.noInit.}: BigInt[256]
s.fromRawUint(padded.toOpenArray(64,95), bigEndian) s.unmarshal(padded.toOpenArray(64,95), bigEndian)
when true: when true:
# The spec allows s to be bigger than the curve order r and the field modulus p. # The spec allows s to be bigger than the curve order r and the field modulus p.
@ -210,10 +210,10 @@ func eth_evm_ecmul*(
var aff{.noInit.}: ECP_ShortW_Aff[Fp[BN254_Snarks], G1] var aff{.noInit.}: ECP_ShortW_Aff[Fp[BN254_Snarks], G1]
aff.affine(P) aff.affine(P)
r.toOpenArray(0, 31).exportRawUint( r.toOpenArray(0, 31).marshal(
aff.x, bigEndian aff.x, bigEndian
) )
r.toOpenArray(32, 63).exportRawUint( r.toOpenArray(32, 63).marshal(
aff.y, bigEndian aff.y, bigEndian
) )

View File

@ -223,7 +223,7 @@ func hashToField*[Field; B1, B2, B3: byte|char, count: static int](
template tv: untyped = uniform_bytes.toOpenArray(elm_offset, elm_offset + L-1) template tv: untyped = uniform_bytes.toOpenArray(elm_offset, elm_offset + L-1)
var big2x {.noInit.}: BigInt[2 * getCurveBitwidth(Field.C)] var big2x {.noInit.}: BigInt[2 * getCurveBitwidth(Field.C)]
big2x.fromRawUint(tv, bigEndian) big2x.unmarshal(tv, bigEndian)
# Reduces modulo p and output in Montgomery domain # Reduces modulo p and output in Montgomery domain
when m == 1: when m == 1:

View File

@ -114,7 +114,7 @@ func powMontUnsafeExponent*[mBits: static int](
var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]] var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]]
powMontUnsafeExponent(a.limbs, exponent, M.limbs, one.limbs, negInvModWord, scratchSpace, spareBits) powMontUnsafeExponent(a.limbs, exponent, M.limbs, one.limbs, negInvModWord, scratchSpace, spareBits)
from ../io/io_bigints import exportRawUint from ../io/io_bigints import marshal
# Workaround recursive dependencies # Workaround recursive dependencies
func powMont*[mBits, eBits: static int]( func powMont*[mBits, eBits: static int](
@ -132,7 +132,7 @@ func powMont*[mBits, eBits: static int](
## This is constant-time: the window optimization does ## This is constant-time: the window optimization does
## not reveal the exponent bits or hamming weight ## not reveal the exponent bits or hamming weight
var expBE {.noInit.}: array[(ebits + 7) div 8, byte] var expBE {.noInit.}: array[(ebits + 7) div 8, byte]
expBE.exportRawUint(exponent, bigEndian) expBE.marshal(exponent, bigEndian)
powMont(a, expBE, M, one, negInvModWord, windowSize, spareBits) powMont(a, expBE, M, one, negInvModWord, windowSize, spareBits)
@ -155,7 +155,7 @@ func powMontUnsafeExponent*[mBits, eBits: static int](
## This uses fixed window optimization ## This uses fixed window optimization
## A window size in the range [1, 5] must be chosen ## A window size in the range [1, 5] must be chosen
var expBE {.noInit.}: array[(ebits + 7) div 8, byte] var expBE {.noInit.}: array[(ebits + 7) div 8, byte]
expBE.exportRawUint(exponent, bigEndian) expBE.marshal(exponent, bigEndian)
powMontUnsafeExponent(a, expBE, M, one, negInvModWord, windowSize, spareBits) powMontUnsafeExponent(a, expBE, M, one, negInvModWord, windowSize, spareBits)

View File

@ -367,7 +367,7 @@ func div10*(a: var Limbs): SecretWord =
# Normalize # Normalize
hi = (hi shl clz) or (lo shr (WordBitWidth - clz)) hi = (hi shl clz) or (lo shr (WordBitWidth - clz))
lo = lo shl clz lo = lo shl clz
unsafeDiv2n1n(a[i], result, hi, lo, norm10) div2n1n(a[i], result, hi, lo, norm10)
# Undo normalization # Undo normalization
result = result shr clz result = result shr clz

View File

@ -165,7 +165,7 @@ func shlAddMod_estimate(a: LimbsViewMut, aLen: int,
# Get a quotient q, at most we will be 2 iterations off # Get a quotient q, at most we will be 2 iterations off
# from the true quotient # from the true quotient
var q, r: SecretWord var q, r: SecretWord
unsafeDiv2n1n(q, r, a0, a1, m0) # Estimate quotient div2n1n(q, r, a0, a1, m0) # Estimate quotient
q = mux( # If n_hi == divisor q = mux( # If n_hi == divisor
a0 == m0, MaxWord, # Quotient == MaxWord (0b1111...1111) a0 == m0, MaxWord, # Quotient == MaxWord (0b1111...1111)
mux( mux(
@ -226,7 +226,7 @@ func shlAddMod(a: LimbsViewMut, aLen: int,
let m0 = M[0] shl (WordBitWidth-R) let m0 = M[0] shl (WordBitWidth-R)
var q, r: SecretWord var q, r: SecretWord
unsafeDiv2n1n(q, r, hi, lo, m0) # (hi, lo) mod M div2n1n(q, r, hi, lo, m0) # (hi, lo) mod M
a[0] = r shr (WordBitWidth-R) a[0] = r shr (WordBitWidth-R)

View File

@ -661,7 +661,7 @@ func powMont*(
## - On input ``a`` is the base, on ``output`` a = a^exponent (mod M) ## - On input ``a`` is the base, on ``output`` a = a^exponent (mod M)
## ``a`` is in the Montgomery domain ## ``a`` is in the Montgomery domain
## - ``exponent`` is the exponent in big-endian canonical format (octet-string) ## - ``exponent`` is the exponent in big-endian canonical format (octet-string)
## Use ``exportRawUint`` for conversion ## Use ``marshal`` for conversion
## - ``M`` is the modulus ## - ``M`` is the modulus
## - ``one`` is 1 (mod M) in montgomery representation ## - ``one`` is 1 (mod M) in montgomery representation
## - ``m0ninv`` is the montgomery magic constant "-1/M[0] mod 2^WordBitWidth" ## - ``m0ninv`` is the montgomery magic constant "-1/M[0] mod 2^WordBitWidth"

View File

@ -69,7 +69,7 @@ declareCurves:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
curve P224: # NIST P-224 curve P224: # NIST P-224
bitwidth: 224 bitwidth: 224
modulus: "0xffffffff_ffffffff_ffffffff_ffffffff_00000000_00000000_00000001" modulus: "0xffffffffffffffffffffffffffffffff000000000000000000000001"
curve BN254_Nogami: # Integer Variable χBased Ate Pairing, 2008, Nogami et al curve BN254_Nogami: # Integer Variable χBased Ate Pairing, 2008, Nogami et al
bitwidth: 254 bitwidth: 254
modulus: "0x2523648240000001ba344d80000000086121000000000013a700000000000013" modulus: "0x2523648240000001ba344d80000000086121000000000013a700000000000013"
@ -173,7 +173,7 @@ declareCurves:
modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"
curve Secp256k1: # Bitcoin curve curve Secp256k1: # Bitcoin curve
bitwidth: 256 bitwidth: 256
modulus: "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" modulus: "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"
curve BLS12_377: curve BLS12_377:
# Zexe curve # Zexe curve
# (p41) https://eprint.iacr.org/2018/962.pdf # (p41) https://eprint.iacr.org/2018/962.pdf

View File

@ -380,7 +380,7 @@ func primeMinus2_BE*[bits: static int](
var tmp = P var tmp = P
discard tmp.sub(2) discard tmp.sub(2)
result.exportRawUint(tmp, bigEndian) result.marshal(tmp, bigEndian)
func primePlus1div2*(P: BigInt): BigInt = func primePlus1div2*(P: BigInt): BigInt =
## Compute (P+1)/2, assumes P is odd ## Compute (P+1)/2, assumes P is odd
@ -415,7 +415,7 @@ func primeMinus1div2_BE*[bits: static int](
discard tmp.sub(1) discard tmp.sub(1)
tmp.shiftRight(1) tmp.shiftRight(1)
result.exportRawUint(tmp, bigEndian) result.marshal(tmp, bigEndian)
func primeMinus3div4_BE*[bits: static int]( func primeMinus3div4_BE*[bits: static int](
P: BigInt[bits] P: BigInt[bits]
@ -434,7 +434,7 @@ func primeMinus3div4_BE*[bits: static int](
discard tmp.sub(3) discard tmp.sub(3)
tmp.shiftRight(2) tmp.shiftRight(2)
result.exportRawUint(tmp, bigEndian) result.marshal(tmp, bigEndian)
func primeMinus5div8_BE*[bits: static int]( func primeMinus5div8_BE*[bits: static int](
P: BigInt[bits] P: BigInt[bits]
@ -453,7 +453,7 @@ func primeMinus5div8_BE*[bits: static int](
discard tmp.sub(5) discard tmp.sub(5)
tmp.shiftRight(3) tmp.shiftRight(3)
result.exportRawUint(tmp, bigEndian) result.marshal(tmp, bigEndian)
func primePlus1Div4_BE*[bits: static int]( func primePlus1Div4_BE*[bits: static int](
P: BigInt[bits] P: BigInt[bits]
@ -475,14 +475,14 @@ func primePlus1Div4_BE*[bits: static int](
# then divide by 2 # then divide by 2
tmp.shiftRight(1) tmp.shiftRight(1)
result.exportRawUint(tmp, bigEndian) result.marshal(tmp, bigEndian)
func toCanonicalIntRepr*[bits: static int]( func toCanonicalIntRepr*[bits: static int](
a: BigInt[bits] a: BigInt[bits]
): array[(bits+7) div 8, byte] {.noInit.} = ): array[(bits+7) div 8, byte] {.noInit.} =
## Export a bigint to its canonical BigEndian representation ## Export a bigint to its canonical BigEndian representation
## (octet-string) ## (octet-string)
result.exportRawUint(a, bigEndian) result.marshal(a, bigEndian)
# ############################################################ # ############################################################
# #

View File

@ -29,20 +29,24 @@ type
debug: debug:
import std/strutils func toHex*(a: SecretWord): string =
const hexChars = "0123456789abcdef"
const L = 2*sizeof(SecretWord)
result = newString(2 + L)
result[0] = '0'
result[1] = 'x'
var a = a
for j in countdown(L-1, 0):
result[j] = hexChars.secretLookup(a and SecretWord 0xF)
a = a shr 4
func toString*(a: Limbs): string = func toString*(a: Limbs): string =
result = "[" result = "["
result.add " 0x" & toHex(BaseType(a[0])) result.add " " & toHex(a[0])
for i in 1 ..< a.len: for i in 1 ..< a.len:
result.add ", 0x" & toHex(BaseType(a[i])) result.add ", " & toHex(a[i])
result.add "])" result.add "])"
func toHex*(a: Limbs): string =
result = "0x"
for i in countdown(a.len-1, 0):
result.add toHex(BaseType(a[i]))
func `$`*(a: BigInt): string = func `$`*(a: BigInt): string =
result = "BigInt[" result = "BigInt["
result.add $BigInt.bits result.add $BigInt.bits

View File

@ -224,7 +224,7 @@ func scalarMulGeneric*[EC](P: var EC, scalar: BigInt, window: static int = 5) =
var var
scratchSpace: array[1 shl window, EC] scratchSpace: array[1 shl window, EC]
scalarCanonicalBE: array[(scalar.bits+7) div 8, byte] # canonical big endian representation scalarCanonicalBE: array[(scalar.bits+7) div 8, byte] # canonical big endian representation
scalarCanonicalBE.exportRawUint(scalar, bigEndian) # Export is constant-time scalarCanonicalBE.marshal(scalar, bigEndian) # Export is constant-time
P.scalarMulGeneric(scalarCanonicalBE, scratchSpace) P.scalarMulGeneric(scalarCanonicalBE, scratchSpace)
func scalarMul*[EC]( func scalarMul*[EC](

View File

@ -184,5 +184,5 @@ func powUnsafeExponent*[F; bits: static int](
## - power analysis ## - power analysis
## - timing analysis ## - timing analysis
var expBE {.noInit.}: array[(bits + 7) div 8, byte] var expBE {.noInit.}: array[(bits + 7) div 8, byte]
expBE.exportRawUint(exponent, bigEndian) expBE.marshal(exponent, bigEndian)
a.powUnsafeExponent(expBE, window) a.powUnsafeExponent(expBE, window)

View File

@ -17,7 +17,7 @@ Constant-time APIs only leak the number of bits, of bytes or words of the
big integer. big integer.
The bytes API are constant-time and do not allocate: The bytes API are constant-time and do not allocate:
- BigInt or octet-string: fromRawUint, fromUint - BigInt or octet-string: unmarshal, fromUint
- Machine sized integers: fromUint - Machine sized integers: fromUint
If you decide to use the internal hex or decimal API, you SHOULD ensure that the data is well-formatted: If you decide to use the internal hex or decimal API, you SHOULD ensure that the data is well-formatted:
@ -33,13 +33,13 @@ The internal API is may be constant-time (temporarily) and may allocate.
The hexadecimal API allocates: The hexadecimal API allocates:
- `toHex` is constant-time - `toHex` is constant-time
- `appendHex` is constant-time - `appendHex` is constant-time
- `fromHex` is **NOT constant-time** (yet), it is intended for debugging or - `fromHex` is constant-time, it is intended for debugging or
(compile-time) configuration. It does not allocate. (compile-time) configuration. It does not allocate.
In particular it scans spaces and underscores and checks if the string In particular it scans spaces and underscores and checks if the string
starts with '0x'. starts with '0x'.
The decimal API allocates: The decimal API allocates:
- `toDecimal` is **NOT constant-time** (yet) and allocates - `toDecimal` is constant-time and allocates
- `fromDecimal` is constant-time and does not allocate. - `fromDecimal` is constant-time and does not allocate.
## Avoiding secret mistakes ## Avoiding secret mistakes

View File

@ -36,7 +36,7 @@ export BigInt, wordsRequired
# prototyping, research and debugging purposes, # prototyping, research and debugging purposes,
# and can use exceptions. # and can use exceptions.
func fromRawUintLE( func unmarshalLE(
dst: var BigInt, dst: var BigInt,
src: openarray[byte]) = src: openarray[byte]) =
## Parse an unsigned integer from its canonical ## Parse an unsigned integer from its canonical
@ -74,7 +74,7 @@ func fromRawUintLE(
for i in dst_idx + 1 ..< dst.limbs.len: for i in dst_idx + 1 ..< dst.limbs.len:
dst.limbs[i] = Zero dst.limbs[i] = Zero
func fromRawUintBE( func unmarshalBE(
dst: var BigInt, dst: var BigInt,
src: openarray[byte]) = src: openarray[byte]) =
## Parse an unsigned integer from its canonical ## Parse an unsigned integer from its canonical
@ -114,7 +114,7 @@ func fromRawUintBE(
for i in dst_idx + 1 ..< dst.limbs.len: for i in dst_idx + 1 ..< dst.limbs.len:
dst.limbs[i] = Zero dst.limbs[i] = Zero
func fromRawUint*( func unmarshal*(
dst: var BigInt, dst: var BigInt,
src: openarray[byte], src: openarray[byte],
srcEndianness: static Endianness) = srcEndianness: static Endianness) =
@ -129,11 +129,11 @@ func fromRawUint*(
## from a canonical integer representation ## from a canonical integer representation
when srcEndianness == littleEndian: when srcEndianness == littleEndian:
dst.fromRawUintLE(src) dst.unmarshalLE(src)
else: else:
dst.fromRawUintBE(src) dst.unmarshalBE(src)
func fromRawUint*( func unmarshal*(
T: type BigInt, T: type BigInt,
src: openarray[byte], src: openarray[byte],
srcEndianness: static Endianness): T {.inline.}= srcEndianness: static Endianness): T {.inline.}=
@ -146,21 +146,21 @@ func fromRawUint*(
## ##
## Can work at compile-time to embed curve moduli ## Can work at compile-time to embed curve moduli
## from a canonical integer representation ## from a canonical integer representation
result.fromRawUint(src, srcEndianness) result.unmarshal(src, srcEndianness)
func fromUint*( func fromUint*(
T: type BigInt, T: type BigInt,
src: SomeUnsignedInt): T {.inline.}= src: SomeUnsignedInt): T {.inline.}=
## Parse a regular unsigned integer ## Parse a regular unsigned integer
## and store it into a BigInt of size `bits` ## and store it into a BigInt of size `bits`
result.fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian) result.unmarshal(cast[array[sizeof(src), byte]](src), cpuEndian)
func fromUint*( func fromUint*(
dst: var BigInt, dst: var BigInt,
src: SomeUnsignedInt) {.inline.}= src: SomeUnsignedInt) {.inline.}=
## Parse a regular unsigned integer ## Parse a regular unsigned integer
## and store it into a BigInt of size `bits` ## and store it into a BigInt of size `bits`
dst.fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian) dst.unmarshal(cast[array[sizeof(src), byte]](src), cpuEndian)
# ############################################################ # ############################################################
# #
@ -180,7 +180,7 @@ template blobFrom(dst: var openArray[byte], src: SomeUnsignedInt, startIdx: int,
for i in 0 ..< sizeof(src): for i in 0 ..< sizeof(src):
dst[startIdx+sizeof(src)-1-i] = toByte(src shr (i * 8)) dst[startIdx+sizeof(src)-1-i] = toByte(src shr (i * 8))
func exportRawUintLE( func marshalLE(
dst: var openarray[byte], dst: var openarray[byte],
src: BigInt) = src: BigInt) =
## Serialize a bigint into its canonical little-endian representation ## Serialize a bigint into its canonical little-endian representation
@ -229,7 +229,7 @@ func exportRawUintLE(
dst[dst_idx+i] = toByte(lo shr ((tail-i)*8)) dst[dst_idx+i] = toByte(lo shr ((tail-i)*8))
return return
func exportRawUintBE( func marshalBE(
dst: var openarray[byte], dst: var openarray[byte],
src: BigInt) = src: BigInt) =
## Serialize a bigint into its canonical big-endian representation ## Serialize a bigint into its canonical big-endian representation
@ -281,7 +281,7 @@ func exportRawUintBE(
dst[tail-1-i] = toByte(lo shr ((tail-i)*8)) dst[tail-1-i] = toByte(lo shr ((tail-i)*8))
return return
func exportRawUint*( func marshal*(
dst: var openarray[byte], dst: var openarray[byte],
src: BigInt, src: BigInt,
dstEndianness: static Endianness) = dstEndianness: static Endianness) =
@ -301,9 +301,9 @@ func exportRawUint*(
zeroMem(dst, dst.len) zeroMem(dst, dst.len)
when dstEndianness == littleEndian: when dstEndianness == littleEndian:
exportRawUintLE(dst, src) marshalLE(dst, src)
else: else:
exportRawUintBE(dst, src) marshalBE(dst, src)
{.pop.} # {.push raises: [].} {.pop.} # {.push raises: [].}
@ -312,54 +312,23 @@ func exportRawUint*(
# Conversion helpers # Conversion helpers
# #
# ############################################################ # ############################################################
# TODO: constant-time
func readHexChar(c: char): uint8 {.inline.}= func readHexChar(c: char): SecretWord {.inline.}=
## Converts an hex char to an int ## Converts an hex char to an int
## CT: leaks position of invalid input if any. template sw(a: char or int): SecretWord = SecretWord(a)
## and leaks if 0..9 or a..f or A..F const k = WordBitWidth - 1
case c
of '0'..'9': result = uint8 ord(c) - ord('0')
of 'a'..'f': result = uint8 ord(c) - ord('a') + 10
of 'A'..'F': result = uint8 ord(c) - ord('A') + 10
else:
raise newException(ValueError, $c & "is not a hexadecimal character")
func skipPrefixes(current_idx: var int, str: string, radix: static range[2..16]) {.inline.} = let c = sw(c)
## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix
## CT:
## - leaks if input length < 2
## - leaks if input start with 0x, 0o or 0b prefix
if str.len < 2: let lowercaseMask = not -(((c - sw'a') or (sw('f') - c)) shr k)
return let uppercaseMask = not -(((c - sw'A') or (sw('F') - c)) shr k)
var val = c - sw'0'
val = val xor ((val xor (c - sw('a') + sw(10))) and lowercaseMask)
val = val xor ((val xor (c - sw('a') + sw(10))) and uppercaseMask)
val = val and sw(0xF) # Prevent overflow of invalid inputs
assert current_idx == 0, "skipPrefixes only works for prefixes (position 0 and 1 of the string)" return val
if str[0] == '0':
case str[1]
of {'x', 'X'}:
assert radix == 16, "Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16)"
current_idx = 2
of {'o', 'O'}:
assert radix == 8, "Parsing mismatch, 0o prefix is only valid for an octal number (base 8)"
current_idx = 2
of {'b', 'B'}:
assert radix == 2, "Parsing mismatch, 0b prefix is only valid for a binary number (base 2)"
current_idx = 2
else: discard
func countNonBlanks(hexStr: string, startPos: int): int =
## Count the number of non-blank characters
## ' ' (space) and '_' (underscore) are considered blank
##
## CT:
## - Leaks white-spaces and non-white spaces position
const blanks = {' ', '_'}
for c in hexStr:
if c in blanks:
result += 1
func hexToPaddedByteArray*(hexStr: string, output: var openArray[byte], order: static[Endianness]) = func hexToPaddedByteArray*(hexStr: string, output: var openArray[byte], order: static[Endianness]) =
## Read a hex string and store it in a byte array `output`. ## Read a hex string and store it in a byte array `output`.
@ -367,31 +336,42 @@ func hexToPaddedByteArray*(hexStr: string, output: var openArray[byte], order: s
## ##
## The source string must be hex big-endian. ## The source string must be hex big-endian.
## The destination array can be big or little endian ## The destination array can be big or little endian
##
## Only characters accepted are 0x or 0X prefix
## and 0-9,a-f,A-F in particular spaces and _ are not valid.
##
## Procedure is constant-time except for the presence (or absence) of the 0x prefix.
##
## This procedure is intended for configuration, prototyping, research and debugging purposes.
## You MUST NOT use it for production.
template sw(a: bool or int): SecretWord = SecretWord(a)
var var
skip = 0 skip = Zero
dstIdx: int dstIdx: int
shift = 4 shift = 4
skipPrefixes(skip, hexStr, 16)
if hexStr.len >= 2:
const blanks = {' ', '_'} skip = sw(2)*(
let nonBlanksCount = countNonBlanks(hexStr, skip) sw(hexStr[0] == '0') and
(sw(hexStr[1] == 'x') or sw(hexStr[1] == 'X'))
)
let maxStrSize = output.len * 2 let maxStrSize = output.len * 2
let size = hexStr.len - skip - nonBlanksCount let size = hexStr.len - skip.int
doAssert size <= maxStrSize, "size: " & $size & " (without blanks or prefix), maxSize: " & $maxStrSize doAssert size <= maxStrSize, "size: " & $size & ", maxSize: " & $maxStrSize
if size < maxStrSize: if size < maxStrSize:
# include extra byte if odd length # include extra byte if odd length
dstIdx = output.len - (size + 1) div 2 dstIdx = output.len - (size + 1) shr 1
# start with shl of 4 if length is even # start with shl of 4 if length is even
shift = 4 - size mod 2 * 4 shift = 4 - (size and 1) * 4
for srcIdx in skip ..< hexStr.len: for srcIdx in skip.int ..< hexStr.len:
if hexStr[srcIdx] in blanks: let c = hexStr[srcIdx]
continue let nibble = byte(c.readHexChar() shl shift)
let nibble = hexStr[srcIdx].readHexChar shl shift
when order == bigEndian: when order == bigEndian:
output[dstIdx] = output[dstIdx] or nibble output[dstIdx] = output[dstIdx] or nibble
else: else:
@ -409,11 +389,13 @@ func nativeEndianToHex(bytes: openarray[byte], order: static[Endianness]): strin
result[1] = 'x' result[1] = 'x'
for i in 0 ..< bytes.len: for i in 0 ..< bytes.len:
when order == system.cpuEndian: when order == system.cpuEndian:
result[2 + 2*i] = hexChars[int bytes[i] shr 4 and 0xF] let bi = bytes[i]
result[2 + 2*i+1] = hexChars[int bytes[i] and 0xF] result[2 + 2*i] = hexChars.secretLookup(SecretWord bi shr 4 and 0xF)
result[2 + 2*i+1] = hexChars.secretLookup(SecretWord bi and 0xF)
else: else:
result[2 + 2*i] = hexChars[int bytes[bytes.high - i] shr 4 and 0xF] let bmi = bytes[bytes.high - i]
result[2 + 2*i+1] = hexChars[int bytes[bytes.high - i] and 0xF] result[2 + 2*i] = hexChars.secretLookup(SecretWord bmi shr 4 and 0xF)
result[2 + 2*i+1] = hexChars.secretLookup(SecretWord bmi and 0xF)
# ############################################################ # ############################################################
# #
@ -429,8 +411,10 @@ func fromHex*(a: var BigInt, s: string) =
## ##
## Hex string is assumed big-endian ## Hex string is assumed big-endian
## ##
## This API is intended for configuration and debugging purposes ## Procedure is constant-time except for the presence (or absence) of the 0x prefix.
## Do not pass secret or private data to it. ##
## This procedure is intended for configuration, prototyping, research and debugging purposes.
## You MUST NOT use it for production.
## ##
## Can work at compile-time to declare curve moduli from their hex strings ## Can work at compile-time to declare curve moduli from their hex strings
@ -440,7 +424,7 @@ func fromHex*(a: var BigInt, s: string) =
hexToPaddedByteArray(s, bytes, bigEndian) hexToPaddedByteArray(s, bytes, bigEndian)
# 2. Convert canonical uint to Big Int # 2. Convert canonical uint to Big Int
a.fromRawUint(bytes, bigEndian) a.unmarshal(bytes, bigEndian)
func fromHex*(T: type BigInt, s: string): T {.noInit.} = func fromHex*(T: type BigInt, s: string): T {.noInit.} =
## Convert a hex string to BigInt that can hold ## Convert a hex string to BigInt that can hold
@ -450,8 +434,10 @@ func fromHex*(T: type BigInt, s: string): T {.noInit.} =
## ##
## Hex string is assumed big-endian ## Hex string is assumed big-endian
## ##
## This API is intended for configuration and debugging purposes ## Procedure is constant-time except for the presence (or absence) of the 0x prefix.
## Do not pass secret or private data to it. ##
## This procedure is intended for configuration, prototyping, research and debugging purposes.
## You MUST NOT use it for production.
## ##
## Can work at compile-time to declare curve moduli from their hex strings ## Can work at compile-time to declare curve moduli from their hex strings
result.fromHex(s) result.fromHex(s)
@ -468,11 +454,13 @@ func appendHex*(dst: var string, big: BigInt, order: static Endianness = bigEndi
## ##
## This is useful to reduce the number of allocations when serializing ## This is useful to reduce the number of allocations when serializing
## Fp towers ## Fp towers
##
## This function may allocate.
# 1. Convert Big Int to canonical uint # 1. Convert Big Int to canonical uint
const canonLen = (big.bits + 8 - 1) div 8 const canonLen = (big.bits + 8 - 1) div 8
var bytes: array[canonLen, byte] var bytes: array[canonLen, byte]
exportRawUint(bytes, big, cpuEndian) marshal(bytes, big, cpuEndian)
# 2 Convert canonical uint to hex # 2 Convert canonical uint to hex
dst.add bytes.nativeEndianToHex(order) dst.add bytes.nativeEndianToHex(order)
@ -646,8 +634,6 @@ const log10_2_Denom = 42039
# #
# 1 and 2 is solved by precomputing the length and make the number of add be fixed. # 1 and 2 is solved by precomputing the length and make the number of add be fixed.
# 3 is easily solved by doing "digitToPrint + ord('0')" instead # 3 is easily solved by doing "digitToPrint + ord('0')" instead
#
# For 4 for now we use non-constant-time division (TODO)
func decimalLength(bits: static int): int = func decimalLength(bits: static int): int =
doAssert bits < (high(uint) div log10_2_Num), doAssert bits < (high(uint) div log10_2_Num),
@ -659,10 +645,11 @@ func decimalLength(bits: static int): int =
func toDecimal*(a: BigInt): string = func toDecimal*(a: BigInt): string =
## Convert to a decimal string. ## Convert to a decimal string.
## ##
## It is intended for configuration, prototyping, research and debugging purposes. ## This procedure is intended for configuration, prototyping, research and debugging purposes.
## You MUST NOT use it for production. ## You MUST NOT use it for production.
## ##
## This function is NOT constant-time at the moment. ## This function is constant-time.
## This function does heap-allocation.
const len = decimalLength(BigInt.bits) const len = decimalLength(BigInt.bits)
result = newString(len) result = newString(len)

View File

@ -27,7 +27,7 @@ func fromUint*(dst: var FF,
src: SomeUnsignedInt) = src: SomeUnsignedInt) =
## Parse a regular unsigned integer ## Parse a regular unsigned integer
## and store it into a Fp or Fr ## and store it into a Fp or Fr
let raw {.noinit.} = (typeof dst.mres).fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian) let raw {.noinit.} = (typeof dst.mres).unmarshal(cast[array[sizeof(src), byte]](src), cpuEndian)
dst.fromBig(raw) dst.fromBig(raw)
func fromInt*(dst: var FF, func fromInt*(dst: var FF,
@ -42,11 +42,11 @@ func fromInt*(dst: var FF,
let isNeg = SecretBool((src shr msb_pos) and 1) let isNeg = SecretBool((src shr msb_pos) and 1)
let src = isNeg.mux(SecretWord -src, SecretWord src) let src = isNeg.mux(SecretWord -src, SecretWord src)
let raw {.noinit.} = (type dst.mres).fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian) let raw {.noinit.} = (type dst.mres).unmarshal(cast[array[sizeof(src), byte]](src), cpuEndian)
dst.fromBig(raw) dst.fromBig(raw)
dst.cneg(isNeg) dst.cneg(isNeg)
func exportRawUint*(dst: var openarray[byte], func marshal*(dst: var openarray[byte],
src: FF, src: FF,
dstEndianness: static Endianness) = dstEndianness: static Endianness) =
## Serialize a finite field element to its canonical big-endian or little-endian ## Serialize a finite field element to its canonical big-endian or little-endian
@ -58,7 +58,7 @@ func exportRawUint*(dst: var openarray[byte],
## If the buffer is bigger, output will be zero-padded left for big-endian ## If the buffer is bigger, output will be zero-padded left for big-endian
## or zero-padded right for little-endian. ## or zero-padded right for little-endian.
## I.e least significant bit is aligned to buffer boundary ## I.e least significant bit is aligned to buffer boundary
exportRawUint(dst, src.toBig(), dstEndianness) marshal(dst, src.toBig(), dstEndianness)
func appendHex*(dst: var string, f: FF, order: static Endianness = bigEndian) = func appendHex*(dst: var string, f: FF, order: static Endianness = bigEndian) =
## Stringify a finite field element to hex. ## Stringify a finite field element to hex.

View File

@ -297,7 +297,7 @@ iterator unpack(scalarByte: byte): bool =
func cyclotomic_exp*[FT](r: var FT, a: FT, exponent: BigInt, invert: bool) {.meter.} = func cyclotomic_exp*[FT](r: var FT, a: FT, exponent: BigInt, invert: bool) {.meter.} =
var eBytes: array[(exponent.bits+7) div 8, byte] var eBytes: array[(exponent.bits+7) div 8, byte]
eBytes.exportRawUint(exponent, bigEndian) eBytes.marshal(exponent, bigEndian)
r.setOne() r.setOne()
for b in eBytes: for b in eBytes:

View File

@ -23,23 +23,6 @@ import
# #
# ############################################################ # ############################################################
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.
# TODO !!! - Replace by constant-time, portable, non-assembly version
# -> use uint128? Compiler might add unwanted branches
{.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".}
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)
func mul*(hi, lo: var Ct[uint32], a, b: Ct[uint32]) {.inline.} = func mul*(hi, lo: var Ct[uint32], a, b: Ct[uint32]) {.inline.} =
## Extended precision multiplication ## Extended precision multiplication
## (hi, lo) <- a*b ## (hi, lo) <- a*b
@ -96,15 +79,14 @@ func smul*(hi, lo: var Ct[uint32], a, b: Ct[uint32]) {.inline.} =
when sizeof(int) == 8: when sizeof(int) == 8:
when defined(vcc): when defined(vcc):
from ./extended_precision_x86_64_msvc import unsafeDiv2n1n, mul, muladd1, muladd2, smul from ./extended_precision_x86_64_msvc import mul, muladd1, muladd2, smul
elif GCCCompatible: elif GCCCompatible:
# TODO: constant-time div2n1n # TODO: constant-time div2n1n
when X86: when X86:
from ./extended_precision_x86_64_gcc import unsafeDiv2n1n
from ./extended_precision_64bit_uint128 import mul, muladd1, muladd2, smul from ./extended_precision_64bit_uint128 import mul, muladd1, muladd2, smul
else: else:
from ./extended_precision_64bit_uint128 import unsafeDiv2n1n, mul, muladd1, muladd2, smul from ./extended_precision_64bit_uint128 import mul, muladd1, muladd2, smul
export unsafeDiv2n1n, mul, muladd1, muladd2, smul export mul, muladd1, muladd2, smul
# ############################################################ # ############################################################
# #

View File

@ -18,24 +18,6 @@ static:
doAssert GCC_Compatible doAssert GCC_Compatible
doAssert sizeof(int) == 8 doAssert sizeof(int) == 8
func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}=
## Division uint128 by uint64
## Warning ⚠️ :
## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE on some platforms
## - if n_hi > d result is undefined
{.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".}
var dblPrec {.noInit.}: uint128
{.emit:[dblPrec, " = (unsigned __int128)", n_hi," << 64 | (unsigned __int128)",n_lo,";"].}
# Don't forget to dereference the var param in C mode
when defined(cpp):
{.emit:[q, " = (NU64)(", dblPrec," / ", d, ");"].}
{.emit:[r, " = (NU64)(", dblPrec," % ", d, ");"].}
else:
{.emit:["*",q, " = (NU64)(", dblPrec," / ", d, ");"].}
{.emit:["*",r, " = (NU64)(", dblPrec," % ", d, ");"].}
func mul*(hi, lo: var Ct[uint64], a, b: Ct[uint64]) {.inline.} = func mul*(hi, lo: var Ct[uint64], a, b: Ct[uint64]) {.inline.} =
## Extended precision multiplication ## Extended precision multiplication
## (hi, lo) <- a*b ## (hi, lo) <- a*b

View File

@ -1,60 +0,0 @@
# 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 ../constant_time/ct_types
# ############################################################
#
# Extended precision primitives for X86-64 on GCC & Clang
#
# ############################################################
static:
doAssert(defined(gcc) or defined(clang) or defined(llvm_gcc))
doAssert sizeof(int) == 8
doAssert X86
func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}=
## Division uint128 by uint64
## Warning ⚠️ :
## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE
## - if n_hi > d result is undefined
{.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".}
# TODO !!! - Replace by constant-time, portable, non-assembly version
# -> use uint128? Compiler might add unwanted branches
# DIV r/m64
# Divide RDX:RAX (n_hi:n_lo) by r/m64
#
# Inputs
# - numerator high word in RDX,
# - numerator low word in RAX,
# - divisor as r/m parameter (register or memory at the compiler discretion)
# Result
# - Quotient in RAX
# - Remainder in RDX
# 1. name the register/memory "divisor"
# 2. don't forget to dereference the var hidden pointer
# 3. -
# 4. no clobbered registers beside explicitly used RAX and RDX
when defined(cpp):
asm """
divq %[divisor]
: "=a" (`q`), "=d" (`r`)
: "d" (`n_hi`), "a" (`n_lo`), [divisor] "rm" (`d`)
:
"""
else:
asm """
divq %[divisor]
: "=a" (`*q`), "=d" (`*r`)
: "d" (`n_hi`), "a" (`n_lo`), [divisor] "rm" (`d`)
:
"""

View File

@ -21,29 +21,11 @@ static:
doAssert sizeof(int) == 8 doAssert sizeof(int) == 8
doAssert X86 doAssert X86
func udiv128(highDividend, lowDividend, divisor: Ct[uint64], remainder: var Ct[uint64]): Ct[uint64] {.importc:"_udiv128", header: "<intrin.h>", nodecl.}
## Division 128 by 64, Microsoft only, 64-bit only,
## returns quotient as return value remainder as var parameter
## Warning ⚠️ :
## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE
## - if n_hi > d result is undefined
func umul128(a, b: Ct[uint64], hi: var Ct[uint64]): Ct[uint64] {.importc:"_umul128", header:"<intrin.h>", nodecl.} func umul128(a, b: Ct[uint64], hi: var Ct[uint64]): Ct[uint64] {.importc:"_umul128", header:"<intrin.h>", nodecl.}
## Unsigned extended precision multiplication ## Unsigned extended precision multiplication
## (hi, lo) <-- a * b ## (hi, lo) <-- a * b
## Return value is the low word ## Return value is the low word
func unsafeDiv2n1n*(q, r: var Ct[uint64], n_hi, n_lo, d: Ct[uint64]) {.inline.}=
## Division uint128 by uint64
## Warning ⚠️ :
## - if n_hi == d, quotient does not fit in an uint64 and will throw SIGFPE
## - if n_hi > d result is undefined
{.warning: "unsafeDiv2n1n is not constant-time at the moment on most hardware".}
# TODO !!! - Replace by constant-time, portable, non-assembly version
# -> use uint128? Compiler might add unwanted branches
q = udiv128(n_hi, n_lo, d, r)
func mul*(hi, lo: var Ct[uint64], a, b: Ct[uint64]) {.inline.} = func mul*(hi, lo: var Ct[uint64], a, b: Ct[uint64]) {.inline.} =
## Extended precision multiplication ## Extended precision multiplication
## (hi, lo) <- a*b ## (hi, lo) <- a*b

View File

@ -0,0 +1,78 @@
# 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 ./ct_types, ./ct_routines, ./multiplexers
func div2n1n*[T: Ct](q, r: var T, n_hi, n_lo, d: T) =
## Division uint128 by uint64 or 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.
# See
# https://www.bearssl.org/bigint.html
# br_divrem
const bits = sizeof(T)*8
q = T(0)
var hi = mux(n_hi == d, T(0), n_hi)
var lo = n_lo
for k in countdown(bits-1, 1):
let j = bits-k
let w = (hi shl j) or (lo shr k)
let ctl = (w >= d) or CTBool[T](hi shr k)
let hi2 = (w-d) shr j
let lo2 = lo - (d shl k)
hi = ctl.mux(hi2, hi)
lo = ctl.mux(lo2, lo)
q = q or (T(ctl) shl k)
let carry = (lo >= d) or hi.isNonZero()
q = q or T(carry)
r = carry.mux(lo - d, lo)
# Performance:
# If division ever becomes a bottleneck, we can avoid binary-shift
# by first computing a single-word reciprocal (mod 2⁶⁴)
# Then using multiplication for each quotient/remainder
# See algorithm 2 (reciprocal) and 4 in https://gmplib.org/~tege/division-paper.pdf
#
# Algorithm 2
# can be made constant-time via Newton-Raphson iterations
# or use binary shift division with
#
# v ← [2⁶⁴-1-d, 2⁶⁴-1]/d
#
# or (for assembly)
#
# v ← not(d << 64)/d
#
# (d normalized)
#
# Algorithm 4
# can be made constant-time via conditional moves.
#
# (q, r) ← DIV2BY1(〈u1, u0〉, d, v)
# In: β/2 ≤ d < β, u1 < d, v = ⌊(β2 1)/d⌋ β
#
# 1 〈q1, q0〉 ← vu1 // umul
# 2 〈q1, q0〉 ← 〈q1, q0〉 + 〈u1, u0〉
# 3 q1 ← (q1 + 1) mod β
# 4 r ← (u0 q1d) mod β // umullo
# 5 if r > q0 // Unpredictable condition
# 6 q1 ← (q1 1) mod β
# 7 r ← (r + d) mod β
# 8 if r ≥ d // Unlikely condition
# 9 q1 ← q1 + 1
# 10 r ← r d
# 11 return q1, r

View File

@ -186,6 +186,22 @@ template cneg*[T: Ct](x: T, ctl: CTBool[T]): T =
template trmFixSystemNotEq*{x != y}[T: Ct](x, y: T): CTBool[T] = template trmFixSystemNotEq*{x != y}[T: Ct](x, y: T): CTBool[T] =
noteq(x, y) noteq(x, y)
# ############################################################
#
# Table lookups
#
# ############################################################
func secretLookup*[T; S: Ct](table: openArray[T], index: S): T =
## Return table[index]
## This is constant-time, whatever the `index`, its value is not leaked
## This is also protected against cache-timing attack by always scanning the whole table
var val: S
for i in 0 ..< table.len:
let selector = S(i) == index
selector.ccopy(val, S table[i])
return T(val)
# ############################################################ # ############################################################
# #
# Optimized hardened zero comparison # Optimized hardened zero comparison

View File

@ -137,25 +137,34 @@ func mux*[T](ctl: CTBool[T], x, y: T): T {.inline.}=
## Returns x if ctl is true ## Returns x if ctl is true
## else returns y ## else returns y
## So equivalent to ctl? x: y ## So equivalent to ctl? x: y
when X86 and GCC_Compatible: when nimvm:
mux_x86(ctl, x, y)
else:
mux_fallback(ctl, x, y) mux_fallback(ctl, x, y)
else:
when X86 and GCC_Compatible:
mux_x86(ctl, x, y)
else:
mux_fallback(ctl, x, y)
func mux*[T: CTBool](ctl: CTBool, x, y: T): T {.inline.}= func mux*[T: CTBool](ctl: CTBool, x, y: T): T {.inline.}=
## Multiplexer / selector ## Multiplexer / selector
## Returns x if ctl is true ## Returns x if ctl is true
## else returns y ## else returns y
## So equivalent to ctl? x: y ## So equivalent to ctl? x: y
when X86 and GCC_Compatible: when nimvm:
mux_x86(ctl, x, y)
else:
mux_fallback(ctl, x, y) mux_fallback(ctl, x, y)
else:
when X86 and GCC_Compatible:
mux_x86(ctl, x, y)
else:
mux_fallback(ctl, x, y)
func ccopy*[T](ctl: CTBool[T], x: var T, y: T) {.inline.}= func ccopy*[T](ctl: CTBool[T], x: var T, y: T) {.inline.}=
## Conditional copy ## Conditional copy
## Copy ``y`` into ``x`` if ``ctl`` is true ## Copy ``y`` into ``x`` if ``ctl`` is true
when X86 and GCC_Compatible: when nimvm:
ccopy_x86(ctl, x, y)
else:
ccopy_fallback(ctl, x, y) ccopy_fallback(ctl, x, y)
else:
when X86 and GCC_Compatible:
ccopy_x86(ctl, x, y)
else:
ccopy_fallback(ctl, x, y)

View File

@ -10,7 +10,8 @@ import
constant_time/[ constant_time/[
ct_types, ct_types,
ct_routines, ct_routines,
multiplexers multiplexers,
ct_division
], ],
compilers/[ compilers/[
addcarry_subborrow, addcarry_subborrow,
@ -25,6 +26,7 @@ export
multiplexers, multiplexers,
addcarry_subborrow, addcarry_subborrow,
extended_precision, extended_precision,
ct_division,
bithacks, bithacks,
staticFor staticFor

View File

@ -211,9 +211,9 @@ func random_long01Seq(rng: var RngState, a: var BigInt) =
rng.random_long01Seq(buf) rng.random_long01Seq(buf)
let order = rng.sample_unsafe([bigEndian, littleEndian]) let order = rng.sample_unsafe([bigEndian, littleEndian])
if order == bigEndian: if order == bigEndian:
a.fromRawUint(buf, bigEndian) a.unmarshal(buf, bigEndian)
else: else:
a.fromRawUint(buf, littleEndian) a.unmarshal(buf, littleEndian)
func random_long01Seq(rng: var RngState, a: var FF) = func random_long01Seq(rng: var RngState, a: var FF) =
## Recursively initialize a BigInt (part of a field) or Field element ## Recursively initialize a BigInt (part of a field) or Field element

View File

@ -37,7 +37,7 @@ func unsafe_ECmul_double_add*[EC](
## ##
## This is highly VULNERABLE to timing attacks and power analysis attacks ## This is highly VULNERABLE to timing attacks and power analysis attacks
var scalarCanonical: array[(scalar.bits+7) div 8, byte] var scalarCanonical: array[(scalar.bits+7) div 8, byte]
scalarCanonical.exportRawUint(scalar, bigEndian) scalarCanonical.marshal(scalar, bigEndian)
var t0{.noInit.}, t1{.noInit.}: typeof(P) var t0{.noInit.}, t1{.noInit.}: typeof(P)
t0.setInf() t0.setInf()

View File

@ -25,13 +25,13 @@ proc mainArith() =
check: x.isZero().bool check: x.isZero().bool
test "isZero for non-zero": test "isZero for non-zero":
block: block:
let x = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let x = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check: not x.isZero().bool check: not x.isZero().bool
block: block:
let x = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000") let x = fromHex(BigInt[128], "0x00000000000000010000000000000000")
check: not x.isZero().bool check: not x.isZero().bool
block: block:
let x = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF") let x = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
check: not x.isZero().bool check: not x.isZero().bool
test "isZero for zero (compile-time)": test "isZero for zero (compile-time)":
@ -39,53 +39,53 @@ proc mainArith() =
check: static(x.isZero().bool) check: static(x.isZero().bool)
test "isZero for non-zero (compile-time)": test "isZero for non-zero (compile-time)":
block: block:
const x = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") const x = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check: static(not x.isZero().bool) check: static(not x.isZero().bool)
block: block:
const x = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000") const x = fromHex(BigInt[128], "0x00000000000000010000000000000000")
check: static(not x.isZero().bool) check: static(not x.isZero().bool)
block: block:
const x = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF") const x = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
check: static(not x.isZero().bool) check: static(not x.isZero().bool)
suite "Arithmetic operations - Addition" & " [" & $WordBitwidth & "-bit mode]": suite "Arithmetic operations - Addition" & " [" & $WordBitwidth & "-bit mode]":
test "Adding 2 zeros": test "Adding 2 zeros":
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") let b = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let carry = a.cadd(b, CtTrue) let carry = a.cadd(b, CtTrue)
check: a.isZero().bool check: a.isZero().bool
test "Adding 1 zero - real addition": test "Adding 1 zero - real addition":
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue) let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let c = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check: check:
bool(a == c) bool(a == c)
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") let b = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let carry = a.cadd(b, CtTrue) let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let c = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check: check:
bool(a == c) bool(a == c)
test "Adding 1 zero - fake addition": test "Adding 1 zero - fake addition":
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtFalse) let carry = a.cadd(b, CtFalse)
let c = a let c = a
check: check:
bool(a == c) bool(a == c)
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") let b = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let carry = a.cadd(b, CtFalse) let carry = a.cadd(b, CtFalse)
let c = a let c = a
@ -94,34 +94,34 @@ proc mainArith() =
test "Adding non-zeros - real addition": test "Adding non-zeros - real addition":
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000") var a = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue) let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000001") let c = fromHex(BigInt[128], "0x00000000000000010000000000000001")
check: check:
bool(a == c) bool(a == c)
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000") let b = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let carry = a.cadd(b, CtTrue) let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000001") let c = fromHex(BigInt[128], "0x00000000000000010000000000000001")
check: check:
bool(a == c) bool(a == c)
test "Adding non-zeros - fake addition": test "Adding non-zeros - fake addition":
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000") var a = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtFalse) let carry = a.cadd(b, CtFalse)
let c = a let c = a
check: check:
bool(a == c) bool(a == c)
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000") let b = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let carry = a.cadd(b, CtFalse) let carry = a.cadd(b, CtFalse)
let c = a let c = a
@ -130,21 +130,21 @@ proc mainArith() =
test "Addition limbs carry": test "Addition limbs carry":
block: block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFE") var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFE")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue) let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF") let c = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
check: check:
bool(a == c) bool(a == c)
not bool(carry) not bool(carry)
block: block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF") var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue) let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000001_00000000_00000000_00000000") let c = fromHex(BigInt[128], "0x00000001000000000000000000000000")
check: check:
bool(a == c) bool(a == c)
not bool(carry) not bool(carry)
@ -164,8 +164,8 @@ proc mainMul() =
test "Same size operand into double size result": test "Same size operand into double size result":
block: block:
var r = canary(BigInt[256]) var r = canary(BigInt[256])
let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE" let a = BigInt[128].fromHex"0x12345678FF11FFAA00321321CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f102e0722" let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f102e0722"
@ -178,7 +178,7 @@ proc mainMul() =
block: block:
var r = canary(BigInt[200]) var r = canary(BigInt[200])
let a = BigInt[29].fromHex"0x12345678" let a = BigInt[29].fromHex"0x12345678"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f665f787f65621ca08" let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f665f787f65621ca08"
@ -189,9 +189,9 @@ proc mainMul() =
test "Destination is properly zero-padded if multiplicands are too short": test "Destination is properly zero-padded if multiplicands are too short":
block: block:
var r = BigInt[200].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DE" var r = BigInt[200].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDE"
let a = BigInt[29].fromHex"0x12345678" let a = BigInt[29].fromHex"0x12345678"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f665f787f65621ca08" let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f665f787f65621ca08"
@ -205,108 +205,108 @@ proc mainMulHigh() =
test "Same size operand into double size result - discard first word": test "Same size operand into double size result - discard first word":
block: block:
var r = canary(BigInt[256]) var r = canary(BigInt[256])
let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE" let a = BigInt[128].fromHex"0x12345678FF11FFAA00321321CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32: when WordBitWidth == 32:
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f" let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f"
else: else:
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8" let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8"
r.prod_high_words(a, b, 1) r.prodhighwords(a, b, 1)
check: bool(r == expected) check: bool(r == expected)
r.prod_high_words(b, a, 1) r.prodhighwords(b, a, 1)
check: bool(r == expected) check: bool(r == expected)
test "Same size operand into double size result - discard first 3 words": test "Same size operand into double size result - discard first 3 words":
block: block:
var r = canary(BigInt[256]) var r = canary(BigInt[256])
let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE" let a = BigInt[128].fromHex"0x12345678FF11FFAA00321321CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32: when WordBitWidth == 32:
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd" let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd"
else: else:
let expected = BigInt[256].fromHex"fd5bdef43d64113" let expected = BigInt[256].fromHex"fd5bdef43d64113"
r.prod_high_words(a, b, 3) r.prodhighwords(a, b, 3)
check: bool(r == expected) check: bool(r == expected)
r.prod_high_words(b, a, 3) r.prodhighwords(b, a, 3)
check: bool(r == expected) check: bool(r == expected)
test "All lower words trigger a carry": test "All lower words trigger a carry":
block: block:
var r = canary(BigInt[256]) var r = canary(BigInt[256])
let a = BigInt[256].fromHex"0xFFFFF000_FFFFF111_FFFFFFFA_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF" let a = BigInt[256].fromHex"0xFFFFF000FFFFF111FFFFFFFAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
let b = BigInt[256].fromHex"0xFFFFFFFF_FFFFF222_FFFFFFFB_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF" let b = BigInt[256].fromHex"0xFFFFFFFFFFFFF222FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
# Full product: # Full product:
# fffff000_ffffe335_00ddc21a_00cf3972_00008109_00000013_ffffffff_fffffffe # fffff000ffffe33500ddc21a00cf39720000810900000013fffffffffffffffe
# 00000fff_00001ccb_00000009_00000000_00000000_00000000_00000000_00000001 # 00000fff00001ccb000000090000000000000000000000000000000000000001
let expected = BigInt[256].fromHex"0xfffff000_ffffe335_00ddc21a_00cf3972_00008109_00000013_ffffffff_fffffffe" let expected = BigInt[256].fromHex"0xfffff000ffffe33500ddc21a00cf39720000810900000013fffffffffffffffe"
when WordBitWidth == 32: when WordBitWidth == 32:
const startWord = 8 const startWord = 8
else: else:
const startWord = 4 const startWord = 4
r.prod_high_words(a, b, startWord) r.prodhighwords(a, b, startWord)
check: bool(r == expected) check: bool(r == expected)
r.prod_high_words(b, a, startWord) r.prodhighwords(b, a, startWord)
check: bool(r == expected) check: bool(r == expected)
test "Different size into large result": test "Different size into large result":
block: block:
var r = canary(BigInt[200]) var r = canary(BigInt[200])
let a = BigInt[29].fromHex"0x12345678" let a = BigInt[29].fromHex"0x12345678"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32: when WordBitWidth == 32:
let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6" let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6"
else: else:
let expected = BigInt[200].fromHex"fd5bdee" let expected = BigInt[200].fromHex"fd5bdee"
r.prod_high_words(a, b, 2) r.prodhighwords(a, b, 2)
check: bool(r == expected) check: bool(r == expected)
r.prod_high_words(b, a, 2) r.prodhighwords(b, a, 2)
check: bool(r == expected) check: bool(r == expected)
test "Destination is properly zero-padded if multiplicands are too short": test "Destination is properly zero-padded if multiplicands are too short":
block: block:
var r = BigInt[200].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DE" var r = BigInt[200].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDE"
let a = BigInt[29].fromHex"0x12345678" let a = BigInt[29].fromHex"0x12345678"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32: when WordBitWidth == 32:
let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6" let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6"
else: else:
let expected = BigInt[200].fromHex"fd5bdee" let expected = BigInt[200].fromHex"fd5bdee"
r.prod_high_words(a, b, 2) r.prodhighwords(a, b, 2)
check: bool(r == expected) check: bool(r == expected)
r.prod_high_words(b, a, 2) r.prodhighwords(b, a, 2)
check: bool(r == expected) check: bool(r == expected)
proc mainSquare() = proc mainSquare() =
suite "Multi-precision multiplication" & " [" & $WordBitwidth & "-bit mode]": suite "Multi-precision multiplication" & " [" & $WordBitwidth & "-bit mode]":
test "Squaring is consistent with multiplication (rBits = 2*aBits)": test "Squaring is consistent with multiplication (rBits = 2*aBits)":
block: block:
let a = BigInt[200].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DE" let a = BigInt[200].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDE"
var r_mul, r_sqr: BigInt[400] var rmul, rsqr: BigInt[400]
r_mul.prod(a, a) rmul.prod(a, a)
r_sqr.square(a) rsqr.square(a)
check: bool(r_mul == r_sqr) check: bool(rmul == rsqr)
test "Squaring is consistent with multiplication (truncated)": test "Squaring is consistent with multiplication (truncated)":
block: block:
let a = BigInt[200].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DE" let a = BigInt[200].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDE"
var r_mul, r_sqr: BigInt[256] var rmul, rsqr: BigInt[256]
r_mul.prod(a, a) rmul.prod(a, a)
r_sqr.square(a) rsqr.square(a)
check: bool(r_mul == r_sqr) check: bool(rmul == rsqr)
proc mainModular() = proc mainModular() =
suite "Modular operations - small modulus" & " [" & $WordBitwidth & "-bit mode]": suite "Modular operations - small modulus" & " [" & $WordBitwidth & "-bit mode]":
@ -347,7 +347,7 @@ proc mainModular() =
"\n expected (ll repr): " & $expected "\n expected (ll repr): " & $expected
test "2^64 mod 3": test "2^64 mod 3":
let a = BigInt[65].fromHex("0x1_00000000_00000000") let a = BigInt[65].fromHex("0x10000000000000000")
let m = BigInt[8].fromUint(3'u8) let m = BigInt[8].fromUint(3'u8)
var r = canary(BigInt[8]) var r = canary(BigInt[8])
@ -404,8 +404,8 @@ proc mainNeg() =
suite "Conditional negation" & " [" & $WordBitwidth & "-bit mode]": suite "Conditional negation" & " [" & $WordBitwidth & "-bit mode]":
test "Conditional negation": test "Conditional negation":
block: block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE") var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF") var b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
let a2 = a let a2 = a
let b2 = b let b2 = b
@ -421,8 +421,8 @@ proc mainNeg() =
bool(b.isZero) bool(b.isZero)
block: block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE") var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF") var b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
let a2 = a let a2 = a
let b2 = b let b2 = b
@ -436,8 +436,8 @@ proc mainNeg() =
test "Conditional negation with carries": test "Conditional negation with carries":
block: block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_FFFFFFFF") var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321FFFFFFFF")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_00000000_00000000") var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFF0000000000000000")
let a2 = a let a2 = a
let b2 = b let b2 = b
@ -453,8 +453,8 @@ proc mainNeg() =
bool(b.isZero) bool(b.isZero)
block: block:
var a = fromHex(BigInt[128], "0x12345678_00000000_00321321_FFFFFFFF") var a = fromHex(BigInt[128], "0x123456780000000000321321FFFFFFFF")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_00000000_00000000") var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFF0000000000000000")
let a2 = a let a2 = a
let b2 = b let b2 = b
@ -468,8 +468,8 @@ proc mainNeg() =
test "Conditional all-zero bit or all-one bit": test "Conditional all-zero bit or all-one bit":
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF") var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
let a2 = a let a2 = a
let b2 = b let b2 = b
@ -485,8 +485,8 @@ proc mainNeg() =
bool(b.isZero) bool(b.isZero)
block: block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000") var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF") var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
let a2 = a let a2 = a
let b2 = b let b2 = b
@ -502,8 +502,8 @@ proc mainCopySwap() =
suite "Copy and Swap" & " [" & $WordBitwidth & "-bit mode]": suite "Copy and Swap" & " [" & $WordBitwidth & "-bit mode]":
test "Conditional copy": test "Conditional copy":
block: block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE") var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
let b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF") let b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
var expected = a var expected = a
a.ccopy(b, CtFalse) a.ccopy(b, CtFalse)
@ -511,8 +511,8 @@ proc mainCopySwap() =
check: bool(expected == a) check: bool(expected == a)
block: block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF") var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
var expected = b var expected = b
a.ccopy(b, CtTrue) a.ccopy(b, CtTrue)
@ -521,8 +521,8 @@ proc mainCopySwap() =
test "Conditional swap": test "Conditional swap":
block: block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE") var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF") var b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
let eA = a let eA = a
let eB = b let eB = b
@ -533,8 +533,8 @@ proc mainCopySwap() =
bool(eB == b) bool(eB == b)
block: block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF") var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
var b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001") var b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let eA = b let eA = b
let eB = a let eB = a
@ -548,7 +548,7 @@ proc mainModularInverse() =
suite "Modular Inverse (with odd modulus)" & " [" & $WordBitwidth & "-bit mode]": suite "Modular Inverse (with odd modulus)" & " [" & $WordBitwidth & "-bit mode]":
# Note: We don't define multi-precision multiplication # Note: We don't define multi-precision multiplication
# because who needs it when you have Montgomery? # because who needs it when you have Montgomery?
# ¯\_(ツ)_ # ¯\(ツ)/¯
test "42^-1 (mod 2017) = 1969": test "42^-1 (mod 2017) = 1969":
block: # small int block: # small int
let a = BigInt[16].fromUint(42'u16) let a = BigInt[16].fromUint(42'u16)

View File

@ -127,8 +127,8 @@ proc main() =
doAssert mLen == mW, "Expected " & $mLen & " bytes but wrote " & $mW & " for " & toHex(mBuf) & " (big-endian)" doAssert mLen == mW, "Expected " & $mLen & " bytes but wrote " & $mW & " for " & toHex(mBuf) & " (big-endian)"
# Build the bigint # Build the bigint
let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian) let aTest = BigInt[aBits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
let mTest = BigInt[mBits].fromRawUint(mBuf.toOpenArray(0, mW-1), bigEndian) let mTest = BigInt[mBits].unmarshal(mBuf.toOpenArray(0, mW-1), bigEndian)
######################################################### #########################################################
# Modulus # Modulus
@ -144,7 +144,7 @@ proc main() =
discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r)
var rConstantine: array[mLen, byte] var rConstantine: array[mLen, byte]
exportRawUint(rConstantine, rTest, bigEndian) marshal(rConstantine, rTest, bigEndian)
# echo "rGMP: ", rGMP.toHex() # echo "rGMP: ", rGMP.toHex()
# echo "rConstantine: ", rConstantine.toHex() # echo "rConstantine: ", rConstantine.toHex()

View File

@ -102,8 +102,8 @@ proc main() =
doAssert bLen >= bW, "Expected at most " & $bLen & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)" doAssert bLen >= bW, "Expected at most " & $bLen & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)"
# Build the bigint # Build the bigint
let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian) let aTest = BigInt[aBits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
let bTest = BigInt[bBits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian) let bTest = BigInt[bBits].unmarshal(bBuf.toOpenArray(0, bW-1), bigEndian)
######################################################### #########################################################
# Multiplication + drop low words # Multiplication + drop low words
@ -132,7 +132,7 @@ proc main() =
discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r)
var rConstantine: array[rLen, byte] var rConstantine: array[rLen, byte]
exportRawUint(rConstantine, rTest, bigEndian) marshal(rConstantine, rTest, bigEndian)
# Note: in bigEndian, GMP aligns left while constantine aligns right # Note: in bigEndian, GMP aligns left while constantine aligns right
doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(rLen-rW, rLen-1), block: doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(rLen-rW, rLen-1), block:

View File

@ -95,8 +95,8 @@ proc main() =
doAssert bLen >= bW, "Expected at most " & $bLen & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)" doAssert bLen >= bW, "Expected at most " & $bLen & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)"
# Build the bigint # Build the bigint
let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian) let aTest = BigInt[aBits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
let bTest = BigInt[bBits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian) let bTest = BigInt[bBits].unmarshal(bBuf.toOpenArray(0, bW-1), bigEndian)
######################################################### #########################################################
# Multiplication # Multiplication
@ -122,7 +122,7 @@ proc main() =
discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r)
var rConstantine: array[rLen, byte] var rConstantine: array[rLen, byte]
exportRawUint(rConstantine, rTest, bigEndian) marshal(rConstantine, rTest, bigEndian)
# Note: in bigEndian, GMP aligns left while constantine aligns right # Note: in bigEndian, GMP aligns left while constantine aligns right
doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(rLen-rW, rLen-1), block: doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(rLen-rW, rLen-1), block:

View File

@ -29,7 +29,7 @@ proc main() =
x += y x += y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -47,7 +47,7 @@ proc main() =
x += y x += y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -65,7 +65,7 @@ proc main() =
x += y x += y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -84,7 +84,7 @@ proc main() =
x -= y x -= y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -102,7 +102,7 @@ proc main() =
x -= y x -= y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -120,7 +120,7 @@ proc main() =
x -= y x -= y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -139,7 +139,7 @@ proc main() =
r.prod(x, y) r.prod(x, y)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
r_bytes.exportRawUint(r, cpuEndian) r_bytes.marshal(r, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -157,7 +157,7 @@ proc main() =
r.prod(x, y) r.prod(x, y)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
r_bytes.exportRawUint(r, cpuEndian) r_bytes.marshal(r, cpuEndian)
check: check:
# Check equality in the Montgomery domain # Check equality in the Montgomery domain
@ -176,7 +176,7 @@ proc main() =
x += y x += y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
let new_x = cast[uint64](x_bytes) let new_x = cast[uint64](x_bytes)
check: check:
@ -195,7 +195,7 @@ proc main() =
x += y x += y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
let new_x = cast[uint64](x_bytes) let new_x = cast[uint64](x_bytes)
check: check:
@ -214,7 +214,7 @@ proc main() =
x += y x += y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
let new_x = cast[uint64](x_bytes) let new_x = cast[uint64](x_bytes)
check: check:
@ -234,7 +234,7 @@ proc main() =
x -= y x -= y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
let new_x = cast[uint64](x_bytes) let new_x = cast[uint64](x_bytes)
check: check:
@ -253,7 +253,7 @@ proc main() =
x -= y x -= y
var x_bytes: array[8, byte] var x_bytes: array[8, byte]
x_bytes.exportRawUint(x, cpuEndian) x_bytes.marshal(x, cpuEndian)
let new_x = cast[uint64](x_bytes) let new_x = cast[uint64](x_bytes)
check: check:
@ -273,7 +273,7 @@ proc main() =
r.prod(x, y) r.prod(x, y)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
r_bytes.exportRawUint(r, cpuEndian) r_bytes.marshal(r, cpuEndian)
let new_r = cast[uint64](r_bytes) let new_r = cast[uint64](r_bytes)
check: check:
@ -292,7 +292,7 @@ proc main() =
r.prod(x, y) r.prod(x, y)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
r_bytes.exportRawUint(r, cpuEndian) r_bytes.marshal(r, cpuEndian)
let new_r = cast[uint64](r_bytes) let new_r = cast[uint64](r_bytes)
check: check:
@ -322,7 +322,7 @@ proc largeField() =
block: block:
var a: Fp[Secp256k1] var a: Fp[Secp256k1]
a.mres = Fp[Secp256k1].getMontyPrimeMinus1() a.mres = Fp[Secp256k1].getMontyPrimeMinus1()
let expected = BigInt[256].fromHex"0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2E" let expected = BigInt[256].fromHex"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2E"
var r: BigInt[256] var r: BigInt[256]
r.fromField(a) r.fromField(a)
@ -333,8 +333,8 @@ proc largeField() =
var d: FpDbl[Secp256k1] var d: FpDbl[Secp256k1]
# Set Montgomery repr to the largest field element in Montgomery Residue form # Set Montgomery repr to the largest field element in Montgomery Residue form
a.mres = BigInt[256].fromHex"0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2E" a.mres = BigInt[256].fromHex"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2E"
d.limbs2x = (BigInt[512].fromHex"0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2E").limbs d.limbs2x = (BigInt[512].fromHex"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2E").limbs
var r, expected: BigInt[256] var r, expected: BigInt[256]

View File

@ -43,7 +43,7 @@ proc main() =
r.prod(n, n) r.prod(n, n)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
r_bytes.exportRawUint(r, cpuEndian) r_bytes.marshal(r, cpuEndian)
let rU64 = cast[uint64](r_bytes) let rU64 = cast[uint64](r_bytes)
check: check:
@ -61,7 +61,7 @@ proc main() =
n.pow(exponent) n.pow(exponent)
var n_bytes: array[8, byte] var n_bytes: array[8, byte]
n_bytes.exportRawUint(n, cpuEndian) n_bytes.marshal(n, cpuEndian)
let r = cast[uint64](n_bytes) let r = cast[uint64](n_bytes)
check: check:
@ -79,7 +79,7 @@ proc main() =
n.pow(exponent) n.pow(exponent)
var n_bytes: array[8, byte] var n_bytes: array[8, byte]
n_bytes.exportRawUint(n, cpuEndian) n_bytes.marshal(n, cpuEndian)
let r = cast[uint64](n_bytes) let r = cast[uint64](n_bytes)
check: check:
@ -97,7 +97,7 @@ proc main() =
n.pow(exponent) n.pow(exponent)
var n_bytes: array[8, byte] var n_bytes: array[8, byte]
n_bytes.exportRawUint(n, cpuEndian) n_bytes.marshal(n, cpuEndian)
let r = cast[uint64](n_bytes) let r = cast[uint64](n_bytes)
check: check:
@ -115,7 +115,7 @@ proc main() =
n.pow(exponent) n.pow(exponent)
var n_bytes: array[8, byte] var n_bytes: array[8, byte]
n_bytes.exportRawUint(n, cpuEndian) n_bytes.marshal(n, cpuEndian)
let r = cast[uint64](n_bytes) let r = cast[uint64](n_bytes)
check: check:

View File

@ -41,7 +41,7 @@ proc exhaustiveCheck(C: static Curve, modulus: static int) =
a.square() a.square()
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
r_bytes.exportRawUint(a, cpuEndian) r_bytes.marshal(a, cpuEndian)
let r = uint16(cast[uint64](r_bytes)) let r = uint16(cast[uint64](r_bytes))
squares_to_roots.mgetOrPut(r, default(set[uint16])).incl(i) squares_to_roots.mgetOrPut(r, default(set[uint16])).incl(i)
@ -69,7 +69,7 @@ proc exhaustiveCheck(C: static Curve, modulus: static int) =
check: bool(a == a2) check: bool(a == a2)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
r_bytes.exportRawUint(a, cpuEndian) r_bytes.marshal(a, cpuEndian)
let r = uint16(cast[uint64](r_bytes)) let r = uint16(cast[uint64](r_bytes))
# r is one of the 2 square roots of `i` # r is one of the 2 square roots of `i`

View File

@ -73,8 +73,8 @@ proc binary_prologue[C: static Curve, N: static int](
doAssert len >= bW, "Expected at most " & $len & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)" doAssert len >= bW, "Expected at most " & $len & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)"
# Build the bigint # Build the bigint
aTest = Fp[C].fromBig BigInt[bits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian) aTest = Fp[C].fromBig BigInt[bits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
bTest = Fp[C].fromBig BigInt[bits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian) bTest = Fp[C].fromBig BigInt[bits].unmarshal(bBuf.toOpenArray(0, bW-1), bigEndian)
proc binary_epilogue[C: static Curve, N: static int]( proc binary_epilogue[C: static Curve, N: static int](
r, a, b: mpz_t, r, a, b: mpz_t,
@ -90,7 +90,7 @@ proc binary_epilogue[C: static Curve, N: static int](
discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r)
var rConstantine: array[N, byte] var rConstantine: array[N, byte]
exportRawUint(rConstantine, rTest, bigEndian) marshal(rConstantine, rTest, bigEndian)
# Note: in bigEndian, GMP aligns left while constantine aligns right # Note: in bigEndian, GMP aligns left while constantine aligns right
doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(N-rW, N-1), block: doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(N-rW, N-1), block:

View File

@ -27,7 +27,7 @@ proc main() =
block: # Sanity check block: # Sanity check
let x = 0'u64 let x = 0'u64
let x_bytes = cast[array[8, byte]](x) let x_bytes = cast[array[8, byte]](x)
let big = BigInt[64].fromRawUint(x_bytes, cpuEndian) let big = BigInt[64].unmarshal(x_bytes, cpuEndian)
check: check:
T(big.limbs[0]) == 0 T(big.limbs[0]) == 0
@ -37,29 +37,29 @@ proc main() =
# "Little-endian" - 2^63 # "Little-endian" - 2^63
let x = 1'u64 shl 63 let x = 1'u64 shl 63
let x_bytes = cast[array[8, byte]](x) let x_bytes = cast[array[8, byte]](x)
let big = BigInt[64].fromRawUint(x_bytes, littleEndian) # It's fine even on big-endian platform. We only want the byte-pattern let big = BigInt[64].unmarshal(x_bytes, littleEndian) # It's fine even on big-endian platform. We only want the byte-pattern
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
exportRawUint(r_bytes, big, littleEndian) marshal(r_bytes, big, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
block: # "Little-endian" - single random block: # "Little-endian" - single random
let x = rng.random_unsafe(uint64) let x = rng.random_unsafe(uint64)
let x_bytes = cast[array[8, byte]](x) let x_bytes = cast[array[8, byte]](x)
let big = BigInt[64].fromRawUint(x_bytes, littleEndian) # It's fine even on big-endian platform. We only want the byte-pattern let big = BigInt[64].unmarshal(x_bytes, littleEndian) # It's fine even on big-endian platform. We only want the byte-pattern
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
exportRawUint(r_bytes, big, littleEndian) marshal(r_bytes, big, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
block: # "Little-endian" - 10 random cases block: # "Little-endian" - 10 random cases
for _ in 0 ..< 10: for _ in 0 ..< 10:
let x = rng.random_unsafe(uint64) let x = rng.random_unsafe(uint64)
let x_bytes = cast[array[8, byte]](x) let x_bytes = cast[array[8, byte]](x)
let big = BigInt[64].fromRawUint(x_bytes, littleEndian) # It's fine even on big-endian platform. We only want the byte-pattern let big = BigInt[64].unmarshal(x_bytes, littleEndian) # It's fine even on big-endian platform. We only want the byte-pattern
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
exportRawUint(r_bytes, big, littleEndian) marshal(r_bytes, big, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
test "Round trip on elliptic curve constants": test "Round trip on elliptic curve constants":

View File

@ -32,7 +32,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[sizeof(BaseType), byte] var r_bytes: array[sizeof(BaseType), byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
block: block:
@ -43,7 +43,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[sizeof(BaseType), byte] var r_bytes: array[sizeof(BaseType), byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
# Mersenne 61 --------------------------------- # Mersenne 61 ---------------------------------
@ -55,7 +55,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
block: block:
@ -66,7 +66,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
block: block:
@ -77,7 +77,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
block: block:
@ -88,7 +88,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[8, byte] var r_bytes: array[8, byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes check: x_bytes == r_bytes
# Mersenne 127 --------------------------------- # Mersenne 127 ---------------------------------
@ -100,7 +100,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[16, byte] var r_bytes: array[16, byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes[0 ..< 8] check: x_bytes == r_bytes[0 ..< 8]
block: # "Little-endian" - single random block: # "Little-endian" - single random
@ -110,7 +110,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[16, byte] var r_bytes: array[16, byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes[0 ..< 8] check: x_bytes == r_bytes[0 ..< 8]
block: # "Little-endian" - 10 random cases block: # "Little-endian" - 10 random cases
@ -121,7 +121,7 @@ proc main() =
f.fromUint(x) f.fromUint(x)
var r_bytes: array[16, byte] var r_bytes: array[16, byte]
exportRawUint(r_bytes, f, littleEndian) marshal(r_bytes, f, littleEndian)
check: x_bytes == r_bytes[0 ..< 8] check: x_bytes == r_bytes[0 ..< 8]
test "Round trip on large constant": test "Round trip on large constant":