diff --git a/constantine/math/arithmetic/limbs_exgcd.nim b/constantine/math/arithmetic/limbs_exgcd.nim index 5cb0347..ca292c1 100644 --- a/constantine/math/arithmetic/limbs_exgcd.nim +++ b/constantine/math/arithmetic/limbs_exgcd.nim @@ -7,7 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ../../platforms/abstractions, + ../../platforms/[abstractions, signed_secret_words], ./limbs, ./limbs_unsaturated # No exceptions allowed diff --git a/constantine/math/arithmetic/limbs_unsaturated.nim b/constantine/math/arithmetic/limbs_unsaturated.nim index 9fac069..34174e8 100644 --- a/constantine/math/arithmetic/limbs_unsaturated.nim +++ b/constantine/math/arithmetic/limbs_unsaturated.nim @@ -6,11 +6,9 @@ # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ../../platforms/abstractions +import ../../platforms/[abstractions, signed_secret_words] type - SignedSecretWord* = distinct SecretWord - LimbsUnsaturated*[N, Excess: static int] = object ## An array of signed secret words ## with each word having their top Excess bits unused between function calls @@ -191,141 +189,13 @@ func setOne*(a: var LimbsUnsaturated) = for i in 1 ..< a.words.len: a[i] = SignedSecretWord(0) -# ############################################################ -# -# Arithmetic -# -# ############################################################ - # Workaround bug +# -------------- + func `xor`*(x,y: SecretWord): SecretWord {.inline.} = # For some reason the template defined in constant_time.nim isn't found SecretWord(x.BaseType xor y.BaseType) -when sizeof(int) == 8 and not defined(Constantine32): - type - SignedBaseType* = int64 -else: - type - SignedBaseType* = int32 - -template fmap(x: SignedSecretWord, op: untyped, y: SignedSecretWord): SignedSecretWord = - ## Unwrap x and y from their distinct type - ## Apply op, and rewrap them - SignedSecretWord(op(SecretWord(x), SecretWord(y))) - -template fmapAsgn(x: SignedSecretWord, op: untyped, y: SignedSecretWord) = - ## Unwrap x and y from their distinct type - ## Apply assignment op, and rewrap them - op(SecretWord(x), SecretWord(y)) - -template `and`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `and`, y) -template `or`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `or`, y) -template `xor`*(x, y: SignedSecretWord): SignedSecretWord = SignedSecretWord(BaseType(x) xor BaseType(y)) -template `not`*(x: SignedSecretWord): SignedSecretWord = SignedSecretWord(not SecretWord(x)) -template `+`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `+`, y) -template `+=`*(x: var SignedSecretWord, y: SignedSecretWord) = fmapAsgn(x, `+=`, y) -template `-`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `-`, y) -template `-=`*(x: var SignedSecretWord, y: SignedSecretWord) = fmapAsgn(x, `-=`, y) - -template `-`*(x: SignedSecretWord): SignedSecretWord = - # We don't use Nim signed integers to avoid range checks - SignedSecretWord(-SecretWord(x)) - -template `*`*(x, y: SignedSecretWord): SignedSecretWord = - # Warning ⚠️ : We assume that hardware multiplication is constant time - # but this is not always true. See https://www.bearssl.org/ctmul.html - fmap(x, `*`, y) - -# shifts -template ashr*(x: SignedSecretWord, y: SomeNumber): SignedSecretWord = - ## Arithmetic right shift - # We need to cast to Nim ints without Nim checks - SignedSecretWord(cast[SignedBaseType](x).ashr(y)) - -template lshr*(x: SignedSecretWord, y: SomeNumber): SignedSecretWord = - ## Logical right shift - SignedSecretWord(SecretWord(x) shr y) - -template lshl*(x: SignedSecretWord, y: SomeNumber): SignedSecretWord = - ## Logical left shift - SignedSecretWord(SecretWord(x) shl y) - -# ############################################################ -# -# Hardened Boolean primitives -# -# ############################################################ - -template `==`*(x, y: SignedSecretWord): SecretBool = - SecretWord(x) == SecretWord(y) - -# ############################################################ -# -# Conditional arithmetic -# -# ############################################################ - -# SignedSecretWord -# ---------------- - -func isNeg*(a: SignedSecretWord): SignedSecretWord {.inline.} = - ## Returns 1 if a is negative - ## and 0 otherwise - a.lshr(WordBitWidth-1) - -func isOdd*(a: SignedSecretWord): SignedSecretWord {.inline.} = - ## Returns 1 if a is odd - ## and 0 otherwise - a and SignedSecretWord(1) - -func isZeroMask*(a: SignedSecretWord): SignedSecretWord {.inline.} = - ## Produce the -1 mask if a is 0 - ## and 0 otherwise - # In x86 assembly, we can use "neg" + "sbb" - -SignedSecretWord(a.SecretWord().isZero()) - -func isNegMask*(a: SignedSecretWord): SignedSecretWord {.inline.} = - ## Produce the -1 mask if a is negative - ## and 0 otherwise - a.ashr(WordBitWidth-1) - -func isOddMask*(a: SignedSecretWord): SignedSecretWord {.inline.} = - ## Produce the -1 mask if a is odd - ## and 0 otherwise - -(a and SignedSecretWord(1)) - -func csetZero*(a: var SignedSecretWord, mask: SignedSecretWord) {.inline.} = - ## Conditionally set `a` to 0 - ## mask must be 0 (0x00000...0000) (kept as is) - ## or -1 (0xFFFF...FFFF) (zeroed) - a = a and mask - -func cneg*( - a: SignedSecretWord, - mask: SignedSecretWord): SignedSecretWord {.inline.} = - ## Conditionally negate `a` - ## mask must be 0 (0x00000...0000) (no negation) - ## or -1 (0xFFFF...FFFF) (negation) - (a xor mask) - mask - -func cadd*( - a: var SignedSecretWord, - b: SignedSecretWord, - mask: SignedSecretWord) {.inline.} = - ## Conditionally add `b` to `a` - ## mask must be 0 (0x00000...0000) (no addition) - ## or -1 (0xFFFF...FFFF) (addition) - a = a + (b and mask) - -func csub*( - a: var SignedSecretWord, - b: SignedSecretWord, - mask: SignedSecretWord) {.inline.} = - ## Conditionally substract `b` from `a` - ## mask must be 0 (0x00000...0000) (no substraction) - ## or -1 (0xFFFF...FFFF) (substraction) - a = a - (b and mask) # UnsaturatedLimbs # ---------------- @@ -371,49 +241,3 @@ func cadd*( ## Carry propagation is deferred for i in 0 ..< a.words.len: a[i].cadd(b[i], mask) - -# ############################################################ -# -# Double-Width signed arithmetic -# -# ############################################################ - -type DSWord* = object - lo*, hi*: SignedSecretWord - -func smulAccNoCarry*(r: var DSWord, a, b: SignedSecretWord) {.inline.}= - ## Signed accumulated multiplication - ## (_, hi, lo) += a*b - ## This assumes no overflowing - var UV: array[2, SecretWord] - var carry: Carry - smul(UV[1], UV[0], SecretWord a, SecretWord b) - addC(carry, UV[0], UV[0], SecretWord r.lo, Carry(0)) - addC(carry, UV[1], UV[1], SecretWord r.hi, carry) - - r.lo = SignedSecretWord UV[0] - r.hi = SignedSecretWord UV[1] - -func ssumprodAccNoCarry*(r: var DSWord, a, u, b, v: SignedSecretWord) {.inline.}= - ## Accumulated sum of products - ## (_, hi, lo) += a*u + b*v - ## This assumes no overflowing - var carry: Carry - var x1, x0, y1, y0: SecretWord - smul(x1, x0, SecretWord a, SecretWord u) - addC(carry, x0, x0, SecretWord r.lo, Carry(0)) - addC(carry, x1, x1, SecretWord r.hi, carry) - smul(y1, y0, SecretWord b, SecretWord v) - addC(carry, x0, x0, y0, Carry(0)) - addC(carry, x1, x1, y1, carry) - - r.lo = SignedSecretWord x0 - r.hi = SignedSecretWord x1 - -func ashr*( - r: var DSWord, - k: SomeInteger) {.inline.} = - ## Arithmetic right-shift of a double-word - ## This does not normalize the excess bits - r.lo = r.lo.lshr(k) or r.hi.lshl(WordBitWidth - k) - r.hi = r.hi.ashr(k) \ No newline at end of file diff --git a/constantine/math/io/io_bigints.nim b/constantine/math/io/io_bigints.nim index b24d65b..b07c5ed 100644 --- a/constantine/math/io/io_bigints.nim +++ b/constantine/math/io/io_bigints.nim @@ -11,7 +11,7 @@ # - Burning memory to ensure secrets are not left after dealloc. import - ../../platforms/[abstractions, endians], + ../../platforms/[abstractions, endians, codecs], ../arithmetic/bigints, ../config/type_bigint @@ -51,7 +51,7 @@ func unmarshalLE[T]( ## - no leaks ## ## Can work at compile-time - ## + ## ## It is possible to use a 63-bit representation out of a 64-bit words ## by setting `wordBitWidth` to something different from sizeof(T) * 8 ## This might be useful for architectures with no add-with-carry instructions. @@ -61,7 +61,7 @@ func unmarshalLE[T]( dst_idx = 0 acc = T(0) acc_len = 0 - + for src_idx in 0 ..< src.len: let src_byte = T(src[src_idx]) @@ -98,7 +98,7 @@ func unmarshalBE[T]( ## - no leaks ## ## Can work at compile-time - ## + ## ## It is possible to use a 63-bit representation out of a 64-bit words ## by setting `wordBitWidth` to something different from sizeof(T) * 8 ## This might be useful for architectures with no add-with-carry instructions. @@ -205,7 +205,7 @@ func marshalLE[T]( wordBitWidth: static int) = ## Serialize a bigint into its canonical little-endian representation ## I.e least significant bit first - ## + ## ## It is possible to use a 63-bit representation out of a 64-bit words ## by setting `wordBitWidth` to something different from sizeof(T) * 8 ## This might be useful for architectures with no add-with-carry instructions. @@ -271,7 +271,7 @@ func marshalBE[T]( ## ## In cryptography specifications, this is often called ## "Octet string to Integer" - ## + ## ## It is possible to use a 63-bit representation out of a 64-bit words ## by setting `wordBitWidth` to something different from sizeof(T) * 8 ## This might be useful for architectures with no add-with-carry instructions. @@ -367,93 +367,6 @@ func marshal*( {.pop.} # {.push raises: [].} -# ############################################################ -# -# Conversion helpers -# -# ############################################################ - -func readHexChar(c: char): SecretWord {.inline.}= - ## Converts an hex char to an int - template sw(a: char or int): SecretWord = SecretWord(a) - const k = WordBitWidth - 1 - - let c = sw(c) - - 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 - - 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`. - ## The string may be shorter than the byte array. - ## - ## 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 = Zero - dstIdx: int - shift = 4 - - 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.int - - doAssert size <= maxStrSize, "size: " & $size & ", maxSize: " & $maxStrSize - - if size < maxStrSize: - # include extra byte if odd length - dstIdx = output.len - (size + 1) shr 1 - # start with shl of 4 if length is even - shift = 4 - (size and 1) * 4 - - 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: - output[output.high - dstIdx] = output[output.high - dstIdx] or nibble - shift = (shift + 4) and 4 - dstIdx += shift shr 2 - -func toHex*(bytes: openarray[byte]): string = - ## Convert a byte-array to its hex representation - ## Output is in lowercase and prefixed with 0x - const hexChars = "0123456789abcdef" - result = newString(2 + 2 * bytes.len) - result[0] = '0' - result[1] = 'x' - for i in 0 ..< bytes.len: - 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) - -func fromHex*[N: static int](T: type array[N, byte], hex: string): T = - hexToPaddedByteArray(hex, result, bigEndian) - # ############################################################ # # Hex conversion @@ -469,7 +382,7 @@ func fromHex*(a: var BigInt, s: string) = ## Hex string is assumed big-endian ## ## 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. ## @@ -478,7 +391,7 @@ func fromHex*(a: var BigInt, s: string) = # 1. Convert to canonical uint const canonLen = (BigInt.bits + 8 - 1) div 8 var bytes: array[canonLen, byte] - hexToPaddedByteArray(s, bytes, bigEndian) + bytes.paddedFromHex(s, bigEndian) # 2. Convert canonical uint to Big Int a.unmarshal(bytes, bigEndian) @@ -492,7 +405,7 @@ func fromHex*(T: type BigInt, s: string): T {.noInit.} = ## Hex string is assumed big-endian ## ## 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. ## @@ -511,7 +424,7 @@ 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 diff --git a/constantine/platforms/codecs.nim b/constantine/platforms/codecs.nim new file mode 100644 index 0000000..0475f45 --- /dev/null +++ b/constantine/platforms/codecs.nim @@ -0,0 +1,170 @@ +# 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 ./abstractions, ./signed_secret_words + +# ############################################################ +# +# Codecs +# +# ############################################################ + +template sw(a: auto): SecretWord = SecretWord(a) +template ssw(a: auto): SignedSecretWord = SignedSecretWord(a) + +# ############################################################ +# +# Hexadecimal +# +# ############################################################ + +func readHexChar(c: char): SecretWord {.inline.} = + ## Converts an hex char to an int + const OOR = ssw 256 # Push chars out-of-range + var c = ssw(c) + OOR + + # '0' -> '9' maps to [0, 9] + c.csub(OOR + ssw('0') - ssw 0, c.isInRangeMask(ssw('0') + OOR, ssw('9') + OOR)) + # 'A' -> 'Z' maps to [10, 16) + c.csub(OOR + ssw('A') - ssw 10, c.isInRangeMask(ssw('A') + OOR, ssw('Z') + OOR)) + # 'a' -> 'z' maps to [10, 16) + c.csub(OOR + ssw('a') - ssw 10, c.isInRangeMask(ssw('a') + OOR, ssw('z') + OOR)) + + c = c and ssw(0xF) # Prevent overflow of invalid inputs + return sw(c) + +func paddedFromHex*(output: var openArray[byte], hexStr: string, order: static[Endianness]) = + ## Read a hex string and store it in a byte array `output`. + ## The string may be shorter than the byte array. + ## + ## 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. + + var + skip = Zero + dstIdx: int + shift = 4 + + 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.int + + doAssert size <= maxStrSize, "size: " & $size & ", maxSize: " & $maxStrSize + + if size < maxStrSize: + # include extra byte if odd length + dstIdx = output.len - (size + 1) shr 1 + # start with shl of 4 if length is even + shift = 4 - (size and 1) * 4 + + 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: + output[output.high - dstIdx] = output[output.high - dstIdx] or nibble + shift = (shift + 4) and 4 + dstIdx += shift shr 2 + +func toHex*(bytes: openarray[byte]): string = + ## Convert a byte-array to its hex representation + ## Output is in lowercase and prefixed with 0x + const hexChars = "0123456789abcdef" + result = newString(2 + 2 * bytes.len) + result[0] = '0' + result[1] = 'x' + for i in 0 ..< bytes.len: + 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) + +func fromHex*[N: static int](T: type array[N, byte], hex: string): T = + result.paddedFromHex(hex, bigEndian) + + +# ############################################################ +# +# Base64 +# +# ############################################################ + +func base64_decode( + dst: var openArray[byte], + src: openArray[char]): int {.used.} = + ## Decode a Base64 string/bytearray input into + ## an octet string + ## This procedure is constant-time, except for new lines, padding and invalid base64 characters + ## + ## Returns -1 if the buffer is too small + ## or the number of bytes written. + ## Bytes are written from the start of the buffer + + # TODO: unexposed, missing comprehensive test suite. + + var s, d = 0 + var vals: array[4, SecretWord] + var bytes: array[3, byte] + + while s < src.len and d < dst.len: + var padding = ssw 0 + + for i in 0 ..< 4: + const OOR = ssw 256 # Push chars out-of-range + + var c = ssw(src[s]) + OOR + s += 1 + + # 'A' -> 'Z' maps to [0, 26) + c.csub(OOR + ssw('A'), c.isInRangeMask(ssw('A') + OOR, ssw('Z') + OOR)) + # 'a' -> 'z' maps to [26, 52) + c.csub(OOR + ssw('a') - ssw 26, c.isInRangeMask(ssw('a') + OOR, ssw('z') + OOR)) + # '0' -> '9' maps to [52, 61) + c.csub(OOR + ssw('0') - ssw 52, c.isInRangeMask(ssw('0') + OOR, ssw('9') + OOR)) + # '+' maps to 62 + c.csub(OOR + ssw('+') - ssw 62, c.isInRangeMask(ssw('+') + OOR, ssw('+') + OOR)) + # '/' maps to 63 + c.csub(OOR + ssw('/') - ssw 63, c.isInRangeMask(ssw('/') + OOR, ssw('/') + OOR)) + # '=' is padding and everything else is ignored + padding.cadd(ssw 1, c.isInRangeMask(ssw('=') + OOR, ssw('=') + OOR)) + + # https://www.rfc-editor.org/rfc/rfc7468#section-2 + # "Furthermore, parsers SHOULD ignore whitespace and other non- + # base64 characters and MUST handle different newline conventions." + # + # Unfortunately, there is no way to deal with newlines, padding and invalid characters + # without revealing that they exist when we do not increment the destination index + if c.int >= OOR.int: + continue + + vals[i] = SecretWord(c) + + bytes[0] = byte((vals[0] shl 2) or (vals[1] shr 4)) + bytes[1] = byte((vals[1] shl 4) or (vals[2] shr 2)) + bytes[2] = byte((vals[2] shl 6) or vals[3] ) + + + for i in 0 ..< 3 - padding.int: + if d >= dst.len: + return -1 + dst[d] = bytes[i] + d += 1 + return d \ No newline at end of file diff --git a/constantine/platforms/gpu/ir.nim b/constantine/platforms/gpu/ir.nim index 913121f..70bee53 100644 --- a/constantine/platforms/gpu/ir.nim +++ b/constantine/platforms/gpu/ir.nim @@ -90,7 +90,7 @@ type DynWord = uint32 or uint64 BigNum[T: DynWord] = object bits: uint32 - limbs: seq[T] + limbs: seq[T] # Serialization # ------------------------------------------------ @@ -102,14 +102,14 @@ func byteLen(bits: SomeInteger): SomeInteger {.inline.} = func wordsRequiredForBits(bits, wordBitwidth: SomeInteger): SomeInteger {.inline.} = ## Compute the number of limbs required ## from the announced bit length - + debug: doAssert wordBitwidth == 32 or wordBitwidth == 64 # Power of 2 (bits + wordBitwidth - 1) shr log2_vartime(uint32 wordBitwidth) # 5x to 55x faster than dividing by wordBitwidth func fromHex[T](a: var BigNum[T], s: string) = var bytes = newSeq[byte](a.bits.byteLen()) - hexToPaddedByteArray(s, bytes, bigEndian) - + bytes.paddedFromHex(s, bigEndian) + # 2. Convert canonical uint to BigNum const wordBitwidth = sizeof(T) * 8 a.limbs.unmarshal(bytes, wordBitwidth, bigEndian) @@ -117,7 +117,7 @@ func fromHex[T](a: var BigNum[T], s: string) = func fromHex[T](BN: type BigNum[T], bits: uint32, s: string): BN = const wordBitwidth = sizeof(T) * 8 let numWords = wordsRequiredForBits(bits, wordBitwidth) - + result.bits = bits result.limbs.setLen(numWords) result.fromHex(s) @@ -160,7 +160,7 @@ func negInvModWord[T](M: BigNum[T]): T = ## ## µ ≡ -1/M[0] (mod 2^64) checkValidModulus(M) - + result = invModBitwidth(M.limbs[0]) # negate to obtain the negative inverse result = not(result) + 1 @@ -175,11 +175,11 @@ type WordSize* = enum size32 size64 - + Field* = enum fp fr - + FieldConst* = object wordTy: TypeRef fieldTy: TypeRef @@ -193,7 +193,7 @@ type prefix*: string wordSize*: WordSize fp*: FieldConst - fr*: FieldConst + fr*: FieldConst Opcode* = enum opFpAdd = "fp_add" @@ -203,7 +203,7 @@ proc setFieldConst(fc: var FieldConst, ctx: ContextRef, wordSize: WordSize, modB let wordTy = case wordSize of size32: ctx.int32_t() of size64: ctx.int64_t() - + let wordBitwidth = case wordSize of size32: 32'u32 of size64: 64'u32 @@ -212,7 +212,7 @@ proc setFieldConst(fc: var FieldConst, ctx: ContextRef, wordSize: WordSize, modB fc.wordTy = wordTy fc.fieldTy = array_t(wordTy, numWords) - + case wordSize of size32: let m = BigNum[uint32].fromHex(modBits, modulus) @@ -239,13 +239,13 @@ proc init*( prefix: string, wordSize: WordSize, fpBits: uint32, fpMod: string, frBits: uint32, frMod: string): CurveMetadata = - + result = C(prefix: prefix, wordSize: wordSize) result.fp.setFieldConst(ctx, wordSize, fpBits, fpMod) result.fr.setFieldConst(ctx, wordSize, frBits, frMod) proc genSymbol*(cm: CurveMetadata, opcode: Opcode): string {.inline.} = - cm.prefix & + cm.prefix & (if cm.wordSize == size32: "32b_" else: "64b_") & $opcode @@ -282,7 +282,7 @@ func getSpareBits*(cm: CurveMetadata, field: Field): uint8 {.inline.} = # ############################################################ # For array access we need to use: -# +# # builder.extractValue(array, index, name) # builder.insertValue(array, index, value, name) # diff --git a/constantine/platforms/signed_secret_words.nim b/constantine/platforms/signed_secret_words.nim new file mode 100644 index 0000000..4fd50c5 --- /dev/null +++ b/constantine/platforms/signed_secret_words.nim @@ -0,0 +1,195 @@ +# 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 ./abstractions + +type SignedSecretWord* = distinct SecretWord + +when sizeof(int) == 8 and not defined(Constantine32): + type + SignedBaseType* = int64 +else: + type + SignedBaseType* = int32 + +# ############################################################ +# +# Arithmetic +# +# ############################################################ + +template fmap(x: SignedSecretWord, op: untyped, y: SignedSecretWord): SignedSecretWord = + ## Unwrap x and y from their distinct type + ## Apply op, and rewrap them + SignedSecretWord(op(SecretWord(x), SecretWord(y))) + +template fmapAsgn(x: SignedSecretWord, op: untyped, y: SignedSecretWord) = + ## Unwrap x and y from their distinct type + ## Apply assignment op, and rewrap them + op(SecretWord(x), SecretWord(y)) + +template `and`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `and`, y) +template `or`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `or`, y) +template `xor`*(x, y: SignedSecretWord): SignedSecretWord = SignedSecretWord(BaseType(x) xor BaseType(y)) +template `not`*(x: SignedSecretWord): SignedSecretWord = SignedSecretWord(not SecretWord(x)) +template `+`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `+`, y) +template `+=`*(x: var SignedSecretWord, y: SignedSecretWord) = fmapAsgn(x, `+=`, y) +template `-`*(x, y: SignedSecretWord): SignedSecretWord = fmap(x, `-`, y) +template `-=`*(x: var SignedSecretWord, y: SignedSecretWord) = fmapAsgn(x, `-=`, y) + +template `-`*(x: SignedSecretWord): SignedSecretWord = + # We don't use Nim signed integers to avoid range checks + SignedSecretWord(-SecretWord(x)) + +template `*`*(x, y: SignedSecretWord): SignedSecretWord = + # Warning ⚠️ : We assume that hardware multiplication is constant time + # but this is not always true. See https://www.bearssl.org/ctmul.html + fmap(x, `*`, y) + +# shifts +template ashr*(x: SignedSecretWord, y: SomeNumber): SignedSecretWord = + ## Arithmetic right shift + # We need to cast to Nim ints without Nim checks + cast[SignedSecretWord](cast[SignedBaseType](x).ashr(y)) + +template lshr*(x: SignedSecretWord, y: SomeNumber): SignedSecretWord = + ## Logical right shift + SignedSecretWord(SecretWord(x) shr y) + +template lshl*(x: SignedSecretWord, y: SomeNumber): SignedSecretWord = + ## Logical left shift + SignedSecretWord(SecretWord(x) shl y) + +# ############################################################ +# +# Hardened Boolean primitives +# +# ############################################################ + +template `==`*(x, y: SignedSecretWord): SecretBool = + SecretWord(x) == SecretWord(y) + +# ############################################################ +# +# Conditional arithmetic +# +# ############################################################ + +# SignedSecretWord +# ---------------- + +func isNeg*(a: SignedSecretWord): SignedSecretWord {.inline.} = + ## Returns 1 if a is negative + ## and 0 otherwise + a.lshr(WordBitWidth-1) + +func isOdd*(a: SignedSecretWord): SignedSecretWord {.inline.} = + ## Returns 1 if a is odd + ## and 0 otherwise + a and SignedSecretWord(1) + +func isZeroMask*(a: SignedSecretWord): SignedSecretWord {.inline.} = + ## Produce the -1 mask if a is 0 + ## and 0 otherwise + # In x86 assembly, we can use "neg" + "sbb" + -SignedSecretWord(a.SecretWord().isZero()) + +func isNegMask*(a: SignedSecretWord): SignedSecretWord {.inline.} = + ## Produce the -1 mask if a is negative + ## and 0 otherwise + a.ashr(WordBitWidth-1) + +func isOddMask*(a: SignedSecretWord): SignedSecretWord {.inline.} = + ## Produce the -1 mask if a is odd + ## and 0 otherwise + -(a and SignedSecretWord(1)) + +func isInRangeMask*(val, lo, hi: SignedSecretWord): SignedSecretWord {.inline.} = + ## Produce 0b11111111 mask if lo <= val <= hi (inclusive range) + ## and 0b00000000 otherwise + let loInvMask = isNegMask(val-lo) # if val-lo < 0 => val < lo + let hiInvMask = isNegMask(hi-val) # if hi-val < 0 => val > hi + return not(loInvMask or hiInvMask) + +func csetZero*(a: var SignedSecretWord, mask: SignedSecretWord) {.inline.} = + ## Conditionally set `a` to 0 + ## mask must be 0 (0x00000...0000) (kept as is) + ## or -1 (0xFFFF...FFFF) (zeroed) + a = a and mask + +func cneg*( + a: SignedSecretWord, + mask: SignedSecretWord): SignedSecretWord {.inline.} = + ## Conditionally negate `a` + ## mask must be 0 (0x00000...0000) (no negation) + ## or -1 (0xFFFF...FFFF) (negation) + (a xor mask) - mask + +func cadd*( + a: var SignedSecretWord, + b: SignedSecretWord, + mask: SignedSecretWord) {.inline.} = + ## Conditionally add `b` to `a` + ## mask must be 0 (0x00000...0000) (no addition) + ## or -1 (0xFFFF...FFFF) (addition) + a = a + (b and mask) + +func csub*( + a: var SignedSecretWord, + b: SignedSecretWord, + mask: SignedSecretWord) {.inline.} = + ## Conditionally substract `b` from `a` + ## mask must be 0 (0x00000...0000) (no substraction) + ## or -1 (0xFFFF...FFFF) (substraction) + a = a - (b and mask) + +# ############################################################ +# +# Double-Width signed arithmetic +# +# ############################################################ + +type DSWord* = object + lo*, hi*: SignedSecretWord + +func smulAccNoCarry*(r: var DSWord, a, b: SignedSecretWord) {.inline.}= + ## Signed accumulated multiplication + ## (_, hi, lo) += a*b + ## This assumes no overflowing + var UV: array[2, SecretWord] + var carry: Carry + smul(UV[1], UV[0], SecretWord a, SecretWord b) + addC(carry, UV[0], UV[0], SecretWord r.lo, Carry(0)) + addC(carry, UV[1], UV[1], SecretWord r.hi, carry) + + r.lo = SignedSecretWord UV[0] + r.hi = SignedSecretWord UV[1] + +func ssumprodAccNoCarry*(r: var DSWord, a, u, b, v: SignedSecretWord) {.inline.}= + ## Accumulated sum of products + ## (_, hi, lo) += a*u + b*v + ## This assumes no overflowing + var carry: Carry + var x1, x0, y1, y0: SecretWord + smul(x1, x0, SecretWord a, SecretWord u) + addC(carry, x0, x0, SecretWord r.lo, Carry(0)) + addC(carry, x1, x1, SecretWord r.hi, carry) + smul(y1, y0, SecretWord b, SecretWord v) + addC(carry, x0, x0, y0, Carry(0)) + addC(carry, x1, x1, y1, carry) + + r.lo = SignedSecretWord x0 + r.hi = SignedSecretWord x1 + +func ashr*( + r: var DSWord, + k: SomeInteger) {.inline.} = + ## Arithmetic right-shift of a double-word + ## This does not normalize the excess bits + r.lo = r.lo.lshr(k) or r.hi.lshl(WordBitWidth - k) + r.hi = r.hi.ashr(k) \ No newline at end of file diff --git a/tests/math/t_ec_template.nim b/tests/math/t_ec_template.nim index e132dc6..8faf79a 100644 --- a/tests/math/t_ec_template.nim +++ b/tests/math/t_ec_template.nim @@ -489,7 +489,7 @@ proc run_EC_mixed_add_impl*( r_generic.double(a) r_mixed.madd(a, aAff) check: bool(r_generic == r_mixed) - + # Aliasing test r_mixed = a r_mixed += aAff @@ -612,7 +612,7 @@ proc run_EC_subgroups_cofactors_impl*( doAssert bool Q.isInSubgroup(), "Subgroup check issue on " & $EC & " with Q: " & Q.toHex() stdout.write '.' - + stdout.write '\n' test(ec, randZ = false, gen = Uniform) @@ -621,7 +621,7 @@ proc run_EC_subgroups_cofactors_impl*( test(ec, randZ = true, gen = HighHammingWeight) test(ec, randZ = false, gen = Long01Sequence) test(ec, randZ = true, gen = Long01Sequence) - + echo " [SUCCESS] Test finished with ", inSubgroup, " points in ", G1_or_G2, " subgroup and ", offSubgroup, " points on curve but not in subgroup (before cofactor clearing)" @@ -815,7 +815,7 @@ proc run_EC_batch_add_impl*[N: static int]( test $ec & " batch addition (N=" & $n & ")": proc test(EC: typedesc, gen: RandomGen) = var points = newSeq[ECP_ShortW_Aff[EC.F, EC.G]](n) - + for i in 0 ..< n: points[i] = rng.random_point(ECP_ShortW_Aff[EC.F, EC.G], randZ = false, gen) @@ -839,10 +839,10 @@ proc run_EC_batch_add_impl*[N: static int]( var points = newSeq[ECP_ShortW_Aff[EC.F, EC.G]](n) let halfN = n div 2 - + for i in 0 ..< halfN: points[i] = rng.random_point(ECP_ShortW_Aff[EC.F, EC.G], randZ = false, gen) - + for i in halfN ..< n: # The special cases test relies on internal knowledge that we sum(points[i], points[i+n/2] # It should be changed if scheduling change, for example if we sum(points[2*i], points[2*i+1]) diff --git a/tests/t_blssig_pop_on_bls12381_g2.nim b/tests/t_blssig_pop_on_bls12381_g2.nim index eb7f653..b34d711 100644 --- a/tests/t_blssig_pop_on_bls12381_g2.nim +++ b/tests/t_blssig_pop_on_bls12381_g2.nim @@ -10,7 +10,7 @@ import std/[os, unittest, strutils], pkg/jsony, ../constantine/blssig_pop_on_bls12381_g2, - ../constantine/math/io/io_bigints, + ../constantine/platforms/codecs, ../constantine/hashes type @@ -70,7 +70,7 @@ type proc parseHook*[N: static int](src: string, pos: var int, value: var array[N, byte]) = var str: string parseHook(src, pos, str) - str.hexToPaddedByteArray(value, bigEndian) + value.paddedFromHex(str, bigEndian) const SkippedTests = [ # By construction, input MUST be 48 bytes, which is enforced at the type-system level. diff --git a/tests/t_ethereum_eip2333_bls12381_key_derivation.nim b/tests/t_ethereum_eip2333_bls12381_key_derivation.nim index a228b1f..47a123c 100644 --- a/tests/t_ethereum_eip2333_bls12381_key_derivation.nim +++ b/tests/t_ethereum_eip2333_bls12381_key_derivation.nim @@ -13,7 +13,7 @@ import ../constantine/math/io/io_bigints, ../constantine/math/config/curves, ../constantine/math/arithmetic/bigints, - ../constantine/platforms/abstractions + ../constantine/platforms/[abstractions, codecs] type SecretKey = matchingOrderBigInt(BLS12_381) @@ -22,7 +22,7 @@ proc toBytes(hex: string): seq[byte] = let length = hex.len shr 1 - int(hex[0] == '0' and (hex[1] in {'x', 'X'})) result.newSeq(length) - hex.hexToPaddedByteArray(result, bigEndian) + result.paddedFromHex(hex, bigEndian) proc test0 = let seed = toBytes"0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04" diff --git a/tests/t_ethereum_evm_precompiles.nim b/tests/t_ethereum_evm_precompiles.nim index ebffd66..4d157dc 100644 --- a/tests/t_ethereum_evm_precompiles.nim +++ b/tests/t_ethereum_evm_precompiles.nim @@ -12,7 +12,7 @@ import # 3rd party pkg/jsony, # Internals - ../constantine/math/io/io_bigints, + ../constantine/platforms/codecs, ../constantine/ethereum_evm_precompiles type @@ -48,7 +48,7 @@ template runBN256Tests(filename: string, funcname: untyped, osize: static int) = # Length: 2 hex characters -> 1 byte var inputbytes = newSeq[byte](test.Input.len div 2) - test.Input.hexToPaddedByteArray(inputbytes, bigEndian) + inputbytes.paddedFromHex(test.Input, bigEndian) var r: array[osize, byte] var expected: array[osize, byte] @@ -57,15 +57,15 @@ template runBN256Tests(filename: string, funcname: untyped, osize: static int) = if status != cttEVM_Success: reset(r) - test.Expected.hexToPaddedByteArray(expected, bigEndian) + expected.paddedFromHex(test.Expected, bigEndian) doAssert r == expected, "[Test Failure]\n" & " " & funcname.astToStr & " status: " & $status & "\n" & " " & "result: " & r.toHex() & "\n" & - " " & "expected: " & expected.toHex() & '\n' - + " " & "expected: " & expected.toHex() & '\n' + stdout.write "Success\n" - + `bn256testrunner _ funcname`() runBN256Tests("bn256Add.json", eth_evm_ecadd, 64) diff --git a/tests/t_hash_sha256_vs_openssl.nim b/tests/t_hash_sha256_vs_openssl.nim index 38c7af7..9a58a61 100644 --- a/tests/t_hash_sha256_vs_openssl.nim +++ b/tests/t_hash_sha256_vs_openssl.nim @@ -1,7 +1,7 @@ import # Internals ../constantine/hashes, - ../constantine/math/io/io_bigints, + ../constantine/platforms/codecs, # Helpers ../helpers/prng_unsafe @@ -156,7 +156,7 @@ proc main() = rng.innerTest(1_000_000 ..< 50_000_000) echo "SHA256 - Differential testing vs OpenSSL - SUCCESS" - + else: echo "SHA256 - Differential testing vs OpenSSL - [SKIPPED]" diff --git a/tests/t_hash_to_field.nim b/tests/t_hash_to_field.nim index 5976455..7f142cf 100644 --- a/tests/t_hash_to_field.nim +++ b/tests/t_hash_to_field.nim @@ -11,7 +11,8 @@ import ../constantine/hash_to_curve/h2c_hash_to_field, ../constantine/math/config/[curves_declaration, type_ff], ../constantine/math/extension_fields/towers, - ../constantine/math/io/[io_bigints, io_fields, io_extfields] + ../constantine/math/io/[io_fields, io_extfields], + ../constantine/platforms/codecs # Test vectors for expandMessageXMD # ---------------------------------------------------------------------- diff --git a/tests/t_kdf_hkdf.nim b/tests/t_kdf_hkdf.nim index 90d4998..41e0e95 100644 --- a/tests/t_kdf_hkdf.nim +++ b/tests/t_kdf_hkdf.nim @@ -7,7 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ../constantine/math/io/io_bigints, + ../constantine/platforms/codecs, ../constantine/[hashes, mac/mac_hmac, kdf/kdf_hkdf] proc hexToBytes(s: string): seq[byte] = @@ -19,7 +19,7 @@ proc hexToBytes(s: string): seq[byte] = (int(s[1] == 'x') or int(s[1] == 'X')) ) result.setLen((s.len - skip) div 2) - s.hexToPaddedByteArray(result, bigEndian) + result.paddedFromHex(s, bigEndian) template test(id, constants: untyped) = proc `test _ id`() = diff --git a/tests/t_mac_hmac_sha256.nim b/tests/t_mac_hmac_sha256.nim index 1409027..f135a64 100644 --- a/tests/t_mac_hmac_sha256.nim +++ b/tests/t_mac_hmac_sha256.nim @@ -10,7 +10,7 @@ import std/unittest, ../constantine/mac/mac_hmac, ../constantine/hashes, - ../constantine/math/io/io_bigints + ../constantine/platforms/codecs type TestVector = object key: seq[byte] @@ -25,15 +25,15 @@ proc doTest(key, data, digest: string) = doAssert (data.len and 1) == 0, "An hex string must be of even length" doAssert (digest.len and 1) == 0, "An hex string must be of even length" doAssert digest.len <= 64, "HMAC-SHA256 hex string must be at most length 64 (32 bytes)" - + tv.key.newSeq(key.len div 2) - key.hexToPaddedByteArray(tv.key, bigEndian) + tv.key.paddedFromHex(key, bigEndian) tv.data.newSeq(data.len div 2) - data.hexToPaddedByteArray(tv.data, bigEndian) + tv.data.paddedFromHex(data, bigEndian) tv.truncatedLen = digest.len div 2 - digest.hexToPaddedByteArray(tv.digest, bigEndian) + tv.digest.paddedFromHex(digest, bigEndian) var output{.noInit.}: array[32, byte]