* create a codecs.nim file for hex/base64 and other encoding conversions

* improve maintenance/readability of hex conversion

* add skeleton of constant-time base64 decoding

* use raw casts

* use raw casts only for same size types
This commit is contained in:
Mamy Ratsimbazafy 2023-02-07 13:10:17 +01:00 committed by GitHub
parent 95114bf707
commit cbb454fff1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 420 additions and 317 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)
#

View File

@ -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)

View File

@ -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])

View File

@ -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.

View File

@ -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"

View File

@ -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)

View File

@ -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]"

View File

@ -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
# ----------------------------------------------------------------------

View File

@ -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`() =

View File

@ -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]