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 =
## Serialize a secret key
## Returns cttBLS_Success if successful
dst.exportRawUint(secret_key.raw, bigEndian)
dst.marshal(secret_key.raw, bigEndian)
return cttBLS_Success
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
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 lexicographically largest will have bit 381 set to 1
# (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
return cttBLS_Success
dst.toOpenArray(0, 48-1).exportRawUint(signature.raw.x.c1, bigEndian)
dst.toOpenArray(48, 96-1).exportRawUint(signature.raw.x.c0, bigEndian)
dst.toOpenArray(0, 48-1).marshal(signature.raw.x.c1, bigEndian)
dst.toOpenArray(48, 96-1).marshal(signature.raw.x.c0, bigEndian)
let isLexicographicallyLargest =
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.
## 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)
if status != cttBLS_Success:
dst.raw.setZero()
@ -240,7 +240,7 @@ func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array
# General case
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
if bool(t >= BLS12_381.Mod()):
@ -294,7 +294,7 @@ func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[
# General case
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
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]
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()):
return cttBLS_CoordinateGreaterOrEqualThanModulus

View File

@ -43,7 +43,7 @@ func parseRawUint(
## Return false if the integer is larger than the field modulus.
## Returns true on success.
var big {.noInit.}: BigInt[254]
big.fromRawUint(src, bigEndian)
big.unmarshal(src, bigEndian)
if not bool(big < Mod(BN254_Snarks)):
return cttEVM_IntLargerThanModulus
@ -136,10 +136,10 @@ func eth_evm_ecadd*(
var aff{.noInit.}: ECP_ShortW_Aff[Fp[BN254_Snarks], G1]
aff.affine(R)
r.toOpenArray(0, 31).exportRawUint(
r.toOpenArray(0, 31).marshal(
aff.x, bigEndian
)
r.toOpenArray(32, 63).exportRawUint(
r.toOpenArray(32, 63).marshal(
aff.y, bigEndian
)
@ -185,7 +185,7 @@ func eth_evm_ecmul*(
var smod{.noInit.}: Fr[BN254_Snarks]
var s{.noInit.}: BigInt[256]
s.fromRawUint(padded.toOpenArray(64,95), bigEndian)
s.unmarshal(padded.toOpenArray(64,95), bigEndian)
when true:
# 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]
aff.affine(P)
r.toOpenArray(0, 31).exportRawUint(
r.toOpenArray(0, 31).marshal(
aff.x, bigEndian
)
r.toOpenArray(32, 63).exportRawUint(
r.toOpenArray(32, 63).marshal(
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)
var big2x {.noInit.}: BigInt[2 * getCurveBitwidth(Field.C)]
big2x.fromRawUint(tv, bigEndian)
big2x.unmarshal(tv, bigEndian)
# Reduces modulo p and output in Montgomery domain
when m == 1:

View File

@ -114,7 +114,7 @@ func powMontUnsafeExponent*[mBits: static int](
var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]]
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
func powMont*[mBits, eBits: static int](
@ -132,7 +132,7 @@ func powMont*[mBits, eBits: static int](
## This is constant-time: the window optimization does
## not reveal the exponent bits or hamming weight
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)
@ -155,7 +155,7 @@ func powMontUnsafeExponent*[mBits, eBits: static int](
## This uses fixed window optimization
## A window size in the range [1, 5] must be chosen
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)

View File

@ -367,7 +367,7 @@ func div10*(a: var Limbs): SecretWord =
# Normalize
hi = (hi shl clz) or (lo shr (WordBitWidth - clz))
lo = lo shl clz
unsafeDiv2n1n(a[i], result, hi, lo, norm10)
div2n1n(a[i], result, hi, lo, norm10)
# Undo normalization
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
# from the true quotient
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
a0 == m0, MaxWord, # Quotient == MaxWord (0b1111...1111)
mux(
@ -226,7 +226,7 @@ func shlAddMod(a: LimbsViewMut, aLen: int,
let m0 = M[0] shl (WordBitWidth-R)
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)

View File

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

View File

@ -69,7 +69,7 @@ declareCurves:
# -----------------------------------------------------------------------------
curve P224: # NIST P-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
bitwidth: 254
modulus: "0x2523648240000001ba344d80000000086121000000000013a700000000000013"
@ -173,7 +173,7 @@ declareCurves:
modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"
curve Secp256k1: # Bitcoin curve
bitwidth: 256
modulus: "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F"
modulus: "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"
curve BLS12_377:
# Zexe curve
# (p41) https://eprint.iacr.org/2018/962.pdf

View File

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

View File

@ -29,20 +29,24 @@ type
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 =
result = "["
result.add " 0x" & toHex(BaseType(a[0]))
result.add " " & toHex(a[0])
for i in 1 ..< a.len:
result.add ", 0x" & toHex(BaseType(a[i]))
result.add ", " & toHex(a[i])
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 =
result = "BigInt["
result.add $BigInt.bits

View File

@ -224,7 +224,7 @@ func scalarMulGeneric*[EC](P: var EC, scalar: BigInt, window: static int = 5) =
var
scratchSpace: array[1 shl window, EC]
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)
func scalarMul*[EC](

View File

@ -184,5 +184,5 @@ func powUnsafeExponent*[F; bits: static int](
## - power analysis
## - timing analysis
var expBE {.noInit.}: array[(bits + 7) div 8, byte]
expBE.exportRawUint(exponent, bigEndian)
expBE.marshal(exponent, bigEndian)
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.
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
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:
- `toHex` 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.
In particular it scans spaces and underscores and checks if the string
starts with '0x'.
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.
## Avoiding secret mistakes

View File

@ -36,7 +36,7 @@ export BigInt, wordsRequired
# prototyping, research and debugging purposes,
# and can use exceptions.
func fromRawUintLE(
func unmarshalLE(
dst: var BigInt,
src: openarray[byte]) =
## Parse an unsigned integer from its canonical
@ -74,7 +74,7 @@ func fromRawUintLE(
for i in dst_idx + 1 ..< dst.limbs.len:
dst.limbs[i] = Zero
func fromRawUintBE(
func unmarshalBE(
dst: var BigInt,
src: openarray[byte]) =
## Parse an unsigned integer from its canonical
@ -114,7 +114,7 @@ func fromRawUintBE(
for i in dst_idx + 1 ..< dst.limbs.len:
dst.limbs[i] = Zero
func fromRawUint*(
func unmarshal*(
dst: var BigInt,
src: openarray[byte],
srcEndianness: static Endianness) =
@ -129,11 +129,11 @@ func fromRawUint*(
## from a canonical integer representation
when srcEndianness == littleEndian:
dst.fromRawUintLE(src)
dst.unmarshalLE(src)
else:
dst.fromRawUintBE(src)
dst.unmarshalBE(src)
func fromRawUint*(
func unmarshal*(
T: type BigInt,
src: openarray[byte],
srcEndianness: static Endianness): T {.inline.}=
@ -146,21 +146,21 @@ func fromRawUint*(
##
## Can work at compile-time to embed curve moduli
## from a canonical integer representation
result.fromRawUint(src, srcEndianness)
result.unmarshal(src, srcEndianness)
func fromUint*(
T: type BigInt,
src: SomeUnsignedInt): T {.inline.}=
## Parse a regular unsigned integer
## 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*(
dst: var BigInt,
src: SomeUnsignedInt) {.inline.}=
## Parse a regular unsigned integer
## 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):
dst[startIdx+sizeof(src)-1-i] = toByte(src shr (i * 8))
func exportRawUintLE(
func marshalLE(
dst: var openarray[byte],
src: BigInt) =
## 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))
return
func exportRawUintBE(
func marshalBE(
dst: var openarray[byte],
src: BigInt) =
## 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))
return
func exportRawUint*(
func marshal*(
dst: var openarray[byte],
src: BigInt,
dstEndianness: static Endianness) =
@ -301,9 +301,9 @@ func exportRawUint*(
zeroMem(dst, dst.len)
when dstEndianness == littleEndian:
exportRawUintLE(dst, src)
marshalLE(dst, src)
else:
exportRawUintBE(dst, src)
marshalBE(dst, src)
{.pop.} # {.push raises: [].}
@ -312,54 +312,23 @@ func exportRawUint*(
# Conversion helpers
#
# ############################################################
# TODO: constant-time
func readHexChar(c: char): uint8 {.inline.}=
func readHexChar(c: char): SecretWord {.inline.}=
## Converts an hex char to an int
## CT: leaks position of invalid input if any.
## and leaks if 0..9 or a..f or A..F
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")
template sw(a: char or int): SecretWord = SecretWord(a)
const k = WordBitWidth - 1
func skipPrefixes(current_idx: var int, str: string, radix: static range[2..16]) {.inline.} =
## 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
let c = sw(c)
if str.len < 2:
return
let lowercaseMask = not -(((c - sw'a') or (sw('f') - c)) shr k)
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)"
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
return val
func hexToPaddedByteArray*(hexStr: string, output: var openArray[byte], order: static[Endianness]) =
## 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 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
skip = 0
skip = Zero
dstIdx: int
shift = 4
skipPrefixes(skip, hexStr, 16)
const blanks = {' ', '_'}
let nonBlanksCount = countNonBlanks(hexStr, skip)
if hexStr.len >= 2:
skip = sw(2)*(
sw(hexStr[0] == '0') and
(sw(hexStr[1] == 'x') or sw(hexStr[1] == 'X'))
)
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:
# 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
shift = 4 - size mod 2 * 4
shift = 4 - (size and 1) * 4
for srcIdx in skip ..< hexStr.len:
if hexStr[srcIdx] in blanks:
continue
let nibble = hexStr[srcIdx].readHexChar shl shift
for srcIdx in skip.int ..< hexStr.len:
let c = hexStr[srcIdx]
let nibble = byte(c.readHexChar() shl shift)
when order == bigEndian:
output[dstIdx] = output[dstIdx] or nibble
else:
@ -409,11 +389,13 @@ func nativeEndianToHex(bytes: openarray[byte], order: static[Endianness]): strin
result[1] = 'x'
for i in 0 ..< bytes.len:
when order == system.cpuEndian:
result[2 + 2*i] = hexChars[int bytes[i] shr 4 and 0xF]
result[2 + 2*i+1] = hexChars[int bytes[i] and 0xF]
let bi = bytes[i]
result[2 + 2*i] = hexChars.secretLookup(SecretWord bi shr 4 and 0xF)
result[2 + 2*i+1] = hexChars.secretLookup(SecretWord bi and 0xF)
else:
result[2 + 2*i] = hexChars[int bytes[bytes.high - i] shr 4 and 0xF]
result[2 + 2*i+1] = hexChars[int bytes[bytes.high - i] and 0xF]
let bmi = bytes[bytes.high - i]
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
##
## This API is intended for configuration and debugging purposes
## Do not pass secret or private data to it.
## 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.
##
## 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)
# 2. Convert canonical uint to Big Int
a.fromRawUint(bytes, bigEndian)
a.unmarshal(bytes, bigEndian)
func fromHex*(T: type BigInt, s: string): T {.noInit.} =
## 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
##
## This API is intended for configuration and debugging purposes
## Do not pass secret or private data to it.
## 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.
##
## Can work at compile-time to declare curve moduli from their hex strings
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
## Fp towers
##
## This function may allocate.
# 1. Convert Big Int to canonical uint
const canonLen = (big.bits + 8 - 1) div 8
var bytes: array[canonLen, byte]
exportRawUint(bytes, big, cpuEndian)
marshal(bytes, big, cpuEndian)
# 2 Convert canonical uint to hex
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.
# 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 =
doAssert bits < (high(uint) div log10_2_Num),
@ -659,10 +645,11 @@ func decimalLength(bits: static int): int =
func toDecimal*(a: BigInt): 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.
##
## This function is NOT constant-time at the moment.
## This function is constant-time.
## This function does heap-allocation.
const len = decimalLength(BigInt.bits)
result = newString(len)

View File

@ -27,7 +27,7 @@ func fromUint*(dst: var FF,
src: SomeUnsignedInt) =
## Parse a regular unsigned integer
## 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)
func fromInt*(dst: var FF,
@ -42,11 +42,11 @@ func fromInt*(dst: var FF,
let isNeg = SecretBool((src shr msb_pos) and 1)
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.cneg(isNeg)
func exportRawUint*(dst: var openarray[byte],
func marshal*(dst: var openarray[byte],
src: FF,
dstEndianness: static Endianness) =
## 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
## or zero-padded right for little-endian.
## 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) =
## 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.} =
var eBytes: array[(exponent.bits+7) div 8, byte]
eBytes.exportRawUint(exponent, bigEndian)
eBytes.marshal(exponent, bigEndian)
r.setOne()
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.} =
## Extended precision multiplication
## (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 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:
# TODO: constant-time div2n1n
when X86:
from ./extended_precision_x86_64_gcc import unsafeDiv2n1n
from ./extended_precision_64bit_uint128 import mul, muladd1, muladd2, smul
else:
from ./extended_precision_64bit_uint128 import unsafeDiv2n1n, mul, muladd1, muladd2, smul
export unsafeDiv2n1n, mul, muladd1, muladd2, smul
from ./extended_precision_64bit_uint128 import mul, muladd1, muladd2, smul
export mul, muladd1, muladd2, smul
# ############################################################
#

View File

@ -18,24 +18,6 @@ static:
doAssert GCC_Compatible
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.} =
## Extended precision multiplication
## (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 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.}
## Unsigned extended precision multiplication
## (hi, lo) <-- a * b
## 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.} =
## Extended precision multiplication
## (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] =
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

View File

@ -137,25 +137,34 @@ func mux*[T](ctl: CTBool[T], x, y: T): T {.inline.}=
## Returns x if ctl is true
## else returns y
## So equivalent to ctl? x: y
when X86 and GCC_Compatible:
mux_x86(ctl, x, y)
else:
when nimvm:
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.}=
## Multiplexer / selector
## Returns x if ctl is true
## else returns y
## So equivalent to ctl? x: y
when X86 and GCC_Compatible:
mux_x86(ctl, x, y)
else:
when nimvm:
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.}=
## Conditional copy
## Copy ``y`` into ``x`` if ``ctl`` is true
when X86 and GCC_Compatible:
ccopy_x86(ctl, x, y)
else:
when nimvm:
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/[
ct_types,
ct_routines,
multiplexers
multiplexers,
ct_division
],
compilers/[
addcarry_subborrow,
@ -25,6 +26,7 @@ export
multiplexers,
addcarry_subborrow,
extended_precision,
ct_division,
bithacks,
staticFor

View File

@ -211,9 +211,9 @@ func random_long01Seq(rng: var RngState, a: var BigInt) =
rng.random_long01Seq(buf)
let order = rng.sample_unsafe([bigEndian, littleEndian])
if order == bigEndian:
a.fromRawUint(buf, bigEndian)
a.unmarshal(buf, bigEndian)
else:
a.fromRawUint(buf, littleEndian)
a.unmarshal(buf, littleEndian)
func random_long01Seq(rng: var RngState, a: var FF) =
## 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
var scalarCanonical: array[(scalar.bits+7) div 8, byte]
scalarCanonical.exportRawUint(scalar, bigEndian)
scalarCanonical.marshal(scalar, bigEndian)
var t0{.noInit.}, t1{.noInit.}: typeof(P)
t0.setInf()

View File

@ -25,13 +25,13 @@ proc mainArith() =
check: x.isZero().bool
test "isZero for non-zero":
block:
let x = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
let x = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check: not x.isZero().bool
block:
let x = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000")
let x = fromHex(BigInt[128], "0x00000000000000010000000000000000")
check: not x.isZero().bool
block:
let x = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF")
let x = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
check: not x.isZero().bool
test "isZero for zero (compile-time)":
@ -39,53 +39,53 @@ proc mainArith() =
check: static(x.isZero().bool)
test "isZero for non-zero (compile-time)":
block:
const x = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
const x = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check: static(not x.isZero().bool)
block:
const x = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000")
const x = fromHex(BigInt[128], "0x00000000000000010000000000000000")
check: static(not x.isZero().bool)
block:
const x = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF")
const x = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
check: static(not x.isZero().bool)
suite "Arithmetic operations - Addition" & " [" & $WordBitwidth & "-bit mode]":
test "Adding 2 zeros":
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let carry = a.cadd(b, CtTrue)
check: a.isZero().bool
test "Adding 1 zero - real addition":
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
let c = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check:
bool(a == c)
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
let c = fromHex(BigInt[128], "0x00000000000000000000000000000001")
check:
bool(a == c)
test "Adding 1 zero - fake addition":
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtFalse)
let c = a
check:
bool(a == c)
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000000")
let carry = a.cadd(b, CtFalse)
let c = a
@ -94,34 +94,34 @@ proc mainArith() =
test "Adding non-zeros - real addition":
block:
var a = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000001")
let c = fromHex(BigInt[128], "0x00000000000000010000000000000001")
check:
bool(a == c)
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
let b = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000001")
let c = fromHex(BigInt[128], "0x00000000000000010000000000000001")
check:
bool(a == c)
test "Adding non-zeros - fake addition":
block:
var a = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtFalse)
let c = a
check:
bool(a == c)
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
let b = fromHex(BigInt[128], "0x00000000_00000001_00000000_00000000")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let b = fromHex(BigInt[128], "0x00000000000000010000000000000000")
let carry = a.cadd(b, CtFalse)
let c = a
@ -130,21 +130,21 @@ proc mainArith() =
test "Addition limbs carry":
block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFE")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFE")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF")
let c = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
check:
bool(a == c)
not bool(carry)
block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let carry = a.cadd(b, CtTrue)
let c = fromHex(BigInt[128], "0x00000001_00000000_00000000_00000000")
let c = fromHex(BigInt[128], "0x00000001000000000000000000000000")
check:
bool(a == c)
not bool(carry)
@ -164,8 +164,8 @@ proc mainMul() =
test "Same size operand into double size result":
block:
var r = canary(BigInt[256])
let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF"
let a = BigInt[128].fromHex"0x12345678FF11FFAA00321321CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f102e0722"
@ -178,7 +178,7 @@ proc mainMul() =
block:
var r = canary(BigInt[200])
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"
@ -189,9 +189,9 @@ proc mainMul() =
test "Destination is properly zero-padded if multiplicands are too short":
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 b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF"
let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f665f787f65621ca08"
@ -205,108 +205,108 @@ proc mainMulHigh() =
test "Same size operand into double size result - discard first word":
block:
var r = canary(BigInt[256])
let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF"
let a = BigInt[128].fromHex"0x12345678FF11FFAA00321321CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32:
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f"
else:
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8"
r.prod_high_words(a, b, 1)
r.prodhighwords(a, b, 1)
check: bool(r == expected)
r.prod_high_words(b, a, 1)
r.prodhighwords(b, a, 1)
check: bool(r == expected)
test "Same size operand into double size result - discard first 3 words":
block:
var r = canary(BigInt[256])
let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF"
let a = BigInt[128].fromHex"0x12345678FF11FFAA00321321CAFECAFE"
let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32:
let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd"
else:
let expected = BigInt[256].fromHex"fd5bdef43d64113"
r.prod_high_words(a, b, 3)
r.prodhighwords(a, b, 3)
check: bool(r == expected)
r.prod_high_words(b, a, 3)
r.prodhighwords(b, a, 3)
check: bool(r == expected)
test "All lower words trigger a carry":
block:
var r = canary(BigInt[256])
let a = BigInt[256].fromHex"0xFFFFF000_FFFFF111_FFFFFFFA_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF"
let b = BigInt[256].fromHex"0xFFFFFFFF_FFFFF222_FFFFFFFB_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF"
let a = BigInt[256].fromHex"0xFFFFF000FFFFF111FFFFFFFAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
let b = BigInt[256].fromHex"0xFFFFFFFFFFFFF222FFFFFFFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
# Full product:
# fffff000_ffffe335_00ddc21a_00cf3972_00008109_00000013_ffffffff_fffffffe
# 00000fff_00001ccb_00000009_00000000_00000000_00000000_00000000_00000001
let expected = BigInt[256].fromHex"0xfffff000_ffffe335_00ddc21a_00cf3972_00008109_00000013_ffffffff_fffffffe"
# fffff000ffffe33500ddc21a00cf39720000810900000013fffffffffffffffe
# 00000fff00001ccb000000090000000000000000000000000000000000000001
let expected = BigInt[256].fromHex"0xfffff000ffffe33500ddc21a00cf39720000810900000013fffffffffffffffe"
when WordBitWidth == 32:
const startWord = 8
else:
const startWord = 4
r.prod_high_words(a, b, startWord)
r.prodhighwords(a, b, startWord)
check: bool(r == expected)
r.prod_high_words(b, a, startWord)
r.prodhighwords(b, a, startWord)
check: bool(r == expected)
test "Different size into large result":
block:
var r = canary(BigInt[200])
let a = BigInt[29].fromHex"0x12345678"
let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF"
let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32:
let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6"
else:
let expected = BigInt[200].fromHex"fd5bdee"
r.prod_high_words(a, b, 2)
r.prodhighwords(a, b, 2)
check: bool(r == expected)
r.prod_high_words(b, a, 2)
r.prodhighwords(b, a, 2)
check: bool(r == expected)
test "Destination is properly zero-padded if multiplicands are too short":
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 b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF"
let b = BigInt[128].fromHex"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
when WordBitWidth == 32:
let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6"
else:
let expected = BigInt[200].fromHex"fd5bdee"
r.prod_high_words(a, b, 2)
r.prodhighwords(a, b, 2)
check: bool(r == expected)
r.prod_high_words(b, a, 2)
r.prodhighwords(b, a, 2)
check: bool(r == expected)
proc mainSquare() =
suite "Multi-precision multiplication" & " [" & $WordBitwidth & "-bit mode]":
test "Squaring is consistent with multiplication (rBits = 2*aBits)":
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)
r_sqr.square(a)
check: bool(r_mul == r_sqr)
rmul.prod(a, a)
rsqr.square(a)
check: bool(rmul == rsqr)
test "Squaring is consistent with multiplication (truncated)":
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)
r_sqr.square(a)
check: bool(r_mul == r_sqr)
rmul.prod(a, a)
rsqr.square(a)
check: bool(rmul == rsqr)
proc mainModular() =
suite "Modular operations - small modulus" & " [" & $WordBitwidth & "-bit mode]":
@ -347,7 +347,7 @@ proc mainModular() =
"\n expected (ll repr): " & $expected
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)
var r = canary(BigInt[8])
@ -404,8 +404,8 @@ proc mainNeg() =
suite "Conditional negation" & " [" & $WordBitwidth & "-bit mode]":
test "Conditional negation":
block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF")
var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
let a2 = a
let b2 = b
@ -421,8 +421,8 @@ proc mainNeg() =
bool(b.isZero)
block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF")
var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
let a2 = a
let b2 = b
@ -436,8 +436,8 @@ proc mainNeg() =
test "Conditional negation with carries":
block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_FFFFFFFF")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_00000000_00000000")
var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321FFFFFFFF")
var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFF0000000000000000")
let a2 = a
let b2 = b
@ -453,8 +453,8 @@ proc mainNeg() =
bool(b.isZero)
block:
var a = fromHex(BigInt[128], "0x12345678_00000000_00321321_FFFFFFFF")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_00000000_00000000")
var a = fromHex(BigInt[128], "0x123456780000000000321321FFFFFFFF")
var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFF0000000000000000")
let a2 = a
let b2 = b
@ -468,8 +468,8 @@ proc mainNeg() =
test "Conditional all-zero bit or all-one bit":
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
let a2 = a
let b2 = b
@ -485,8 +485,8 @@ proc mainNeg() =
bool(b.isZero)
block:
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
var b = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF")
var a = fromHex(BigInt[128], "0x00000000000000000000000000000000")
var b = fromHex(BigInt[128], "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
let a2 = a
let b2 = b
@ -502,8 +502,8 @@ proc mainCopySwap() =
suite "Copy and Swap" & " [" & $WordBitwidth & "-bit mode]":
test "Conditional copy":
block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE")
let b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF")
var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
let b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
var expected = a
a.ccopy(b, CtFalse)
@ -511,8 +511,8 @@ proc mainCopySwap() =
check: bool(expected == a)
block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
let b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
var expected = b
a.ccopy(b, CtTrue)
@ -521,8 +521,8 @@ proc mainCopySwap() =
test "Conditional swap":
block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF")
var a = fromHex(BigInt[128], "0x12345678FF11FFAA00321321CAFECAFE")
var b = fromHex(BigInt[128], "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
let eA = a
let eB = b
@ -533,8 +533,8 @@ proc mainCopySwap() =
bool(eB == b)
block:
var a = fromHex(BigInt[128], "0x00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF")
var b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000001")
var a = fromHex(BigInt[128], "0x00000000FFFFFFFFFFFFFFFFFFFFFFFF")
var b = fromHex(BigInt[128], "0x00000000000000000000000000000001")
let eA = b
let eB = a
@ -548,7 +548,7 @@ proc mainModularInverse() =
suite "Modular Inverse (with odd modulus)" & " [" & $WordBitwidth & "-bit mode]":
# Note: We don't define multi-precision multiplication
# because who needs it when you have Montgomery?
# ¯\_(ツ)_
# ¯\(ツ)/¯
test "42^-1 (mod 2017) = 1969":
block: # small int
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)"
# Build the bigint
let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian)
let mTest = BigInt[mBits].fromRawUint(mBuf.toOpenArray(0, mW-1), bigEndian)
let aTest = BigInt[aBits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
let mTest = BigInt[mBits].unmarshal(mBuf.toOpenArray(0, mW-1), bigEndian)
#########################################################
# Modulus
@ -144,7 +144,7 @@ proc main() =
discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r)
var rConstantine: array[mLen, byte]
exportRawUint(rConstantine, rTest, bigEndian)
marshal(rConstantine, rTest, bigEndian)
# echo "rGMP: ", rGMP.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)"
# Build the bigint
let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian)
let bTest = BigInt[bBits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian)
let aTest = BigInt[aBits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
let bTest = BigInt[bBits].unmarshal(bBuf.toOpenArray(0, bW-1), bigEndian)
#########################################################
# 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)
var rConstantine: array[rLen, byte]
exportRawUint(rConstantine, rTest, bigEndian)
marshal(rConstantine, rTest, bigEndian)
# Note: in bigEndian, GMP aligns left while constantine aligns right
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)"
# Build the bigint
let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian)
let bTest = BigInt[bBits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian)
let aTest = BigInt[aBits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
let bTest = BigInt[bBits].unmarshal(bBuf.toOpenArray(0, bW-1), bigEndian)
#########################################################
# Multiplication
@ -122,7 +122,7 @@ proc main() =
discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r)
var rConstantine: array[rLen, byte]
exportRawUint(rConstantine, rTest, bigEndian)
marshal(rConstantine, rTest, bigEndian)
# Note: in bigEndian, GMP aligns left while constantine aligns right
doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(rLen-rW, rLen-1), block:

View File

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

View File

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

View File

@ -41,7 +41,7 @@ proc exhaustiveCheck(C: static Curve, modulus: static int) =
a.square()
var r_bytes: array[8, byte]
r_bytes.exportRawUint(a, cpuEndian)
r_bytes.marshal(a, cpuEndian)
let r = uint16(cast[uint64](r_bytes))
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)
var r_bytes: array[8, byte]
r_bytes.exportRawUint(a, cpuEndian)
r_bytes.marshal(a, cpuEndian)
let r = uint16(cast[uint64](r_bytes))
# 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)"
# Build the bigint
aTest = Fp[C].fromBig BigInt[bits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian)
bTest = Fp[C].fromBig BigInt[bits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian)
aTest = Fp[C].fromBig BigInt[bits].unmarshal(aBuf.toOpenArray(0, aW-1), bigEndian)
bTest = Fp[C].fromBig BigInt[bits].unmarshal(bBuf.toOpenArray(0, bW-1), bigEndian)
proc binary_epilogue[C: static Curve, N: static int](
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)
var rConstantine: array[N, byte]
exportRawUint(rConstantine, rTest, bigEndian)
marshal(rConstantine, rTest, bigEndian)
# Note: in bigEndian, GMP aligns left while constantine aligns right
doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(N-rW, N-1), block:

View File

@ -27,7 +27,7 @@ proc main() =
block: # Sanity check
let x = 0'u64
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:
T(big.limbs[0]) == 0
@ -37,29 +37,29 @@ proc main() =
# "Little-endian" - 2^63
let x = 1'u64 shl 63
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]
exportRawUint(r_bytes, big, littleEndian)
marshal(r_bytes, big, littleEndian)
check: x_bytes == r_bytes
block: # "Little-endian" - single random
let x = rng.random_unsafe(uint64)
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]
exportRawUint(r_bytes, big, littleEndian)
marshal(r_bytes, big, littleEndian)
check: x_bytes == r_bytes
block: # "Little-endian" - 10 random cases
for _ in 0 ..< 10:
let x = rng.random_unsafe(uint64)
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]
exportRawUint(r_bytes, big, littleEndian)
marshal(r_bytes, big, littleEndian)
check: x_bytes == r_bytes
test "Round trip on elliptic curve constants":

View File

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