Merge pull request #86 from status-im/feat/bip32

feat: bip32
This commit is contained in:
Iuri Matias 2021-01-05 10:38:05 -05:00 committed by GitHub
commit 6ec7ae077a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 151 additions and 1 deletions

8
.gitmodules vendored
View File

@ -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

View File

@ -76,3 +76,5 @@ task tests, "Run all tests":
buildAndRunTest "callrpc"
buildAndRunTest "migrations"
buildAndRunTest "mailservers"
buildAndRunTest "bip32"

View File

@ -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)

View File

@ -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"

View File

@ -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
nim_status/lib/seeds.nim Normal file
View File

16
test/nim/bip32.nim Normal file
View File

@ -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")

1
vendor/nim-normalize vendored Submodule

@ -0,0 +1 @@
Subproject commit db9a74ad6a301f991c477fc2d90894957f640654

1
vendor/nim-unicodedb vendored Submodule

@ -0,0 +1 @@
Subproject commit 7c6ee4bfc184d7121896a098d68b639a96df7af1