mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
2019 12 17 sign ext key (#959)
* Create ExtSign, use it on ExtPrivateKey * Add documentation on how to Sign things with ExtSign * Add ExtSign.sign for a bip32 path * Make sign(bytes,path) not return a Future, add documentation * Address nadav's code review * Fix mdoc sign.md
This commit is contained in:
parent
c67b2978bf
commit
67eb7df235
9 changed files with 139 additions and 55 deletions
|
@ -3,29 +3,29 @@ package org.bitcoins.core.crypto
|
||||||
import org.bitcoins.testkit.core.gen.CryptoGenerators
|
import org.bitcoins.testkit.core.gen.CryptoGenerators
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import org.scalacheck.{Gen, Prop, Properties}
|
import org.scalacheck.{Gen, Prop, Properties}
|
||||||
|
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
|
|
||||||
class ExtKeySpec extends Properties("ExtKeySpec") {
|
class ExtKeySpec extends BitcoinSUnitTest {
|
||||||
private val logger = BitcoinSLogger.logger
|
private val nonHardened: Gen[UInt32] =
|
||||||
property("serialization symmetry") = {
|
Gen.choose(0L, ((1L << 31) - 1)).map(UInt32(_))
|
||||||
|
private val hardened: Gen[UInt32] =
|
||||||
|
Gen.choose(1L << 31, (1L << 32) - 1).map(UInt32(_))
|
||||||
|
|
||||||
|
it must "have serialization symmetry" in {
|
||||||
Prop.forAll(CryptoGenerators.extKey) { extKey =>
|
Prop.forAll(CryptoGenerators.extKey) { extKey =>
|
||||||
ExtKey.fromString(extKey.toString) == Success(extKey) &&
|
ExtKey.fromString(extKey.toString) == Success(extKey) &&
|
||||||
ExtKey(extKey.bytes) == extKey
|
ExtKey(extKey.bytes) == extKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def nonHardened: Gen[UInt32] =
|
it must "have derivation identity 1" in {
|
||||||
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,
|
Prop.forAllNoShrink(CryptoGenerators.extPrivateKey,
|
||||||
nonHardened,
|
nonHardened,
|
||||||
nonHardened,
|
nonHardened,
|
||||||
nonHardened) { (m, a, b, c) =>
|
nonHardened) { (m, a, b, c) =>
|
||||||
//https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#the-key-tree
|
//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
|
//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
|
val path1 = m
|
||||||
|
@ -57,11 +57,11 @@ class ExtKeySpec extends Properties("ExtKeySpec") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property("derivation identity 2") = {
|
it must "derivation identity 2" in {
|
||||||
Prop.forAllNoShrink(CryptoGenerators.extPrivateKey,
|
Prop.forAllNoShrink(CryptoGenerators.extPrivateKey,
|
||||||
hardened,
|
hardened,
|
||||||
nonHardened,
|
nonHardened,
|
||||||
nonHardened) { (m, aH, b, c) =>
|
nonHardened) { (m, aH, b, c) =>
|
||||||
//https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#the-key-tree
|
//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
|
//N(m/aH/b/c) = N(m/aH/b)/c = N(m/aH)/b/c
|
||||||
val path1 = m
|
val path1 = m
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.bitcoins.core.crypto
|
||||||
|
|
||||||
|
import org.bitcoins.testkit.core.gen.{CryptoGenerators, HDGenerators}
|
||||||
|
import org.bitcoins.testkit.util.{BitcoinSAsyncTest, BitcoinSUnitTest}
|
||||||
|
|
||||||
|
class ExtSignTest extends BitcoinSAsyncTest {
|
||||||
|
|
||||||
|
it must "be able to sign something that extends ExtSignKey" in {
|
||||||
|
forAll(CryptoGenerators.extPrivateKey, CryptoGenerators.sha256Digest) {
|
||||||
|
case (extPrivKey, hash) =>
|
||||||
|
val sig = extPrivKey.sign(hash.bytes)
|
||||||
|
assert(extPrivKey.publicKey.verify(hash,sig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "be able to sign a specific path of a ext key" in {
|
||||||
|
forAllAsync(CryptoGenerators.extPrivateKey, CryptoGenerators.sha256Digest, HDGenerators.bip32Path) {
|
||||||
|
case (extPrivKey, hash, path) =>
|
||||||
|
val sigF = extPrivKey.deriveAndSignFuture(hash.bytes,path)
|
||||||
|
val childPubKey = extPrivKey.deriveChildPubKey(path).get
|
||||||
|
sigF.map { sig =>
|
||||||
|
assert(childPubKey.key.verify(hash,sig))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,8 +29,6 @@ class SignTest extends BitcoinSUnitTest {
|
||||||
val sigF = signTestImpl.signFunction(hash.bytes)
|
val sigF = signTestImpl.signFunction(hash.bytes)
|
||||||
|
|
||||||
sigF.map(sig => assert(pubKey.verify(hash.hex, sig)))
|
sigF.map(sig => assert(pubKey.verify(hash.hex, sig)))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ import org.bitcoins.core.hd.{BIP32Node, BIP32Path}
|
||||||
import org.bitcoins.core.number.{UInt32, UInt8}
|
import org.bitcoins.core.number.{UInt32, UInt8}
|
||||||
import org.bitcoins.core.protocol.NetworkElement
|
import org.bitcoins.core.protocol.NetworkElement
|
||||||
import org.bitcoins.core.util._
|
import org.bitcoins.core.util._
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.{ByteVector, HexStringSyntax}
|
||||||
import scodec.bits.HexStringSyntax
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
import scala.concurrent.Future
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,7 +157,7 @@ object ExtKey extends Factory[ExtKey] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed abstract class ExtPrivateKey extends ExtKey {
|
sealed abstract class ExtPrivateKey extends ExtKey with ExtSign {
|
||||||
import ExtKeyVersion._
|
import ExtKeyVersion._
|
||||||
|
|
||||||
override protected type VersionType = ExtKeyPrivVersion
|
override protected type VersionType = ExtKeyPrivVersion
|
||||||
|
@ -210,6 +210,20 @@ sealed abstract class ExtPrivateKey extends ExtKey {
|
||||||
def deriveChildPrivKey(idx: Long): Try[ExtPrivateKey] = {
|
def deriveChildPrivKey(idx: Long): Try[ExtPrivateKey] = {
|
||||||
Try(UInt32(idx)).map(deriveChildPrivKey)
|
Try(UInt32(idx)).map(deriveChildPrivKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def publicKey: ECPublicKey = key.publicKey
|
||||||
|
|
||||||
|
override def signFunction: ByteVector => Future[ECDigitalSignature] = {
|
||||||
|
key.signFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Signs the given bytes with the given [[BIP32Path path]] */
|
||||||
|
override def deriveAndSignFuture: (
|
||||||
|
ByteVector,
|
||||||
|
BIP32Path) => Future[ECDigitalSignature] = {
|
||||||
|
case (bytes, path) =>
|
||||||
|
deriveChildPrivKey(path).signFunction(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ExtPrivateKey extends Factory[ExtPrivateKey] {
|
object ExtPrivateKey extends Factory[ExtPrivateKey] {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.bitcoins.core.crypto
|
package org.bitcoins.core.crypto
|
||||||
|
|
||||||
|
import org.bitcoins.core.hd.BIP32Path
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.concurrent.duration.DurationInt
|
import scala.concurrent.duration.DurationInt
|
||||||
|
@ -58,3 +59,14 @@ object Sign {
|
||||||
}, publicKey)
|
}, publicKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A signing interface for [[ExtKey]] */
|
||||||
|
trait ExtSign extends Sign {
|
||||||
|
|
||||||
|
def deriveAndSignFuture: (ByteVector, BIP32Path) => Future[ECDigitalSignature]
|
||||||
|
|
||||||
|
/** First derives the child key that corresponds to [[BIP32Path path]] and then signs */
|
||||||
|
def sign(bytes: ByteVector, path: BIP32Path): ECDigitalSignature = {
|
||||||
|
Await.result(deriveAndSignFuture(bytes, path), 30.seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -65,39 +65,6 @@ After providing this information, you can generate a validly signed bitcoin tran
|
||||||
|
|
||||||
To see a complete example of this, see [our `TxBuilder` example](txbuilder.md)
|
To see a complete example of this, see [our `TxBuilder` example](txbuilder.md)
|
||||||
|
|
||||||
### The [`Sign` API](/api/org/bitcoins/core/crypto/Sign)
|
|
||||||
|
|
||||||
This is the API we define to sign things with. It takes in an arbitrary byte vector and returns a `Future[ECDigitalSignature]`. The reason we incorporate `Future`s here is for extensibility of this API. We would like to provide implementations of this API for hardware devices, which need to be asynchrnous since they may require user input.
|
|
||||||
|
|
||||||
From [`core/src/main/scala/org/bitcoins/core/crypto/Sign.scala`](/api/org/bitcoins/core/crypto/Sign):
|
|
||||||
|
|
||||||
```scala mdoc
|
|
||||||
import scodec.bits._
|
|
||||||
import org.bitcoins.core.crypto._
|
|
||||||
import scala.concurrent._
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
trait Sign {
|
|
||||||
def signFunction: ByteVector => Future[ECDigitalSignature]
|
|
||||||
|
|
||||||
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
|
|
||||||
signFunction(bytes)
|
|
||||||
|
|
||||||
def sign(bytes: ByteVector): ECDigitalSignature = {
|
|
||||||
Await.result(signFuture(bytes), 30.seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
def publicKey: ECPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The `ByteVector` that is input to the `signFunction` should be the hash that is output from [`TransactionSignatureSerializer`](/api/org/bitcoins/core/crypto/TransactionSignatureSerializer)'s `hashForSignature` method. Our in-memory [`ECKey`](/api/org/bitcoins/core/crypto/ECKey) types implement the `Sign` API.
|
|
||||||
|
|
||||||
If you wanted to implement a new `Sign` api for a hardware wallet, you can easily pass it into the `TxBuilder`/`Signer` classes to allow for you to use those devices to sign with Bitcoin-S.
|
|
||||||
|
|
||||||
This API is currently used to sign ordinary transactions with our [`Signer`](/api/org/bitcoins/core/wallet/signer/Signer)s. The `Signer` subtypes (i.e. `P2PKHSigner`) implement the specific functionality needed to produce a valid digital signature for their corresponding script type.
|
|
||||||
|
|
||||||
### Verifying a transaction's script is valid (does not check if UTXO is valid)
|
### Verifying a transaction's script is valid (does not check if UTXO is valid)
|
||||||
|
|
||||||
Transactions are run through the interpreter to check their validity. These are packaged up into an object called `ScriptProgram`, which contains the following:
|
Transactions are run through the interpreter to check their validity. These are packaged up into an object called `ScriptProgram`, which contains the following:
|
||||||
|
|
|
@ -127,3 +127,7 @@ firstAccountAddress.value
|
||||||
// HD path to generate an address at:
|
// HD path to generate an address at:
|
||||||
val nextAddressPath: SegWitHDPath = firstAddressPath.next
|
val nextAddressPath: SegWitHDPath = firstAddressPath.next
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Signing things with HD keys
|
||||||
|
|
||||||
|
Please see [sign.md](sign.md) for information on how to sign things with HD keys.
|
61
docs/core/sign.md
Normal file
61
docs/core/sign.md
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
---
|
||||||
|
id: sign
|
||||||
|
title: Sign api
|
||||||
|
---
|
||||||
|
|
||||||
|
### The [`Sign` API](org/bitcoins/core/crypto/Sign.scala)
|
||||||
|
|
||||||
|
This is the API we define to sign things with. It takes in an arbitrary byte vector and returns a `Future[ECDigitalSignature]`. The reason we incorporate `Future`s here is for extensibility of this API. We would like to provide implementations of this API for hardware devices, which need to be asynchrnous since they may require user input.
|
||||||
|
|
||||||
|
From [Sign.scala](../../core/src/main/scala/org/bitcoins/core/crypto/Sign.scala):
|
||||||
|
|
||||||
|
```scala mdoc
|
||||||
|
import scodec.bits._
|
||||||
|
import org.bitcoins.core.crypto._
|
||||||
|
import scala.concurrent._
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
trait Sign {
|
||||||
|
def signFunction: ByteVector => Future[ECDigitalSignature]
|
||||||
|
|
||||||
|
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
|
||||||
|
signFunction(bytes)
|
||||||
|
|
||||||
|
def sign(bytes: ByteVector): ECDigitalSignature = {
|
||||||
|
Await.result(signFuture(bytes), 30.seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
def publicKey: ECPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The `ByteVector` that is input to the `signFunction` should be the hash that is output from [`TransactionSignatureSerializer`](/api/org/bitcoins/core/crypto/TransactionSignatureSerializer)'s `hashForSignature` method. Our in-memory [`ECKey`](/api/org/bitcoins/core/crypto/ECKey) types implement the `Sign` API.
|
||||||
|
|
||||||
|
If you wanted to implement a new `Sign` api for a hardware wallet, you can easily pass it into the `TxBuilder`/`Signer` classes to allow for you to use those devices to sign with Bitcoin-S.
|
||||||
|
|
||||||
|
This API is currently used to sign ordinary transactions with our [`Signer`](/api/org/bitcoins/core/wallet/signer/Signer)s. The `Signer` subtypes (i.e. `P2PKHSigner`) implement the specific functionality needed to produce a valid digital signature for their corresponding script type.
|
||||||
|
|
||||||
|
|
||||||
|
### The [`ExtSign`](../../core/src/main/scala/org/bitcoins/core/crypto/Sign.scala) API.
|
||||||
|
|
||||||
|
An [ExtKey](org/bitcoins/core/crypto/ExtKey.scala) is a data structure that can be used to generate more keys from a parent key. For more information look at [hd-keys.md](hd-keys.md)
|
||||||
|
|
||||||
|
You can sign with `ExtPrivateKey` the same way you could with a normal `ECPrivateKey`.
|
||||||
|
|
||||||
|
```scala mdoc:to-string
|
||||||
|
import org.bitcoins.core.hd._
|
||||||
|
import org.bitcoins.core.crypto._
|
||||||
|
|
||||||
|
val extPrivKey = ExtPrivateKey(ExtKeyVersion.SegWitMainNetPriv)
|
||||||
|
|
||||||
|
extPrivKey.sign(DoubleSha256Digest.empty.bytes)
|
||||||
|
|
||||||
|
val path = BIP32Path(Vector(BIP32Node(0,false)))
|
||||||
|
|
||||||
|
extPrivKey.sign(DoubleSha256Digest.empty.bytes,path)
|
||||||
|
```
|
||||||
|
|
||||||
|
With `ExtSign`, you can use `ExtPrivateKey` to sign transactions inside of `TxBuilder` since `UTXOSpendingInfo` takes in `Sign` as a parameter.
|
||||||
|
|
||||||
|
You can also provide a `path` to use to derive a child `ExtPrivateKey`, and then sign with that child private key
|
|
@ -5,6 +5,7 @@
|
||||||
"core/core-intro",
|
"core/core-intro",
|
||||||
"core/addresses",
|
"core/addresses",
|
||||||
"core/hd-keys",
|
"core/hd-keys",
|
||||||
|
"core/sign",
|
||||||
"core/txbuilder"
|
"core/txbuilder"
|
||||||
],
|
],
|
||||||
"RPC clients": [
|
"RPC clients": [
|
||||||
|
|
Loading…
Add table
Reference in a new issue