* HMAC-SHA256

* EIP2333

* activate EIP2333 tests and faster random test case generation
This commit is contained in:
Mamy Ratsimbazafy 2022-08-16 12:07:57 +02:00 committed by GitHub
parent 9770b3108c
commit 094445482b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 933 additions and 36 deletions

View File

@ -217,6 +217,15 @@ proc reduce2x*(T: typedesc, iters: int) =
bench("Redc 2x", $T & " <- " & $doublePrec(T), iters):
r.redc2x(t)
proc reduce2xViaDivision*(T: typedesc, iters: int) =
const bits2x = 2 * T.C.getCurveBitWidth()
var r: matchingBigInt(T.C)
let t = rng.random_unsafe(BigInt[bits2x])
bench("Reduction via division", $T & " <- " & $doublePrec(T), iters):
r.reduce(t, T.fieldMod())
proc main() =
separator()
sum(Fp[BLS12_381], iters = 10_000_000)
@ -237,6 +246,8 @@ proc main() =
square2xBench(768, 384, iters = 10_000_000)
reduce2x(Fp[BN254_Snarks], iters = 10_000_000)
reduce2x(Fp[BLS12_381], iters = 10_000_000)
reduce2xViaDivision(Fp[BN254_Snarks], iters = 10_000)
reduce2xViaDivision(Fp[BLS12_381], iters = 10_000)
separator()
main()

View File

@ -34,7 +34,7 @@ proc benchPoly1305_constantine[T](msg: openarray[T], msgComment: string, iters:
0x4a, 0xbf, 0xf6, 0xaf, 0x41, 0x49, 0xf5, 0x1b
]
bench("Poly1305 - Constantine - " & msgComment, msg.len, iters):
poly1305.auth(tag, msg, ikm)
poly1305.mac(tag, msg, ikm)
when isMainModule:
proc main() =

View File

@ -205,11 +205,17 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
# Message Authentication Code
# ----------------------------------------------------------
("tests/t_mac_poly1305.nim", false),
("tests/t_mac_hmac_sha256.nim", false),
# KDF
# ----------------------------------------------------------
("tests/t_kdf_hkdf.nim", false),
# Protocols
# ----------------------------------------------------------
("tests/t_ethereum_evm_precompiles.nim", false),
("tests/t_blssig_pop_on_bls12381_g2.nim", false),
("tests/t_ethereum_eip2333_bls12381_key_derivation.nim", false),
]
# For temporary (hopefully) investigation that can only be reproduced in CI
@ -228,7 +234,11 @@ const skipSanitizers = [
"tests/math/t_ec_sage_bls12_381.nim",
"tests/t_hash_to_field.nim",
"tests/t_hash_to_curve.nim",
"tests/t_hash_to_curve_random.nim"
"tests/t_hash_to_curve_random.nim",
"tests/t_mac_poly1305.nim",
"tests/t_mac_hmac.nim",
"tests/t_kdf_hkdf.nim",
"tests/t_ethereum_eip2333_bls12381_key_derivation"
]
when defined(windows):

View File

@ -0,0 +1,177 @@
# 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
./hashes,
./kdf/kdf_hkdf,
./math/config/[curves, type_ff],
./math/arithmetic/[bigints, limbs_montgomery],
./math/io/io_bigints,
./platforms/endians
# EIP2333: BLS12-381 Key Generation
# ------------------------------------------------------------
#
# https://eips.ethereum.org/EIPS/eip-2333
{.push raises: [].} # No exceptions
type SecretKey = matchingOrderBigInt(BLS12_381)
func hkdf_mod_r[T: char|byte](secretKey: var SecretKey, ikm: openArray[byte], key_info: openArray[T]) =
## Ethereum 2 EIP-2333, extracts this from the BLS signature schemes
# 1. salt = "BLS-SIG-KEYGEN-SALT-"
# 2. SK = 0
# 3. while SK == 0:
# 4. salt = H(salt)
# 5. PRK = HKDF-Extract(salt, IKM || I2OSP(0, 1))
# 6. OKM = HKDF-Expand(PRK, key_info || I2OSP(L, 2), L)
# 7. SK = OS2IP(OKM) mod r
# 8. return SK
const salt0 = "BLS-SIG-KEYGEN-SALT-"
var ctx{.noInit.}: HKDF[sha256]
var prk{.noInit.}: array[sha256.digestSize(), byte]
var salt {.noInit.}: array[sha256.digestSize(), byte]
sha256.hash(salt, salt0)
while true:
# 5. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM || I2OSP(0, 1))
ctx.hkdf_extract_init(salt, ikm)
ctx.hkdf_extract_append_to_IKM([byte 0])
ctx.hkdf_extract_finish(prk)
# curve order r = 52435875175126190479447740508185965837690552500527637822603658699938581184513
# const L = ceil((1.5 * ceil(log2(r))) / 8) = 48
# https://www.wolframalpha.com/input/?i=ceil%28%281.5+*+ceil%28log2%2852435875175126190479447740508185965837690552500527637822603658699938581184513%29%29%29+%2F+8%29
# 6. OKM = HKDF-Expand(PRK, key_info || I2OSP(L, 2), L)
const L = 48
var okm{.noInit.}: array[L, byte]
const L_octetstring = L.uint16.toBytesBE()
ctx.hkdfExpand(okm, prk, key_info, append = L_octetstring)
# 7. x = OS2IP(OKM) mod r
# We reduce mod r via Montgomery reduction, instead of bigint division
# as constant-time division works bits by bits (384 bits) while
# Montgomery reduction works word by word, quadratically so 6*6 = 36 on 64-bit CPUs.
# With R ≡ (2^WordBitWidth)^numWords (mod M)
# redc2xMont(a) computes a/R
# mulMont(a, b) computes a.b.R⁻¹
var seckeyDbl{.noInit.}: BigInt[2 * BLS12_381.getCurveOrderBitWidth()]
seckeyDbl.unmarshal(okm, bigEndian)
# secretKey.reduce(seckeyDbl, BLS12_381.getCurveOrder())
secretKey.limbs.redc2xMont(seckeyDbl.limbs, # seckey/R
BLS12_381.getCurveOrder().limbs, Fr[BLS12_381].getNegInvModWord(),
Fr[BLS12_381].getSpareBits())
secretKey.limbs.mulMont(secretKey.limbs, Fr[BLS12_381].getR2modP().limbs, # (seckey/R) * R² * R⁻¹ = seckey
BLS12_381.getCurveOrder().limbs, Fr[BLS12_381].getNegInvModWord(),
Fr[BLS12_381].getSpareBits())
if bool secretKey.isZero():
# Chance of 2⁻²⁵⁶ to happen
sha256.hash(salt, salt)
else:
return
iterator ikm_to_lamport_SK(
lamportSecretKeyChunk: var array[32, byte],
ikm: array[32, byte], salt: array[4, byte]): int =
## Generate a Lamport secret key
## This uses an iterator to stream HKDF
## instead of allocating 255*32 bytes ~= 8KB
var ctx{.noInit.}: HKDF[sha256]
var prk{.noInit.}: array[32, byte]
# 0. PRK = HKDF-Extract(salt, IKM)
ctx.hkdfExtract(prk, salt, ikm)
# 1. OKM = HKDF-Expand(PRK, "" , L)
# with L = K * 255 and K = 32 (sha256 output)
{.push checks: off.} # No OverflowError or IndexError allowed
for i in ctx.hkdfExpandChunk(
lamportSecretKeyChunk,
prk, "",""):
yield i
func parent_SK_to_lamport_PK(
lamportPublicKey: var array[32, byte],
parentSecretKey: SecretKey,
index: uint32) =
## Derives the index'th child's lamport PublicKey
## from the parent SecretKey
# 0. salt = I2OSP(index, 4)
let salt{.noInit.} = index.toBytesBE()
# 1. IKM = I2OSP(parent_SK, 32)
var ikm {.noinit.}: array[32, byte]
ikm.marshal(parentSecretKey, bigEndian)
# Reorganized the spec to save on stack allocations
# by reusing buffers and using streaming HKDF
# 5. lamport_PK = ""
var ctx{.noInit.}: sha256
ctx.init()
var tmp{.noInit.}, chunk{.noInit.}: array[32, byte]
{.push checks: off.} # No OverflowError or IndexError allowed
# 2. lamport_0 = IKM_to_lamport_SK(IKM, salt)
# 6. for i = 1, .., 255 (inclusive)
# lamport_PK = lamport_PK | SHA256(lamport_0[i])
for i in ikm_to_lamport_SK(chunk, ikm, salt):
sha256.hash(tmp, chunk)
ctx.update(tmp)
if i == 254:
# We iterate from 0
break
# 3. not_IKM = flip_bits(parent_SK)
for i in 0 ..< 32:
ikm[i] = not ikm[i]
# 4. lamport_1 = IKM_to_lamport_SK(not_IKM, salt)
# 7. for i = 1, .., 255 (inclusive)
# lamport_PK = lamport_PK | SHA256(lamport_1[i])
for i in ikm_to_lamport_SK(chunk, ikm, salt):
sha256.hash(tmp, chunk)
ctx.update(tmp)
if i == 254:
# We iterate from 0
break
# 8. compressed_lamport_PK = SHA256(lamport_PK)
# 9. return compressed_lamport_PK
ctx.finish(lamportPublicKey)
func derive_child_secretKey*(
childSecretKey: var SecretKey,
parentSecretKey: SecretKey,
index: uint32
): bool =
## EIP2333 Child Key derivation function
var compressed_lamport_PK{.noInit.}: array[32, byte]
# 0. compressed_lamport_PK = parent_SK_to_lamport_PK(parent_SK, index)
parent_SK_to_lamport_PK(
compressed_lamport_PK,
parentSecretKey,
index,
)
childSecretKey.hkdf_mod_r(compressed_lamport_PK, key_info = "")
return true
func derive_master_secretKey*(
masterSecretKey: var SecretKey,
ikm: openArray[byte]
): bool =
## EIP2333 Master key derivation
if ikm.len < 32:
return false
masterSecretKey.hkdf_mod_r(ikm, key_info = "")
return true

