Skip to content

Commit

Permalink
Implement Keystore primitives EIP-2333
Browse files Browse the repository at this point in the history
  • Loading branch information
mratsim committed Jun 2, 2020
1 parent b435f1a commit ba7a2fb
Show file tree
Hide file tree
Showing 5 changed files with 449 additions and 24 deletions.
3 changes: 3 additions & 0 deletions blscurve/bls_sig_io.nim
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ const

func exportRaw*(secretKey: SecretKey): array[RawSecretKeySize, byte] {.inline.}=
## Serialize a secret key into its raw binary representation
# TODO: the SecretKey size is actually not 384 bit
# but 255 bit since the curve order requires 255-bit
# What uses exportRaw?
discard result.serialize(secretKey)

func exportRaw*(publicKey: PublicKey): array[RawPublicKeySize, byte] {.inline.}=
Expand Down
58 changes: 35 additions & 23 deletions blscurve/bls_signature_scheme.nim
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,39 @@ func fastAggregateVerify*[T: byte|char](
aggregate.point.add(publicKeys[i].point)
return coreVerify(aggregate, message, signature, DST)

func hkdf_mod_r*(secretKey: var SecretKey, ikm: openArray[byte]): bool =
## Ethereum 2 EIP-2333, extracts this from the BLS signature schemes
# 1. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM)
# 2. OKM = HKDF-Expand(PRK, "", L)
# 3. SK = OS2IP(OKM) mod r
# 4. return SK
const salt = "BLS-SIG-KEYGEN-SALT-"
var ctx: HMAC[sha256]
var prk: MDigest[sha256.bits]

# 1. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM)
ctx.hkdfExtract(prk, salt, ikm)

# 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

# 2. OKM = HKDF-Expand(PRK, "", L)
const L = 48
var okm: array[L, byte]
ctx.hkdfExpand(prk, "", okm) # TODO: this will likely be changed to match BLS-02 construction

# 3. x = OS2IP(OKM) mod r
# 5. SK = x
var dseckey: DBIG_384
if not dseckey.fromBytes(okm):
return false

{.noSideEffect.}:
BIG_384_dmod(secretKey.intVal, dseckey, CURVE_Order)

return true

func keyGen*(ikm: openarray[byte], publicKey: var PublicKey, secretKey: var SecretKey): bool =
## Generate a (public key, secret key) pair
## from the input keying material `ikm`
Expand Down Expand Up @@ -509,31 +542,10 @@ func keyGen*(ikm: openarray[byte], publicKey: var PublicKey, secretKey: var Secr
if ikm.len < 32:
return false

const salt = "BLS-SIG-KEYGEN-SALT-"
var ctx: HMAC[sha256]
var prk: MDigest[sha256.bits]

# 1. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM)
ctx.hkdfExtract(prk, salt, ikm)

# 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

# 2. OKM = HKDF-Expand(PRK, "", L)
const L = 48
var okm: array[L, byte]
ctx.hkdfExpand(prk, "", okm)

# 3. x = OS2IP(OKM) mod r
# 5. SK = x
var dseckey: DBIG_384
if not dseckey.fromBytes(okm):
let ok = secretKey.hkdf_mod_r(ikm)
if not ok:
return false

{.noSideEffect.}:
BIG_384_dmod(secretKey.intVal, dseckey, CURVE_Order)

# 4. xP = x * P
# 6. PK = point_to_pubkey(xP)
publicKey = privToPub(secretKey)
Expand Down
282 changes: 282 additions & 0 deletions blscurve/eip_2333_draft.md

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions blscurve/eth2_keygen.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Nim-BLSCurve
# Copyright (c) 2018 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.

# Implementation of Ethereum 2 Key derivation
# https://eips.ethereum.org/EIPS/eip-2333

{.push raises: [Defect].}

import
# third-party
nimcrypto/[hmac, sha2], stew/endians2,
# internal
./milagro, ./common, ./hkdf,
./hash_to_curve,
./bls_signature_scheme

func ikm_to_lamport_SK(
ikm: openArray[byte],
salt: array[4, byte],
lamportSecretKey: var array[255, array[32, byte]]) =
## Generate a Lamport private key
# TODO: consider using an HKDF iterator

var ctx: HMAC[sha256]
var prk: MDigest[sha256.bits]

# 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)
const L = sizeof(lamportSecretKey)
let okm = cast[ptr array[L, byte]](lamportSecretKey.addr)
# TODO: this will likely be changed to match BLS-02 construction
# regarding salt and prk
ctx.hkdfExpand(prk, "", okm[])

func parent_SK_to_lamport_PK(
parentSecretKey: SecretKey,
index: uint32,
lamportPublicKey: var array[32, byte]) =
## Derives the index'th child's lamport PublicKey
## from the parent SecretKey

# 0. salt = I2OSP(index, 4)
let salt = index.toBytesBE()
static: doAssert sizeof(salt) == 4

# 1. IKM = I2OSP(parent_SK, 32)
# While the BLS prime is 381-bit (48 bytes)
# the curve order is 255-bit (32 bytes)
# and a secret key would always fit in 32 bytes
var ikm {.noInit.}: array[32, byte]
doAssert ikm.serialize(parentSecretKey)

# Reorganized the spec to save on stack allocations
# and limit stackoverflow potential.
# As an additional optimization we could do the HKDF-Expand
# in a streaming fashion 32 byte-chunk per 32 byte-chunk
# via an iterator

# 5. lamport_PK = ""
var ctx: sha256
ctx.init()

# 2. lamport_0 = IKM_to_lamport_SK(IKM, salt)
# TODO: this uses 8KB and has a high stack-overflow potential
var lamport {.noInit.}: array[255, array[32, byte]]
ikm.ikm_to_lamport_SK(salt, lamport)

# TODO: unclear inclusive/exclusive ranges in spec
# assuming exclusive:
# https://github.com/ethereum/EIPs/issues/2337#issuecomment-637521421
# 6. for i = 0 to 255
# lamport_PK = lamport_PK | SHA256(lamport_0[i])
for i in 0 ..< 255:
ctx.update(lamport[i])

# 3. not_IKM = flip_bits(IKM)
var not_ikm {.noInit.}: array[32, byte]
for i in 0 ..< 32:
not_ikm[i] = not ikm[i]

# 4. lamport_1 = IKM_to_lamport_SK(not_IKM, salt)
# We reuse the previous buffer to limit stack usage
ikm.ikm_to_lamport_SK(salt, lamport)

# TODO: inclusive/exclusive range?
# 7. for i = 0 to 255
# lamport_PK = lamport_PK | SHA256(lamport_1[i])
for i in 0 ..< 255:
ctx.update(lamport[i])

discard ctx.finish(lamportPublicKey)

func derive_child_secretKey*(
childSecretKey: var SecretKey,
parentSecretKey: SecretKey,
index: uint32
): bool =
## Child Key derivation function
var compressed_lamport_PK: array[32, byte]
# 0. compressed_lamport_PK = parent_SK_to_lamport_PK(parent_SK, index)
parent_SK_to_lamport_PK(
parentSecretKey,
index,
compressed_lamport_PK
)
childSecretKey.hkdf_mod_r(compressed_lamport_PK)

func derive_master_secretKey*(
masterSecretKey: var SecretKey,
ikm: openArray[byte]
): bool =
## Master key derivation

# TODO: BLS KeyGen MUST be 32 bytes
# https://github.com/ethereum/EIPs/issues/2337#issuecomment-637548497
if ikm.len < 16:
return false

masterSecretKey.hkdf_mod_r(ikm)
2 changes: 1 addition & 1 deletion blscurve/hkdf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func hkdfExpand*[T;I: char|byte](ctx: var HMAC[T],
##
## Output:
## - output: OKM (output keying material). The PRK is expanded to match
## the output length, the result is tored in output.
## the output length, the result is stored in output.
##
## Temporary:
## - ctx: a HMAC["cryptographic-hash"] context, for example HMAC[sha256].
Expand Down

0 comments on commit ba7a2fb

Please sign in to comment.