Codecs (#217)
* 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:
parent
95114bf707
commit
cbb454fff1
|
@ -7,7 +7,7 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
../../platforms/abstractions,
|
../../platforms/[abstractions, signed_secret_words],
|
||||||
./limbs, ./limbs_unsaturated
|
./limbs, ./limbs_unsaturated
|
||||||
|
|
||||||
# No exceptions allowed
|
# No exceptions allowed
|
||||||
|
|
|
@ -6,11 +6,9 @@
|
||||||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
# * 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.
|
# 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
|
type
|
||||||
SignedSecretWord* = distinct SecretWord
|
|
||||||
|
|
||||||
LimbsUnsaturated*[N, Excess: static int] = object
|
LimbsUnsaturated*[N, Excess: static int] = object
|
||||||
## An array of signed secret words
|
## An array of signed secret words
|
||||||
## with each word having their top Excess bits unused between function calls
|
## 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:
|
for i in 1 ..< a.words.len:
|
||||||
a[i] = SignedSecretWord(0)
|
a[i] = SignedSecretWord(0)
|
||||||
|
|
||||||
# ############################################################
|
|
||||||
#
|
|
||||||
# Arithmetic
|
|
||||||
#
|
|
||||||
# ############################################################
|
|
||||||
|
|
||||||
# Workaround bug
|
# Workaround bug
|
||||||
|
# --------------
|
||||||
|
|
||||||
func `xor`*(x,y: SecretWord): SecretWord {.inline.} =
|
func `xor`*(x,y: SecretWord): SecretWord {.inline.} =
|
||||||
# For some reason the template defined in constant_time.nim isn't found
|
# For some reason the template defined in constant_time.nim isn't found
|
||||||
SecretWord(x.BaseType xor y.BaseType)
|
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
|
# UnsaturatedLimbs
|
||||||
# ----------------
|
# ----------------
|
||||||
|
@ -371,49 +241,3 @@ func cadd*(
|
||||||
## Carry propagation is deferred
|
## Carry propagation is deferred
|
||||||
for i in 0 ..< a.words.len:
|
for i in 0 ..< a.words.len:
|
||||||
a[i].cadd(b[i], mask)
|
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)
|
|
|
@ -11,7 +11,7 @@
|
||||||
# - Burning memory to ensure secrets are not left after dealloc.
|
# - Burning memory to ensure secrets are not left after dealloc.
|
||||||
|
|
||||||
import
|
import
|
||||||
../../platforms/[abstractions, endians],
|
../../platforms/[abstractions, endians, codecs],
|
||||||
../arithmetic/bigints,
|
../arithmetic/bigints,
|
||||||
../config/type_bigint
|
../config/type_bigint
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ func unmarshalLE[T](
|
||||||
## - no leaks
|
## - no leaks
|
||||||
##
|
##
|
||||||
## Can work at compile-time
|
## Can work at compile-time
|
||||||
##
|
##
|
||||||
## It is possible to use a 63-bit representation out of a 64-bit words
|
## 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
|
## by setting `wordBitWidth` to something different from sizeof(T) * 8
|
||||||
## This might be useful for architectures with no add-with-carry instructions.
|
## This might be useful for architectures with no add-with-carry instructions.
|
||||||
|
@ -61,7 +61,7 @@ func unmarshalLE[T](
|
||||||
dst_idx = 0
|
dst_idx = 0
|
||||||
acc = T(0)
|
acc = T(0)
|
||||||
acc_len = 0
|
acc_len = 0
|
||||||
|
|
||||||
for src_idx in 0 ..< src.len:
|
for src_idx in 0 ..< src.len:
|
||||||
let src_byte = T(src[src_idx])
|
let src_byte = T(src[src_idx])
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ func unmarshalBE[T](
|
||||||
## - no leaks
|
## - no leaks
|
||||||
##
|
##
|
||||||
## Can work at compile-time
|
## Can work at compile-time
|
||||||
##
|
##
|
||||||
## It is possible to use a 63-bit representation out of a 64-bit words
|
## 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
|
## by setting `wordBitWidth` to something different from sizeof(T) * 8
|
||||||
## This might be useful for architectures with no add-with-carry instructions.
|
## This might be useful for architectures with no add-with-carry instructions.
|
||||||
|
@ -205,7 +205,7 @@ func marshalLE[T](
|
||||||
wordBitWidth: static int) =
|
wordBitWidth: static int) =
|
||||||
## Serialize a bigint into its canonical little-endian representation
|
## Serialize a bigint into its canonical little-endian representation
|
||||||
## I.e least significant bit first
|
## I.e least significant bit first
|
||||||
##
|
##
|
||||||
## It is possible to use a 63-bit representation out of a 64-bit words
|
## 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
|
## by setting `wordBitWidth` to something different from sizeof(T) * 8
|
||||||
## This might be useful for architectures with no add-with-carry instructions.
|
## 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
|
## In cryptography specifications, this is often called
|
||||||
## "Octet string to Integer"
|
## "Octet string to Integer"
|
||||||
##
|
##
|
||||||
## It is possible to use a 63-bit representation out of a 64-bit words
|
## 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
|
## by setting `wordBitWidth` to something different from sizeof(T) * 8
|
||||||
## This might be useful for architectures with no add-with-carry instructions.
|
## This might be useful for architectures with no add-with-carry instructions.
|
||||||
|
@ -367,93 +367,6 @@ func marshal*(
|
||||||
|
|
||||||
{.pop.} # {.push raises: [].}
|
{.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
|
# Hex conversion
|
||||||
|
@ -469,7 +382,7 @@ func fromHex*(a: var BigInt, s: string) =
|
||||||
## Hex string is assumed big-endian
|
## Hex string is assumed big-endian
|
||||||
##
|
##
|
||||||
## Procedure is constant-time except for the presence (or absence) of the 0x prefix.
|
## 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.
|
## This procedure is intended for configuration, prototyping, research and debugging purposes.
|
||||||
## You MUST NOT use it for production.
|
## You MUST NOT use it for production.
|
||||||
##
|
##
|
||||||
|
@ -478,7 +391,7 @@ func fromHex*(a: var BigInt, s: string) =
|
||||||
# 1. Convert to canonical uint
|
# 1. Convert to canonical uint
|
||||||
const canonLen = (BigInt.bits + 8 - 1) div 8
|
const canonLen = (BigInt.bits + 8 - 1) div 8
|
||||||
var bytes: array[canonLen, byte]
|
var bytes: array[canonLen, byte]
|
||||||
hexToPaddedByteArray(s, bytes, bigEndian)
|
bytes.paddedFromHex(s, bigEndian)
|
||||||
|
|
||||||
# 2. Convert canonical uint to Big Int
|
# 2. Convert canonical uint to Big Int
|
||||||
a.unmarshal(bytes, bigEndian)
|
a.unmarshal(bytes, bigEndian)
|
||||||
|
@ -492,7 +405,7 @@ func fromHex*(T: type BigInt, s: string): T {.noInit.} =
|
||||||
## Hex string is assumed big-endian
|
## Hex string is assumed big-endian
|
||||||
##
|
##
|
||||||
## Procedure is constant-time except for the presence (or absence) of the 0x prefix.
|
## 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.
|
## This procedure is intended for configuration, prototyping, research and debugging purposes.
|
||||||
## You MUST NOT use it for production.
|
## You MUST NOT use it for production.
|
||||||
##
|
##
|
||||||
|
@ -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
|
## This is useful to reduce the number of allocations when serializing
|
||||||
## Fp towers
|
## Fp towers
|
||||||
##
|
##
|
||||||
## This function may allocate.
|
## This function may allocate.
|
||||||
|
|
||||||
# 1. Convert Big Int to canonical uint
|
# 1. Convert Big Int to canonical uint
|
||||||
|
|
|
@ -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
|
|
@ -90,7 +90,7 @@ type
|
||||||
DynWord = uint32 or uint64
|
DynWord = uint32 or uint64
|
||||||
BigNum[T: DynWord] = object
|
BigNum[T: DynWord] = object
|
||||||
bits: uint32
|
bits: uint32
|
||||||
limbs: seq[T]
|
limbs: seq[T]
|
||||||
|
|
||||||
# Serialization
|
# Serialization
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------
|
||||||
|
@ -102,14 +102,14 @@ func byteLen(bits: SomeInteger): SomeInteger {.inline.} =
|
||||||
func wordsRequiredForBits(bits, wordBitwidth: SomeInteger): SomeInteger {.inline.} =
|
func wordsRequiredForBits(bits, wordBitwidth: SomeInteger): SomeInteger {.inline.} =
|
||||||
## Compute the number of limbs required
|
## Compute the number of limbs required
|
||||||
## from the announced bit length
|
## from the announced bit length
|
||||||
|
|
||||||
debug: doAssert wordBitwidth == 32 or wordBitwidth == 64 # Power of 2
|
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
|
(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) =
|
func fromHex[T](a: var BigNum[T], s: string) =
|
||||||
var bytes = newSeq[byte](a.bits.byteLen())
|
var bytes = newSeq[byte](a.bits.byteLen())
|
||||||
hexToPaddedByteArray(s, bytes, bigEndian)
|
bytes.paddedFromHex(s, bigEndian)
|
||||||
|
|
||||||
# 2. Convert canonical uint to BigNum
|
# 2. Convert canonical uint to BigNum
|
||||||
const wordBitwidth = sizeof(T) * 8
|
const wordBitwidth = sizeof(T) * 8
|
||||||
a.limbs.unmarshal(bytes, wordBitwidth, bigEndian)
|
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 =
|
func fromHex[T](BN: type BigNum[T], bits: uint32, s: string): BN =
|
||||||
const wordBitwidth = sizeof(T) * 8
|
const wordBitwidth = sizeof(T) * 8
|
||||||
let numWords = wordsRequiredForBits(bits, wordBitwidth)
|
let numWords = wordsRequiredForBits(bits, wordBitwidth)
|
||||||
|
|
||||||
result.bits = bits
|
result.bits = bits
|
||||||
result.limbs.setLen(numWords)
|
result.limbs.setLen(numWords)
|
||||||
result.fromHex(s)
|
result.fromHex(s)
|
||||||
|
@ -160,7 +160,7 @@ func negInvModWord[T](M: BigNum[T]): T =
|
||||||
##
|
##
|
||||||
## µ ≡ -1/M[0] (mod 2^64)
|
## µ ≡ -1/M[0] (mod 2^64)
|
||||||
checkValidModulus(M)
|
checkValidModulus(M)
|
||||||
|
|
||||||
result = invModBitwidth(M.limbs[0])
|
result = invModBitwidth(M.limbs[0])
|
||||||
# negate to obtain the negative inverse
|
# negate to obtain the negative inverse
|
||||||
result = not(result) + 1
|
result = not(result) + 1
|
||||||
|
@ -175,11 +175,11 @@ type
|
||||||
WordSize* = enum
|
WordSize* = enum
|
||||||
size32
|
size32
|
||||||
size64
|
size64
|
||||||
|
|
||||||
Field* = enum
|
Field* = enum
|
||||||
fp
|
fp
|
||||||
fr
|
fr
|
||||||
|
|
||||||
FieldConst* = object
|
FieldConst* = object
|
||||||
wordTy: TypeRef
|
wordTy: TypeRef
|
||||||
fieldTy: TypeRef
|
fieldTy: TypeRef
|
||||||
|
@ -193,7 +193,7 @@ type
|
||||||
prefix*: string
|
prefix*: string
|
||||||
wordSize*: WordSize
|
wordSize*: WordSize
|
||||||
fp*: FieldConst
|
fp*: FieldConst
|
||||||
fr*: FieldConst
|
fr*: FieldConst
|
||||||
|
|
||||||
Opcode* = enum
|
Opcode* = enum
|
||||||
opFpAdd = "fp_add"
|
opFpAdd = "fp_add"
|
||||||
|
@ -203,7 +203,7 @@ proc setFieldConst(fc: var FieldConst, ctx: ContextRef, wordSize: WordSize, modB
|
||||||
let wordTy = case wordSize
|
let wordTy = case wordSize
|
||||||
of size32: ctx.int32_t()
|
of size32: ctx.int32_t()
|
||||||
of size64: ctx.int64_t()
|
of size64: ctx.int64_t()
|
||||||
|
|
||||||
let wordBitwidth = case wordSize
|
let wordBitwidth = case wordSize
|
||||||
of size32: 32'u32
|
of size32: 32'u32
|
||||||
of size64: 64'u32
|
of size64: 64'u32
|
||||||
|
@ -212,7 +212,7 @@ proc setFieldConst(fc: var FieldConst, ctx: ContextRef, wordSize: WordSize, modB
|
||||||
|
|
||||||
fc.wordTy = wordTy
|
fc.wordTy = wordTy
|
||||||
fc.fieldTy = array_t(wordTy, numWords)
|
fc.fieldTy = array_t(wordTy, numWords)
|
||||||
|
|
||||||
case wordSize
|
case wordSize
|
||||||
of size32:
|
of size32:
|
||||||
let m = BigNum[uint32].fromHex(modBits, modulus)
|
let m = BigNum[uint32].fromHex(modBits, modulus)
|
||||||
|
@ -239,13 +239,13 @@ proc init*(
|
||||||
prefix: string, wordSize: WordSize,
|
prefix: string, wordSize: WordSize,
|
||||||
fpBits: uint32, fpMod: string,
|
fpBits: uint32, fpMod: string,
|
||||||
frBits: uint32, frMod: string): CurveMetadata =
|
frBits: uint32, frMod: string): CurveMetadata =
|
||||||
|
|
||||||
result = C(prefix: prefix, wordSize: wordSize)
|
result = C(prefix: prefix, wordSize: wordSize)
|
||||||
result.fp.setFieldConst(ctx, wordSize, fpBits, fpMod)
|
result.fp.setFieldConst(ctx, wordSize, fpBits, fpMod)
|
||||||
result.fr.setFieldConst(ctx, wordSize, frBits, frMod)
|
result.fr.setFieldConst(ctx, wordSize, frBits, frMod)
|
||||||
|
|
||||||
proc genSymbol*(cm: CurveMetadata, opcode: Opcode): string {.inline.} =
|
proc genSymbol*(cm: CurveMetadata, opcode: Opcode): string {.inline.} =
|
||||||
cm.prefix &
|
cm.prefix &
|
||||||
(if cm.wordSize == size32: "32b_" else: "64b_") &
|
(if cm.wordSize == size32: "32b_" else: "64b_") &
|
||||||
$opcode
|
$opcode
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ func getSpareBits*(cm: CurveMetadata, field: Field): uint8 {.inline.} =
|
||||||
# ############################################################
|
# ############################################################
|
||||||
|
|
||||||
# For array access we need to use:
|
# For array access we need to use:
|
||||||
#
|
#
|
||||||
# builder.extractValue(array, index, name)
|
# builder.extractValue(array, index, name)
|
||||||
# builder.insertValue(array, index, value, name)
|
# builder.insertValue(array, index, value, name)
|
||||||
#
|
#
|
||||||
|
|
|
@ -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)
|
|
@ -489,7 +489,7 @@ proc run_EC_mixed_add_impl*(
|
||||||
r_generic.double(a)
|
r_generic.double(a)
|
||||||
r_mixed.madd(a, aAff)
|
r_mixed.madd(a, aAff)
|
||||||
check: bool(r_generic == r_mixed)
|
check: bool(r_generic == r_mixed)
|
||||||
|
|
||||||
# Aliasing test
|
# Aliasing test
|
||||||
r_mixed = a
|
r_mixed = a
|
||||||
r_mixed += aAff
|
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()
|
doAssert bool Q.isInSubgroup(), "Subgroup check issue on " & $EC & " with Q: " & Q.toHex()
|
||||||
|
|
||||||
stdout.write '.'
|
stdout.write '.'
|
||||||
|
|
||||||
stdout.write '\n'
|
stdout.write '\n'
|
||||||
|
|
||||||
test(ec, randZ = false, gen = Uniform)
|
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 = true, gen = HighHammingWeight)
|
||||||
test(ec, randZ = false, gen = Long01Sequence)
|
test(ec, randZ = false, gen = Long01Sequence)
|
||||||
test(ec, randZ = true, gen = Long01Sequence)
|
test(ec, randZ = true, gen = Long01Sequence)
|
||||||
|
|
||||||
echo " [SUCCESS] Test finished with ", inSubgroup, " points in ", G1_or_G2, " subgroup and ",
|
echo " [SUCCESS] Test finished with ", inSubgroup, " points in ", G1_or_G2, " subgroup and ",
|
||||||
offSubgroup, " points on curve but not in subgroup (before cofactor clearing)"
|
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 & ")":
|
test $ec & " batch addition (N=" & $n & ")":
|
||||||
proc test(EC: typedesc, gen: RandomGen) =
|
proc test(EC: typedesc, gen: RandomGen) =
|
||||||
var points = newSeq[ECP_ShortW_Aff[EC.F, EC.G]](n)
|
var points = newSeq[ECP_ShortW_Aff[EC.F, EC.G]](n)
|
||||||
|
|
||||||
for i in 0 ..< n:
|
for i in 0 ..< n:
|
||||||
points[i] = rng.random_point(ECP_ShortW_Aff[EC.F, EC.G], randZ = false, gen)
|
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)
|
var points = newSeq[ECP_ShortW_Aff[EC.F, EC.G]](n)
|
||||||
|
|
||||||
let halfN = n div 2
|
let halfN = n div 2
|
||||||
|
|
||||||
for i in 0 ..< halfN:
|
for i in 0 ..< halfN:
|
||||||
points[i] = rng.random_point(ECP_ShortW_Aff[EC.F, EC.G], randZ = false, gen)
|
points[i] = rng.random_point(ECP_ShortW_Aff[EC.F, EC.G], randZ = false, gen)
|
||||||
|
|
||||||
for i in halfN ..< n:
|
for i in halfN ..< n:
|
||||||
# The special cases test relies on internal knowledge that we sum(points[i], points[i+n/2]
|
# 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])
|
# It should be changed if scheduling change, for example if we sum(points[2*i], points[2*i+1])
|
||||||
|
|
|
@ -10,7 +10,7 @@ import
|
||||||
std/[os, unittest, strutils],
|
std/[os, unittest, strutils],
|
||||||
pkg/jsony,
|
pkg/jsony,
|
||||||
../constantine/blssig_pop_on_bls12381_g2,
|
../constantine/blssig_pop_on_bls12381_g2,
|
||||||
../constantine/math/io/io_bigints,
|
../constantine/platforms/codecs,
|
||||||
../constantine/hashes
|
../constantine/hashes
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -70,7 +70,7 @@ type
|
||||||
proc parseHook*[N: static int](src: string, pos: var int, value: var array[N, byte]) =
|
proc parseHook*[N: static int](src: string, pos: var int, value: var array[N, byte]) =
|
||||||
var str: string
|
var str: string
|
||||||
parseHook(src, pos, str)
|
parseHook(src, pos, str)
|
||||||
str.hexToPaddedByteArray(value, bigEndian)
|
value.paddedFromHex(str, bigEndian)
|
||||||
|
|
||||||
const SkippedTests = [
|
const SkippedTests = [
|
||||||
# By construction, input MUST be 48 bytes, which is enforced at the type-system level.
|
# By construction, input MUST be 48 bytes, which is enforced at the type-system level.
|
||||||
|
|
|
@ -13,7 +13,7 @@ import
|
||||||
../constantine/math/io/io_bigints,
|
../constantine/math/io/io_bigints,
|
||||||
../constantine/math/config/curves,
|
../constantine/math/config/curves,
|
||||||
../constantine/math/arithmetic/bigints,
|
../constantine/math/arithmetic/bigints,
|
||||||
../constantine/platforms/abstractions
|
../constantine/platforms/[abstractions, codecs]
|
||||||
|
|
||||||
type SecretKey = matchingOrderBigInt(BLS12_381)
|
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'}))
|
let length = hex.len shr 1 - int(hex[0] == '0' and (hex[1] in {'x', 'X'}))
|
||||||
|
|
||||||
result.newSeq(length)
|
result.newSeq(length)
|
||||||
hex.hexToPaddedByteArray(result, bigEndian)
|
result.paddedFromHex(hex, bigEndian)
|
||||||
|
|
||||||
proc test0 =
|
proc test0 =
|
||||||
let seed = toBytes"0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
|
let seed = toBytes"0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import
|
||||||
# 3rd party
|
# 3rd party
|
||||||
pkg/jsony,
|
pkg/jsony,
|
||||||
# Internals
|
# Internals
|
||||||
../constantine/math/io/io_bigints,
|
../constantine/platforms/codecs,
|
||||||
../constantine/ethereum_evm_precompiles
|
../constantine/ethereum_evm_precompiles
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -48,7 +48,7 @@ template runBN256Tests(filename: string, funcname: untyped, osize: static int) =
|
||||||
|
|
||||||
# Length: 2 hex characters -> 1 byte
|
# Length: 2 hex characters -> 1 byte
|
||||||
var inputbytes = newSeq[byte](test.Input.len div 2)
|
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 r: array[osize, byte]
|
||||||
var expected: 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:
|
if status != cttEVM_Success:
|
||||||
reset(r)
|
reset(r)
|
||||||
|
|
||||||
test.Expected.hexToPaddedByteArray(expected, bigEndian)
|
expected.paddedFromHex(test.Expected, bigEndian)
|
||||||
|
|
||||||
doAssert r == expected, "[Test Failure]\n" &
|
doAssert r == expected, "[Test Failure]\n" &
|
||||||
" " & funcname.astToStr & " status: " & $status & "\n" &
|
" " & funcname.astToStr & " status: " & $status & "\n" &
|
||||||
" " & "result: " & r.toHex() & "\n" &
|
" " & "result: " & r.toHex() & "\n" &
|
||||||
" " & "expected: " & expected.toHex() & '\n'
|
" " & "expected: " & expected.toHex() & '\n'
|
||||||
|
|
||||||
stdout.write "Success\n"
|
stdout.write "Success\n"
|
||||||
|
|
||||||
`bn256testrunner _ funcname`()
|
`bn256testrunner _ funcname`()
|
||||||
|
|
||||||
runBN256Tests("bn256Add.json", eth_evm_ecadd, 64)
|
runBN256Tests("bn256Add.json", eth_evm_ecadd, 64)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import
|
import
|
||||||
# Internals
|
# Internals
|
||||||
../constantine/hashes,
|
../constantine/hashes,
|
||||||
../constantine/math/io/io_bigints,
|
../constantine/platforms/codecs,
|
||||||
# Helpers
|
# Helpers
|
||||||
../helpers/prng_unsafe
|
../helpers/prng_unsafe
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ proc main() =
|
||||||
rng.innerTest(1_000_000 ..< 50_000_000)
|
rng.innerTest(1_000_000 ..< 50_000_000)
|
||||||
|
|
||||||
echo "SHA256 - Differential testing vs OpenSSL - SUCCESS"
|
echo "SHA256 - Differential testing vs OpenSSL - SUCCESS"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
echo "SHA256 - Differential testing vs OpenSSL - [SKIPPED]"
|
echo "SHA256 - Differential testing vs OpenSSL - [SKIPPED]"
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ import
|
||||||
../constantine/hash_to_curve/h2c_hash_to_field,
|
../constantine/hash_to_curve/h2c_hash_to_field,
|
||||||
../constantine/math/config/[curves_declaration, type_ff],
|
../constantine/math/config/[curves_declaration, type_ff],
|
||||||
../constantine/math/extension_fields/towers,
|
../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
|
# Test vectors for expandMessageXMD
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
../constantine/math/io/io_bigints,
|
../constantine/platforms/codecs,
|
||||||
../constantine/[hashes, mac/mac_hmac, kdf/kdf_hkdf]
|
../constantine/[hashes, mac/mac_hmac, kdf/kdf_hkdf]
|
||||||
|
|
||||||
proc hexToBytes(s: string): seq[byte] =
|
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'))
|
(int(s[1] == 'x') or int(s[1] == 'X'))
|
||||||
)
|
)
|
||||||
result.setLen((s.len - skip) div 2)
|
result.setLen((s.len - skip) div 2)
|
||||||
s.hexToPaddedByteArray(result, bigEndian)
|
result.paddedFromHex(s, bigEndian)
|
||||||
|
|
||||||
template test(id, constants: untyped) =
|
template test(id, constants: untyped) =
|
||||||
proc `test _ id`() =
|
proc `test _ id`() =
|
||||||
|
|
|
@ -10,7 +10,7 @@ import
|
||||||
std/unittest,
|
std/unittest,
|
||||||
../constantine/mac/mac_hmac,
|
../constantine/mac/mac_hmac,
|
||||||
../constantine/hashes,
|
../constantine/hashes,
|
||||||
../constantine/math/io/io_bigints
|
../constantine/platforms/codecs
|
||||||
|
|
||||||
type TestVector = object
|
type TestVector = object
|
||||||
key: seq[byte]
|
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 (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 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)"
|
doAssert digest.len <= 64, "HMAC-SHA256 hex string must be at most length 64 (32 bytes)"
|
||||||
|
|
||||||
tv.key.newSeq(key.len div 2)
|
tv.key.newSeq(key.len div 2)
|
||||||
key.hexToPaddedByteArray(tv.key, bigEndian)
|
tv.key.paddedFromHex(key, bigEndian)
|
||||||
|
|
||||||
tv.data.newSeq(data.len div 2)
|
tv.data.newSeq(data.len div 2)
|
||||||
data.hexToPaddedByteArray(tv.data, bigEndian)
|
tv.data.paddedFromHex(data, bigEndian)
|
||||||
|
|
||||||
tv.truncatedLen = digest.len div 2
|
tv.truncatedLen = digest.len div 2
|
||||||
digest.hexToPaddedByteArray(tv.digest, bigEndian)
|
tv.digest.paddedFromHex(digest, bigEndian)
|
||||||
|
|
||||||
var output{.noInit.}: array[32, byte]
|
var output{.noInit.}: array[32, byte]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue