From 26954f905a230b4e6f984f7d97df28b1821b488c Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Mon, 28 Feb 2022 09:23:26 +0100 Subject: [PATCH] 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) --- constantine/blssig_pop_on_bls12381_g2.nim | 16 +- constantine/ethereum_evm_precompiles.nim | 12 +- .../hash_to_curve/h2c_hash_to_field.nim | 2 +- .../math/arithmetic/bigints_montgomery.nim | 6 +- constantine/math/arithmetic/limbs.nim | 2 +- .../math/arithmetic/limbs_division.nim | 4 +- .../math/arithmetic/limbs_montgomery.nim | 2 +- .../math/config/curves_declaration.nim | 4 +- constantine/math/config/precompute.nim | 12 +- constantine/math/config/type_bigint.nim | 20 +- constantine/math/elliptic/ec_scalar_mul.nim | 2 +- .../math/extension_fields/exponentiations.nim | 2 +- constantine/math/io/README.md | 6 +- constantine/math/io/io_bigints.nim | 157 +++++++-------- constantine/math/io/io_fields.nim | 8 +- .../math/pairing/cyclotomic_subgroup.nim | 2 +- .../compilers/extended_precision.nim | 24 +-- .../extended_precision_64bit_uint128.nim | 18 -- .../extended_precision_x86_64_gcc.nim | 60 ------ .../extended_precision_x86_64_msvc.nim | 18 -- .../platforms/constant_time/ct_division.nim | 78 ++++++++ .../platforms/constant_time/ct_routines.nim | 16 ++ .../platforms/constant_time/multiplexers.nim | 27 ++- constantine/platforms/primitives.nim | 4 +- helpers/prng_unsafe.nim | 4 +- .../math/support/ec_reference_scalar_mult.nim | 2 +- tests/math/t_bigints.nim | 186 +++++++++--------- tests/math/t_bigints_mod_vs_gmp.nim | 6 +- .../math/t_bigints_mul_high_words_vs_gmp.nim | 6 +- tests/math/t_bigints_mul_vs_gmp.nim | 6 +- tests/math/t_finite_fields.nim | 36 ++-- tests/math/t_finite_fields_powinv.nim | 10 +- tests/math/t_finite_fields_sqrt.nim | 4 +- tests/math/t_finite_fields_vs_gmp.nim | 6 +- tests/math/t_io_bigints.nim | 14 +- tests/math/t_io_fields.nim | 18 +- 36 files changed, 391 insertions(+), 409 deletions(-) delete mode 100644 constantine/platforms/compilers/extended_precision_x86_64_gcc.nim create mode 100644 constantine/platforms/constant_time/ct_division.nim diff --git a/constantine/blssig_pop_on_bls12381_g2.nim b/constantine/blssig_pop_on_bls12381_g2.nim index 07074fe..1dcc894 100644 --- a/constantine/blssig_pop_on_bls12381_g2.nim +++ b/constantine/blssig_pop_on_bls12381_g2.nim @@ -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 diff --git a/constantine/ethereum_evm_precompiles.nim b/constantine/ethereum_evm_precompiles.nim index 30d1772..945ef5d 100644 --- a/constantine/ethereum_evm_precompiles.nim +++ b/constantine/ethereum_evm_precompiles.nim @@ -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 ) diff --git a/constantine/hash_to_curve/h2c_hash_to_field.nim b/constantine/hash_to_curve/h2c_hash_to_field.nim index 0632f8c..492bea4 100644 --- a/constantine/hash_to_curve/h2c_hash_to_field.nim +++ b/constantine/hash_to_curve/h2c_hash_to_field.nim @@ -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: diff --git a/constantine/math/arithmetic/bigints_montgomery.nim b/constantine/math/arithmetic/bigints_montgomery.nim index 31a316a..6065080 100644 --- a/constantine/math/arithmetic/bigints_montgomery.nim +++ b/constantine/math/arithmetic/bigints_montgomery.nim @@ -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) diff --git a/constantine/math/arithmetic/limbs.nim b/constantine/math/arithmetic/limbs.nim index 55fefce..1064388 100644 --- a/constantine/math/arithmetic/limbs.nim +++ b/constantine/math/arithmetic/limbs.nim @@ -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 diff --git a/constantine/math/arithmetic/limbs_division.nim b/constantine/math/arithmetic/limbs_division.nim index 44240c0..72e6894 100644 --- a/constantine/math/arithmetic/limbs_division.nim +++ b/constantine/math/arithmetic/limbs_division.nim @@ -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) diff --git a/constantine/math/arithmetic/limbs_montgomery.nim b/constantine/math/arithmetic/limbs_montgomery.nim index 9e2be6d..3c0b96b 100644 --- a/constantine/math/arithmetic/limbs_montgomery.nim +++ b/constantine/math/arithmetic/limbs_montgomery.nim @@ -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" diff --git a/constantine/math/config/curves_declaration.nim b/constantine/math/config/curves_declaration.nim index 4836b5b..216c9e3 100644 --- a/constantine/math/config/curves_declaration.nim +++ b/constantine/math/config/curves_declaration.nim @@ -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 diff --git a/constantine/math/config/precompute.nim b/constantine/math/config/precompute.nim index 18b065e..58dcbb9 100644 --- a/constantine/math/config/precompute.nim +++ b/constantine/math/config/precompute.nim @@ -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) # ############################################################ # diff --git a/constantine/math/config/type_bigint.nim b/constantine/math/config/type_bigint.nim index 15de197..33ed2b5 100644 --- a/constantine/math/config/type_bigint.nim +++ b/constantine/math/config/type_bigint.nim @@ -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 diff --git a/constantine/math/elliptic/ec_scalar_mul.nim b/constantine/math/elliptic/ec_scalar_mul.nim index 260b7f4..6126149 100644 --- a/constantine/math/elliptic/ec_scalar_mul.nim +++ b/constantine/math/elliptic/ec_scalar_mul.nim @@ -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]( diff --git a/constantine/math/extension_fields/exponentiations.nim b/constantine/math/extension_fields/exponentiations.nim index 46d7539..61ac8cd 100644 --- a/constantine/math/extension_fields/exponentiations.nim +++ b/constantine/math/extension_fields/exponentiations.nim @@ -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) diff --git a/constantine/math/io/README.md b/constantine/math/io/README.md index 4d7d059..f87ef63 100644 --- a/constantine/math/io/README.md +++ b/constantine/math/io/README.md @@ -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 diff --git a/constantine/math/io/io_bigints.nim b/constantine/math/io/io_bigints.nim index 66b1140..2fc1b93 100644 --- a/constantine/math/io/io_bigints.nim +++ b/constantine/math/io/io_bigints.nim @@ -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) diff --git a/constantine/math/io/io_fields.nim b/constantine/math/io/io_fields.nim index 16147a2..f97fb96 100644 --- a/constantine/math/io/io_fields.nim +++ b/constantine/math/io/io_fields.nim @@ -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. diff --git a/constantine/math/pairing/cyclotomic_subgroup.nim b/constantine/math/pairing/cyclotomic_subgroup.nim index dd2f310..522757a 100644 --- a/constantine/math/pairing/cyclotomic_subgroup.nim +++ b/constantine/math/pairing/cyclotomic_subgroup.nim @@ -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: diff --git a/constantine/platforms/compilers/extended_precision.nim b/constantine/platforms/compilers/extended_precision.nim index f3eb103..117a551 100644 --- a/constantine/platforms/compilers/extended_precision.nim +++ b/constantine/platforms/compilers/extended_precision.nim @@ -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 # ############################################################ # diff --git a/constantine/platforms/compilers/extended_precision_64bit_uint128.nim b/constantine/platforms/compilers/extended_precision_64bit_uint128.nim index 1eceb13..b8d2393 100644 --- a/constantine/platforms/compilers/extended_precision_64bit_uint128.nim +++ b/constantine/platforms/compilers/extended_precision_64bit_uint128.nim @@ -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 diff --git a/constantine/platforms/compilers/extended_precision_x86_64_gcc.nim b/constantine/platforms/compilers/extended_precision_x86_64_gcc.nim deleted file mode 100644 index 3fb1d7e..0000000 --- a/constantine/platforms/compilers/extended_precision_x86_64_gcc.nim +++ /dev/null @@ -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`) - : - """ diff --git a/constantine/platforms/compilers/extended_precision_x86_64_msvc.nim b/constantine/platforms/compilers/extended_precision_x86_64_msvc.nim index 960dc14..3216b85 100644 --- a/constantine/platforms/compilers/extended_precision_x86_64_msvc.nim +++ b/constantine/platforms/compilers/extended_precision_x86_64_msvc.nim @@ -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: "", 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:"", 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 diff --git a/constantine/platforms/constant_time/ct_division.nim b/constantine/platforms/constant_time/ct_division.nim new file mode 100644 index 0000000..6e8cf8b --- /dev/null +++ b/constantine/platforms/constant_time/ct_division.nim @@ -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 \ No newline at end of file diff --git a/constantine/platforms/constant_time/ct_routines.nim b/constantine/platforms/constant_time/ct_routines.nim index 93bbb4c..106a898 100644 --- a/constantine/platforms/constant_time/ct_routines.nim +++ b/constantine/platforms/constant_time/ct_routines.nim @@ -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 diff --git a/constantine/platforms/constant_time/multiplexers.nim b/constantine/platforms/constant_time/multiplexers.nim index 2fed158..657435d 100644 --- a/constantine/platforms/constant_time/multiplexers.nim +++ b/constantine/platforms/constant_time/multiplexers.nim @@ -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) diff --git a/constantine/platforms/primitives.nim b/constantine/platforms/primitives.nim index 7ec6c50..78ed928 100644 --- a/constantine/platforms/primitives.nim +++ b/constantine/platforms/primitives.nim @@ -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 diff --git a/helpers/prng_unsafe.nim b/helpers/prng_unsafe.nim index 025b555..ef8af1b 100644 --- a/helpers/prng_unsafe.nim +++ b/helpers/prng_unsafe.nim @@ -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 diff --git a/tests/math/support/ec_reference_scalar_mult.nim b/tests/math/support/ec_reference_scalar_mult.nim index 82148ca..bae7185 100644 --- a/tests/math/support/ec_reference_scalar_mult.nim +++ b/tests/math/support/ec_reference_scalar_mult.nim @@ -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() diff --git a/tests/math/t_bigints.nim b/tests/math/t_bigints.nim index c0364a6..898385d 100644 --- a/tests/math/t_bigints.nim +++ b/tests/math/t_bigints.nim @@ -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) diff --git a/tests/math/t_bigints_mod_vs_gmp.nim b/tests/math/t_bigints_mod_vs_gmp.nim index 62ab73c..c4c1ade 100644 --- a/tests/math/t_bigints_mod_vs_gmp.nim +++ b/tests/math/t_bigints_mod_vs_gmp.nim @@ -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() diff --git a/tests/math/t_bigints_mul_high_words_vs_gmp.nim b/tests/math/t_bigints_mul_high_words_vs_gmp.nim index e513221..6159e40 100644 --- a/tests/math/t_bigints_mul_high_words_vs_gmp.nim +++ b/tests/math/t_bigints_mul_high_words_vs_gmp.nim @@ -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: diff --git a/tests/math/t_bigints_mul_vs_gmp.nim b/tests/math/t_bigints_mul_vs_gmp.nim index 79985e9..2e0a10d 100644 --- a/tests/math/t_bigints_mul_vs_gmp.nim +++ b/tests/math/t_bigints_mul_vs_gmp.nim @@ -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: diff --git a/tests/math/t_finite_fields.nim b/tests/math/t_finite_fields.nim index 5e64e36..baf67f4 100644 --- a/tests/math/t_finite_fields.nim +++ b/tests/math/t_finite_fields.nim @@ -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] diff --git a/tests/math/t_finite_fields_powinv.nim b/tests/math/t_finite_fields_powinv.nim index 096c706..54ca297 100644 --- a/tests/math/t_finite_fields_powinv.nim +++ b/tests/math/t_finite_fields_powinv.nim @@ -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: diff --git a/tests/math/t_finite_fields_sqrt.nim b/tests/math/t_finite_fields_sqrt.nim index ab22451..fd76ded 100644 --- a/tests/math/t_finite_fields_sqrt.nim +++ b/tests/math/t_finite_fields_sqrt.nim @@ -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` diff --git a/tests/math/t_finite_fields_vs_gmp.nim b/tests/math/t_finite_fields_vs_gmp.nim index 522f0e1..76d3e7b 100644 --- a/tests/math/t_finite_fields_vs_gmp.nim +++ b/tests/math/t_finite_fields_vs_gmp.nim @@ -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: diff --git a/tests/math/t_io_bigints.nim b/tests/math/t_io_bigints.nim index 700ae42..ea13d31 100644 --- a/tests/math/t_io_bigints.nim +++ b/tests/math/t_io_bigints.nim @@ -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": diff --git a/tests/math/t_io_fields.nim b/tests/math/t_io_fields.nim index 39180a3..30fa1d9 100644 --- a/tests/math/t_io_fields.nim +++ b/tests/math/t_io_fields.nim @@ -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":