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.
|
||||
|
||||
import
|
||||
../../platforms/abstractions,
|
||||
../../platforms/[abstractions, signed_secret_words],
|
||||
./limbs, ./limbs_unsaturated
|
||||
|
||||
# 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).
|
||||
# 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)
|
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -108,7 +108,7 @@ func wordsRequiredForBits(bits, wordBitwidth: SomeInteger): SomeInteger {.inline
|
|||
|
||||
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
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,7 +57,7 @@ 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" &
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import
|
||||
# Internals
|
||||
../constantine/hashes,
|
||||
../constantine/math/io/io_bigints,
|
||||
../constantine/platforms/codecs,
|
||||
# Helpers
|
||||
../helpers/prng_unsafe
|
||||
|
||||
|
|
|
@ -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
|
||||
# ----------------------------------------------------------------------
|
||||
|
|
|
@ -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`() =
|
||||
|
|
|
@ -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]
|
||||
|
@ -27,13 +27,13 @@ proc doTest(key, data, digest: string) =
|
|||
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]
|
||||
|
||||
|
|
Loading…
Reference in New Issue