mirror of
https://github.com/logos-storage/constantine.git
synced 2026-01-02 13:13:07 +00:00
Eip2333 (#202)
* HMAC-SHA256 * EIP2333 * activate EIP2333 tests and faster random test case generation
This commit is contained in:
parent
9770b3108c
commit
094445482b
@ -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()
|
||||
|
||||
@ -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() =
|
||||
|
||||
@ -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):
|
||||
|
||||
177
constantine/ethereum_eip2333_bls12381_key_derivation.nim
Normal file
177
constantine/ethereum_eip2333_bls12381_key_derivation.nim
Normal 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
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
11
constantine/kdf/README.txt
Normal file
11
constantine/kdf/README.txt
Normal 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*(...)
|
||||
```
|
||||
200
constantine/kdf/kdf_hkdf.nim
Normal file
200
constantine/kdf/kdf_hkdf.nim
Normal 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
11
constantine/mac/README.md
Normal 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
|
||||
```
|
||||
105
constantine/mac/mac_hmac.nim
Normal file
105
constantine/mac/mac_hmac.nim
Normal 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()
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
@ -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]
|
||||
@ -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
|
||||
|
||||
123
tests/t_ethereum_eip2333_bls12381_key_derivation.nim
Normal file
123
tests/t_ethereum_eip2333_bls12381_key_derivation.nim
Normal 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
109
tests/t_kdf_hkdf.nim
Normal 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
114
tests/t_mac_hmac_sha256.nim
Normal 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"
|
||||
)
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user