Add hex conversion
This commit is contained in:
parent
007c5e3071
commit
cadd81b978
|
@ -155,7 +155,7 @@ template shiftAddImpl(a: var Fp, c: Word) =
|
|||
# if carry > hi (negative result) we must do "a+= p"
|
||||
|
||||
let neg = carry < hi
|
||||
let tooBig = not over and (over_p or (carry < hi))
|
||||
let tooBig = not neg and (over_p or (carry < hi))
|
||||
|
||||
add(a, Fp.P, neg)
|
||||
sub(a, Fp.P, tooBig)
|
||||
|
|
|
@ -15,7 +15,151 @@ import
|
|||
|
||||
# ############################################################
|
||||
#
|
||||
# Constant-time hex to byte conversion
|
||||
# Parsing from canonical inputs to internal representation
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
func parseRawUintLE(
|
||||
src: openarray[byte],
|
||||
bits: static int): BigInt[bits] {.inline.}=
|
||||
## Parse an unsigned integer from its canonical
|
||||
## 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
|
||||
|
||||
for src_idx in 0 ..< src.len:
|
||||
let src_byte = Word(src[src_idx])
|
||||
|
||||
# buffer reads
|
||||
acc = acc or (src_byte shl acc_len)
|
||||
acc_len += 8 # We count bit by bit
|
||||
|
||||
# if full, dump
|
||||
if acc_len >= WordBitSize:
|
||||
result[dst_idx] = acc and MaxWord
|
||||
inc dst_idx
|
||||
acc_len -= WordBitSize
|
||||
acc = src_byte shr (8 - acc_len)
|
||||
|
||||
if acc_len != 0:
|
||||
result[dst_idx] = acc
|
||||
|
||||
func parseRawUint*(
|
||||
src: openarray[byte],
|
||||
bits: static int,
|
||||
srcEndianness: 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
|
||||
|
||||
when srcEndianness == littleEndian:
|
||||
parseRawUintLE(src, bits)
|
||||
else:
|
||||
{.error: "Not implemented at the moment".}
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Serialising from internal representation to canonical format
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
template bigEndianXX[T: uint16 or uint32 or uint64](outp: pointer, inp: ptr T) =
|
||||
when T is uint64:
|
||||
bigEndian64(outp, inp)
|
||||
elif T is uint32:
|
||||
bigEndian32(outp, inp)
|
||||
elif T is uint16:
|
||||
bigEndian16(outp, inp)
|
||||
|
||||
template littleEndianXX[T: uint16 or uint32 or uint64](outp: pointer, inp: ptr T) =
|
||||
when T is uint64:
|
||||
littleEndian64(outp, inp)
|
||||
elif T is uint32:
|
||||
littleEndian32(outp, inp)
|
||||
elif T is uint16:
|
||||
littleEndian16(outp, inp)
|
||||
|
||||
func dumpRawUintLE(
|
||||
dst: var openarray[byte],
|
||||
src: BigInt) {.inline.}=
|
||||
## Serialize a bigint into its canonical little-endian representation
|
||||
## I.e least significant bit is aligned to buffer boundary
|
||||
|
||||
var
|
||||
src_idx, dst_idx = 0
|
||||
acc: BaseType = 0
|
||||
acc_len = 0
|
||||
|
||||
var tail = dst.len
|
||||
while tail > 0:
|
||||
let w = if src_idx < src.limbs.len: src[src_idx].BaseType
|
||||
else: 0
|
||||
inc src_idx
|
||||
|
||||
if acc_len == 0:
|
||||
# Edge case, we need to refill the buffer to output 64-bit
|
||||
# as we can only read 63-bit per word
|
||||
acc = w
|
||||
acc_len = WordBitSize
|
||||
else:
|
||||
let lo = (w shl acc_len) or acc
|
||||
dec acc_len
|
||||
acc = w shr (WordBitSize - acc_len)
|
||||
|
||||
if tail >= sizeof(Word):
|
||||
# Unrolled copy
|
||||
# debugecho src.repr
|
||||
littleEndianXX(dst[dst_idx].addr, lo.unsafeAddr)
|
||||
dst_idx += sizeof(Word)
|
||||
tail -= sizeof(Word)
|
||||
else:
|
||||
# Process the tail
|
||||
when cpuEndian == littleEndian:
|
||||
# When requesting little-endian on little-endian platform
|
||||
# we can just copy each byte
|
||||
for i in dst_idx ..< tail:
|
||||
dst[dst_idx] = byte(lo shr (i-dst_idx))
|
||||
else:
|
||||
# We need to copy from the end
|
||||
for i in 0 ..< tail:
|
||||
dst[dst_idx] = byte(lo shr (tail-i))
|
||||
|
||||
func dumpRawUint*(
|
||||
dst: var openarray[byte],
|
||||
src: BigInt,
|
||||
dstEndianness: static Endianness) =
|
||||
## Serialize a bigint into its canonical big-endian or little endian
|
||||
## representation.
|
||||
## A destination buffer of size "BigInt.bits div 8" at minimum is needed.
|
||||
##
|
||||
## If the buffer is bigger, output will be zero-padded left for big-endian
|
||||
## or zero-padded right for little-endian.
|
||||
## I.e least significant bit is aligned to buffer boundary
|
||||
|
||||
if dst.len < static(BigInt.bits div 8):
|
||||
raise newException(ValueError, "BigInt -> Raw int conversion: destination buffer is too small")
|
||||
|
||||
when BigInt.bits == 0:
|
||||
zeroMem(dst, dst.len)
|
||||
|
||||
when dstEndianness == littleEndian:
|
||||
dumpRawUintLE(dst, src)
|
||||
else:
|
||||
{.error: "Not implemented at the moment".}
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Conversion helpers
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
|
@ -69,146 +213,96 @@ func readDecChar(c: range['0'..'9']): int {.inline.}=
|
|||
# specialization without branching for base <= 10.
|
||||
ord(c) - ord('0')
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Parsing from canonical inputs to internal representation
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
func parseRawUintLE(
|
||||
src: openarray[byte],
|
||||
bits: static int): BigInt[bits] {.inline.}=
|
||||
## Parse an unsigned integer from its canonical
|
||||
## little-endian unsigned representation
|
||||
## And store it into a BigInt of size bits
|
||||
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.
|
||||
##
|
||||
## CT:
|
||||
## - no leaks
|
||||
|
||||
## The source string must be hex big-endian.
|
||||
## The destination array can beb ig or little endian
|
||||
let maxStrSize = output.len * 2
|
||||
var
|
||||
dst_idx = 0
|
||||
acc = Word(0)
|
||||
acc_len = 0
|
||||
skip = 0
|
||||
dstIdx: int
|
||||
shift = 4
|
||||
skipPrefixes(skip, hexStr, 16)
|
||||
let size = hexStr.len - skip
|
||||
|
||||
for src_idx in 0 ..< src.len:
|
||||
let src_byte = Word(src[src_idx])
|
||||
doAssert size <= maxStrSize
|
||||
|
||||
# buffer reads
|
||||
acc = acc or (src_byte shl acc_len)
|
||||
acc_len += 8 # We count bit by bit
|
||||
if size < maxStrSize:
|
||||
# include extra byte if odd length
|
||||
dstIdx = output.len - (size + 1) div 2
|
||||
# start with shl of 4 if length is even
|
||||
shift = 4 - size mod 2 * 4
|
||||
|
||||
# if full, dump
|
||||
if acc_len >= WordBitSize:
|
||||
result[dst_idx] = acc and MaxWord
|
||||
inc dst_idx
|
||||
acc_len -= WordBitSize
|
||||
acc = src_byte shr (8 - acc_len)
|
||||
|
||||
if acc_len != 0:
|
||||
result[dst_idx] = acc
|
||||
|
||||
func parseRawUint*(
|
||||
src: openarray[byte],
|
||||
bits: static int,
|
||||
order: 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
|
||||
|
||||
when order == littleEndian:
|
||||
parseRawUintLE(src, bits)
|
||||
else:
|
||||
{.error: "Not implemented at the moment".}
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Serialising from internal representation to canonical format
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
template bigEndianXX[T: uint16 or uint32 or uint64](outp: pointer, inp: ptr T) =
|
||||
when T is uint64:
|
||||
bigEndian64(outp, inp)
|
||||
elif T is uint32:
|
||||
bigEndian32(outp, inp)
|
||||
elif T is uint16:
|
||||
bigEndian16(outp, inp)
|
||||
|
||||
template littleEndianXX[T: uint16 or uint32 or uint64](outp: pointer, inp: ptr T) =
|
||||
when T is uint64:
|
||||
littleEndian64(outp, inp)
|
||||
elif T is uint32:
|
||||
littleEndian32(outp, inp)
|
||||
elif T is uint16:
|
||||
littleEndian16(outp, inp)
|
||||
|
||||
func dumpRawUintLE(
|
||||
dst: var openarray[byte],
|
||||
src: BigInt) {.inline.}=
|
||||
## Serialize a bigint into its canonical big-endian representation
|
||||
## I.e least significant bit is aligned to buffer boundary
|
||||
|
||||
var
|
||||
src_idx, dst_idx = 0
|
||||
acc: BaseType = 0
|
||||
acc_len = 0
|
||||
|
||||
var tail = dst.len
|
||||
while tail > 0:
|
||||
let w = if src_idx < src.limbs.len: src[src_idx].BaseType
|
||||
else: 0
|
||||
inc src_idx
|
||||
|
||||
if acc_len == 0:
|
||||
# Edge case, we need to refill the buffer to output 64-bit
|
||||
# as we can only read 63-bit per word
|
||||
acc = w
|
||||
acc_len = WordBitSize
|
||||
for srcIdx in skip ..< hexStr.len:
|
||||
let nibble = hexStr[srcIdx].readHexChar shl shift
|
||||
when order == bigEndian:
|
||||
output[dstIdx] = output[dstIdx] or nibble
|
||||
else:
|
||||
let lo = (w shl acc_len) or acc
|
||||
dec acc_len
|
||||
acc = w shr (WordBitSize - acc_len)
|
||||
output[output.high - dstIdx] = output[output.high - dstIdx] or nibble
|
||||
shift = (shift + 4) and 4
|
||||
dstIdx += shift shr 2
|
||||
|
||||
if tail >= sizeof(Word):
|
||||
# Unrolled copy
|
||||
# debugecho src.repr
|
||||
littleEndianXX(dst[dst_idx].addr, lo.unsafeAddr)
|
||||
dst_idx += sizeof(Word)
|
||||
tail -= sizeof(Word)
|
||||
else:
|
||||
# Process the tail
|
||||
when cpuEndian == littleEndian:
|
||||
# When requesting little-endian on little-endian platform
|
||||
# we can just copy each byte
|
||||
for i in dst_idx ..< tail:
|
||||
dst[dst_idx] = byte(lo shr (i-dst_idx))
|
||||
else:
|
||||
# We need to copy from the end
|
||||
for i in 0 ..< tail:
|
||||
dst[dst_idx] = byte(lo shr (tail-i))
|
||||
func toHex(bytes: openarray[byte], order: static[Endianness]): string =
|
||||
## Convert a byte-array to its hex representation
|
||||
## Output is in lowercase and not prefixed.
|
||||
const hexChars = "0123456789abcdef"
|
||||
|
||||
func dumpRawUint*(
|
||||
dst: var openarray[byte],
|
||||
src: BigInt,
|
||||
order: static Endianness) =
|
||||
## Serialize a bigint into its canonical big-endian or little endian
|
||||
## representation.
|
||||
## A destination buffer of size "BigInt.bits div 8" at minimum is needed.
|
||||
result = newString(2 * bytes.len)
|
||||
for i in 0 ..< bytes.len:
|
||||
when order == system.cpuEndian:
|
||||
result[2*i] = hexChars[int bytes[i] shr 4 and 0xF]
|
||||
result[2*i+1] = hexChars[int bytes[i] and 0xF]
|
||||
else:
|
||||
result[2*i] = hexChars[int bytes[bytes.high - i] shr 4 and 0xF]
|
||||
result[2*i+1] = hexChars[int bytes[bytes.high - i] and 0xF]
|
||||
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Hex conversion
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
func fromHex*(T: type BigInt, s: string): T =
|
||||
## Convert a hex string to BigInt that can hold
|
||||
## the specified number of bits
|
||||
##
|
||||
## If the buffer is bigger, output will be zero-padded left for big-endian
|
||||
## or zero-padded right for little-endian.
|
||||
## I.e least significant bit is aligned to buffer boundary
|
||||
## For example `fromHex(BigInt[256], "0x123456")`
|
||||
##
|
||||
## Hex string is assumed big-endian
|
||||
|
||||
if dst.len < static(BigInt.bits div 8):
|
||||
raise newException(ValueError, "BigInt -> Raw int conversion: destination buffer is too small")
|
||||
# 1. Convert to canonical uint
|
||||
const canonLen = (T.bits + 8 - 1) div 8
|
||||
var bytes: array[canonLen, byte]
|
||||
hexToPaddedByteArray(s, bytes, littleEndian)
|
||||
|
||||
when BigInt.bits == 0:
|
||||
zeroMem(dst, dst.len)
|
||||
# 2. Convert canonical uint to Big Int
|
||||
result = parseRawUint(bytes, T.bits, littleEndian)
|
||||
|
||||
func dumpHex*(big: BigInt, order: static Endianness = bigEndian): string =
|
||||
## Stringify an int to hex.
|
||||
## Note. Leading zeros are not removed.
|
||||
##
|
||||
## This is a raw memory dump. Output will be padded with 0
|
||||
## if the big int does not use the full memory allocated for it.
|
||||
##
|
||||
## Regardless of the machine endianness the output will be big-endian hex.
|
||||
##
|
||||
## For example a BigInt representing 10 will be
|
||||
## - 0x0A for BigInt[8]
|
||||
## - 0x000A for BigInt[16]
|
||||
## - 0x00000000_0000000A for BigInt[64]
|
||||
##
|
||||
## CT:
|
||||
## - no leaks
|
||||
|
||||
# 1. Convert Big Int to canonical uint
|
||||
const canonLen = (big.bits + 8 - 1) div 8
|
||||
var bytes: array[canonLen, byte]
|
||||
dumpRawUint(bytes, big, cpuEndian)
|
||||
|
||||
# 2 Convert canonical uint to hex
|
||||
result = bytes.toHex(order)
|
||||
|
||||
when order == littleEndian:
|
||||
dumpRawUintLE(dst, src)
|
||||
else:
|
||||
{.error: "Not implemented at the moment".}
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
test_word_types
|
||||
test_word_types,
|
||||
test_io
|
||||
|
|
|
@ -60,3 +60,25 @@ suite "IO":
|
|||
var r_bytes: array[8, byte]
|
||||
dumpRawUint(r_bytes, big, littleEndian)
|
||||
check: x_bytes == r_bytes
|
||||
|
||||
test "Round trip on elliptic curve constants":
|
||||
block: # Secp256k1 - https://en.bitcoin.it/wiki/Secp256k1
|
||||
const p = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"
|
||||
let x = fromHex(BigInt[256], p)
|
||||
let hex = x.dumpHex(bigEndian)
|
||||
|
||||
check: p == hex
|
||||
|
||||
block: # alt-BN128 - https://github.com/ethereum/py_ecc/blob/master/py_ecc/fields/field_properties.py
|
||||
const p = "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47"
|
||||
let x = fromHex(BigInt[254], p)
|
||||
let hex = x.dumpHex(bigEndian)
|
||||
|
||||
check: p == hex
|
||||
|
||||
block: # BLS12-381 - https://github.com/ethereum/py_ecc/blob/master/py_ecc/fields/field_properties.py
|
||||
const p = "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab"
|
||||
let x = fromHex(BigInt[381], p)
|
||||
let hex = x.dumpHex(bigEndian)
|
||||
|
||||
check: p == hex
|
||||
|
|
Loading…
Reference in New Issue