Relaxed construction from BE bytes (#57)
* Support automatic padding when constructing values from byte arrays * add a test case for fromBytesBE
This commit is contained in:
parent
6ae8de932d
commit
f05feeb865
61
stint/io.nim
61
stint/io.nim
|
@ -300,6 +300,53 @@ func dumpHex*(x: Stint or StUint): string {.inline.}=
|
||||||
# TODO: Have a default static argument in the previous proc. Currently we get
|
# TODO: Have a default static argument in the previous proc. Currently we get
|
||||||
# "Cannot evaluate at compile-time".
|
# "Cannot evaluate at compile-time".
|
||||||
|
|
||||||
|
proc initFromBytesBE*[bits: static[int]](val: var Stuint[bits], ba: openarray[byte], allowPadding: static[bool] = true) =
|
||||||
|
## Initializes a UInt[bits] value from a byte buffer storing a big-endian
|
||||||
|
## representation of a number.
|
||||||
|
##
|
||||||
|
## If `allowPadding` is set to false, the input array must be exactly
|
||||||
|
## (bits div 8) bytes long. Otherwise, it may be shorter and the remaining
|
||||||
|
## bytes will be assumed to be zero.
|
||||||
|
|
||||||
|
const N = bits div 8
|
||||||
|
|
||||||
|
when not allowPadding:
|
||||||
|
assert(ba.len == N)
|
||||||
|
else:
|
||||||
|
assert ba.len <= N
|
||||||
|
|
||||||
|
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
||||||
|
let r_ptr {.restrict.} = cast[ptr array[N, byte]](val.addr)
|
||||||
|
|
||||||
|
when system.cpuEndian == bigEndian:
|
||||||
|
# TODO: due to https://github.com/status-im/nim-stint/issues/38
|
||||||
|
# We can't cast a stack byte array to stuint with a convenient proc signature.
|
||||||
|
when allowPadding:
|
||||||
|
let baseIdx = N - val.len
|
||||||
|
for i, b in ba: r_ptr[baseIdx + i] = b
|
||||||
|
else:
|
||||||
|
for i, b in ba: r_ptr[i] = b
|
||||||
|
else:
|
||||||
|
when allowPadding:
|
||||||
|
let baseIdx = ba.len - 1
|
||||||
|
for i, b in ba: r_ptr[baseIdx - i] = b
|
||||||
|
else:
|
||||||
|
for i, b in ba: r_ptr[N-1 - i] = b
|
||||||
|
|
||||||
|
func significantBytesBE*(val: openarray[byte]): int =
|
||||||
|
## Returns the number of significant trailing bytes in a big endian
|
||||||
|
## representation of a number.
|
||||||
|
for i in 0 ..< val.len:
|
||||||
|
if val[i] != 0:
|
||||||
|
return val.len - i
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
func fromBytesBE*(T: type Stuint, ba: openarray[byte],
|
||||||
|
allowPadding: static[bool] = true): T =
|
||||||
|
## This function provides a convenience wrapper around `initFromBytesBE`.
|
||||||
|
result.initFromBytesBE(ba, allowPadding)
|
||||||
|
|
||||||
func readUintBE*[bits: static[int]](ba: openarray[byte]): Stuint[bits] =
|
func readUintBE*[bits: static[int]](ba: openarray[byte]): Stuint[bits] =
|
||||||
## Convert a big-endian array of (bits div 8) Bytes to an UInt[bits] (in native host endianness)
|
## Convert a big-endian array of (bits div 8) Bytes to an UInt[bits] (in native host endianness)
|
||||||
## Input:
|
## Input:
|
||||||
|
@ -308,19 +355,7 @@ func readUintBE*[bits: static[int]](ba: openarray[byte]): Stuint[bits] =
|
||||||
## - A unsigned integer of the same size with `bits` bits
|
## - A unsigned integer of the same size with `bits` bits
|
||||||
##
|
##
|
||||||
## ⚠ If the openarray length is bigger than bits div 8, part converted is undefined behaviour.
|
## ⚠ If the openarray length is bigger than bits div 8, part converted is undefined behaviour.
|
||||||
|
result.initFromBytesBE(ba, false)
|
||||||
const N = bits div 8
|
|
||||||
assert(ba.len == N)
|
|
||||||
|
|
||||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
|
||||||
let r_ptr {.restrict.} = cast[ptr array[N, byte]](result.addr)
|
|
||||||
for i, val in ba:
|
|
||||||
when system.cpuEndian == bigEndian:
|
|
||||||
# TODO: due to https://github.com/status-im/nim-stint/issues/38
|
|
||||||
# We can't cast a stack byte array to stuint with a convenient proc signature.
|
|
||||||
r_ptr[i] = val
|
|
||||||
else:
|
|
||||||
r_ptr[N-1 - i] = val
|
|
||||||
|
|
||||||
func toByteArrayBE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte] =
|
func toByteArrayBE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte] =
|
||||||
## Convert a uint[bits] to to a big-endian array of bits div 8 bytes
|
## Convert a uint[bits] to to a big-endian array of bits div 8 bytes
|
||||||
|
|
|
@ -133,6 +133,18 @@ suite "Testing conversion functions: Hex, Bytes, Endianness using secp256k1 curv
|
||||||
test "hex -> big-endian array -> uint256":
|
test "hex -> big-endian array -> uint256":
|
||||||
check: readUintBE[256](SECPK1_N_BYTES) == SECPK1_N
|
check: readUintBE[256](SECPK1_N_BYTES) == SECPK1_N
|
||||||
|
|
||||||
|
test "uint256 -> minimal big-endian array -> uint256":
|
||||||
|
# test drive the conversion logic by testing the first 25 factorials:
|
||||||
|
var f = 1.stuint(256)
|
||||||
|
for i in 2 .. 25:
|
||||||
|
f = f * i.stuint(256)
|
||||||
|
let
|
||||||
|
bytes = f.toByteArrayBE
|
||||||
|
nonZeroBytes = significantBytesBE(bytes)
|
||||||
|
fRestored = Uint256.fromBytesBE(bytes.toOpenArray(bytes.len - nonZeroBytes,
|
||||||
|
bytes.len - 1))
|
||||||
|
check f == fRestored
|
||||||
|
|
||||||
test "uint256 -> big-endian array -> hex":
|
test "uint256 -> big-endian array -> hex":
|
||||||
check: SECPK1_N.toByteArrayBE == SECPK1_N_BYTES
|
check: SECPK1_N.toByteArrayBE == SECPK1_N_BYTES
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue