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

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

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

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

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

View File

@ -1,7 +1,7 @@
import
# Internals
../constantine/hashes,
../constantine/math/io/io_bigints,
../constantine/platforms/codecs,
# Helpers
../helpers/prng_unsafe

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