From 6ec1fa889d519ffa53fb745376cc0e974a8530d1 Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 9 Jun 2021 15:45:13 +0200 Subject: [PATCH] Add hardened derivation leading zeros test A new test vectors was introduced in https://github.com/bitcoin/bips/pull/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. --- .../bitcoin/DeterministicWalletSpec.scala | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/test/scala/fr/acinq/bitcoin/DeterministicWalletSpec.scala b/src/test/scala/fr/acinq/bitcoin/DeterministicWalletSpec.scala index 5368bd2b..3cc42141 100644 --- a/src/test/scala/fr/acinq/bitcoin/DeterministicWalletSpec.scala +++ b/src/test/scala/fr/acinq/bitcoin/DeterministicWalletSpec.scala @@ -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 { @@ -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") @@ -92,6 +92,7 @@ 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") @@ -99,6 +100,20 @@ class DeterministicWalletSpec extends FlatSpec { 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") @@ -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)) @@ -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)