mirror of
https://github.com/btcsuite/btcd.git
synced 2025-02-23 14:40:44 +01:00
txscript: add new RawTxInTapscriptSignature to generate tapsript sigs
In this commit, we add a new function `RawTxInTapscriptSignature` that will be used to generate signatures in the _tapscript_ context. Note that this differs from top-level taproot as a distinct sighash is used, and we _always_ accept a root hash to perform the proper tweak.
This commit is contained in:
parent
37f8c8ba0a
commit
6fc4199ee4
9 changed files with 272 additions and 14 deletions
|
@ -27,5 +27,11 @@ func NewPrivateKey() (*PrivateKey, error) {
|
|||
return secp.GeneratePrivateKey()
|
||||
}
|
||||
|
||||
// PrivKeyFromScalar instantiates a new private key from a scalar encoded as a
|
||||
// big integer.
|
||||
func PrivKeyFromScalar(key *ModNScalar) *PrivateKey {
|
||||
return &PrivateKey{Key: *key}
|
||||
}
|
||||
|
||||
// PrivKeyBytesLen defines the length in bytes of a serialized private key.
|
||||
const PrivKeyBytesLen = 32
|
||||
|
|
|
@ -11,6 +11,7 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo
|
|||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
|
@ -43,6 +44,11 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
|||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -79,3 +85,5 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -12,6 +12,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||
)
|
||||
|
|
|
@ -8,6 +8,7 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo
|
|||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
|
@ -38,6 +39,11 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
|||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
|
@ -73,3 +79,5 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
5
go.mod
5
go.mod
|
@ -9,9 +9,11 @@ require (
|
|||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
|
||||
github.com/btcsuite/winsvc v1.0.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
|
||||
github.com/decred/dcrd/lru v1.0.0
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/jrick/logrotate v1.0.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
)
|
||||
|
@ -19,9 +21,10 @@ require (
|
|||
require (
|
||||
github.com/aead/siphash v1.0.1 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
replace github.com/btcsuite/btcd/btcutil => ./btcutil
|
||||
|
|
9
go.sum
9
go.sum
|
@ -15,6 +15,7 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3
|
|||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
|
@ -55,6 +56,11 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5
|
|||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -91,6 +97,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
@ -99,3 +106,5 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -63,14 +63,12 @@ func WitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int, amt int64
|
|||
return wire.TxWitness{sig, pkData}, nil
|
||||
}
|
||||
|
||||
// RawTxInWitnessSignature returns a valid schnorr signature required to
|
||||
// perform a taproot key-spend of the specified input. An explicit sighash is
|
||||
// always attached, which can be removed by a caller if they wish to
|
||||
// implicitly use the "default" sighash with a 64-byte signature.
|
||||
//
|
||||
// TODO(roasbeef): also need a tapscript version of this as well
|
||||
// RawTxInTaprootSignature returns a valid schnorr signature required to
|
||||
// perform a taproot key-spend of the specified input. If SigHashDefault was
|
||||
// specified, then the returned signature is 64-byte in length, as it omits the
|
||||
// additional byte to denote the sighash type.
|
||||
func RawTxInTaprootSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
|
||||
amt int64, pkScript []byte, hashType SigHashType,
|
||||
amt int64, pkScript []byte, tapScriptRootHash []byte, hashType SigHashType,
|
||||
key *btcec.PrivateKey) ([]byte, error) {
|
||||
|
||||
// First, we'll start by compute the top-level taproot sighash.
|
||||
|
@ -82,27 +80,48 @@ func RawTxInTaprootSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Before we sign the sighash, we'll need to apply the taptweak to the
|
||||
// private key based on the tapScriptRootHash.
|
||||
privKeyTweak := TweakTaprootPrivKey(key, tapScriptRootHash)
|
||||
|
||||
// With the sighash constructed, we can sign it with the specified
|
||||
// private key.
|
||||
signature, err := schnorr.Sign(key, sigHash)
|
||||
signature, err := schnorr.Sign(privKeyTweak, sigHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, append the sighash type to the final sig.
|
||||
return append(signature.Serialize(), byte(hashType)), nil
|
||||
sig := signature.Serialize()
|
||||
|
||||
// If this is sighash default, then we can just return the signature
|
||||
// directly.
|
||||
if hashType&SigHashDefault == SigHashDefault {
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// Otherwise, append the sighash type to the final sig.
|
||||
return append(sig, byte(hashType)), nil
|
||||
}
|
||||
|
||||
// TaprootWitnessSignature returns a valid witness stack that can be used to
|
||||
// spend the key-spend path of a taproot input as specified in BIP 342.
|
||||
// spend the key-spend path of a taproot input as specified in BIP 342 and BIP
|
||||
// 86. This method assumes that the public key included in pkScript was
|
||||
// generated using ComputeTaprootKeyNoScript that commits to a fake root
|
||||
// tapscript hash. If not, then RawTxInTaprootSignature should be used with the
|
||||
// actual committed contents.
|
||||
//
|
||||
// TODO(roasbeef): add support for annex even tho it's non-standard?
|
||||
func TaprootWitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
|
||||
amt int64, pkScript []byte, hashType SigHashType,
|
||||
key *btcec.PrivateKey) (wire.TxWitness, error) {
|
||||
|
||||
// As we're assuming this was a BIP 86 key, we use an empty root hash
|
||||
// which means output key commits to just the public key.
|
||||
fakeTapscriptRootHash := []byte{}
|
||||
|
||||
sig, err := RawTxInTaprootSignature(
|
||||
tx, sigHashes, idx, amt, pkScript, hashType, key,
|
||||
tx, sigHashes, idx, amt, pkScript, fakeTapscriptRootHash,
|
||||
hashType, key,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -114,6 +133,45 @@ func TaprootWitnessSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
|
|||
return wire.TxWitness{sig}, nil
|
||||
}
|
||||
|
||||
// RawTxInTapscriptSignature computes a raw schnorr signature for a signature
|
||||
// generated from a tapscript leaf. This differs from the
|
||||
// RawTxInTaprootSignature which is used to generate signatures for top-level
|
||||
// taproot key spends.
|
||||
//
|
||||
// TODO(roasbeef): actually add code-sep to interface? not really used
|
||||
// anywhere....
|
||||
func RawTxInTapscriptSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,
|
||||
amt int64, pkScript []byte, tapLeaf TapLeaf, hashType SigHashType,
|
||||
privKey *btcec.PrivateKey) ([]byte, error) {
|
||||
|
||||
// First, we'll start by compute the top-level taproot sighash.
|
||||
tapLeafHash := tapLeaf.TapHash()
|
||||
sigHash, err := calcTaprootSignatureHashRaw(
|
||||
sigHashes, hashType, tx, idx,
|
||||
NewCannedPrevOutputFetcher(pkScript, amt),
|
||||
WithBaseTapscriptVersion(blankCodeSepValue, tapLeafHash[:]),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the sighash constructed, we can sign it with the specified
|
||||
// private key.
|
||||
signature, err := schnorr.Sign(privKey, sigHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, append the sighash type to the final sig if it's not the
|
||||
// default sighash value (in which case appending it is disallowed).
|
||||
if hashType != SigHashDefault {
|
||||
return append(signature.Serialize(), byte(hashType)), nil
|
||||
}
|
||||
|
||||
// The default sighash case where we'll return _just_ the signature.
|
||||
return signature.Serialize(), nil
|
||||
}
|
||||
|
||||
// RawTxInSignature returns the serialized ECDSA signature for the input idx of
|
||||
// the given transaction, with hashType appended to it.
|
||||
func RawTxInSignature(tx *wire.MsgTx, idx int, subScript []byte,
|
||||
|
|
|
@ -96,6 +96,9 @@ func VerifyTaprootKeySpend(witnessProgram []byte, rawSig []byte, tx *wire.MsgTx,
|
|||
// ControlBlock houses the structured witness input for a taproot spend. This
|
||||
// includes the internal taproot key, the leaf version, and finally a nearly
|
||||
// complete merkle inclusion proof for the main taproot commitment.
|
||||
//
|
||||
// TODO(roasbeef): method to serialize control block that commits to even
|
||||
// y-bit, which pops up everywhere even tho 32 byte keys
|
||||
type ControlBlock struct {
|
||||
// InternalKey is the internal public key in the taproot commitment.
|
||||
InternalKey *secp.PublicKey
|
||||
|
@ -272,6 +275,54 @@ func ComputeTaprootOutputKey(pubKey *btcec.PublicKey,
|
|||
return btcec.NewPublicKey(&taprootKey.X, &taprootKey.Y)
|
||||
}
|
||||
|
||||
// ComputeTaprootKeyNoScript calculates the top-level taproot output key given
|
||||
// an internal key, and a desire that the only way an output can be spent is
|
||||
// with the keyspend path. This is useful for normal wallet operations that
|
||||
// don't need any other additional spending conditions.
|
||||
func ComputeTaprootKeyNoScript(internalKey *btcec.PublicKey) *btcec.PublicKey {
|
||||
// We'll compute a custom tap tweak hash that just commits to the key,
|
||||
// rather than an actual root hash.
|
||||
fakeScriptroot := []byte{}
|
||||
|
||||
return ComputeTaprootOutputKey(internalKey, fakeScriptroot)
|
||||
}
|
||||
|
||||
// TweakTaprootPrivKey applies the same operation as ComputeTaprootOutputKey,
|
||||
// but on the private key instead. The final key is derived as: privKey +
|
||||
// h_tapTweak(internalKey || merkleRoot) % N, where N is the order of the
|
||||
// secp256k1 curve, and merkleRoot is the root hash of the tapscript tree.
|
||||
func TweakTaprootPrivKey(privKey *btcec.PrivateKey,
|
||||
scriptRoot []byte) *btcec.PrivateKey {
|
||||
|
||||
// If the corresponding public key has an odd y coordinate, then we'll
|
||||
// negate the private key as specified in BIP 341.
|
||||
privKeyScalar := &privKey.Key
|
||||
pubKeyBytes := privKey.PubKey().SerializeCompressed()
|
||||
if pubKeyBytes[0] == btcec.PubKeyFormatCompressedOdd {
|
||||
privKeyScalar.Negate()
|
||||
}
|
||||
|
||||
// Next, we'll compute the tap tweak hash that commits to the internal
|
||||
// key and the merkle script root. We'll snip off the extra parity byte
|
||||
// from the compressed serialization and use that directly.
|
||||
schnorrKeyBytes := pubKeyBytes[1:]
|
||||
tapTweakHash := chainhash.TaggedHash(
|
||||
chainhash.TagTapTweak, schnorrKeyBytes, scriptRoot,
|
||||
)
|
||||
|
||||
// Map the private key to a ModNScalar which is needed to perform
|
||||
// operation mod the curve order.
|
||||
var tweakScalar btcec.ModNScalar
|
||||
tweakScalar.SetBytes((*[32]byte)(tapTweakHash))
|
||||
|
||||
// Now that we have the private key in its may negated form, we'll add
|
||||
// the script root as a tweak. As we're using a ModNScalar all
|
||||
// operations are already normalized mod the curve order.
|
||||
privTweak := privKeyScalar.Add(&tweakScalar)
|
||||
|
||||
return btcec.PrivKeyFromScalar(privTweak)
|
||||
}
|
||||
|
||||
// VerifyTaprootLeafCommitment attempts to verify a taproot commitment of the
|
||||
// revealed script within the taprootWitnessProgram (a schnorr public key)
|
||||
// given the required information included in the control block. An error is
|
||||
|
|
|
@ -8,13 +8,40 @@ import (
|
|||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
testPubBytes, _ = hex.DecodeString("F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9")
|
||||
testPubBytes, _ = hex.DecodeString("F9308A019258C31049344F85F89D5229B" +
|
||||
"531C845836F99B08601F113BCE036F9")
|
||||
|
||||
// rootKey is the test root key defined in the test vectors:
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
|
||||
rootKey, _ = hdkeychain.NewKeyFromString(
|
||||
"xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLi" +
|
||||
"sriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu",
|
||||
)
|
||||
|
||||
// accountPath is the base path for BIP86 (m/86'/0'/0').
|
||||
accountPath = []uint32{
|
||||
86 + hdkeychain.HardenedKeyStart, hdkeychain.HardenedKeyStart,
|
||||
hdkeychain.HardenedKeyStart,
|
||||
}
|
||||
expectedExternalAddresses = []string{
|
||||
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
|
||||
"bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
|
||||
}
|
||||
expectedInternalAddresses = []string{
|
||||
"bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
|
||||
}
|
||||
)
|
||||
|
||||
// TestControlBlockParsing tests that we're able to generate and parse a valid
|
||||
|
@ -131,3 +158,90 @@ func TestControlBlockParsing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTaprootScriptSpendTweak tests that for any 32-byte hypothetical script
|
||||
// root, the resulting tweaked public key is the same as tweaking the private
|
||||
// key, then generating a public key from that. This test a quickcheck test to
|
||||
// assert the following invariant:
|
||||
//
|
||||
// * taproot_tweak_pubkey(pubkey_gen(seckey), h)[1] ==
|
||||
// pubkey_gen(taproot_tweak_seckey(seckey, h))
|
||||
func TestTaprootScriptSpendTweak(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Assert that if we use this x value as the hash of the script root,
|
||||
// then if we generate a tweaked public key, it's the same key as if we
|
||||
// used that key to generate the tweaked
|
||||
// private key, and then generated the public key from that.
|
||||
f := func(x [32]byte) bool {
|
||||
privKey, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Generate the tweaked public key using the x value as the
|
||||
// script root.
|
||||
tweakedPub := ComputeTaprootOutputKey(privKey.PubKey(), x[:])
|
||||
|
||||
// Now we'll generate the corresponding tweaked private key.
|
||||
tweakedPriv := TweakTaprootPrivKey(privKey, x[:])
|
||||
|
||||
// The public key for this private key should be the same as
|
||||
// the tweaked public key we generate above.
|
||||
return tweakedPub.IsEqual(tweakedPriv.PubKey()) &&
|
||||
bytes.Equal(
|
||||
schnorr.SerializePubKey(tweakedPub),
|
||||
schnorr.SerializePubKey(tweakedPriv.PubKey()),
|
||||
)
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatalf("tweaked public/private key mapping is "+
|
||||
"incorrect: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestTaprootConstructKeyPath tests the key spend only taproot construction.
|
||||
func TestTaprootConstructKeyPath(t *testing.T) {
|
||||
checkPath := func(branch uint32, expectedAddresses []string) {
|
||||
path, err := derivePath(rootKey, append(accountPath, branch))
|
||||
require.NoError(t, err)
|
||||
|
||||
for index, expectedAddr := range expectedAddresses {
|
||||
extendedKey, err := path.Derive(uint32(index))
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKey, err := extendedKey.ECPubKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
tapKey := ComputeTaprootKeyNoScript(pubKey)
|
||||
|
||||
addr, err := btcutil.NewAddressTaproot(
|
||||
schnorr.SerializePubKey(tapKey),
|
||||
&chaincfg.MainNetParams,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, expectedAddr, addr.String())
|
||||
}
|
||||
}
|
||||
checkPath(0, expectedExternalAddresses)
|
||||
checkPath(1, expectedInternalAddresses)
|
||||
}
|
||||
|
||||
func derivePath(key *hdkeychain.ExtendedKey, path []uint32) (
|
||||
*hdkeychain.ExtendedKey, error) {
|
||||
|
||||
var (
|
||||
currentKey = key
|
||||
err error
|
||||
)
|
||||
for _, pathPart := range path {
|
||||
currentKey, err = currentKey.Derive(pathPart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return currentKey, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue