Add constant-time raw bytes/integers parsing
This commit is contained in:
parent
b496f57c68
commit
43ac4972a0
|
@ -43,7 +43,7 @@ type Word* = Ct[uint64]
|
||||||
const WordBitSize* = sizeof(Word) * 8 - 1
|
const WordBitSize* = sizeof(Word) * 8 - 1
|
||||||
## Limbs are 63-bit by default
|
## Limbs are 63-bit by default
|
||||||
|
|
||||||
func words_required(bits: static int): static int =
|
func words_required(bits: int): int {.compileTime.}=
|
||||||
(bits + WordBitSize - 1) div WordBitSize
|
(bits + WordBitSize - 1) div WordBitSize
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -59,6 +59,9 @@ const MaxWord* = (not Ct[uint64](0)) shr 1
|
||||||
template `[]`*(a: Bigint, idx: int): Word =
|
template `[]`*(a: Bigint, idx: int): Word =
|
||||||
a.limbs[idx]
|
a.limbs[idx]
|
||||||
|
|
||||||
|
template `[]=`*(a: var Bigint, idx: int, w: Word) =
|
||||||
|
a.limbs[idx] = w
|
||||||
|
|
||||||
# ############################################################
|
# ############################################################
|
||||||
#
|
#
|
||||||
# BigInt primitives
|
# BigInt primitives
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
# Constantine
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# TODO ⚠️:
|
||||||
|
# - Constant-time validation for parsing secret keys
|
||||||
|
# - Burning memory to ensure secrets are not left after dealloc.
|
||||||
|
|
||||||
|
import ./word_types, ./bigints
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
#
|
||||||
|
# Constant-time hex to byte conversion
|
||||||
|
#
|
||||||
|
# ############################################################
|
||||||
|
|
||||||
|
func readHexChar(c: char): uint8 {.inline.}=
|
||||||
|
## Converts an hex char to an int
|
||||||
|
## CT: leaks position of invalid input if any.
|
||||||
|
case c
|
||||||
|
of '0'..'9': result = uint8 ord(c) - ord('0')
|
||||||
|
of 'a'..'f': result = uint8 ord(c) - ord('a') + 10
|
||||||
|
of 'A'..'F': result = uint8 ord(c) - ord('A') + 10
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, $c & "is not a hexadecimal character")
|
||||||
|
|
||||||
|
func skipPrefixes(current_idx: var int, str: string, radix: static range[2..16]) {.inline.} =
|
||||||
|
## Returns the index of the first meaningful char in `hexStr` by skipping
|
||||||
|
## "0x" prefix
|
||||||
|
## CT:
|
||||||
|
## - leaks if input length < 2
|
||||||
|
## - leaks if input start with 0x, 0o or 0b prefix
|
||||||
|
|
||||||
|
if str.len < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert current_idx == 0, "skipPrefixes only works for prefixes (position 0 and 1 of the string)"
|
||||||
|
if str[0] == '0':
|
||||||
|
case str[1]
|
||||||
|
of {'x', 'X'}:
|
||||||
|
assert radix == 16, "Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16)"
|
||||||
|
current_idx = 2
|
||||||
|
of {'o', 'O'}:
|
||||||
|
assert radix == 8, "Parsing mismatch, 0o prefix is only valid for an octal number (base 8)"
|
||||||
|
current_idx = 2
|
||||||
|
of {'b', 'B'}:
|
||||||
|
assert radix == 2, "Parsing mismatch, 0b prefix is only valid for a binary number (base 2)"
|
||||||
|
current_idx = 2
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
func nextNonBlank(current_idx: var int, s: string) {.inline.} =
|
||||||
|
## Move the current index, skipping white spaces and "_" characters.
|
||||||
|
## CT:
|
||||||
|
## - Leaks white-spaces and non-white spaces position
|
||||||
|
|
||||||
|
const blanks = {' ', '_'}
|
||||||
|
|
||||||
|
inc current_idx
|
||||||
|
while current_idx < s.len and s[current_idx] in blanks:
|
||||||
|
inc current_idx
|
||||||
|
|
||||||
|
func readDecChar(c: range['0'..'9']): int {.inline.}=
|
||||||
|
## Converts a decimal char to an int
|
||||||
|
# specialization without branching for base <= 10.
|
||||||
|
ord(c) - ord('0')
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
#
|
||||||
|
# Parsing from canonical inputs to internal representation
|
||||||
|
#
|
||||||
|
# ############################################################
|
||||||
|
|
||||||
|
func parseRawUint*(
|
||||||
|
input: openarray[byte],
|
||||||
|
bits: static int,
|
||||||
|
endian: static Endianness): BigInt[bits] =
|
||||||
|
## Parse an unsigned integer from its canonical
|
||||||
|
## big-endian or little-endian unsigned representation
|
||||||
|
## And store it into a BigInt of size bits
|
||||||
|
##
|
||||||
|
## CT:
|
||||||
|
## - no leaks
|
||||||
|
|
||||||
|
var
|
||||||
|
dst_idx = 0
|
||||||
|
acc = Word(0)
|
||||||
|
acc_len = 0
|
||||||
|
|
||||||
|
template body(){.dirty.} =
|
||||||
|
let src_byte = Word(input[src_idx])
|
||||||
|
|
||||||
|
acc = acc and (src_byte shl acc_len)
|
||||||
|
acc_len += 8 # We count bit by bit
|
||||||
|
|
||||||
|
if acc_len >= WordBitSize:
|
||||||
|
result[dst_idx] = acc and MaxWord
|
||||||
|
inc dst_idx
|
||||||
|
acc_len -= WordBitSize
|
||||||
|
acc = src_byte shr (8 - acc_len)
|
||||||
|
|
||||||
|
when endian == bigEndian:
|
||||||
|
for src_idx in countdown(input.high, 0):
|
||||||
|
body()
|
||||||
|
else:
|
||||||
|
for src_idx in 0 ..< input.len:
|
||||||
|
body()
|
||||||
|
|
||||||
|
if acc_len != 0:
|
||||||
|
result[dst_idx] = acc
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
#
|
||||||
|
# Serialising from internal representation to canonical format
|
||||||
|
#
|
||||||
|
# ############################################################
|
|
@ -0,0 +1,31 @@
|
||||||
|
# constantine
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# 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 unittest,
|
||||||
|
../constantine/[io, bigints]
|
||||||
|
|
||||||
|
type T = uint64
|
||||||
|
|
||||||
|
suite "IO":
|
||||||
|
test "Parsing raw integers":
|
||||||
|
block: # Sanity check
|
||||||
|
let x = 0'u64
|
||||||
|
let x_bytes = cast[array[8, byte]](x)
|
||||||
|
let big = parseRawUint(x_bytes, 64, cpuEndian)
|
||||||
|
|
||||||
|
check:
|
||||||
|
T(big[0]) == 0
|
||||||
|
T(big[1]) == 0
|
||||||
|
|
||||||
|
block: # 2^63 is properly represented on 2 limbs
|
||||||
|
let x = 1'u64 shl 63
|
||||||
|
let x_bytes = cast[array[8, byte]](x)
|
||||||
|
let big = parseRawUint(x_bytes, 64, cpuEndian)
|
||||||
|
|
||||||
|
check:
|
||||||
|
T(big[0]) == 0
|
||||||
|
T(big[1]) == 1
|
Loading…
Reference in New Issue