commit
6ec7ae077a
|
@ -87,4 +87,10 @@
|
|||
url = https://github.com/arnetheduck/nim-result
|
||||
[submodule "vendor/nim-byteutils"]
|
||||
path = vendor/nim-byteutils
|
||||
url = https://github.com/status-im/nim-byteutils/
|
||||
url = https://github.com/status-im/nim-byteutils
|
||||
[submodule "vendor/nim-normalize"]
|
||||
path = vendor/nim-normalize
|
||||
url = https://github.com/nitely/nim-normalize
|
||||
[submodule "vendor/nim-unicodedb"]
|
||||
path = vendor/nim-unicodedb
|
||||
url = https://github.com/nitely/nim-unicodedb
|
||||
|
|
|
@ -76,3 +76,5 @@ task tests, "Run all tests":
|
|||
buildAndRunTest "callrpc"
|
||||
buildAndRunTest "migrations"
|
||||
buildAndRunTest "mailservers"
|
||||
buildAndRunTest "bip32"
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import
|
||||
std/[parseutils, strutils],
|
||||
normalize,
|
||||
secp256k1,
|
||||
stew/[results],
|
||||
nimcrypto/[sha2, pbkdf2, hash, hmac],
|
||||
account/[types, paths]
|
||||
|
||||
export KeySeed, Mnemonic, SecretKeyResult, KeyPath
|
||||
|
||||
proc getSeed*(mnemonic: Mnemonic, password: KeystorePass = ""): KeySeed =
|
||||
let salt = toNFKD("mnemonic" & password)
|
||||
KeySeed sha512.pbkdf2(mnemonic.string, salt, 2048, 64)
|
||||
|
||||
proc child(self: ExtendedPrivKey, child: PathLevel): ExtendedPrivKeyResult =
|
||||
var hctx: HMAC[sha512]
|
||||
hctx.init(self.chainCode)
|
||||
if child.isNonHardened():
|
||||
hctx.update(self.secretKey.toPublicKey().toRawCompressed())
|
||||
else:
|
||||
hctx.update([0.byte])
|
||||
hctx.update(self.secretKey.toRaw())
|
||||
hctx.update(child.toBEBytes());
|
||||
let hmacResult = hctx.finish();
|
||||
|
||||
var secretKey = hmacResult.data[0..31]
|
||||
let chainCode = hmacResult.data[32..63]
|
||||
|
||||
var pk = self.secretKey.toRaw()[0..^1]
|
||||
var sk = SkSecretKey.fromRaw(secretKey)
|
||||
if sk.isOk:
|
||||
let tweakResult = tweakAdd(sk.get(), pk)
|
||||
if tweakResult.isErr: return err($tweakResult.error())
|
||||
return ok(ExtendedPrivKey(
|
||||
secretKey: sk.get(),
|
||||
chainCode: chainCode
|
||||
))
|
||||
|
||||
err($sk.error())
|
||||
|
||||
proc derive*(seed: Keyseed, path: KeyPath): SecretKeyResult =
|
||||
let hmacResult = sha512.hmac("Bitcoin seed", seq[byte] seed)
|
||||
let secretKey = hmacResult.data[0..31]
|
||||
let chainCode = hmacResult.data[32..63]
|
||||
let sk = SkSecretKey.fromRaw(secretKey)
|
||||
if sk.isErr(): return err("Invalid secret key")
|
||||
|
||||
var extPrivK = ExtendedPrivKey(
|
||||
secretKey: sk.get(),
|
||||
chainCode: chainCode
|
||||
)
|
||||
|
||||
for child in path.pathNodes:
|
||||
if child.isErr(): return err(child.error())
|
||||
|
||||
let r = extPrivK.child(child.get())
|
||||
if r.isErr(): return err(r.error())
|
||||
extPrivK = r.get()
|
||||
|
||||
ok(extPrivK.secretKey)
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import strutils
|
||||
import stew/[results]
|
||||
import ./types
|
||||
|
||||
const HARDENED_INDEX: uint32 = 1 shl 31;
|
||||
|
||||
proc isNonHardened*(self: PathLevel): bool = (self.uint32 and HARDENED_INDEX) == 0
|
||||
|
||||
func parse*(T: type PathLevel, value: string): PathLevelResult =
|
||||
var child: string
|
||||
var mask: uint32
|
||||
|
||||
if value.endsWith("'"):
|
||||
child = value[0..^2]
|
||||
mask = HARDENED_INDEX
|
||||
else:
|
||||
child = value
|
||||
mask = 0
|
||||
|
||||
let index: uint32 = parseUInt(child).uint32
|
||||
if (index and HARDENED_INDEX) == 0:
|
||||
PathLevelResult.ok(PathLevel (index or mask))
|
||||
else:
|
||||
PathLevelResult.err("Invalid index number")
|
||||
|
||||
proc toBEBytes*(x: PathLevel): array[4, byte] =
|
||||
# BigEndian
|
||||
result[3] = ((x.uint32 shr 0) and 0xff).byte
|
||||
result[2] = ((x.uint32 shr 8) and 0xff).byte
|
||||
result[1] = ((x.uint32 shr 16) and 0xff).byte
|
||||
result[0] = ((x.uint32 shr 24) and 0xff).byte
|
||||
|
||||
iterator pathNodes*(path: KeyPath): PathLevelResult =
|
||||
try:
|
||||
for elem in path.string.split("/"):
|
||||
if elem == "m": continue
|
||||
yield PathLevel.parse(elem)
|
||||
except ValueError:
|
||||
doAssert false, "Invalid Key Path"
|
|
@ -0,0 +1,23 @@
|
|||
import secp256k1
|
||||
import stew/[results]
|
||||
|
||||
type
|
||||
Mnemonic* = distinct string
|
||||
|
||||
KeySeed* = distinct seq[byte]
|
||||
|
||||
KeystorePass* = string
|
||||
|
||||
KeyPath* = distinct string
|
||||
|
||||
PathLevel* = distinct uint32
|
||||
|
||||
PathLevelResult* = Result[PathLevel, string]
|
||||
|
||||
ExtendedPrivKey* = object
|
||||
secretKey*: SkSecretKey
|
||||
chainCode*: seq[byte]
|
||||
|
||||
ExtendedPrivKeyResult* = Result[ExtendedPrivKey, string]
|
||||
|
||||
SecretKeyResult* = SkResult[SkSecretKey]
|
|
@ -0,0 +1,16 @@
|
|||
import ../../nim_status/lib/account
|
||||
import stew/[results]
|
||||
import secp256k1
|
||||
import eth/keys
|
||||
import byteutils
|
||||
|
||||
let seed = getSeed(Mnemonic "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside")
|
||||
|
||||
|
||||
# This derivation path will generate a private key corresponding to wallet i=0
|
||||
assert $(derive(seed, KeyPath "m/44'/60'/0'/0/0").get()) == "ff1e68eb7bf2f48651c47ef0177eb815857322257c5894bb4cfd1176c9989314"
|
||||
|
||||
# This derivation path will generate a status public key
|
||||
let pk = derive(seed, KeyPath "m/43'/60'/1581'/0'/0").get()
|
||||
assert($pk == "38ac4da490b5c48a06d0a2fe7900c56b6639b88f6f71303590f28047411981c2")
|
||||
assert($pk.toPublicKey() == "04ac5a45f2a90052cee10b22b74832f2deb814d58e35aa3f01a249160615a238aef3a34ba86e1817fc8c5a6e93e3c5f159f6c46e922c85d47bafc4b78e07718279")
|
|
@ -0,0 +1 @@
|
|||
Subproject commit db9a74ad6a301f991c477fc2d90894957f640654
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7c6ee4bfc184d7121896a098d68b639a96df7af1
|
Loading…
Reference in New Issue