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:
Chris Stewart 2019-12-20 12:47:10 -06:00 committed by GitHub
parent c67b2978bf
commit 67eb7df235
9 changed files with 139 additions and 55 deletions

View file

@ -3,25 +3,25 @@ package org.bitcoins.core.crypto
import org.bitcoins.testkit.core.gen.CryptoGenerators
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.testkit.util.BitcoinSUnitTest
import org.scalacheck.{Gen, Prop, Properties}
import scala.util.Success
class ExtKeySpec extends Properties("ExtKeySpec") {
private val logger = BitcoinSLogger.logger
property("serialization symmetry") = {
class ExtKeySpec extends BitcoinSUnitTest {
private val nonHardened: Gen[UInt32] =
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 =>
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") = {
it must "have derivation identity 1" in {
Prop.forAllNoShrink(CryptoGenerators.extPrivateKey,
nonHardened,
nonHardened,
@ -57,7 +57,7 @@ class ExtKeySpec extends Properties("ExtKeySpec") {
}
}
property("derivation identity 2") = {
it must "derivation identity 2" in {
Prop.forAllNoShrink(CryptoGenerators.extPrivateKey,
hardened,
nonHardened,

View file

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

View file

@ -29,8 +29,6 @@ class SignTest extends BitcoinSUnitTest {
val sigF = signTestImpl.signFunction(hash.bytes)
sigF.map(sig => assert(pubKey.verify(hash.hex, sig)))
}
}
}

View file

@ -5,10 +5,10 @@ import org.bitcoins.core.hd.{BIP32Node, BIP32Path}
import org.bitcoins.core.number.{UInt32, UInt8}
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util._
import scodec.bits.ByteVector
import scodec.bits.HexStringSyntax
import scodec.bits.{ByteVector, HexStringSyntax}
import scala.annotation.tailrec
import scala.concurrent.Future
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._
override protected type VersionType = ExtKeyPrivVersion
@ -210,6 +210,20 @@ sealed abstract class ExtPrivateKey extends ExtKey {
def deriveChildPrivKey(idx: Long): Try[ExtPrivateKey] = {
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] {

View file

@ -1,5 +1,6 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.hd.BIP32Path
import scodec.bits.ByteVector
import scala.concurrent.duration.DurationInt
@ -58,3 +59,14 @@ object Sign {
}, 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)
}
}

View file

@ -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)
### 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)
Transactions are run through the interpreter to check their validity. These are packaged up into an object called `ScriptProgram`, which contains the following:

View file

@ -127,3 +127,7 @@ firstAccountAddress.value
// HD path to generate an address at:
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
View 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

View file

@ -5,6 +5,7 @@
"core/core-intro",
"core/addresses",
"core/hd-keys",
"core/sign",
"core/txbuilder"
],
"RPC clients": [