2020-05-19 17:30:28 +00:00
|
|
|
# beacon_chain
|
2021-06-21 08:35:24 +00:00
|
|
|
# Copyright (c) 2018-2021 Status Research & Development GmbH
|
2020-05-19 17:30:28 +00:00
|
|
|
# Licensed and distributed under either of
|
|
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
|
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
2020-11-27 22:16:13 +00:00
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
2020-05-19 17:30:28 +00:00
|
|
|
import
|
2020-09-24 05:27:56 +00:00
|
|
|
# Standard library
|
2020-10-02 15:46:05 +00:00
|
|
|
std/[algorithm, math, parseutils, strformat, strutils, typetraits, unicode],
|
|
|
|
# Third-party libraries
|
|
|
|
normalize,
|
2020-09-24 05:27:56 +00:00
|
|
|
# Status libraries
|
2020-10-02 15:46:05 +00:00
|
|
|
stew/[results, bitseqs, bitops2], stew/shims/macros,
|
|
|
|
bearssl, eth/keyfile/uuid, blscurve, json_serialization,
|
2020-08-20 13:01:08 +00:00
|
|
|
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, scrypt],
|
2020-10-02 15:46:05 +00:00
|
|
|
# Local modules
|
2020-08-20 13:01:08 +00:00
|
|
|
libp2p/crypto/crypto as lcrypto,
|
2021-08-12 13:08:20 +00:00
|
|
|
./datatypes/base, ./signatures
|
|
|
|
|
|
|
|
export base
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-20 13:01:08 +00:00
|
|
|
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
|
|
|
|
import nimcrypto/utils as ncrutils
|
|
|
|
|
2020-06-01 19:48:20 +00:00
|
|
|
export
|
2020-08-02 17:26:57 +00:00
|
|
|
results, burnMem, writeValue, readValue
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-10-06 11:38:18 +00:00
|
|
|
{.localPassC: "-fno-lto".} # no LTO for crypto
|
2020-05-20 07:01:20 +00:00
|
|
|
|
2020-05-19 17:30:28 +00:00
|
|
|
type
|
2020-08-02 17:26:57 +00:00
|
|
|
ChecksumFunctionKind* = enum
|
|
|
|
sha256Checksum = "sha256"
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
Sha256Params* = object
|
|
|
|
Sha256Digest* = MDigest[256]
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
ChecksumBytes* = distinct seq[byte]
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
Checksum* = object
|
|
|
|
case function*: ChecksumFunctionKind
|
|
|
|
of sha256Checksum:
|
|
|
|
params*: Sha256Params
|
|
|
|
message*: Sha256Digest
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
Aes128CtrIv* = distinct seq[byte]
|
|
|
|
|
|
|
|
Aes128CtrParams* = object
|
|
|
|
iv*: Aes128CtrIv
|
|
|
|
|
|
|
|
CipherFunctionKind* = enum
|
|
|
|
aes128CtrCipher = "aes-128-ctr"
|
|
|
|
|
|
|
|
CipherBytes* = distinct seq[byte]
|
|
|
|
|
|
|
|
Cipher* = object
|
|
|
|
case function*: CipherFunctionKind
|
|
|
|
of aes128ctrCipher:
|
|
|
|
params*: Aes128CtrParams
|
|
|
|
message*: CipherBytes
|
|
|
|
|
|
|
|
KdfKind* = enum
|
|
|
|
kdfPbkdf2 = "pbkdf2"
|
|
|
|
kdfScrypt = "scrypt"
|
|
|
|
|
|
|
|
ScryptSalt* = distinct seq[byte]
|
|
|
|
|
|
|
|
ScryptParams* = object
|
2020-10-09 16:41:53 +00:00
|
|
|
dklen: uint64
|
2020-05-19 17:30:28 +00:00
|
|
|
n, p, r: int
|
2020-08-02 17:26:57 +00:00
|
|
|
salt: ScryptSalt
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
Pbkdf2Salt* = distinct seq[byte]
|
|
|
|
|
|
|
|
PrfKind* = enum # Pseudo-random-function Kind
|
|
|
|
HmacSha256 = "hmac-sha256"
|
|
|
|
|
|
|
|
Pbkdf2Params* = object
|
2020-10-09 16:41:53 +00:00
|
|
|
dklen*: uint64
|
|
|
|
c*: uint64
|
2020-08-02 17:26:57 +00:00
|
|
|
prf*: PrfKind
|
|
|
|
salt*: Pbkdf2Salt
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-10-09 16:41:53 +00:00
|
|
|
DecryptionStatus* = enum
|
|
|
|
Success = "Success"
|
|
|
|
InvalidPassword = "Invalid password"
|
|
|
|
InvalidKeystore = "Invalid keystore"
|
|
|
|
|
2020-06-23 19:11:07 +00:00
|
|
|
# https://github.com/ethereum/EIPs/blob/4494da0966afa7318ec0157948821b19c4248805/EIPS/eip-2386.md#specification
|
|
|
|
Wallet* = object
|
|
|
|
uuid*: UUID
|
|
|
|
name*: WalletName
|
|
|
|
version*: uint
|
|
|
|
walletType* {.serializedFieldName: "type"}: string
|
|
|
|
# TODO: The use of `JsonString` can be removed once we
|
|
|
|
# solve the serialization problem for `Crypto[T]`
|
2020-08-02 17:26:57 +00:00
|
|
|
crypto*: Crypto
|
2020-06-23 19:11:07 +00:00
|
|
|
nextAccount* {.serializedFieldName: "nextaccount".}: Natural
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
Kdf* = object
|
|
|
|
case function*: KdfKind
|
|
|
|
of kdfPbkdf2:
|
|
|
|
pbkdf2Params* {.serializedFieldName: "params".}: Pbkdf2Params
|
|
|
|
of kdfScrypt:
|
|
|
|
scryptParams* {.serializedFieldName: "params".}: ScryptParams
|
|
|
|
message*: string
|
|
|
|
|
|
|
|
Crypto* = object
|
|
|
|
kdf*: Kdf
|
|
|
|
checksum*: Checksum
|
|
|
|
cipher*: Cipher
|
|
|
|
|
|
|
|
Keystore* = object
|
|
|
|
crypto*: Crypto
|
2020-08-05 22:38:55 +00:00
|
|
|
description*: ref string
|
2020-08-02 17:26:57 +00:00
|
|
|
pubkey*: ValidatorPubKey
|
|
|
|
path*: KeyPath
|
|
|
|
uuid*: string
|
|
|
|
version*: int
|
|
|
|
|
2020-08-20 13:01:08 +00:00
|
|
|
NetKeystore* = object
|
|
|
|
crypto*: Crypto
|
|
|
|
description*: ref string
|
|
|
|
pubkey*: lcrypto.PublicKey
|
|
|
|
uuid*: string
|
|
|
|
version*: int
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
KsResult*[T] = Result[T, string]
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-06-01 19:48:20 +00:00
|
|
|
Eth2KeyKind* = enum
|
|
|
|
signingKeyKind # Also known as voting key
|
|
|
|
withdrawalKeyKind
|
|
|
|
|
2020-06-23 19:11:07 +00:00
|
|
|
UUID* = distinct string
|
|
|
|
WalletName* = distinct string
|
2020-06-01 19:48:20 +00:00
|
|
|
Mnemonic* = distinct string
|
|
|
|
KeyPath* = distinct string
|
|
|
|
KeySeed* = distinct seq[byte]
|
2020-10-02 15:46:05 +00:00
|
|
|
KeystorePass* = object
|
|
|
|
str*: string
|
2020-06-01 19:48:20 +00:00
|
|
|
|
|
|
|
Credentials* = object
|
|
|
|
mnemonic*: Mnemonic
|
2020-08-02 17:26:57 +00:00
|
|
|
keystore*: Keystore
|
2020-06-01 19:48:20 +00:00
|
|
|
signingKey*: ValidatorPrivKey
|
|
|
|
withdrawalKey*: ValidatorPrivKey
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
SimpleHexEncodedTypes = ScryptSalt|ChecksumBytes|CipherBytes
|
|
|
|
|
2020-05-19 17:30:28 +00:00
|
|
|
const
|
2020-08-02 17:26:57 +00:00
|
|
|
keyLen = 32
|
2020-05-20 07:22:21 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
scryptParams = ScryptParams(
|
2020-10-09 16:41:53 +00:00
|
|
|
dklen: uint64 keyLen,
|
2020-05-19 17:30:28 +00:00
|
|
|
n: 2^18,
|
2020-08-02 18:46:12 +00:00
|
|
|
p: 1,
|
|
|
|
r: 8
|
2020-05-19 17:30:28 +00:00
|
|
|
)
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
pbkdf2Params = Pbkdf2Params(
|
2020-10-09 16:41:53 +00:00
|
|
|
dklen: uint64 keyLen,
|
|
|
|
c: uint64(2^18),
|
2020-08-02 17:26:57 +00:00
|
|
|
prf: HmacSha256
|
2020-05-19 17:30:28 +00:00
|
|
|
)
|
|
|
|
|
2020-06-01 19:48:20 +00:00
|
|
|
# https://eips.ethereum.org/EIPS/eip-2334
|
|
|
|
eth2KeyPurpose = 12381
|
|
|
|
eth2CoinType* = 3600
|
2020-10-19 19:02:48 +00:00
|
|
|
baseKeyPath* = [Natural eth2KeyPurpose, eth2CoinType]
|
2020-06-01 19:48:20 +00:00
|
|
|
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md
|
|
|
|
wordListLen = 2048
|
2020-08-21 19:36:42 +00:00
|
|
|
maxWordLen = 16
|
2020-06-02 19:59:51 +00:00
|
|
|
|
2020-06-23 19:11:07 +00:00
|
|
|
UUID.serializesAsBaseIn Json
|
2020-08-02 17:26:57 +00:00
|
|
|
KeyPath.serializesAsBaseIn Json
|
2020-06-23 19:11:07 +00:00
|
|
|
WalletName.serializesAsBaseIn Json
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
ChecksumFunctionKind.serializesAsTextInJson
|
|
|
|
CipherFunctionKind.serializesAsTextInJson
|
|
|
|
PrfKind.serializesAsTextInJson
|
|
|
|
KdfKind.serializesAsTextInJson
|
|
|
|
|
2020-06-23 19:11:07 +00:00
|
|
|
template `$`*(m: Mnemonic): string =
|
|
|
|
string(m)
|
|
|
|
|
2020-07-17 20:59:50 +00:00
|
|
|
template `==`*(lhs, rhs: WalletName): bool =
|
|
|
|
string(lhs) == string(rhs)
|
|
|
|
|
|
|
|
template `$`*(x: WalletName): string =
|
|
|
|
string(x)
|
|
|
|
|
2020-10-19 19:02:48 +00:00
|
|
|
# TODO: `burnMem` in nimcrypto could use distinctBase
|
|
|
|
# to make its usage less error-prone.
|
|
|
|
template burnMem*(m: var (Mnemonic|TaintedString)) =
|
2020-08-20 14:59:59 +00:00
|
|
|
ncrutils.burnMem(string m)
|
2020-06-23 19:11:07 +00:00
|
|
|
|
2020-10-19 19:02:48 +00:00
|
|
|
template burnMem*(m: var KeySeed) =
|
|
|
|
ncrutils.burnMem(distinctBase m)
|
|
|
|
|
2020-10-02 15:46:05 +00:00
|
|
|
template burnMem*(m: var KeystorePass) =
|
|
|
|
ncrutils.burnMem(m.str)
|
|
|
|
|
2020-08-21 19:36:42 +00:00
|
|
|
func longName*(wallet: Wallet): string =
|
|
|
|
if wallet.name.string == wallet.uuid.string:
|
|
|
|
wallet.name.string
|
|
|
|
else:
|
|
|
|
wallet.name.string & " (" & wallet.uuid.string & ")"
|
|
|
|
|
2020-07-07 15:51:02 +00:00
|
|
|
proc getRandomBytes*(rng: var BrHmacDrbgContext, n: Natural): seq[byte]
|
|
|
|
{.raises: [Defect].} =
|
|
|
|
result = newSeq[byte](n)
|
|
|
|
brHmacDrbgGenerate(rng, result)
|
|
|
|
|
2020-07-14 19:00:35 +00:00
|
|
|
macro wordListArray*(filename: static string,
|
|
|
|
maxWords: static int = 0,
|
2020-08-21 19:36:42 +00:00
|
|
|
minWordLen: static int = 0,
|
|
|
|
maxWordLen: static int = high(int)): untyped =
|
2020-06-02 19:59:51 +00:00
|
|
|
result = newTree(nnkBracket)
|
2021-02-02 22:31:01 +00:00
|
|
|
var words = slurp(filename.replace('\\', '/')).splitLines()
|
2020-06-02 19:59:51 +00:00
|
|
|
for word in words:
|
2020-08-21 19:36:42 +00:00
|
|
|
if word.len >= minWordLen and word.len <= maxWordLen:
|
2020-07-14 19:00:35 +00:00
|
|
|
result.add newCall("cstring", newLit(word))
|
|
|
|
if maxWords > 0 and result.len >= maxWords:
|
|
|
|
return
|
2020-06-02 19:59:51 +00:00
|
|
|
|
|
|
|
const
|
2020-07-14 19:00:35 +00:00
|
|
|
englishWords = wordListArray("english_word_list.txt",
|
2020-08-21 19:36:42 +00:00
|
|
|
maxWords = wordListLen,
|
|
|
|
maxWordLen = maxWordLen)
|
2020-09-29 16:49:09 +00:00
|
|
|
englishWordsDigest =
|
|
|
|
"AD90BF3BEB7B0EB7E5ACD74727DC0DA96E0A280A258354E7293FB7E211AC03DB".toDigest
|
|
|
|
|
|
|
|
proc checkEnglishWords(): bool =
|
|
|
|
if len(englishWords) != wordListLen:
|
|
|
|
false
|
|
|
|
else:
|
|
|
|
var ctx: sha256
|
|
|
|
ctx.init()
|
|
|
|
for item in englishWords:
|
|
|
|
ctx.update($item)
|
|
|
|
ctx.finish() == englishWordsDigest
|
2020-08-21 19:36:42 +00:00
|
|
|
|
|
|
|
static:
|
2020-09-29 16:49:09 +00:00
|
|
|
doAssert(checkEnglishWords(), "English words array is corrupted!")
|
2020-06-01 19:48:20 +00:00
|
|
|
|
2020-09-24 05:27:56 +00:00
|
|
|
func validateKeyPath*(path: TaintedString): Result[KeyPath, cstring] =
|
|
|
|
var digitCount: int
|
|
|
|
var number: BiggestUint
|
|
|
|
try:
|
|
|
|
for elem in path.string.split("/"):
|
2020-09-29 16:49:09 +00:00
|
|
|
# TODO: doesn't "m" have to be the first character and is it the only
|
|
|
|
# place where it is valid?
|
2020-09-24 05:27:56 +00:00
|
|
|
if elem == "m":
|
|
|
|
continue
|
|
|
|
# parseBiggestUInt can raise if overflow
|
|
|
|
digitCount = elem.parseBiggestUInt(number)
|
|
|
|
if digitCount == 0:
|
|
|
|
return err("Invalid derivation path")
|
|
|
|
except ValueError:
|
|
|
|
return err("KeyPath contains invalid number(s)")
|
|
|
|
|
|
|
|
return ok(KeyPath path)
|
2020-06-01 19:48:20 +00:00
|
|
|
|
|
|
|
iterator pathNodes(path: KeyPath): Natural =
|
2020-09-24 05:27:56 +00:00
|
|
|
# TODO: we have exceptions there
|
|
|
|
# and this iterator is used to derive secret keys
|
|
|
|
# if we fail we want to scrub secrets from memory
|
2020-06-01 19:48:20 +00:00
|
|
|
try:
|
2020-09-24 05:27:56 +00:00
|
|
|
for elem in path.string.split("/"):
|
|
|
|
if elem == "m": continue
|
|
|
|
yield parseBiggestUInt(elem)
|
2020-06-01 19:48:20 +00:00
|
|
|
except ValueError:
|
|
|
|
doAssert false, "Make sure you've validated the key path with `validateKeyPath`"
|
|
|
|
|
|
|
|
func makeKeyPath*(validatorIdx: Natural,
|
|
|
|
keyType: Eth2KeyKind): KeyPath =
|
|
|
|
# https://eips.ethereum.org/EIPS/eip-2334
|
|
|
|
let use = case keyType
|
|
|
|
of withdrawalKeyKind: "0"
|
|
|
|
of signingKeyKind: "0/0"
|
|
|
|
|
|
|
|
try:
|
|
|
|
KeyPath &"m/{eth2KeyPurpose}/{eth2CoinType}/{validatorIdx}/{use}"
|
|
|
|
except ValueError:
|
|
|
|
raiseAssert "All values above can be converted successfully to strings"
|
|
|
|
|
2020-10-02 15:46:05 +00:00
|
|
|
func isControlRune(r: Rune): bool =
|
|
|
|
let r = int r
|
|
|
|
(r >= 0 and r < 0x20) or (r >= 0x7F and r < 0xA0)
|
|
|
|
|
|
|
|
proc init*(T: type KeystorePass, input: string): T =
|
|
|
|
for rune in toNFKD(input):
|
|
|
|
if not isControlRune(rune):
|
|
|
|
result.str.add rune
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
func getSeed*(mnemonic: Mnemonic, password: KeystorePass): KeySeed =
|
2020-06-01 19:48:20 +00:00
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed
|
2020-10-02 16:08:57 +00:00
|
|
|
let salt = toNFKD("mnemonic" & password.str)
|
2020-06-01 19:48:20 +00:00
|
|
|
KeySeed sha512.pbkdf2(mnemonic.string, salt, 2048, 64)
|
|
|
|
|
2020-08-21 19:36:42 +00:00
|
|
|
template add(m: var Mnemonic, s: cstring) =
|
|
|
|
m.string.add s
|
|
|
|
|
2020-07-07 15:51:02 +00:00
|
|
|
proc generateMnemonic*(
|
|
|
|
rng: var BrHmacDrbgContext,
|
2020-10-28 18:35:31 +00:00
|
|
|
words: openArray[cstring] = englishWords,
|
|
|
|
entropyParam: openArray[byte] = @[]): Mnemonic =
|
2020-08-21 19:36:42 +00:00
|
|
|
## Generates a valid BIP-0039 mnenomic:
|
|
|
|
## https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic
|
2020-06-01 19:48:20 +00:00
|
|
|
var entropy: seq[byte]
|
|
|
|
if entropyParam.len == 0:
|
2020-07-07 15:51:02 +00:00
|
|
|
setLen(entropy, 32)
|
|
|
|
brHmacDrbgGenerate(rng, entropy)
|
2020-06-01 19:48:20 +00:00
|
|
|
else:
|
|
|
|
doAssert entropyParam.len >= 128 and
|
|
|
|
entropyParam.len <= 256 and
|
|
|
|
entropyParam.len mod 32 == 0
|
|
|
|
entropy = @entropyParam
|
|
|
|
|
|
|
|
let
|
|
|
|
checksumBits = entropy.len div 4 # ranges from 4 to 8
|
|
|
|
mnemonicWordCount = 12 + (checksumBits - 4) * 3
|
|
|
|
checksum = sha256.digest(entropy)
|
|
|
|
|
|
|
|
entropy.add byte(checksum.data.getBitsBE(0 ..< checksumBits))
|
|
|
|
|
2020-08-21 19:36:42 +00:00
|
|
|
# Make sure the string won't be reallocated as this may
|
|
|
|
# leave partial copies of the mnemonic in memory:
|
|
|
|
result = Mnemonic newStringOfCap(mnemonicWordCount * maxWordLen)
|
|
|
|
result.add words[entropy.getBitsBE(0..10)]
|
2020-06-02 19:59:51 +00:00
|
|
|
|
2020-06-01 19:48:20 +00:00
|
|
|
for i in 1 ..< mnemonicWordCount:
|
|
|
|
let
|
|
|
|
firstBit = i*11
|
|
|
|
lastBit = firstBit + 10
|
2020-08-21 19:36:42 +00:00
|
|
|
result.add " "
|
|
|
|
result.add words[entropy.getBitsBE(firstBit..lastBit)]
|
|
|
|
|
|
|
|
proc cmpIgnoreCase(lhs: cstring, rhs: string): int =
|
|
|
|
# TODO: This is a bit silly.
|
|
|
|
# Nim should have a `cmp` function for C strings.
|
|
|
|
cmpIgnoreCase($lhs, rhs)
|
|
|
|
|
|
|
|
proc validateMnemonic*(inputWords: TaintedString,
|
|
|
|
outputMnemonic: var Mnemonic): bool =
|
|
|
|
## Accept a case-insensitive input string and returns `true`
|
|
|
|
## if it represents a valid mnenomic. The `outputMnemonic`
|
|
|
|
## value will be populated with a normalized lower-case
|
|
|
|
## version of the mnemonic using a single space separator.
|
|
|
|
##
|
|
|
|
## The `outputMnemonic` value may be populated partially
|
|
|
|
## with sensitive data even in case of validator failure.
|
|
|
|
## Make sure to burn the received data after usage.
|
|
|
|
|
2020-10-02 15:46:05 +00:00
|
|
|
let words = strutils.strip(inputWords.string.toNFKD).split(Whitespace)
|
2020-08-21 19:36:42 +00:00
|
|
|
if words.len < 12 or words.len > 24 or words.len mod 3 != 0:
|
|
|
|
return false
|
|
|
|
|
|
|
|
# Make sure the string won't be re-allocated as this may
|
|
|
|
# leave partial copies of the mnemonic in memory:
|
|
|
|
outputMnemonic = Mnemonic newStringOfCap(words.len * maxWordLen)
|
2020-06-01 19:48:20 +00:00
|
|
|
|
2020-08-21 19:36:42 +00:00
|
|
|
for word in words:
|
|
|
|
let foundIdx = binarySearch(englishWords, word, cmpIgnoreCase)
|
|
|
|
if foundIdx == -1:
|
|
|
|
return false
|
|
|
|
if outputMnemonic.string.len > 0:
|
|
|
|
outputMnemonic.add " "
|
|
|
|
outputMnemonic.add englishWords[foundIdx]
|
|
|
|
|
|
|
|
return true
|
2020-06-01 19:48:20 +00:00
|
|
|
|
|
|
|
proc deriveChildKey*(parentKey: ValidatorPrivKey,
|
|
|
|
index: Natural): ValidatorPrivKey =
|
2020-06-08 14:56:56 +00:00
|
|
|
let success = derive_child_secretKey(SecretKey result,
|
|
|
|
SecretKey parentKey,
|
|
|
|
uint32 index)
|
|
|
|
# TODO `derive_child_secretKey` is reporting pre-condition
|
|
|
|
# failures with return values. We should turn the checks
|
|
|
|
# into asserts inside the function.
|
|
|
|
doAssert success
|
2020-06-01 19:48:20 +00:00
|
|
|
|
|
|
|
proc deriveMasterKey*(seed: KeySeed): ValidatorPrivKey =
|
2020-06-08 14:56:56 +00:00
|
|
|
let success = derive_master_secretKey(SecretKey result,
|
|
|
|
seq[byte] seed)
|
|
|
|
# TODO `derive_master_secretKey` is reporting pre-condition
|
|
|
|
# failures with return values. We should turn the checks
|
|
|
|
# into asserts inside the function.
|
|
|
|
doAssert success
|
2020-06-01 19:48:20 +00:00
|
|
|
|
|
|
|
proc deriveMasterKey*(mnemonic: Mnemonic,
|
2020-08-02 17:26:57 +00:00
|
|
|
password: KeystorePass): ValidatorPrivKey =
|
2020-06-01 19:48:20 +00:00
|
|
|
deriveMasterKey(getSeed(mnemonic, password))
|
|
|
|
|
|
|
|
proc deriveChildKey*(masterKey: ValidatorPrivKey,
|
|
|
|
path: KeyPath): ValidatorPrivKey =
|
|
|
|
result = masterKey
|
|
|
|
for idx in pathNodes(path):
|
2020-10-19 19:02:48 +00:00
|
|
|
result = deriveChildKey(result, idx)
|
|
|
|
|
|
|
|
proc deriveChildKey*(masterKey: ValidatorPrivKey,
|
|
|
|
path: openArray[Natural]): ValidatorPrivKey =
|
|
|
|
result = masterKey
|
|
|
|
for idx in path:
|
2020-09-24 05:27:56 +00:00
|
|
|
# TODO: we have exceptions in pathNodes unless `validateKeyPath`
|
|
|
|
# was called,
|
|
|
|
# and this iterator is used to derive secret keys
|
|
|
|
# if we fail we want to scrub secrets from memory
|
2020-06-01 19:48:20 +00:00
|
|
|
result = deriveChildKey(result, idx)
|
|
|
|
|
|
|
|
proc keyFromPath*(mnemonic: Mnemonic,
|
2020-08-02 17:26:57 +00:00
|
|
|
password: KeystorePass,
|
2020-06-01 19:48:20 +00:00
|
|
|
path: KeyPath): ValidatorPrivKey =
|
|
|
|
deriveChildKey(deriveMasterKey(mnemonic, password), path)
|
|
|
|
|
2020-10-28 18:35:31 +00:00
|
|
|
proc shaChecksum(key, cipher: openArray[byte]): Sha256Digest =
|
2020-05-19 17:30:28 +00:00
|
|
|
var ctx: sha256
|
|
|
|
ctx.init()
|
|
|
|
ctx.update(key)
|
|
|
|
ctx.update(cipher)
|
2020-08-02 17:26:57 +00:00
|
|
|
result = ctx.finish()
|
2020-05-20 07:22:21 +00:00
|
|
|
ctx.clear()
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-10-28 18:35:31 +00:00
|
|
|
proc writeJsonHexString(s: OutputStream, data: openArray[byte])
|
2020-08-02 17:26:57 +00:00
|
|
|
{.raises: [IOError, Defect].} =
|
|
|
|
s.write '"'
|
2020-08-20 13:01:08 +00:00
|
|
|
s.write ncrutils.toHex(data, {HexFlags.LowerCase})
|
2020-08-02 17:26:57 +00:00
|
|
|
s.write '"'
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
proc readValue*(r: var JsonReader, value: var Pbkdf2Salt)
|
|
|
|
{.raises: [SerializationError, IOError, Defect].} =
|
|
|
|
var s = r.readValue(string)
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
if s.len == 0 or s.len mod 16 != 0:
|
|
|
|
r.raiseUnexpectedValue(
|
2020-08-20 13:01:08 +00:00
|
|
|
"The Pbkdf2Salt salt must have a non-zero length divisible by 16")
|
2020-05-27 14:05:32 +00:00
|
|
|
|
2020-08-20 13:01:08 +00:00
|
|
|
value = Pbkdf2Salt ncrutils.fromHex(s)
|
|
|
|
let length = len(seq[byte](value))
|
|
|
|
if length == 0 or (length mod 8) != 0:
|
2020-08-02 17:26:57 +00:00
|
|
|
r.raiseUnexpectedValue(
|
|
|
|
"The Pbkdf2Salt must be a valid hex string")
|
|
|
|
|
|
|
|
proc readValue*(r: var JsonReader, value: var Aes128CtrIv)
|
|
|
|
{.raises: [SerializationError, IOError, Defect].} =
|
|
|
|
var s = r.readValue(string)
|
|
|
|
|
|
|
|
if s.len != 32:
|
|
|
|
r.raiseUnexpectedValue(
|
|
|
|
"The aes-128-ctr IV must be a string of length 32")
|
|
|
|
|
2020-08-20 13:01:08 +00:00
|
|
|
value = Aes128CtrIv ncrutils.fromHex(s)
|
|
|
|
if len(seq[byte](value)) != 16:
|
2020-08-02 17:26:57 +00:00
|
|
|
r.raiseUnexpectedValue(
|
|
|
|
"The aes-128-ctr IV must be a valid hex string")
|
|
|
|
|
|
|
|
proc readValue*[T: SimpleHexEncodedTypes](r: var JsonReader, value: var T)
|
|
|
|
{.raises: [SerializationError, IOError, Defect].} =
|
2020-08-20 13:01:08 +00:00
|
|
|
value = T ncrutils.fromHex(r.readValue(string))
|
|
|
|
if len(seq[byte](value)) == 0:
|
2020-08-02 17:26:57 +00:00
|
|
|
r.raiseUnexpectedValue("Valid hex string expected")
|
|
|
|
|
|
|
|
proc readValue*(r: var JsonReader, value: var Kdf)
|
|
|
|
{.raises: [SerializationError, IOError, Defect].} =
|
|
|
|
var
|
|
|
|
functionSpecified = false
|
|
|
|
paramsSpecified = false
|
|
|
|
|
|
|
|
for fieldName in readObjectFields(r):
|
|
|
|
case fieldName
|
|
|
|
of "function":
|
|
|
|
value.function = r.readValue(KdfKind)
|
|
|
|
functionSpecified = true
|
|
|
|
|
|
|
|
of "params":
|
|
|
|
if functionSpecified:
|
|
|
|
case value.function
|
|
|
|
of kdfPbkdf2:
|
|
|
|
r.readValue(value.pbkdf2Params)
|
|
|
|
of kdfScrypt:
|
|
|
|
r.readValue(value.scryptParams)
|
|
|
|
else:
|
|
|
|
r.raiseUnexpectedValue(
|
|
|
|
"The 'params' field must be specified after the 'function' field")
|
|
|
|
paramsSpecified = true
|
|
|
|
|
|
|
|
of "message":
|
|
|
|
r.readValue(value.message)
|
|
|
|
|
|
|
|
else:
|
|
|
|
r.raiseUnexpectedField(fieldName, "Kdf")
|
|
|
|
|
|
|
|
if not (functionSpecified and paramsSpecified):
|
|
|
|
r.raiseUnexpectedValue(
|
|
|
|
"The Kdf value should have sub-fields named 'function' and 'params'")
|
|
|
|
|
|
|
|
template writeValue*(w: var JsonWriter,
|
|
|
|
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
|
|
|
|
writeJsonHexString(w.stream, distinctBase value)
|
|
|
|
|
|
|
|
template bytes(value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv): seq[byte] =
|
|
|
|
distinctBase value
|
|
|
|
|
2020-08-02 18:46:12 +00:00
|
|
|
func scrypt(password: openArray[char], salt: openArray[byte],
|
|
|
|
N, r, p, keyLen: static[int]): array[keyLen, byte] =
|
|
|
|
let (xyvLen, bLen) = scryptCalc(N, r, p)
|
|
|
|
var xyv = newSeq[uint32](xyvLen)
|
|
|
|
var b = newSeq[byte](bLen)
|
|
|
|
discard scrypt(password, salt, N, r, p, xyv, b, result)
|
|
|
|
|
2020-10-09 16:41:53 +00:00
|
|
|
func areValid(params: Pbkdf2Params): bool =
|
|
|
|
# https://www.ietf.org/rfc/rfc2898.txt
|
|
|
|
if params.c == 0 or params.dkLen == 0 or params.salt.bytes.len == 0:
|
|
|
|
return false
|
|
|
|
|
|
|
|
let hLen = case params.prf
|
|
|
|
of HmacSha256: 256 / 8
|
|
|
|
|
|
|
|
params.dklen <= high(uint32).uint64 * hLen.uint64
|
|
|
|
|
|
|
|
func areValid(params: ScryptParams): bool =
|
|
|
|
params.dklen == scryptParams.dklen and
|
|
|
|
params.n == scryptParams.n and
|
|
|
|
params.r == scryptParams.r and
|
|
|
|
params.p == scryptParams.p and
|
|
|
|
params.salt.bytes.len > 0
|
|
|
|
|
|
|
|
proc decryptCryptoField*(crypto: Crypto,
|
|
|
|
password: KeystorePass,
|
|
|
|
outSecret: var seq[byte]): DecryptionStatus =
|
|
|
|
if crypto.cipher.message.bytes.len == 0:
|
|
|
|
return InvalidKeystore
|
2020-08-02 17:26:57 +00:00
|
|
|
|
|
|
|
let decKey = case crypto.kdf.function
|
|
|
|
of kdfPbkdf2:
|
|
|
|
template params: auto = crypto.kdf.pbkdf2Params
|
2020-10-09 16:41:53 +00:00
|
|
|
if not params.areValid or params.c > high(int).uint64:
|
|
|
|
return InvalidKeystore
|
|
|
|
sha256.pbkdf2(password.str,
|
|
|
|
params.salt.bytes,
|
|
|
|
int params.c,
|
|
|
|
int params.dklen)
|
2020-08-02 17:26:57 +00:00
|
|
|
of kdfScrypt:
|
2020-08-02 18:46:12 +00:00
|
|
|
template params: auto = crypto.kdf.scryptParams
|
2020-10-09 16:41:53 +00:00
|
|
|
if not params.areValid:
|
|
|
|
return InvalidKeystore
|
2020-10-02 15:46:05 +00:00
|
|
|
@(scrypt(password.str,
|
2020-08-02 18:46:12 +00:00
|
|
|
params.salt.bytes,
|
|
|
|
scryptParams.n,
|
|
|
|
scryptParams.r,
|
|
|
|
scryptParams.p,
|
2020-10-09 16:41:53 +00:00
|
|
|
int scryptParams.dklen))
|
2020-08-02 17:26:57 +00:00
|
|
|
|
|
|
|
let derivedChecksum = shaChecksum(decKey.toOpenArray(16, 31),
|
|
|
|
crypto.cipher.message.bytes)
|
|
|
|
if derivedChecksum != crypto.checksum.message:
|
2020-10-09 16:41:53 +00:00
|
|
|
return InvalidPassword
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-10-09 16:41:53 +00:00
|
|
|
var aesCipher: CTR[aes128]
|
|
|
|
outSecret.setLen(crypto.cipher.message.bytes.len)
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
aesCipher.init(decKey.toOpenArray(0, 15), crypto.cipher.params.iv.bytes)
|
2020-10-09 16:41:53 +00:00
|
|
|
aesCipher.decrypt(crypto.cipher.message.bytes, outSecret)
|
2020-05-19 17:30:28 +00:00
|
|
|
aesCipher.clear()
|
|
|
|
|
2020-10-09 16:41:53 +00:00
|
|
|
return Success
|
2020-06-23 19:11:07 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
func cstringToStr(v: cstring): string = $v
|
|
|
|
|
|
|
|
proc decryptKeystore*(keystore: Keystore,
|
|
|
|
password: KeystorePass): KsResult[ValidatorPrivKey] =
|
2020-10-09 16:41:53 +00:00
|
|
|
var secret: seq[byte]
|
|
|
|
defer: burnMem(secret)
|
|
|
|
let status = decryptCryptoField(keystore.crypto, password, secret)
|
|
|
|
case status
|
|
|
|
of Success:
|
|
|
|
ValidatorPrivKey.fromRaw(secret).mapErr(cstringToStr)
|
|
|
|
else:
|
|
|
|
err $status
|
2020-06-23 19:11:07 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
proc decryptKeystore*(keystore: JsonString,
|
|
|
|
password: KeystorePass): KsResult[ValidatorPrivKey] =
|
|
|
|
let keystore = try: Json.decode(keystore.string, Keystore)
|
|
|
|
except SerializationError as e:
|
|
|
|
return err e.formatMsg("<keystore>")
|
|
|
|
decryptKeystore(keystore, password)
|
2020-06-23 19:11:07 +00:00
|
|
|
|
2020-08-20 13:01:08 +00:00
|
|
|
proc writeValue*(writer: var JsonWriter, value: lcrypto.PublicKey) {.
|
|
|
|
inline, raises: [IOError, Defect].} =
|
|
|
|
writer.writeValue(ncrutils.toHex(value.getBytes().get(),
|
|
|
|
{HexFlags.LowerCase}))
|
|
|
|
|
|
|
|
proc readValue*(reader: var JsonReader, value: var lcrypto.PublicKey) {.
|
|
|
|
raises: [SerializationError, IOError, Defect].} =
|
|
|
|
let res = init(lcrypto.PublicKey, reader.readValue(string))
|
|
|
|
if res.isOk():
|
|
|
|
value = res.get()
|
|
|
|
else:
|
|
|
|
# TODO: Can we provide better diagnostic?
|
|
|
|
raiseUnexpectedValue(reader, "Valid hex-encoded public key expected")
|
|
|
|
|
|
|
|
proc decryptNetKeystore*(nkeystore: NetKeystore,
|
|
|
|
password: KeystorePass): KsResult[lcrypto.PrivateKey] =
|
2020-10-09 16:41:53 +00:00
|
|
|
var secret: seq[byte]
|
|
|
|
defer: burnMem(secret)
|
|
|
|
let status = decryptCryptoField(nkeystore.crypto, password, secret)
|
|
|
|
case status
|
|
|
|
of Success:
|
|
|
|
let res = lcrypto.PrivateKey.init(secret)
|
|
|
|
if res.isOk:
|
|
|
|
ok res.get
|
2020-08-20 13:01:08 +00:00
|
|
|
else:
|
2020-10-09 16:41:53 +00:00
|
|
|
err "Invalid key"
|
2020-08-20 13:01:08 +00:00
|
|
|
else:
|
2020-10-09 16:41:53 +00:00
|
|
|
err $status
|
2020-08-20 13:01:08 +00:00
|
|
|
|
|
|
|
proc decryptNetKeystore*(nkeystore: JsonString,
|
|
|
|
password: KeystorePass): KsResult[lcrypto.PrivateKey] =
|
|
|
|
try:
|
|
|
|
let keystore = Json.decode(string(nkeystore), NetKeystore)
|
|
|
|
return decryptNetKeystore(keystore, password)
|
|
|
|
except SerializationError as exc:
|
|
|
|
return err(exc.formatMsg("<keystore>"))
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
proc createCryptoField(kdfKind: KdfKind,
|
2020-07-07 15:51:02 +00:00
|
|
|
rng: var BrHmacDrbgContext,
|
2020-10-28 18:35:31 +00:00
|
|
|
secret: openArray[byte],
|
2020-10-02 15:46:05 +00:00
|
|
|
password = KeystorePass.init "",
|
2020-10-28 18:35:31 +00:00
|
|
|
salt: openArray[byte] = @[],
|
|
|
|
iv: openArray[byte] = @[]): Crypto =
|
2020-06-23 19:11:07 +00:00
|
|
|
type AES = aes128
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 18:46:12 +00:00
|
|
|
let kdfSalt =
|
2020-08-02 17:26:57 +00:00
|
|
|
if salt.len > 0:
|
|
|
|
doAssert salt.len == keyLen
|
|
|
|
@salt
|
|
|
|
else:
|
|
|
|
getRandomBytes(rng, keyLen)
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-06-23 19:11:07 +00:00
|
|
|
let aesIv = if iv.len > 0:
|
|
|
|
doAssert iv.len == AES.sizeBlock
|
|
|
|
@iv
|
2020-06-01 19:48:20 +00:00
|
|
|
else:
|
2020-07-07 15:51:02 +00:00
|
|
|
getRandomBytes(rng, AES.sizeBlock)
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 18:46:12 +00:00
|
|
|
var decKey: seq[byte]
|
2020-08-02 17:26:57 +00:00
|
|
|
let kdf = case kdfKind
|
|
|
|
of kdfPbkdf2:
|
2020-10-02 15:46:05 +00:00
|
|
|
decKey = sha256.pbkdf2(password.str,
|
2020-08-02 18:46:12 +00:00
|
|
|
kdfSalt,
|
2020-10-09 16:41:53 +00:00
|
|
|
int pbkdf2Params.c,
|
|
|
|
int pbkdf2Params.dklen)
|
2020-08-02 17:26:57 +00:00
|
|
|
var params = pbkdf2Params
|
2020-08-02 18:46:12 +00:00
|
|
|
params.salt = Pbkdf2Salt kdfSalt
|
2020-08-02 17:26:57 +00:00
|
|
|
Kdf(function: kdfPbkdf2, pbkdf2Params: params, message: "")
|
|
|
|
of kdfScrypt:
|
2020-10-02 15:46:05 +00:00
|
|
|
decKey = @(scrypt(password.str, kdfSalt,
|
2020-08-02 18:46:12 +00:00
|
|
|
scryptParams.n, scryptParams.r, scryptParams.p, keyLen))
|
|
|
|
var params = scryptParams
|
|
|
|
params.salt = ScryptSalt kdfSalt
|
|
|
|
Kdf(function: kdfScrypt, scryptParams: params, message: "")
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
var
|
|
|
|
aesCipher: CTR[AES]
|
|
|
|
cipherMsg = newSeq[byte](secret.len)
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-05-20 07:22:21 +00:00
|
|
|
aesCipher.init(decKey.toOpenArray(0, 15), aesIv)
|
2020-05-19 17:30:28 +00:00
|
|
|
aesCipher.encrypt(secret, cipherMsg)
|
|
|
|
aesCipher.clear()
|
|
|
|
|
2020-06-23 19:11:07 +00:00
|
|
|
let sum = shaChecksum(decKey.toOpenArray(16, 31), cipherMsg)
|
2020-05-19 17:30:28 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
Crypto(
|
2020-06-23 19:11:07 +00:00
|
|
|
kdf: kdf,
|
|
|
|
checksum: Checksum(
|
2020-08-02 17:26:57 +00:00
|
|
|
function: sha256Checksum,
|
|
|
|
message: sum),
|
2020-06-23 19:11:07 +00:00
|
|
|
cipher: Cipher(
|
2020-08-02 17:26:57 +00:00
|
|
|
function: aes128CtrCipher,
|
|
|
|
params: Aes128CtrParams(iv: Aes128CtrIv aesIv),
|
|
|
|
message: CipherBytes cipherMsg))
|
|
|
|
|
2020-08-20 13:01:08 +00:00
|
|
|
proc createNetKeystore*(kdfKind: KdfKind,
|
|
|
|
rng: var BrHmacDrbgContext,
|
|
|
|
privKey: lcrypto.PrivateKey,
|
2020-10-02 15:46:05 +00:00
|
|
|
password = KeystorePass.init "",
|
2020-08-20 13:01:08 +00:00
|
|
|
description = "",
|
2020-10-28 18:35:31 +00:00
|
|
|
salt: openArray[byte] = @[],
|
|
|
|
iv: openArray[byte] = @[]): NetKeystore =
|
2020-08-20 13:01:08 +00:00
|
|
|
let
|
|
|
|
secret = privKey.getBytes().get()
|
|
|
|
cryptoField = createCryptoField(kdfKind, rng, secret, password, salt, iv)
|
|
|
|
pubKey = privKey.getKey().get()
|
|
|
|
uuid = uuidGenerate().expect("Random bytes should be available")
|
|
|
|
|
|
|
|
NetKeystore(
|
|
|
|
crypto: cryptoField,
|
|
|
|
pubkey: pubKey,
|
|
|
|
description: newClone(description),
|
|
|
|
uuid: $uuid,
|
2020-09-30 11:04:54 +00:00
|
|
|
version: 1
|
2020-08-20 13:01:08 +00:00
|
|
|
)
|
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
proc createKeystore*(kdfKind: KdfKind,
|
|
|
|
rng: var BrHmacDrbgContext,
|
|
|
|
privKey: ValidatorPrivkey,
|
2020-10-02 15:46:05 +00:00
|
|
|
password = KeystorePass.init "",
|
2020-08-02 17:26:57 +00:00
|
|
|
path = KeyPath "",
|
2020-08-02 17:54:48 +00:00
|
|
|
description = "",
|
2020-10-28 18:35:31 +00:00
|
|
|
salt: openArray[byte] = @[],
|
|
|
|
iv: openArray[byte] = @[]): Keystore =
|
2020-06-23 19:11:07 +00:00
|
|
|
let
|
|
|
|
secret = privKey.toRaw[^32..^1]
|
2020-08-02 17:26:57 +00:00
|
|
|
cryptoField = createCryptoField(kdfKind, rng, secret, password, salt, iv)
|
2020-06-23 19:11:07 +00:00
|
|
|
pubkey = privKey.toPubKey()
|
|
|
|
uuid = uuidGenerate().expect("Random bytes should be available")
|
2020-06-01 19:48:20 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
Keystore(
|
|
|
|
crypto: cryptoField,
|
2021-06-01 11:13:40 +00:00
|
|
|
pubkey: pubkey.toPubKey(),
|
2020-08-02 17:26:57 +00:00
|
|
|
path: path,
|
2020-08-05 22:38:55 +00:00
|
|
|
description: newClone(description),
|
2020-08-02 17:26:57 +00:00
|
|
|
uuid: $uuid,
|
|
|
|
version: 4)
|
2020-06-01 19:48:20 +00:00
|
|
|
|
2020-08-02 17:26:57 +00:00
|
|
|
proc createWallet*(kdfKind: KdfKind,
|
2020-07-07 15:51:02 +00:00
|
|
|
rng: var BrHmacDrbgContext,
|
2020-10-19 19:02:48 +00:00
|
|
|
seed: KeySeed,
|
2020-06-23 19:11:07 +00:00
|
|
|
name = WalletName "",
|
2020-10-28 18:35:31 +00:00
|
|
|
salt: openArray[byte] = @[],
|
|
|
|
iv: openArray[byte] = @[],
|
2020-10-02 15:46:05 +00:00
|
|
|
password = KeystorePass.init "",
|
2020-06-23 19:11:07 +00:00
|
|
|
nextAccount = none(Natural),
|
|
|
|
pretty = true): Wallet =
|
|
|
|
let
|
|
|
|
uuid = UUID $(uuidGenerate().expect("Random bytes should be available"))
|
2020-08-02 17:26:57 +00:00
|
|
|
crypto = createCryptoField(kdfKind, rng, distinctBase seed,
|
|
|
|
password, salt, iv)
|
2020-06-23 19:11:07 +00:00
|
|
|
Wallet(
|
|
|
|
uuid: uuid,
|
|
|
|
name: if name.string.len > 0: name
|
|
|
|
else: WalletName(uuid),
|
|
|
|
version: 1,
|
|
|
|
walletType: "hierarchical deterministic",
|
2020-08-02 17:26:57 +00:00
|
|
|
crypto: crypto,
|
2020-06-23 19:11:07 +00:00
|
|
|
nextAccount: nextAccount.get(0))
|
|
|
|
|
2021-08-20 23:37:45 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v0.12.2/specs/phase0/deposit-contract.md#withdrawal-credentials
|
2021-06-01 11:13:40 +00:00
|
|
|
func makeWithdrawalCredentials*(k: ValidatorPubKey): Eth2Digest =
|
2020-06-16 12:16:43 +00:00
|
|
|
var bytes = eth2digest(k.toRaw())
|
2020-06-01 19:48:20 +00:00
|
|
|
bytes.data[0] = BLS_WITHDRAWAL_PREFIX.uint8
|
|
|
|
bytes
|
|
|
|
|
2021-08-20 23:37:45 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v0.12.2/specs/phase0/deposit-contract.md#withdrawal-credentials
|
2021-06-01 11:13:40 +00:00
|
|
|
proc makeWithdrawalCredentials*(k: CookedPubKey): Eth2Digest =
|
|
|
|
makeWithdrawalCredentials(k.toPubKey())
|
|
|
|
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
proc prepareDeposit*(cfg: RuntimeConfig,
|
2021-06-01 11:13:40 +00:00
|
|
|
withdrawalPubKey: CookedPubKey,
|
|
|
|
signingKey: ValidatorPrivKey, signingPubKey: CookedPubKey,
|
2020-07-17 20:59:50 +00:00
|
|
|
amount = MAX_EFFECTIVE_BALANCE.Gwei): DepositData =
|
|
|
|
var res = DepositData(
|
|
|
|
amount: amount,
|
2021-06-01 11:13:40 +00:00
|
|
|
pubkey: signingPubKey.toPubKey(),
|
2020-07-17 20:59:50 +00:00
|
|
|
withdrawal_credentials: makeWithdrawalCredentials(withdrawalPubKey))
|
|
|
|
|
Implement split preset/config support (#2710)
* Implement split preset/config support
This is the initial bulk refactor to introduce runtime config values in
a number of places, somewhat replacing the existing mechanism of loading
network metadata.
It still needs more work, this is the initial refactor that introduces
runtime configuration in some of the places that need it.
The PR changes the way presets and constants work, to match the spec. In
particular, a "preset" now refers to the compile-time configuration
while a "cfg" or "RuntimeConfig" is the dynamic part.
A single binary can support either mainnet or minimal, but not both.
Support for other presets has been removed completely (can be readded,
in case there's need).
There's a number of outstanding tasks:
* `SECONDS_PER_SLOT` still needs fixing
* loading custom runtime configs needs redoing
* checking constants against YAML file
* yeerongpilly support
`build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG`
* load fork epoch from config
* fix fork digest sent in status
* nicer error string for request failures
* fix tools
* one more
* fixup
* fixup
* fixup
* use "standard" network definition folder in local testnet
Files are loaded from their standard locations, including genesis etc,
to conform to the format used in the `eth2-networks` repo.
* fix launch scripts, allow unknown config values
* fix base config of rest test
* cleanups
* bundle mainnet config using common loader
* fix spec links and names
* only include supported preset in binary
* drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
|
|
|
res.signature = get_deposit_signature(cfg, res, signingKey).toValidatorSig()
|
2020-07-17 20:59:50 +00:00
|
|
|
return res
|