mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
Implmenetation of ExtPrivateKey, passing BIP32 test vectors
ExtKey serialization symmetry spec passing Derivation symmetry for non hardened indices implement ExtKey.deriveChildPubKey implementing invariants as they are specified in bip32, adding comments Moving all ECKeys types into one file so we can create an ADT
This commit is contained in:
parent
11f46fc248
commit
4e0766538c
@ -1,6 +1,6 @@
|
||||
//test in assembly := {}
|
||||
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3")
|
||||
//testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "3")
|
||||
|
||||
coverageExcludedPackages := ".*gen"
|
||||
|
||||
|
14
src/main/scala/org/bitcoins/core/crypto/ChainCode.scala
Normal file
14
src/main/scala/org/bitcoins/core/crypto/ChainCode.scala
Normal file
@ -0,0 +1,14 @@
|
||||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoins.core.protocol.NetworkElement
|
||||
import org.bitcoins.core.util.{BitcoinSUtil, Factory}
|
||||
|
||||
sealed abstract class ChainCode extends NetworkElement
|
||||
object ChainCode extends Factory[ChainCode] {
|
||||
private case class ChainCodeImpl(override val hex: String) extends ChainCode {
|
||||
require(bytes.size == 32, "ChainCode must be 32 bytes in size, got: " + bytes.size)
|
||||
}
|
||||
|
||||
def fromBytes(bytes: Seq[Byte]): ChainCode = ChainCodeImpl(BitcoinSUtil.encodeHex(bytes))
|
||||
}
|
||||
|
@ -1,12 +1,299 @@
|
||||
package org.bitcoins.core.crypto
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.SecureRandom
|
||||
|
||||
import org.bitcoin.NativeSecp256k1
|
||||
import org.bitcoins.core.config.{MainNet, NetworkParameters, TestNet3}
|
||||
import org.bitcoins.core.protocol.NetworkElement
|
||||
import org.bitcoins.core.protocol.blockchain.{MainNetChainParams, SecretKey, TestNetChainParams}
|
||||
import org.bitcoins.core.util.{BitcoinSUtil, _}
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair
|
||||
import org.spongycastle.crypto.digests.SHA256Digest
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator
|
||||
import org.spongycastle.crypto.params.{ECKeyGenerationParameters, ECPrivateKeyParameters, ECPublicKeyParameters}
|
||||
import org.spongycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/**
|
||||
* Created by chris on 2/16/16.
|
||||
*/
|
||||
trait ECKey {
|
||||
def privateKey : Option[ECPrivateKey]
|
||||
def publicKey : ECPublicKey
|
||||
* Created by chris on 2/16/16.
|
||||
*/
|
||||
sealed abstract class BaseECKey extends NetworkElement {
|
||||
|
||||
def hex : String = BitcoinSUtil.encodeHex(bytes)
|
||||
|
||||
def bytes : Seq[Byte]
|
||||
|
||||
/**
|
||||
* Signs a given sequence of bytes with the signingKey
|
||||
* @param dataToSign the bytes to be signed
|
||||
* @param signingKey the key to sign the bytes with
|
||||
* @return the digital signature
|
||||
*/
|
||||
private def sign(dataToSign : Seq[Byte], signingKey : BaseECKey): ECDigitalSignature = {
|
||||
require(dataToSign.length == 32 && signingKey.bytes.length <= 32)
|
||||
val signature = NativeSecp256k1.sign(dataToSign.toArray, signingKey.bytes.toArray)
|
||||
ECDigitalSignature(signature)
|
||||
}
|
||||
|
||||
def sign(hash: DoubleSha256Digest, signingKey: BaseECKey): ECDigitalSignature = sign(hash.bytes,signingKey)
|
||||
|
||||
def sign(hash: DoubleSha256Digest): ECDigitalSignature = sign(hash,this)
|
||||
|
||||
@deprecated("Deprecated in favor of signing algorithm inside of secp256k1", "2/20/2017")
|
||||
private def oldSign(dataToSign: Seq[Byte], signingKey: BaseECKey): ECDigitalSignature = {
|
||||
val signer: ECDSASigner = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()))
|
||||
val privKey: ECPrivateKeyParameters = new ECPrivateKeyParameters(
|
||||
new BigInteger(1,signingKey.bytes.toArray), CryptoParams.curve)
|
||||
signer.init(true, privKey)
|
||||
val components : Array[BigInteger] = signer.generateSignature(dataToSign.toArray)
|
||||
val (r,s) = (components(0),components(1))
|
||||
val signature = ECDigitalSignature(r,s)
|
||||
//make sure the signature follows BIP62's low-s value
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures
|
||||
//bitcoinj implementation
|
||||
//https://github.com/bitcoinj/bitcoinj/blob/1e66b9a8e38d9ad425507bf5f34d64c5d3d23bb8/core/src/main/java/org/bitcoinj/core/ECKey.java#L551
|
||||
val signatureLowS = DERSignatureUtil.lowS(signature)
|
||||
require(signatureLowS.isDEREncoded, "We must create DER encoded signatures when signing a piece of data, got: " + signatureLowS)
|
||||
signatureLowS
|
||||
}
|
||||
}
|
||||
|
||||
case class ECKeyImpl(privateKey : Option[ECPrivateKey], publicKey : ECPublicKey) extends ECKey
|
||||
/**
|
||||
* Created by chris on 2/16/16.
|
||||
*/
|
||||
sealed abstract class ECPrivateKey extends BaseECKey {
|
||||
/** Signifies if the this private key corresponds to a compressed public key */
|
||||
def isCompressed : Boolean
|
||||
|
||||
/** Derives the public for a the private key */
|
||||
def publicKey : ECPublicKey = {
|
||||
val pubKeyBytes : Seq[Byte] = NativeSecp256k1.computePubkey(bytes.toArray, isCompressed)
|
||||
require(NativeSecp256k1.isValidPubKey(pubKeyBytes.toArray), "secp256k1 failed to generate a valid public key, got: " + BitcoinSUtil.encodeHex(pubKeyBytes))
|
||||
ECPublicKey(pubKeyBytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a [[ECPrivateKey]] to WIF
|
||||
* https://en.bitcoin.it/wiki/Wallet_import_format
|
||||
* @return
|
||||
*/
|
||||
def toWIF(network: NetworkParameters): String = {
|
||||
val networkByte = network.privateKey
|
||||
//append 1 byte to the end of the priv key byte representation if we need a compressed pub key
|
||||
val fullBytes = if (isCompressed) networkByte +: (bytes ++ Seq(1.toByte)) else networkByte +: bytes
|
||||
val hash = CryptoUtil.doubleSHA256(fullBytes)
|
||||
val checksum = hash.bytes.take(4)
|
||||
val encodedPrivKey = fullBytes ++ checksum
|
||||
Base58.encode(encodedPrivKey)
|
||||
}
|
||||
|
||||
override def toString = "ECPrivateKey(" + hex + "," + isCompressed +")"
|
||||
}
|
||||
|
||||
object ECPrivateKey extends Factory[ECPrivateKey] {
|
||||
|
||||
private case class ECPrivateKeyImpl(override val bytes : Seq[Byte], isCompressed: Boolean) extends ECPrivateKey {
|
||||
require(NativeSecp256k1.secKeyVerify(bytes.toArray), "Invalid key according to secp256k1, hex: " + BitcoinSUtil.encodeHex(bytes))
|
||||
}
|
||||
|
||||
override def fromBytes(bytes : Seq[Byte]) : ECPrivateKey = fromBytes(bytes,true)
|
||||
|
||||
@tailrec
|
||||
def fromBytes(bytes: Seq[Byte], isCompressed: Boolean): ECPrivateKey = {
|
||||
if (bytes.size == 32) ECPrivateKeyImpl(bytes,isCompressed)
|
||||
else if (bytes.size < 32) {
|
||||
//means we need to pad the private key with 0 bytes so we have 32 bytes
|
||||
val paddingNeeded = 32 - bytes.size
|
||||
val padding = for { _ <- 0 until paddingNeeded} yield 0.toByte
|
||||
ECPrivateKey.fromBytes(padding ++ bytes, isCompressed)
|
||||
}
|
||||
//this is for the case when java serialies a BigInteger to 33 bytes to hold the signed num representation
|
||||
else if (bytes.size == 33) ECPrivateKey.fromBytes(bytes.slice(1,33), isCompressed)
|
||||
else throw new IllegalArgumentException("Private keys cannot be greater than 33 bytes in size, got: " +
|
||||
BitcoinSUtil.encodeHex(bytes) + " which is of size: " + bytes.size)
|
||||
}
|
||||
|
||||
def fromHex(hex: String, isCompressed: Boolean): ECPrivateKey = fromBytes(BitcoinSUtil.decodeHex(hex), isCompressed)
|
||||
|
||||
/** Generates a fresh [[ECPrivateKey]] that has not been used before. */
|
||||
def apply() : ECPrivateKey = ECPrivateKey(true)
|
||||
|
||||
def apply(isCompressed: Boolean) = freshPrivateKey(isCompressed)
|
||||
|
||||
/** Generates a fresh [[ECPrivateKey]] that has not been used before. */
|
||||
def freshPrivateKey : ECPrivateKey = freshPrivateKey(true)
|
||||
|
||||
def freshPrivateKey(isCompressed: Boolean): ECPrivateKey = {
|
||||
val secureRandom = new SecureRandom
|
||||
val generator : ECKeyPairGenerator = new ECKeyPairGenerator
|
||||
val keyGenParams : ECKeyGenerationParameters = new ECKeyGenerationParameters(CryptoParams.curve, secureRandom)
|
||||
generator.init(keyGenParams)
|
||||
val keypair : AsymmetricCipherKeyPair = generator.generateKeyPair
|
||||
val privParams: ECPrivateKeyParameters = keypair.getPrivate.asInstanceOf[ECPrivateKeyParameters]
|
||||
val priv : BigInteger = privParams.getD
|
||||
val bytes = priv.toByteArray
|
||||
ECPrivateKey.fromBytes(bytes,isCompressed)
|
||||
}
|
||||
/**
|
||||
* Takes in a base58 string and converts it into a private key.
|
||||
* Private keys starting with 'K', 'L', or 'c' correspond to compressed public keys.
|
||||
* https://en.bitcoin.it/wiki/Wallet_import_format
|
||||
*
|
||||
* @param WIF Wallet Import Format. Encoded in Base58
|
||||
* @return
|
||||
*/
|
||||
def fromWIFToPrivateKey(WIF : String): ECPrivateKey = {
|
||||
val isCompressed = ECPrivateKey.isCompressed(WIF)
|
||||
val privateKeyBytes = trimFunction(WIF)
|
||||
ECPrivateKey.fromBytes(privateKeyBytes,isCompressed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in WIF private key as a sequence of bytes and determines if it corresponds to a compressed public key.
|
||||
* If the private key corresponds to a compressed public key, the last byte should be 0x01, and
|
||||
* the WIF string will have started with K or L instead of 5 (or c instead of 9 on testnet).
|
||||
*
|
||||
* @param bytes private key in bytes
|
||||
* @return
|
||||
*/
|
||||
def isCompressed(bytes : Seq[Byte]): Boolean = {
|
||||
val validCompressedBytes: Seq[Byte] =
|
||||
MainNetChainParams.base58Prefix(SecretKey) ++ TestNetChainParams.base58Prefixes(SecretKey)
|
||||
val validCompressedBytesInHex: Seq[String] = validCompressedBytes.map(byte => BitcoinSUtil.encodeHex(byte))
|
||||
val firstByteHex = BitcoinSUtil.encodeHex(bytes.head)
|
||||
if (validCompressedBytesInHex.contains(firstByteHex)) bytes(bytes.length - 5) == 0x01.toByte
|
||||
else false
|
||||
}
|
||||
|
||||
def isCompressed(WIF : String) : Boolean = {
|
||||
val bytes = Base58.decode(WIF)
|
||||
isCompressed(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* When decoding a WIF private key, we drop the first byte (network byte), and the last 4 bytes (checksum).
|
||||
* If the private key corresponds to a compressed public key, we drop the last byte again.
|
||||
* https://en.bitcoin.it/wiki/Wallet_import_format
|
||||
* @param WIF Wallet Import Format. Encoded in Base58
|
||||
* @return
|
||||
*/
|
||||
private def trimFunction(WIF : String): Seq[Byte] = {
|
||||
val bytesChecked = Base58.decodeCheck(WIF)
|
||||
|
||||
//see https://en.bitcoin.it/wiki/List_of_address_prefixes
|
||||
//for where '5' and '9' come from
|
||||
bytesChecked match {
|
||||
case Success(bytes) if uncompressedKeyPrefixes.contains(WIF.headOption) => bytes.tail
|
||||
case Success(bytes) if isCompressed(WIF) => bytes.tail.dropRight(1)
|
||||
case Success(bytes) => bytes.tail
|
||||
case Failure(exception) => throw exception
|
||||
}
|
||||
}
|
||||
|
||||
/** The Base58 prefixes that represent compressed private keys */
|
||||
def compressedKeyPrefixes = Seq(Some('K'), Some('L'), Some('c'))
|
||||
|
||||
/** The Base58 prefixes that represent uncompressed private keys */
|
||||
def uncompressedKeyPrefixes = Seq(Some('5'),Some('9'))
|
||||
|
||||
/** Returns the [[NetworkParameters]] from a serialized WIF key */
|
||||
def parseNetworkFromWIF(wif: String): Try[NetworkParameters] = {
|
||||
val decoded = Base58.decodeCheck(wif)
|
||||
decoded.map { bytes =>
|
||||
val b = bytes.head
|
||||
if (b == TestNetChainParams.base58Prefixes(SecretKey).head) {
|
||||
TestNet3
|
||||
} else if (b == MainNetChainParams.base58Prefixes(SecretKey).head) {
|
||||
MainNet
|
||||
} else throw new IllegalArgumentException("Cannot match wif private key with a network, prefix was: " + BitcoinSUtil.encodeHex(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Created by chris on 2/16/16.
|
||||
*/
|
||||
sealed abstract class ECPublicKey extends BaseECKey {
|
||||
def verify(hash : HashDigest, signature : ECDigitalSignature) : Boolean = verify(hash.bytes, signature)
|
||||
|
||||
/** Verifies if a given piece of data is signed by the [[ECPrivateKey]]'s corresponding [[ECPublicKey]]. */
|
||||
def verify(data : Seq[Byte], signature : ECDigitalSignature): Boolean = {
|
||||
logger.debug("PubKey for verifying: " + BitcoinSUtil.encodeHex(bytes))
|
||||
logger.debug("Data to verify: " + BitcoinSUtil.encodeHex(data))
|
||||
logger.debug("Signature to check against data: " + signature.hex)
|
||||
val result = NativeSecp256k1.verify(data.toArray, signature.bytes.toArray, bytes.toArray)
|
||||
if (!result) {
|
||||
//if signature verification fails with libsecp256k1 we need to use our old
|
||||
//verification function from spongy castle, this is needed because early blockchain
|
||||
//transactions can have weird non strict der encoded digital signatures
|
||||
//bitcoin core implements this functionality here:
|
||||
//https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.cpp#L16-L165
|
||||
//TODO: Implement functionality in Bitcoin Core linked above
|
||||
oldVerify(data,signature)
|
||||
} else result
|
||||
}
|
||||
|
||||
def verify(hex : String, signature : ECDigitalSignature) : Boolean = verify(BitcoinSUtil.decodeHex(hex),signature)
|
||||
|
||||
override def toString = "ECPublicKey(" + hex + ")"
|
||||
|
||||
@deprecated("Deprecated in favor of using verify functionality inside of secp256k1", "2/20/2017")
|
||||
private def oldVerify(data: Seq[Byte], signature: ECDigitalSignature): Boolean = {
|
||||
/** The elliptic curve used by bitcoin. */
|
||||
def curve = CryptoParams.curve
|
||||
/** This represents this public key in the bouncy castle library */
|
||||
def publicKeyParams = new ECPublicKeyParameters(curve.getCurve.decodePoint(bytes.toArray), curve)
|
||||
|
||||
val resultTry = Try {
|
||||
val signer = new ECDSASigner
|
||||
signer.init(false, publicKeyParams)
|
||||
signature match {
|
||||
case EmptyDigitalSignature => signer.verifySignature(data.toArray, java.math.BigInteger.valueOf(0), java.math.BigInteger.valueOf(0))
|
||||
case sig: ECDigitalSignature =>
|
||||
logger.debug("Public key bytes: " + BitcoinSUtil.encodeHex(bytes))
|
||||
val rBigInteger: BigInteger = new BigInteger(signature.r.toString())
|
||||
val sBigInteger: BigInteger = new BigInteger(signature.s.toString())
|
||||
signer.verifySignature(data.toArray, rBigInteger, sBigInteger)
|
||||
}
|
||||
}
|
||||
resultTry.getOrElse(false)
|
||||
}
|
||||
|
||||
/** Checks if the [[ECPublicKey]] is compressed */
|
||||
def isCompressed: Boolean = bytes.size == 33
|
||||
|
||||
/** Checks if the [[ECPublicKey]] is valid according to secp256k1 */
|
||||
def isFullyValid = ECPublicKey.isFullyValid(bytes)
|
||||
}
|
||||
|
||||
object ECPublicKey extends Factory[ECPublicKey] {
|
||||
|
||||
private case class ECPublicKeyImpl(override val bytes : Seq[Byte]) extends ECPublicKey {
|
||||
//unfortunately we cannot place ANY invariants here
|
||||
//because of old transactions on the blockchain that have weirdly formatted public keys. Look at example in script_tests.json
|
||||
//https://github.com/bitcoin/bitcoin/blob/master/src/test/data/script_tests.json#L457
|
||||
//bitcoin core only checks CPubKey::IsValid()
|
||||
//this means we can have public keys with only one byte i.e. 0x00 or no bytes.
|
||||
//Eventually we would like this to be CPubKey::IsFullyValid() but since we are remaining backwards compatible
|
||||
//we cannot do this. If there ever is a hard fork this would be a good thing to add.
|
||||
}
|
||||
|
||||
override def fromBytes(bytes : Seq[Byte]) : ECPublicKey = ECPublicKeyImpl(bytes)
|
||||
|
||||
def apply() = freshPublicKey
|
||||
|
||||
/** Generates a fresh [[ECPublicKey]] that has not been used before. */
|
||||
def freshPublicKey = ECPrivateKey.freshPrivateKey.publicKey
|
||||
|
||||
|
||||
/** Checks if the public key is valid according to secp256k1
|
||||
* Mimics this function in bitcoin core
|
||||
* [[https://github.com/bitcoin/bitcoin/blob/27765b6403cece54320374b37afb01a0cfe571c3/src/pubkey.cpp#L207-L212]]
|
||||
*/
|
||||
def isFullyValid(bytes: Seq[Byte]): Boolean = Try(NativeSecp256k1.isValidPubKey(bytes.toArray)).isSuccess
|
||||
|
||||
}
|
225
src/main/scala/org/bitcoins/core/crypto/ExtKey.scala
Normal file
225
src/main/scala/org/bitcoins/core/crypto/ExtKey.scala
Normal file
@ -0,0 +1,225 @@
|
||||
package org.bitcoins.core.crypto
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
import org.bitcoin.NativeSecp256k1
|
||||
import org.bitcoins.core.number.{UInt32, UInt8}
|
||||
import org.bitcoins.core.protocol.NetworkElement
|
||||
import org.bitcoins.core.util._
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/** Represents an extended key as defined by BIP32
|
||||
* [[https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki]]
|
||||
*/
|
||||
sealed abstract class ExtKey extends NetworkElement {
|
||||
require(bytes.size == 78, "ExtKey must be 78 bytes in size, got: " + bytes.size)
|
||||
/** The network and private/public key identifier for this key */
|
||||
def version: ExtKeyVersion
|
||||
/** 0 for master nodes, 1 for level-1 derived keys, .... */
|
||||
def depth: UInt8
|
||||
/** The fingerprint of the parent key */
|
||||
def fingerprint: Seq[Byte]
|
||||
/** Child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized.
|
||||
* (0x00000000 if master key) */
|
||||
def childNum: UInt32
|
||||
|
||||
/** In order to prevent these from depending solely on the key itself,
|
||||
* we extend both private and public keys first with an extra 256 bits of entropy.
|
||||
* This extension, called the chain code,
|
||||
* is identical for corresponding private and public keys, and consists of 32 bytes. */
|
||||
def chainCode: ChainCode
|
||||
|
||||
/** The key at this path */
|
||||
def key: BaseECKey
|
||||
|
||||
def deriveChildPubKey(idx: UInt32): Try[ExtPublicKey] = this match {
|
||||
case priv: ExtPrivateKey =>
|
||||
Success(priv.deriveChildPrivKey(idx).extPublicKey)
|
||||
case pub: ExtPublicKey => pub.deriveChildPubKey(idx)
|
||||
}
|
||||
|
||||
def deriveChildPubKey(idx: Long): Try[ExtPublicKey] = {
|
||||
Try(UInt32(idx)).flatMap(deriveChildPubKey(_))
|
||||
}
|
||||
|
||||
override def hex: String = key match {
|
||||
case priv: ECPrivateKey =>
|
||||
version.hex + depth.hex + BitcoinSUtil.encodeHex(fingerprint) +
|
||||
childNum.hex + chainCode.hex + "00" + priv.hex
|
||||
case pub: ECPublicKey =>
|
||||
version.hex + depth.hex + BitcoinSUtil.encodeHex(fingerprint) +
|
||||
childNum.hex + chainCode.hex + pub.hex
|
||||
}
|
||||
|
||||
override def toString: String = {
|
||||
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
|
||||
val encoded = Base58.encode(bytes ++ checksum)
|
||||
require(Base58.decodeCheck(encoded).isSuccess)
|
||||
encoded
|
||||
}
|
||||
}
|
||||
|
||||
object ExtKey extends Factory[ExtKey] {
|
||||
val hardenedIdx = UInt32(NumberUtil.pow2(31).toLong)
|
||||
|
||||
/** Takes in a base58 string and tries to convert it to an extended key */
|
||||
def fromString(base58: String): Try[ExtKey] = {
|
||||
val decoded: Try[Seq[Byte]] = Base58.decodeCheck(base58)
|
||||
val extKey = decoded.flatMap { bytes =>
|
||||
require(bytes.size == 78, "Not 78 bytes")
|
||||
val version: Try[ExtKeyVersion] = ExtKeyVersion(bytes.take(4)) match {
|
||||
case Some(v) => Success(v)
|
||||
case None => Failure(new IllegalArgumentException("Invalid version for ExtKey"))
|
||||
}
|
||||
val depth = UInt8(bytes.slice(4,5))
|
||||
val fp = bytes.slice(5,9)
|
||||
val childNum = UInt32(bytes.slice(9,13))
|
||||
val chainCode = ChainCode(bytes.slice(13,45))
|
||||
val key: Try[ExtKey] = version.map {
|
||||
case x @ (MainNetPub | TestNet3Pub) =>
|
||||
val pub = ECPublicKey(bytes.slice(45,78))
|
||||
ExtPublicKey(x,depth,fp,childNum,chainCode,pub)
|
||||
case x @ (MainNetPriv | TestNet3Priv) =>
|
||||
require(bytes(45) == 0, "Byte at index 46 must be zero for a ExtPrivateKey, got: " + BitcoinSUtil.encodeHex(bytes(45)))
|
||||
val priv = ECPrivateKey(bytes.slice(46,78))
|
||||
ExtPrivateKey(x,depth,fp,childNum,chainCode,priv)
|
||||
}
|
||||
key
|
||||
}
|
||||
extKey
|
||||
}
|
||||
|
||||
override def fromBytes(bytes: Seq[Byte]): ExtKey = {
|
||||
val privTry = Try(ExtPrivateKey(bytes))
|
||||
if (privTry.isSuccess) privTry.get
|
||||
else {
|
||||
ExtPublicKey(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed abstract class ExtPrivateKey extends ExtKey {
|
||||
override def key: ECPrivateKey
|
||||
|
||||
def deriveChildPrivKey(idx: UInt32): ExtPrivateKey = {
|
||||
val data: Seq[Byte] = if (idx >= ExtKey.hardenedIdx) {
|
||||
//derive hardened key
|
||||
0.toByte +: ((key.bytes) ++ idx.bytes)
|
||||
} else {
|
||||
//derive non hardened key
|
||||
key.publicKey.bytes ++ idx.bytes
|
||||
}
|
||||
val hmac = CryptoUtil.hmac512(chainCode.bytes,data)
|
||||
val (il,ir) = hmac.splitAt(32)
|
||||
//should be ECGroup addition
|
||||
//parse256(IL) + kpar (mod n)
|
||||
val childKey = ECPrivateKey(NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray))
|
||||
val fp = CryptoUtil.sha256Hash160(key.publicKey.bytes).bytes.take(4)
|
||||
ExtPrivateKey(version, depth + UInt8.one, fp, idx,
|
||||
ChainCode(ir),childKey)
|
||||
}
|
||||
|
||||
def extPublicKey: ExtPublicKey = version match {
|
||||
case MainNetPriv => ExtPublicKey(MainNetPub,depth,fingerprint,childNum,chainCode,key.publicKey)
|
||||
case TestNet3Priv => ExtPublicKey(TestNet3Pub,depth,fingerprint,childNum,chainCode,key.publicKey)
|
||||
case MainNetPub | TestNet3Pub => throw new IllegalArgumentException("Cannot have pubkey version in ExtPrivateKey, got: " + version)
|
||||
}
|
||||
|
||||
def deriveChildPrivKey(idx: Long): Try[ExtPrivateKey] = {
|
||||
Try(UInt32(idx)).map(deriveChildPrivKey(_))
|
||||
}
|
||||
}
|
||||
object ExtPrivateKey extends Factory[ExtPrivateKey] {
|
||||
private case class ExtPrivateKeyImpl(version: ExtKeyVersion, depth: UInt8,
|
||||
fingerprint: Seq[Byte], childNum: UInt32,
|
||||
chainCode: ChainCode, key: ECPrivateKey) extends ExtPrivateKey {
|
||||
require(fingerprint.size == 4, "Fingerprint must be 4 bytes in size, got: " + fingerprint)
|
||||
}
|
||||
|
||||
override def fromBytes(bytes: Seq[Byte]): ExtPrivateKey = {
|
||||
require(bytes.size == 78, "ExtPrivateKey can only be 78 bytes")
|
||||
val base58 = Base58.encode(bytes ++ CryptoUtil.doubleSHA256(bytes).bytes.take(4))
|
||||
ExtKey.fromString(base58) match {
|
||||
case Success(priv: ExtPrivateKey) => priv
|
||||
case Success(_: ExtPublicKey) => throw new IllegalArgumentException("Cannot create ext public in ExtPrivateKey")
|
||||
case f: Failure[_] => throw f.exception
|
||||
}
|
||||
}
|
||||
def apply(version: ExtKeyVersion, depth: UInt8,
|
||||
fingerprint: Seq[Byte], child: UInt32,
|
||||
chainCode: ChainCode, privateKey: ECPrivateKey): ExtPrivateKey = {
|
||||
ExtPrivateKeyImpl(version, depth, fingerprint, child, chainCode, privateKey)
|
||||
}
|
||||
|
||||
/** Generates a master private key
|
||||
* https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#master-key-generation
|
||||
* */
|
||||
def apply(version: ExtKeyVersion, seedOpt: Option[Seq[Byte]] = None): ExtPrivateKey = {
|
||||
val seed = seedOpt match {
|
||||
case Some(bytes) => bytes
|
||||
case None => ECPrivateKey().bytes
|
||||
}
|
||||
val i = CryptoUtil.hmac512("Bitcoin seed".map(_.toByte), seed)
|
||||
val (il,ir) = i.splitAt(32)
|
||||
val masterPrivKey = ECPrivateKey(il)
|
||||
val fp = UInt32.zero.bytes
|
||||
ExtPrivateKey(version, UInt8.zero, fp, UInt32.zero,
|
||||
ChainCode(ir),masterPrivKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed abstract class ExtPublicKey extends ExtKey {
|
||||
override def key: ECPublicKey
|
||||
|
||||
final override def deriveChildPubKey(idx: UInt32): Try[ExtPublicKey] = {
|
||||
if (idx >= ExtKey.hardenedIdx) {
|
||||
Failure(new IllegalArgumentException("Cannot derive hardened child from extended public key"))
|
||||
} else {
|
||||
val data = key.bytes ++ idx.bytes
|
||||
val hmac = CryptoUtil.hmac512(chainCode.bytes, data)
|
||||
val (il,ir) = hmac.splitAt(32)
|
||||
val priv = ECPrivateKey(il)
|
||||
val tweaked = NativeSecp256k1.pubKeyTweakAdd(key.bytes.toArray,
|
||||
hmac.toArray,
|
||||
priv.isCompressed)
|
||||
val childPubKey = ECPublicKey(tweaked)
|
||||
val bi = BigInt(new BigInteger(1,priv.bytes.toArray))
|
||||
//we do not handle this case since it is impossible
|
||||
//In case parse256(IL) ≥ n or Ki is the point at infinity, the resulting key is invalid,
|
||||
//and one should proceed with the next value for i.
|
||||
//https://botbot.me/freenode/bitcoin-wizards/2017-11-20/?tz=America/Chicago
|
||||
val cc = ChainCode(ir)
|
||||
val fp = CryptoUtil.sha256Hash160(key.bytes).bytes.take(4)
|
||||
Success(ExtPublicKey(version,depth + UInt8.one, fp,idx,cc,childPubKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ExtPublicKey extends Factory[ExtPublicKey] {
|
||||
private case class ExtPublicKeyImpl(version: ExtKeyVersion, depth: UInt8,
|
||||
fingerprint: Seq[Byte], childNum: UInt32,
|
||||
chainCode: ChainCode, key: ECPublicKey) extends ExtPublicKey
|
||||
|
||||
def apply(version: ExtKeyVersion, depth: UInt8,
|
||||
fingerprint: Seq[Byte], child: UInt32, chainCode: ChainCode, publicKey: ECPublicKey): ExtPublicKey = {
|
||||
ExtPublicKeyImpl(version, depth, fingerprint, child, chainCode, publicKey)
|
||||
}
|
||||
|
||||
override def fromBytes(bytes: Seq[Byte]): ExtPublicKey = {
|
||||
require(bytes.size == 78, "ExtPublicKey can only be 78 bytes")
|
||||
val base58 = Base58.encode(bytes ++ CryptoUtil.doubleSHA256(bytes).bytes.take(4))
|
||||
ExtKey.fromString(base58) match {
|
||||
case Success(_: ExtPrivateKey) =>
|
||||
throw new IllegalArgumentException("Cannot create ext privatkey in ExtPublicKey")
|
||||
case Success(pub: ExtPublicKey) => pub
|
||||
case f: Failure[_] => throw f.exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
31
src/main/scala/org/bitcoins/core/crypto/ExtKeyVersion.scala
Normal file
31
src/main/scala/org/bitcoins/core/crypto/ExtKeyVersion.scala
Normal file
@ -0,0 +1,31 @@
|
||||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoins.core.protocol.NetworkElement
|
||||
import org.bitcoins.core.util.BitcoinSUtil
|
||||
|
||||
sealed abstract class ExtKeyVersion extends NetworkElement {
|
||||
def bytes: Seq[Byte]
|
||||
override def hex = BitcoinSUtil.encodeHex(bytes)
|
||||
}
|
||||
|
||||
case object MainNetPub extends ExtKeyVersion {
|
||||
override def bytes = Seq(0x04, 0x88, 0xb2, 0x1E).map(_.toByte)
|
||||
}
|
||||
|
||||
case object MainNetPriv extends ExtKeyVersion {
|
||||
override def bytes = Seq(0x04,0x88,0xAD,0xE4).map(_.toByte)
|
||||
}
|
||||
|
||||
case object TestNet3Pub extends ExtKeyVersion {
|
||||
override def bytes = Seq(0x04,0x35,0x87,0xCF).map(_.toByte)
|
||||
}
|
||||
|
||||
case object TestNet3Priv extends ExtKeyVersion {
|
||||
override def bytes = Seq(0x04,0x35,0x83,0x94).map(_.toByte)
|
||||
}
|
||||
|
||||
object ExtKeyVersion {
|
||||
private val all: Seq[ExtKeyVersion] = Seq(MainNetPriv,MainNetPub,TestNet3Pub, TestNet3Priv)
|
||||
|
||||
def apply(bytes: Seq[Byte]): Option[ExtKeyVersion] = all.find(_.bytes == bytes)
|
||||
}
|
@ -19,5 +19,5 @@ trait NetworkElement {
|
||||
/** The byte representation of the NetworkElement */
|
||||
def bytes : Seq[Byte] = BitcoinSUtil.decodeHex(hex)
|
||||
|
||||
def logger: BitcoinSLogger = BitcoinSLogger
|
||||
def logger = BitcoinSLogger.logger
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package org.bitcoins.core.util
|
||||
import java.security.MessageDigest
|
||||
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.spongycastle.crypto.digests.RIPEMD160Digest
|
||||
import org.spongycastle.crypto.digests.{RIPEMD160Digest, SHA512Digest}
|
||||
import org.spongycastle.crypto.macs.HMac
|
||||
import org.spongycastle.crypto.params.KeyParameter
|
||||
|
||||
/**
|
||||
* Created by chris on 1/14/16.
|
||||
@ -62,6 +64,15 @@ trait CryptoUtil {
|
||||
}
|
||||
|
||||
val emptyDoubleSha256Hash = DoubleSha256Digest("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
def hmac512(key: Seq[Byte], data: Seq[Byte]): Seq[Byte] = {
|
||||
val hmac512 = new HMac(new SHA512Digest())
|
||||
hmac512.init(new KeyParameter(key.toArray))
|
||||
hmac512.update(data.toArray,0,data.size)
|
||||
val output = new Array[Byte](64)
|
||||
hmac512.doFinal(output,0)
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
object CryptoUtil extends CryptoUtil
|
||||
|
44
src/test/scala/org/bitcoins/core/crypto/ExtKeySpec.scala
Normal file
44
src/test/scala/org/bitcoins/core/crypto/ExtKeySpec.scala
Normal file
@ -0,0 +1,44 @@
|
||||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoins.core.gen.CryptoGenerators
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.scalacheck.{Gen, Prop, Properties}
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
class ExtKeySpec extends Properties("ExtKeySpec") {
|
||||
private val logger = BitcoinSLogger.logger
|
||||
property("serialization symmetry") = {
|
||||
Prop.forAll(CryptoGenerators.extKey) { extKey =>
|
||||
ExtKey.fromString(extKey.toString) == Success(extKey) &&
|
||||
ExtKey(extKey.bytes) == extKey
|
||||
}
|
||||
}
|
||||
|
||||
private def nonHardened: Gen[UInt32] = Gen.choose(0L, ((1L << 31) - 1)).map(UInt32(_))
|
||||
private def hardened: Gen[UInt32] = Gen.choose(1L<<31, (1L<<32)-1).map(UInt32(_))
|
||||
|
||||
property("derivation identity 1") = {
|
||||
Prop.forAllNoShrink(CryptoGenerators.extPrivateKey, nonHardened, nonHardened,nonHardened) { (m,a,b,c) =>
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#the-key-tree
|
||||
//N(m/a/b/c) = N(m/a/b)/c = N(m/a)/b/c = N(m)/a/b/c = M/a/b/c
|
||||
val path1 = m.deriveChildPrivKey(a).deriveChildPrivKey(b).deriveChildPrivKey(c).extPublicKey
|
||||
val path2 = m.deriveChildPrivKey(a).deriveChildPrivKey(b).extPublicKey.deriveChildPubKey(c).get
|
||||
val path3 = m.deriveChildPrivKey(a).extPublicKey.deriveChildPubKey(b).get.deriveChildPubKey(c).get
|
||||
val path4 = m.extPublicKey.deriveChildPubKey(a).get.deriveChildPubKey(b).get.deriveChildPubKey(c).get
|
||||
path1 == path2 && path2 == path3 && path3 == path4
|
||||
}
|
||||
}
|
||||
|
||||
property("derivation identity 2") = {
|
||||
Prop.forAllNoShrink(CryptoGenerators.extPrivateKey, hardened,nonHardened,nonHardened) { (m, aH,b,c) =>
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#the-key-tree
|
||||
//N(m/aH/b/c) = N(m/aH/b)/c = N(m/aH)/b/c
|
||||
val path1 = m.deriveChildPrivKey(aH).deriveChildPrivKey(b).deriveChildPrivKey(c).extPublicKey
|
||||
val path2 = m.deriveChildPrivKey(aH).deriveChildPrivKey(b).extPublicKey.deriveChildPubKey(c).get
|
||||
val path3 = m.deriveChildPrivKey(aH).extPublicKey.deriveChildPubKey(b).get.deriveChildPubKey(c).get
|
||||
path1 == path2 && path2 == path3
|
||||
}
|
||||
}
|
||||
}
|
118
src/test/scala/org/bitcoins/core/crypto/ExtKeyTest.scala
Normal file
118
src/test/scala/org/bitcoins/core/crypto/ExtKeyTest.scala
Normal file
@ -0,0 +1,118 @@
|
||||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.util.BitcoinSUtil
|
||||
import org.scalatest.{FlatSpec, MustMatchers}
|
||||
|
||||
class ExtKeyTest extends FlatSpec with MustMatchers {
|
||||
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
|
||||
"ExtKey" must "pass the test vectors in BIP32" in {
|
||||
//master key
|
||||
val seed = BitcoinSUtil.decodeHex("000102030405060708090a0b0c0d0e0f")
|
||||
val masterPriv = ExtPrivateKey(MainNetPriv, Some(seed))
|
||||
masterPriv.toString must be ("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi")
|
||||
|
||||
//master public key
|
||||
val masterPub = masterPriv.extPublicKey
|
||||
masterPub.toString must be ("xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8")
|
||||
|
||||
//derive child
|
||||
val hidx = ExtKey.hardenedIdx
|
||||
val m0h = masterPriv.deriveChildPrivKey(hidx)
|
||||
m0h.toString must be ("xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7")
|
||||
|
||||
val m0hPub = m0h.extPublicKey
|
||||
m0hPub.toString must be ("xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw")
|
||||
|
||||
val m0h1 = m0h.deriveChildPrivKey(UInt32.one)
|
||||
m0h1.toString must be ("xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs")
|
||||
|
||||
val m0h1Pub = m0h1.extPublicKey
|
||||
m0h1Pub.toString must be ("xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ")
|
||||
|
||||
val m0h12h = m0h1.deriveChildPrivKey(UInt32(2) + ExtKey.hardenedIdx)
|
||||
m0h12h.toString must be ("xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM")
|
||||
|
||||
val m0h12hPub = m0h12h.extPublicKey
|
||||
m0h12hPub.toString must be ("xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5")
|
||||
|
||||
val m0h12h2 = m0h12h.deriveChildPrivKey(UInt32(2))
|
||||
m0h12h2.toString must be ("xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334")
|
||||
|
||||
val m0h12h2Pub = m0h12h2.extPublicKey
|
||||
m0h12h2Pub.toString must be ("xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV")
|
||||
|
||||
val m0h12h21000000000 = m0h12h2.deriveChildPrivKey(UInt32(1000000000))
|
||||
m0h12h21000000000.toString must be ("xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76")
|
||||
|
||||
val m0h12h21000000000Pub = m0h12h21000000000.extPublicKey
|
||||
m0h12h21000000000Pub.toString must be ("xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy")
|
||||
}
|
||||
|
||||
it must "pass test vector 2 in BIP32" in {
|
||||
val seed = BitcoinSUtil.decodeHex("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542")
|
||||
|
||||
val masterPriv = ExtPrivateKey(MainNetPriv,Some(seed))
|
||||
masterPriv.toString must be ("xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U")
|
||||
|
||||
val masterPub = masterPriv.extPublicKey
|
||||
masterPub.toString must be ("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
|
||||
|
||||
val m0 = masterPriv.deriveChildPrivKey(UInt32.zero)
|
||||
m0.toString must be ("xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt")
|
||||
|
||||
val m0Pub = m0.extPublicKey
|
||||
m0Pub.toString must be ("xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH")
|
||||
|
||||
val m02147483647h = m0.deriveChildPrivKey(ExtKey.hardenedIdx + UInt32(2147483647))
|
||||
m02147483647h.toString must be ("xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9")
|
||||
|
||||
val m02147483647hPub = m02147483647h.extPublicKey
|
||||
m02147483647hPub.toString must be ("xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a")
|
||||
|
||||
val m02147483647h1 = m02147483647h.deriveChildPrivKey(UInt32.one)
|
||||
m02147483647h1.toString must be ("xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef")
|
||||
|
||||
val m02147483647h1Pub = m02147483647h1.extPublicKey
|
||||
m02147483647h1Pub.toString must be ("xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon")
|
||||
|
||||
val m02147483647h12147483646h = m02147483647h1.deriveChildPrivKey(ExtKey.hardenedIdx + UInt32(2147483646))
|
||||
m02147483647h12147483646h.toString must be ("xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc")
|
||||
|
||||
val m02147483647h12147483646hPub = m02147483647h12147483646h.extPublicKey
|
||||
m02147483647h12147483646hPub.toString must be ("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL")
|
||||
|
||||
val m02147483647h12147483646h2 = m02147483647h12147483646h.deriveChildPrivKey(UInt32(2))
|
||||
m02147483647h12147483646h2.toString must be ("xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j")
|
||||
|
||||
val m02147483647h12147483646h2Pub = m02147483647h12147483646h2.extPublicKey
|
||||
m02147483647h12147483646h2Pub.toString must be ("xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt")
|
||||
}
|
||||
|
||||
it must "pass test vector 3 in BIP32" in {
|
||||
val seed = BitcoinSUtil.decodeHex("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be")
|
||||
val masterPrivKey = ExtPrivateKey(MainNetPriv,Some(seed))
|
||||
masterPrivKey.toString must be ("xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6")
|
||||
|
||||
val masterPubKey = masterPrivKey.extPublicKey
|
||||
masterPubKey.toString must be ("xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13")
|
||||
|
||||
val m0h = masterPrivKey.deriveChildPrivKey(ExtKey.hardenedIdx)
|
||||
m0h.toString must be ("xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L")
|
||||
|
||||
val m0hPub = m0h.extPublicKey
|
||||
m0hPub.toString must be ("xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y")
|
||||
}
|
||||
|
||||
it must "have derivation symmetry with (1<<31)-1, last i before hardened keys" in {
|
||||
//xprv9s21ZrQH143K4QWHDnxmxUbzAQYiDavkg14kQcmZjP2KaSB1PZs5BUsyNGSrWXTzZ9qwyJo5yzvDe3fWybykc8CQPDZMaKupTeVbkfG7osL
|
||||
//actual priv key 68e5ed2b2c8fc5a6605107d29d074e3d6ccb119c2811007e32f48305176f814c
|
||||
val str = "xprv9s21ZrQH143K4LCRq4tUZUt3fiTNZr6QTiep3HGzMxtSwfxKAhBmNJJnsmoyWuYZCPC4DNsiVwToHJbxZtq4iEkozBhMzWNTiCH4tzJNjPi"
|
||||
val masterPriv = ExtKey.fromString(str).get.asInstanceOf[ExtPrivateKey]
|
||||
val idx = UInt32((1L << 31) - 1)
|
||||
val path1 = masterPriv.deriveChildPrivKey(idx).extPublicKey.key
|
||||
val path2 = masterPriv.extPublicKey.deriveChildPubKey(idx).get.key
|
||||
path1 must be (path2)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user