Skip to content

Commit

Permalink
Add hardened derivation leading zeros test (#54)
Browse files Browse the repository at this point in the history
A new test vectors was introduced in bitcoin/bips#1030
because some wallets didn't handle leading zeros correctly in hardened
derivation.

We already handled that case correctly, but didn't have a test vector for it.
  • Loading branch information
t-bast authored Jun 14, 2021
1 parent 899a5e7 commit 2be7607
Showing 1 changed file with 22 additions and 6 deletions.
28 changes: 22 additions & 6 deletions src/test/scala/fr/acinq/bitcoin/DeterministicWalletSpec.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package fr.acinq.bitcoin

import java.math.BigInteger
import java.nio.ByteOrder

import org.scalatest.FlatSpec
import scodec.bits._

import java.math.BigInteger
import java.nio.ByteOrder
import scala.util.Random

class DeterministicWalletSpec extends FlatSpec {
Expand Down Expand Up @@ -59,6 +58,7 @@ class DeterministicWalletSpec extends FlatSpec {

assert(encode(derivePrivateKey(m, hardened(0) :: 1L :: hardened(2) :: 2L :: 1000000000L :: Nil), xprv) === "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76")
}

it should "generate and derive keys (test vector #2)" in {
val m = generate(hex"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542")
assert(encode(m, xprv) === "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U")
Expand Down Expand Up @@ -92,13 +92,28 @@ class DeterministicWalletSpec extends FlatSpec {
val m0_2147483647h_1_2147483646h_2_pub = publicKey(m0_2147483647h_1_2147483646h_2)
assert(encode(m0_2147483647h_1_2147483646h_2_pub, xpub) === "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt")
}

it should "generate and derive keys (test vector #3)" in {
val m = generate(hex"4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be")
assert(encode(m, xprv) === "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6")
assert(encode(publicKey(m), xpub) == "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13")
assert(encode(derivePrivateKey(m, hardened(0)), xprv) === "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L")
assert(encode(publicKey(derivePrivateKey(m, hardened(0))), xpub) == "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y")
}

it should "generate and derive keys (test vector #4)" in {
val m = generate(hex"3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678")
assert(encode(m, xprv) === "xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv")
assert(encode(publicKey(m), xpub) === "xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa")
val m_0h = derivePrivateKey(m, hardened(0))
assert(m_0h.privateKey.value.head === 0) // private key starts with 0x00
assert(encode(m_0h, xprv) === "xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G")
assert(encode(publicKey(m_0h), xpub) === "xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m")
val m_0h_1h = derivePrivateKey(m_0h, hardened(1))
assert(encode(m_0h_1h, xprv) === "xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1")
assert(encode(publicKey(m_0h_1h), xpub) === "xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt")
}

it should "be possible to go up the private key chain if you have the master pub key and a child private key!!" in {
val m = generate(hex"000102030405060708090a0b0c0d0e0f")
assert(encode(m, xprv) === "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi")
Expand All @@ -114,10 +129,10 @@ class DeterministicWalletSpec extends FlatSpec {
// to the parent private key
val I = Crypto.hmac512(m_pub.chaincode, m_pub.publickeybytes ++ writeUInt32(42, ByteOrder.BIG_ENDIAN))
val IL = I.take(32)
val IR = I.takeRight(32)
val guess = new BigInteger(1, m42.secretkeybytes.toArray).subtract(new BigInteger(1, IL.toArray)).mod(Crypto.curve.getN)
assert(guess === k)
}

it should "parse string-formatted derivation paths" in {
assert(KeyPath("m/44'/0'/0'/0") == KeyPath(hardened(44) :: hardened(0) :: hardened(0) :: 0L :: Nil))
assert(KeyPath("/44'/0'/0'/0") == KeyPath(hardened(44) :: hardened(0) :: hardened(0) :: 0L :: Nil))
Expand All @@ -134,13 +149,14 @@ class DeterministicWalletSpec extends FlatSpec {
}
})
}

it should "be able to derive private keys" in {
val random = new Random()
val seed = new Array[Byte](32)
for (i <- 0 until 50) {
for (_ <- 0 until 50) {
random.nextBytes(seed)
val master = DeterministicWallet.generate(ByteVector.view(seed))
for (j <- 0 until 50) {
for (_ <- 0 until 50) {
val index = random.nextLong()
val priv = DeterministicWallet.derivePrivateKey(master, index)

Expand Down

0 comments on commit 2be7607

Please sign in to comment.