View File

@ -116,7 +116,7 @@ func expandMessageXMD*[B1, B2, B3: byte|char, len_in_bytes: static int](
let ell = ceilDiv(output.len.uint, DigestSize.uint)
const zPad = default(array[BlockSize, byte])
var l_i_b_str0 {.noInit.}: array[3, byte]
l_i_b_str0.asBytesBE(output.len.uint16, pos = 0)
l_i_b_str0.dumpRawInt(output.len.uint16, cursor = 0, bigEndian)
l_i_b_str0[2] = 0
var b0 {.noinit, align: DigestSize.}: array[DigestSize, byte]

View File

@ -276,11 +276,11 @@ func hashBuffer(ctx: var Sha256Context) =
# Public API
# ----------------------------------------------------------------
func digestSize*(H: type sha256): int =
template digestSize*(H: type sha256): int =
## Returns the output size in bytes
32
func internalBlockSize*(H: type sha256): int =
template internalBlockSize*(H: type sha256): int =
## Returns the byte size of the hash function ingested blocks
64

View File

@ -0,0 +1,11 @@
# Key Derivation Functions
Note:
We prefix the filename with "kdf" to prevents name collision between a modulename and the function
which leads to confusing error messages
```Nim
# in kdf_hkdf
func hkdf*(...)
```

View File

@ -0,0 +1,200 @@
# 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
../hashes,
../mac/mac_hmac,
../platforms/primitives
# HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
# ------------------------------------------------------------
#
# https://datatracker.ietf.org/doc/html/rfc5869
{.push raises: [].} # No exceptions
type HKDF*[H: CryptoHash] = object
hmac: HMAC[H]
func hkdf_extract_init*[H: CryptoHash, S, I: char|byte](
ctx: var HKDF[H],
salt: openArray[S],
ikm: openArray[I]) {.inline.}=
ctx.hmac.init(salt)
ctx.hmac.update(ikm)
func hkdf_extract_append_to_IKM*[H: CryptoHash, T: char|byte](
ctx: var HKDF[H], append: openArray[T]) {.inline.} =
ctx.hmac.update(append)
func hkdf_extract_finish*[H: CryptoHash, N: static int](
ctx: var HKDF[H], prk: var array[N, byte]) {.inline.} =
## Allows appending to IKM without allocating it on the heap
static: doAssert H.digestSize == N
ctx.hmac.finish(prk)
func hkdfExtract*[H: CryptoHash;S,I: char|byte, N: static int](
ctx: var HKDF[H],
prk: var array[N, byte],
salt: openArray[S],
ikm: openArray[I]) {.inline.} =
## "Extract" step of HKDF.
## Extract a fixed size pseudom-random key
## from an optional salt value
## and a secret input keying material.
##
## Inputs:
## - salt: a buffer to an optional salt value (set to nil if unused)
## - ikm: "input keying material", the secret value to hash.
## If a protocol needs to append to the IKM, it is recommended
## to use the:
## hkdf_extract_init,
## hkdf_extract_append_to_IKM
## hkdf_extract_finish
## to avoid allocating and exposing secrets to the heap.
##
## Output:
## - prk: a pseudo random key of fixed size. The size is the same as the cryptographic hash chosen.
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
static: doAssert N == H.digestSize()
ctx.hkdf_extract_init(salt, ikm)
ctx.hkdf_extract_finish(prk)
iterator hkdfExpandChunk*[H: CryptoHash; N: static int; I, A: char|byte](
ctx: var HKDF[H],
chunk: var array[N, byte],
prk: array[N, byte],
info: openArray[I],
append: openArray[A]): int =
## "Expand" step of HKDF, with an iterator with up to 255 iterations.
##
## Note: The output MUST be at most 255 iterations as per RFC5869
## https://datatracker.ietf.org/doc/html/rfc5869
##
## Expand a fixed size pseudo random-key
## into several pseudo-random keys
##
## Inputs:
## - prk: a pseudo random key (PRK) of fixed size. The size is the same as the cryptographic hash chosen.
## - info: optional context and application specific information (set to nil if unused)
## - append:
## Compared to the spec we add a specific append procedure to do
## OKM = HKDF-Expand(PRK, key_info || I2OSP(L, 2), L)
## without having additional allocation on the heap
## Input/Output:
## - chunk:
## In: OKMᵢ₋₁ (output keying material chunk i-1)
## Out: OKMᵢ (output keying material chunk i).
##
## Output:
## - returns the current chunk number i
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
const HashLen = H.digestSize()
static: doAssert N == HashLen
{.push checks: off.} # No OverflowError or IndexError allowed
for i in 0 ..< 255:
ctx.hmac.init(prk)
# T(0) = empty string
if i != 0:
ctx.hmac.update(chunk)
ctx.hmac.update(info)
ctx.hmac.update(append)
ctx.hmac.update([uint8(i)+1]) # For byte 255, this append "0" and not "256"
ctx.hmac.finish(chunk)
yield i
func hkdfExpand*[H: CryptoHash; K: static int; I, A: char|byte](
ctx: var HKDF[H],
output: var openArray[byte],
prk: array[K, byte],
info: openArray[I],
append: openArray[A]) =
## "Expand" step of HKDF
## Expand a fixed size pseudo random-key
## into several pseudo-random keys
##
## Inputs:
## - prk: a pseudo random key (PRK) of fixed size. The size is the same as the cryptographic hash chosen.
## - info: optional context and application specific information (set to nil if unused)
## - append:
## Compared to the spec we add a specific append procedure to do
## OKM = HKDF-Expand(PRK, key_info || I2OSP(L, 2), L)
## without having additional allocation on the heap
## Output:
## - output: OKM (output keying material). The PRK is expanded to match
## the output length, the result is stored in output.
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
const HashLen = H.digestSize()
static: doAssert K == HashLen
debug:
doAssert output.len <= 255*HashLen
var t{.noInit.}: array[HashLen, byte]
{.push checks: off.} # No OverflowError or IndexError allowed
for i in ctx.hkdfExpandChunk(t, prk, info, append):
let iStart = i * HashLen
let size = min(HashLen, output.len - iStart)
copy(output, iStart, t, 0, size)
if iStart+HashLen >= output.len:
break
# ctx.clear() - TODO: very expensive
func hkdfExpand*[H: CryptoHash; K: static int; I: char|byte](
ctx: var HKDF[H],
output: var openArray[byte],
prk: array[K, byte],
info: openArray[I]) {.inline.} =
## "Expand" step of HKDF
## Expand a fixed size pseudo random-key
## into several pseudo-random keys
##
## Inputs:
## - prk: a pseudo random key (PRK) of fixed size. The size is the same as the cryptographic hash chosen.
## - info: optional context and application specific information (set to nil if unused)
## Output:
## - output: OKM (output keying material). The PRK is expanded to match
## the output length, the result is stored in output.
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
hkdfExpand(ctx, output, prk, info, default(array[0, byte]))
func hkdf*[H: CryptoHash, N: static int, O, S, K, I: char|byte](
Hash: typedesc[H],
output: var openArray[O],
salt: openArray[S],
ikm: openArray[K],
info: openArray[I]) {.inline.} =
## HKDF
## Inputs:
## - A hash function, with an output digest length HashLen
## - An opttional salt value (non-secret random value), if not provided,
## it is set to an array of HashLen zero bytes
## - A secret Input Keying Material
## - info: an optional context and application specific information for domain separation
## it can be an empty string
var ctx{.noInit.}: HMAC[H]
var prk{.noInit.}: array[H.digestSize(), byte]
ctx.hkdfExtract(prk, salt, ikm)
ctx.hkdfExpand(output, prk, info)

11
constantine/mac/README.md Normal file
View File

@ -0,0 +1,11 @@
# Message Authentication Codes
Note:
We prefix the filename with "mac" to prevents name collision between a modulename and the types
which leads to confusing error messages
```Nim
# in mac_poly1305
type poly1305* = Poly1305_CTX
```

View File

@ -0,0 +1,105 @@
# 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
../hashes,
../platforms/primitives
# HMAC: Keyed-Hashing for Message Authentication
# ----------------------------------------------
#
# https://datatracker.ietf.org/doc/html/rfc2104
#
# Test vectors:
# - https://datatracker.ietf.org/doc/html/rfc4231
# - https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program
# - http://csrc.nist.gov/groups/STM/cavp/documents/mac/hmactestvectors.zip
{.push raises: [].} # No exceptions
type HMAC*[H: CryptoHash] = object
inner: H
outer: H
func init*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], secretKey: openArray[T]) =
## Initialize a HMAC-based Message Authentication Code
## with a pre-shared secret key
## between the parties that want to authenticate messages between each other.
##
## Keys should be at least the same size as the hash function output size.
##
## Keys need to be chosen at random (or using a cryptographically strong
## pseudo-random generator seeded with a random seed), and periodically
## refreshed.
var key{.noInit.}: array[H.internalBlockSize(), byte]
if secretKey.len <= key.len:
copy(key, 0, secretKey, 0, secretKey.len)
for i in secretKey.len ..< key.len:
key[i] = byte 0
else:
ctx.inner.init()
ctx.inner.update(secretKey)
ctx.inner.finish(cast[ptr array[32, byte]](key.addr)[])
for i in H.digestSize() ..< key.len:
key[i] = byte 0
# Spec: inner hash
for i in 0 ..< H.internalBlockSize():
key[i] = key[i] xor byte 0x36
ctx.inner.init()
ctx.inner.update(key)
# Spec: outer hash (by cancelling previous xor)
for i in 0 ..< H.internalBlockSize():
key[i] = key[i] xor (byte 0x36 xor byte 0x5C)
ctx.outer.init()
ctx.outer.update(key)
func update*[H: CryptoHash, T: char|byte](ctx: var HMAC[H], message: openArray[T]) =
## Append a message to a HMAC authentication context.
## for incremental HMAC computation.
ctx.inner.update(message)
func finish*[H: CryptoHash, T: char|byte, N: static int](ctx: var HMAC[H], tag: var array[N, T]) =
## Finalize a HMAC authentication
## and output an authentication tag to the `tag` buffer
##
## Output may be used truncated, with the leftmost bits are kept.
## It is recommended that the tag length is at least half the length of the hash output
## and at least 80-bits.
static: doAssert N == H.digestSize()
ctx.inner.finish(tag)
ctx.outer.update(tag)
ctx.outer.finish(tag)
func clear*[H: CryptoHash](ctx: var HMAC[H]) =
## Clear the context internal buffers
# TODO: ensure compiler cannot optimize the code away
ctx.inner.clear()
ctx.outer.clear()
func mac*[T: char|byte, H: CryptoHash, N: static int](
Hash: type HMAC[H],
tag: var array[N, byte],
message: openArray[T],
secretKey: openarray[T],
clearMem = false) =
## Produce an authentication tag from a message
## and a preshared unique non-reused secret key
static: doAssert N == H.digestSize()
var ctx {.noInit.}: HMAC[H]
ctx.init(secretKey)
ctx.update(message)
ctx.finish(tag)
if clearMem:
ctx.clear()

View File

@ -328,7 +328,7 @@ func clear*(ctx: var Poly1305_CTX) =
ctx.msgLen = 0
ctx.bufIdx = 0
func auth*[T: char|byte](
func mac*[T: char|byte](
_: type poly1305,
tag: var array[16, byte],
message: openArray[T],
@ -345,11 +345,11 @@ func auth*[T: char|byte](
if clearMem:
ctx.clear()
func auth*[T: char|byte](
func mac*[T: char|byte](
_: type poly1305,
message: openArray[T],
nonReusedKey: array[32, byte],
clearMem = false): array[16, byte]{.noInit.}=
## Produce an authentication tag from a message
## and a preshared unique non-reused secret key
poly1305.auth(result, message, nonReusedKey, clearMem)
poly1305.mac(result, message, nonReusedKey, clearMem)

View File

@ -76,13 +76,9 @@ func dumpRawInt*[T: byte|char](
for i in 0'u ..< L:
dst[cursor+i] = toByte(src shr ((L-i-1) * 8))
func asBytesBE*[N: static int](
r: var array[N, byte],
num: SomeUnsignedInt,
pos: static int) {.inline.}=
func toBytesBE*(num: SomeUnsignedInt): array[sizeof(num), byte] {.noInit, inline.}=
## Store an integer into an array of bytes
## in big endian representation
const L = sizeof(num)
static: doAssert N - (pos + L) >= 0
for i in 0 ..< L:
r[i] = toByte(num shr ((L-1-i) * 8))
result[i] = toByte(num shr ((L-1-i) * 8))

View File

@ -54,8 +54,8 @@ func setZero*[N](a: var array[N, SomeNumber]){.inline.} =
for i in 0 ..< a.len:
a[i] = 0
func copy*[N: static int, T: byte|char](
dst: var array[N, byte],
func copy*[T: byte|char](
dst: var openArray[byte],
dStart: SomeInteger,
src: openArray[T],
sStart: SomeInteger,
@ -69,5 +69,6 @@ func copy*[N: static int, T: byte|char](
doAssert 0 <= dStart and dStart+len <= dst.len.uint, "dStart: " & $dStart & ", dStart+len: " & $(dStart+len) & ", dst.len: " & $dst.len
doAssert 0 <= sStart and sStart+len <= src.len.uint, "sStart: " & $sStart & ", sStart+len: " & $(sStart+len) & ", src.len: " & $src.len
{.push checks: off.} # No OverflowError or IndexError allowed
for i in 0 ..< len:
dst[dStart + i] = byte src[sStart + i]

View File

@ -9,6 +9,7 @@
import
../constantine/platforms/abstractions,
../constantine/math/arithmetic,
../constantine/math/arithmetic/limbs_montgomery,
../constantine/math/config/curves,
../constantine/math/elliptic/[
ec_shortweierstrass_affine,
@ -135,20 +136,38 @@ func sample_unsafe*[T](rng: var RngState, src: openarray[T]): T =
# - A bias is a result that is consistently off from the true value i.e.
# a deviation of an estimate from the quantity under observation
func reduceViaMont[N: static int, F](reduced: var array[N, SecretWord], unreduced: array[2*N, SecretWord], _: typedesc[F]) =
# reduced.reduce(unreduced, FF.fieldMod())
# ----------------------------------------
# With R ≡ (2^WordBitWidth)^numWords (mod p)
# redc2xMont(a) computes a/R (mod p)
# mulMont(a, b) computes a.b.R⁻¹ (mod p)
# Hence with b = R², this computes a (mod p).
# Montgomery reduction works word by word, quadratically
# so for 384-bit prime (6-words on 64-bit CPUs), so 6*6 = 36, twice for multiplication and reduction
# significantly faster than division which works bit-by-bit due to constant-time requirement
reduced.redc2xMont(unreduced, # a/R
F.fieldMod().limbs, F.getNegInvModWord(),
F.getSpareBits())
reduced.mulMont(reduced, F.getR2modP().limbs, # (a/R) * R² * R⁻¹ ≡ a (mod p)
F.fieldMod().limbs, F.getNegInvModWord(),
F.getSpareBits())
func random_unsafe(rng: var RngState, a: var Limbs) =
## Initialize standalone limbs
for i in 0 ..< a.len:
a[i] = SecretWord(rng.next())
func random_unsafe(rng: var RngState, a: var BigInt) =
## Initialize a standalone BigInt
for i in 0 ..< a.limbs.len:
a.limbs[i] = SecretWord(rng.next())
rng.random_unsafe(a.limbs)
func random_unsafe(rng: var RngState, a: var FF) =
## Initialize a Field element
## Unsafe: for testing and benchmarking purposes only
var reduced, unreduced{.noInit.}: typeof(a.mres)
var unreduced{.noinit.}: Limbs[2*a.mres.limbs.len]
rng.random_unsafe(unreduced)
# Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, FF.fieldMod())
a.mres.getMont(reduced, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord(), FF.getSpareBits())
a.mres.limbs.reduceViaMont(unreduced, FF)
func random_unsafe(rng: var RngState, a: var ExtensionField) =
## Recursively initialize an extension Field element
@ -162,24 +181,27 @@ func random_word_highHammingWeight(rng: var RngState): BaseType =
for _ in 0 ..< numZeros:
result = result.clearBit rng.random_unsafe(WordBitWidth)
func random_highHammingWeight(rng: var RngState, a: var Limbs) =
## Initialize standalone limbs
## with high Hamming weight
## to have a higher probability of triggering carries
for i in 0 ..< a.len:
a[i] = SecretWord rng.random_word_highHammingWeight()
func random_highHammingWeight(rng: var RngState, a: var BigInt) =
## Initialize a standalone BigInt
## with high Hamming weight
## to have a higher probability of triggering carries
for i in 0 ..< a.limbs.len:
a.limbs[i] = SecretWord rng.random_word_highHammingWeight()
rng.random_highHammingWeight(a.limbs)
func random_highHammingWeight(rng: var RngState, a: var FF) =
## Recursively initialize a BigInt (part of a field) or Field element
## Unsafe: for testing and benchmarking purposes only
## The result will have a high Hamming Weight
## to have a higher probability of triggering carries
var reduced, unreduced{.noInit.}: typeof(a.mres)
var unreduced{.noinit.}: Limbs[2*a.mres.limbs.len]
rng.random_highHammingWeight(unreduced)
# Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, FF.fieldMod())
a.mres.getMont(reduced, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord(), FF.getSpareBits())
a.mres.limbs.reduceViaMont(unreduced, FF)
func random_highHammingWeight(rng: var RngState, a: var ExtensionField) =
## Recursively initialize an extension Field element
@ -215,16 +237,23 @@ func random_long01Seq(rng: var RngState, a: var BigInt) =
else:
a.unmarshal(buf, littleEndian)
func random_long01Seq(rng: var RngState, a: var Limbs) =
## Initialize standalone limbs
## It is skewed towards producing strings of 1111... and 0000
## to trigger edge cases
const bits = a.len*WordBitWidth
var t{.noInit.}: BigInt[bits]
rng.random_long01Seq(t)
a = t.limbs
func random_long01Seq(rng: var RngState, a: var FF) =
## Recursively initialize a BigInt (part of a field) or Field element
## It is skewed towards producing strings of 1111... and 0000
## to trigger edge cases
var reduced, unreduced{.noInit.}: typeof(a.mres)
var unreduced{.noinit.}: Limbs[2*a.mres.limbs.len]
rng.random_long01Seq(unreduced)
# Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, FF.fieldMod())
a.mres.getMont(reduced, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord(), FF.getSpareBits())
a.mres.limbs.reduceViaMont(unreduced, FF)
func random_long01Seq(rng: var RngState, a: var ExtensionField) =
## Recursively initialize an extension Field element

View File

@ -0,0 +1,123 @@
# Nim-BLSCurve
# Copyright (c) 2018-Present Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
std/unittest,
../constantine/ethereum_eip2333_bls12381_key_derivation,
../constantine/math/io/io_bigints,
../constantine/math/config/curves,
../constantine/math/arithmetic/bigints,
../constantine/platforms/abstractions
type SecretKey = matchingOrderBigInt(BLS12_381)
proc toBytes(hex: string): seq[byte] =
doAssert (hex.len and 1) == 0, "Input hex must have an even number of characters"
let length = hex.len shr 1 - int(hex[0] == '0' and (hex[1] in {'x', 'X'}))
result.newSeq(length)
hex.hexToPaddedByteArray(result, bigEndian)
proc test0 =
let seed = toBytes"0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
let expectedMaster = "6083874454709270928345386274498605044986640685124978867557563392430687146096"
let child_index = 0'u32
let expectedChild = "20397789859736650942317412262472558107875392172444076792671091975210932703118"
var master: SecretKey
let ok0 = master.derive_master_secretKey(seed)
doAssert ok0
var eMaster: SecretKey
doAssert bool eMaster.fromDecimal(expectedMaster)
doAssert bool(master == eMaster)
var child: SecretKey
let ok1 = child.derive_child_secretKey(master, child_index)
doAssert ok1
var eChild: SecretKey
doAssert bool eChild.fromDecimal(expectedChild)
doAssert bool(child == eChild)
proc test1 =
let seed = toBytes"0x3141592653589793238462643383279502884197169399375105820974944592"
let expectedMaster = "29757020647961307431480504535336562678282505419141012933316116377660817309383"
let child_index = 3141592653'u32
let expectedChild = "25457201688850691947727629385191704516744796114925897962676248250929345014287"
var master: SecretKey
let ok0 = master.derive_master_secretKey(seed)
doAssert ok0
var eMaster: SecretKey
doAssert bool eMaster.fromDecimal(expectedMaster)
doAssert bool(master == eMaster)
var child: SecretKey
let ok1 = child.derive_child_secretKey(master, child_index)
doAssert ok1
var eChild: SecretKey
doAssert bool eChild.fromDecimal(expectedChild)
doAssert bool(child == eChild)
proc test2 =
let seed = toBytes"0x0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00"
let expectedMaster = "27580842291869792442942448775674722299803720648445448686099262467207037398656"
let child_index = 4294967295'u32
let expectedChild = "29358610794459428860402234341874281240803786294062035874021252734817515685787"
var master: SecretKey
let ok0 = master.derive_master_secretKey(seed)
doAssert ok0
var eMaster: SecretKey
doAssert bool eMaster.fromDecimal(expectedMaster)
doAssert bool(master == eMaster)
var child: SecretKey
let ok1 = child.derive_child_secretKey(master, child_index)
doAssert ok1
var eChild: SecretKey
doAssert bool eChild.fromDecimal(expectedChild)
doAssert bool(child == eChild)
proc test3 =
let seed = toBytes"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
let expectedMaster = "19022158461524446591288038168518313374041767046816487870552872741050760015818"
let child_index = 42'u32
let expectedChild = "31372231650479070279774297061823572166496564838472787488249775572789064611981"
var master: SecretKey
let ok0 = master.derive_master_secretKey(seed)
doAssert ok0
var eMaster: SecretKey
doAssert bool eMaster.fromDecimal(expectedMaster)
doAssert bool(master == eMaster)
var child: SecretKey
let ok1 = child.derive_child_secretKey(master, child_index)
doAssert ok1
var eChild: SecretKey
doAssert bool eChild.fromDecimal(expectedChild)
doAssert bool(child == eChild)
suite "Key Derivation (EIP-2333)":
test "Test 0":
test0()
test "Test 1":
test1()
test "Test 2":
test2()
test "Test 3":
test3()

109
tests/t_kdf_hkdf.nim Normal file
View File

@ -0,0 +1,109 @@
# 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
stew/byteutils,
../constantine/[hashes, mac/mac_hmac, kdf/kdf_hkdf]
proc hexToBytes(s: string): seq[byte] =
if s.len != 0: return hexToSeqByte(s)
template test(id, constants: untyped) =
proc `test _ id`() =
# We create a proc to avoid allocating too much globals.
constants
let
bikm = hexToBytes(IKM)
bsalt = hexToBytes(salt)
binfo = hexToBytes(info)
bprk = hexToBytes(PRK)
bokm = hexToBytes(OKM)
var output = newSeq[byte](L)
var ctx: HKDF[HashType]
var prk: array[HashType.digestSize, byte]
# let salt = if bsalt.len == 0: nil
# else: bsalt[0].unsafeAddr
# let ikm = if bikm.len == 0: nil
# else: bikm[0].unsafeAddr
# let info = if binfo.len == 0: nil
# else: binfo[0].unsafeAddr
let
salt = bsalt
ikm = bikm
info = binfo
hkdfExtract(ctx, prk, salt, ikm)
hkdfExpand(ctx, output, prk, info)
doAssert @(prk) == bprk, "\nComputed 0x" & toHex(prk) &
"\nbut expected " & PRK & '\n'
doAssert output == bokm, "\nComputed 0x" & toHex(output) &
"\nbut expected " & OKM & '\n'
echo "HKDF Test ", astToStr(id), " - SUCCESS"
`test _ id`()
test 1: # Basic test case with SHA-256
type HashType = sha256
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = "0x000102030405060708090a0b0c"
info = "0xf0f1f2f3f4f5f6f7f8f9"
L = 42
PRK = "0x077709362c2e32df0ddc3f0dc47bba63" &
"90b6c73bb50f9c3122ec844ad7c2b3e5"
OKM = "0x3cb25f25faacd57a90434f64d0362f2a" &
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" &
"34007208d5b887185865"
test 2: # Test with SHA-256 and longer inputs/outputs
type HashType = sha256
const
IKM = "0x000102030405060708090a0b0c0d0e0f" &
"101112131415161718191a1b1c1d1e1f" &
"202122232425262728292a2b2c2d2e2f" &
"303132333435363738393a3b3c3d3e3f" &
"404142434445464748494a4b4c4d4e4f"
salt = "0x606162636465666768696a6b6c6d6e6f" &
"707172737475767778797a7b7c7d7e7f" &
"808182838485868788898a8b8c8d8e8f" &
"909192939495969798999a9b9c9d9e9f" &
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
info = "0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf" &
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" &
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" &
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef" &
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
L = 82
PRK = "0x06a6b88c5853361a06104c9ceb35b45c" &
"ef760014904671014a193f40c15fc244"
OKM = "0xb11e398dc80327a1c8e7f78c596a4934" &
"4f012eda2d4efad8a050cc4c19afa97c" &
"59045a99cac7827271cb41c65e590e09" &
"da3275600c2f09b8367793a9aca3db71" &
"cc30c58179ec3e87c14c01d5c1f3434f" &
"1d87"
test 3: # Test with SHA-256 and zero-length salt/info
type HashType = sha256
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = ""
info = ""
L = 42
PRK = "0x19ef24a32c717b167f33a91d6f648bdf" &
"96596776afdb6377ac434c1c293ccb04"
OKM = "0x8da4e775a563c18f715f802a063c5a31" &
"b8a11f5c5ee1879ec3454e5f3c738d2d" &
"9d201395faa4b61a96c8"

114
tests/t_mac_hmac_sha256.nim Normal file
View File

@ -0,0 +1,114 @@
# 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
std/unittest,
../constantine/mac/mac_hmac,
../constantine/hashes,
../constantine/math/io/io_bigints
type TestVector = object
key: seq[byte]
data: seq[byte]
digest: array[32, byte]
truncatedLen: int
proc doTest(key, data, digest: string) =
var tv: TestVector
doAssert (key.len and 1) == 0, "An hex string must be of even length"
doAssert (data.len and 1) == 0, "An hex string must be of even length"
doAssert (digest.len and 1) == 0, "An hex string must be of even length"
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.data.newSeq(data.len div 2)
data.hexToPaddedByteArray(tv.data, bigEndian)
tv.truncatedLen = digest.len div 2
digest.hexToPaddedByteArray(tv.digest, bigEndian)
var output{.noInit.}: array[32, byte]
HMAC[sha256].mac(output, tv.data, tv.key)
doAssert tv.digest.toOpenArray(tv.digest.len-tv.truncatedLen, tv.digest.len-1) == output.toOpenArray(0, tv.truncatedLen-1)
suite "[Message Authentication Code] HMAC-SHA256":
test "Test vector 1 - RFC4231":
doTest(
key = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
data = "4869205468657265",
digest = "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
)
test "Test vector 2 - RFC4231":
doTest(
key = "4a656665",
data = "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
digest = "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
)
test "Test vector 3 - RFC4231":
doTest(
key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
data = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
digest = "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
)
test "Test vector 4 - RFC4231":
doTest(
key = "0102030405060708090a0b0c0d0e0f10111213141516171819",
data = "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
digest = "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
)
test "Test vector 5 - RFC4231":
doTest(
key = "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
data = "546573742057697468205472756e636174696f6e",
digest = "a3b6167473100ee06e0c796c2955552b"
)
test "Test vector 6 - RFC4231":
doTest(
key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaa",
data = "54657374205573696e67204c61726765" &
"72205468616e20426c6f636b2d53697a" &
"65204b6579202d2048617368204b6579" &
"204669727374",
digest = "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
)
test "Test vector 7 - RFC4231":
doTest(
key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" &
"aaaaaa",
data = "54686973206973206120746573742075" &
"73696e672061206c6172676572207468" &
"616e20626c6f636b2d73697a65206b65" &
"7920616e642061206c61726765722074" &
"68616e20626c6f636b2d73697a652064" &
"6174612e20546865206b6579206e6565" &
"647320746f2062652068617368656420" &
"6265666f7265206265696e6720757365" &
"642062792074686520484d414320616c" &
"676f726974686d2e",
digest = "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
)

View File

@ -26,6 +26,6 @@ suite "[Message Authentication Code] Poly1305":
]
var tag: array[16, byte]
poly1305.auth(tag, message, ikm)
poly1305.mac(tag, message, ikm)
doAssert tag == expectedTag