mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
Merge pull request #6361 from guggero/musig2
Musig2: add new MuSig2 RPC methods to `signrpc`
This commit is contained in:
commit
9bbee09497
26 changed files with 5220 additions and 307 deletions
|
@ -43,7 +43,7 @@ linters-settings:
|
|||
- G402 # Look for bad TLS connection settings.
|
||||
- G306 # Poor file permissions used when writing to a new file.
|
||||
staticcheck:
|
||||
go: "1.16"
|
||||
go: "1.18"
|
||||
checks: ["-SA1019"]
|
||||
|
||||
linters:
|
||||
|
|
117
docs/musig2.md
Normal file
117
docs/musig2.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
# MuSig2
|
||||
|
||||
With `lnd v0.15.0-beta` a new, experimental MuSig2 API was added to the
|
||||
`signrpc` subserver RPC. With MuSig2 it is possible to combine the public keys
|
||||
of multiple signing participants into a single, combined key. The signers can
|
||||
then later come together and cooperatively produce a single signature that is
|
||||
valid for that combined public key. MuSig2 therefore is an interactive `n-of-n`
|
||||
signature scheme that produces a final/complete Schnorr signature out of `n`
|
||||
partial signatures.
|
||||
|
||||
**NOTE**: At the time the MuSig2 code in `btcd`/`lnd` was written, there was no
|
||||
"official" MuSig2 support merged to either `bitcoind` or `secp256k1`. Therefore,
|
||||
some smaller details in the signing protocol could change in the future that
|
||||
might not be backward compatible. So this API must be seen as highly
|
||||
experimental and backward compatibility can't be guaranteed at this point.
|
||||
|
||||
## References
|
||||
* [MuSig2 paper](https://eprint.iacr.org/2020/1261.pdf)
|
||||
* [Draft MuSig
|
||||
BIP](https://github.com/jonasnick/bips/blob/musig2/bip-musig2.mediawiki)
|
||||
* [MuSig2 implementation discussion in
|
||||
`bitcoind`](https://github.com/bitcoin/bitcoin/issues/23326)
|
||||
|
||||
## A note on security
|
||||
|
||||
The MuSig2 signing protocol is secure from leaking private key information of
|
||||
the signers **as long as the same secret nonce is never used multiple times**.
|
||||
If the same nonce is used for signing multiple times then the private key can
|
||||
leak. To avoid this security risk, the `signrpc.MuSig2Sign` RPC can only be
|
||||
called a single time for the same session ID. This has the implication that if a
|
||||
signing session fails or is aborted (for example because a participant isn't
|
||||
responsive or the message changes after some participants have already started
|
||||
signing), a completely new signing session needs to be initialized, which
|
||||
internally creates fresh nonces.
|
||||
|
||||
## Examples
|
||||
|
||||
An API is sometimes easiest explained by showing concrete usage examples. Here
|
||||
we take a look at the MuSig2 integration tests in `lnd`, since they both serve
|
||||
to test the RPCs and to showcase the different use cases.
|
||||
|
||||
### 3-of-3 Taproot key spend path (BIP-0086)
|
||||
|
||||
See `testTaprootMuSig2KeySpendBip86` in
|
||||
[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see
|
||||
the full code.
|
||||
|
||||
This example uses combines the public keys of 3 participants into a shared
|
||||
MuSig2 public key that is then tweaked with the
|
||||
[BIP-0086](https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki#address-derivation)
|
||||
TapTweak to be turned into a Taproot public key that can be used directly as the
|
||||
`pkScript` of a p2tr output on chain.
|
||||
|
||||
The most notable parameter for this to work is the `TaprootTweak` parameter in
|
||||
the `MuSig2CreateSession` RPC call:
|
||||
|
||||
```go
|
||||
taprootTweak := &signrpc.TaprootTweakDesc{
|
||||
KeySpendOnly: true,
|
||||
}
|
||||
|
||||
sessResp1, err := node.SignerClient.MuSig2CreateSession(
|
||||
ctx, &signrpc.MuSig2SessionRequest{
|
||||
KeyLoc: keyDesc1.KeyLoc,
|
||||
AllSignerPubkeys: allPubKeys,
|
||||
TaprootTweak: taprootTweak,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 3-of-3 Taproot key spend path with root hash commitment
|
||||
|
||||
See `testTaprootMuSig2KeySpendRootHash` in
|
||||
[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see
|
||||
the full code.
|
||||
|
||||
This is very similar to the above example but with the main difference that the
|
||||
p2tr output on chain not only commits to a key spend path but also a script
|
||||
path. The MuSig2 combined key becomes the Taproot internal key and the TapTweak
|
||||
commits to both the internal key and the Taproot script merkle root hash.
|
||||
|
||||
The most notable parameter for this to work is the `TaprootTweak` parameter in
|
||||
the `MuSig2CreateSession` RPC call:
|
||||
|
||||
```go
|
||||
taprootTweak := &signrpc.TaprootTweakDesc{
|
||||
ScriptRoot: rootHash[:],
|
||||
}
|
||||
|
||||
sessResp1, err := node.SignerClient.MuSig2CreateSession(
|
||||
ctx, &signrpc.MuSig2SessionRequest{
|
||||
KeyLoc: keyDesc1.KeyLoc,
|
||||
AllSignerPubkeys: allPubKeys,
|
||||
TaprootTweak: taprootTweak,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 3-of-3 `OP_CHECKSIG` in Taproot script spend path
|
||||
|
||||
See `testTaprootMuSig2CombinedLeafKeySpend` in
|
||||
[`lntest/itest/lnd_taproot_test.go`](../lntest/itest/lnd_taproot_test.go) to see
|
||||
the full code.
|
||||
|
||||
This example is definitely the most involved one. To be able to use a MuSig2
|
||||
combined key and then spend it through a Taproot script spend with an
|
||||
`OP_CHECKSIG` script, the following steps need to be performed:
|
||||
|
||||
1. Derive signing keys on signers, combine them through `MuSig2CombineKeys`.
|
||||
2. Create a Taproot script tree with a script leaf `<combinedKey> OP_CHECKSIG`.
|
||||
3. Create the Taproot key by committing to the script tree root hash.
|
||||
4. When spending the output, the message being signed needs to be the sighash of
|
||||
a Taproot script spend that also commits to the leaf hash.
|
||||
5. The final witness stack needs to contain the combined signature, the leaf
|
||||
script and the control block (which contains the internal public key and the
|
||||
inclusion proof if there were any other script leaves).
|
||||
|
|
@ -25,6 +25,16 @@ addresses](https://github.com/lightningnetwork/lnd/pull/6263). Using
|
|||
then watch it on chain. Taproot script spends are also supported through the
|
||||
`signrpc.SignOutputRaw` RPC (`/v2/signer/signraw` in REST).
|
||||
|
||||
## MuSig2
|
||||
|
||||
The [`signrpc.Signer` RPC service now supports EXPERIMENTAL MuSig2
|
||||
signing](https://github.com/lightningnetwork/lnd/pull/6361).
|
||||
|
||||
More information can be found in the [MuSig2 documentation](../musig2.md).
|
||||
Note that the MuSig2 BIP is not final yet and therefore the MuSig2 API must be
|
||||
considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
releases. Backward compatibility is not guaranteed!
|
||||
|
||||
## `lncli`
|
||||
|
||||
* Add [auto-generated command-line completions](https://github.com/lightningnetwork/lnd/pull/4177)
|
||||
|
|
105
go.mod
105
go.mod
|
@ -1,11 +1,10 @@
|
|||
module github.com/lightningnetwork/lnd
|
||||
|
||||
require (
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
|
||||
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220330201728-074266215c26
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0
|
||||
github.com/btcsuite/btcd/btcutil v1.1.1
|
||||
github.com/btcsuite/btcd/btcutil/psbt v1.1.3
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
|
||||
|
@ -31,7 +30,6 @@ require (
|
|||
github.com/jedib0t/go-pretty/v6 v6.2.7
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/jrick/logrotate v1.0.0
|
||||
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect
|
||||
github.com/kkdai/bstream v1.0.0
|
||||
github.com/lightninglabs/neutrino v0.13.2
|
||||
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display
|
||||
|
@ -50,7 +48,6 @@ require (
|
|||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02
|
||||
github.com/urfave/cli v1.22.4
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0
|
||||
go.etcd.io/etcd/client/v3 v3.5.0
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||
|
@ -58,14 +55,108 @@ require (
|
|||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1
|
||||
gopkg.in/macaroon.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/aead/siphash v1.0.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||
github.com/btcsuite/winsvc v1.0.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/decred/dcrd/lru v1.0.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/fergusstrange/embedded-postgres v1.10.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.10.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.8.1 // indirect
|
||||
github.com/jackc/puddle v1.1.3 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect
|
||||
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/lib/pq v1.10.3 // indirect
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nwaples/rardecode v1.1.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.26.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/client/v2 v2.305.0 // indirect
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/raft/v3 v3.5.0 // indirect
|
||||
go.etcd.io/etcd/server/v3 v3.5.0 // indirect
|
||||
go.opentelemetry.io/contrib v0.20.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.17.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
// This replace is for https://github.com/advisories/GHSA-w73w-5m7g-f7qc
|
||||
replace github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt v3.2.1+incompatible
|
||||
|
||||
|
@ -83,6 +174,6 @@ replace github.com/lightninglabs/neutrino => github.com/lightninglabs/neutrino v
|
|||
|
||||
// If you change this please also update .github/pull_request_template.md and
|
||||
// docs/INSTALL.md.
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
retract v0.0.2
|
||||
|
|
6
go.sum
6
go.sum
|
@ -79,8 +79,9 @@ github.com/btcsuite/btcd v0.22.0-beta.0.20220330201728-074266215c26 h1:dgH5afJco
|
|||
github.com/btcsuite/btcd v0.22.0-beta.0.20220330201728-074266215c26/go.mod h1:taIcYprAW2g6Z9S0gGUxyR+zDwimyDMK5ePOX+iJ2ds=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
|
||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY=
|
||||
|
@ -757,6 +758,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -782,6 +784,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -980,6 +983,7 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
228
input/musig2.go
Normal file
228
input/musig2.go
Normal file
|
@ -0,0 +1,228 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
const (
|
||||
// MuSig2PartialSigSize is the size of a MuSig2 partial signature.
|
||||
// Because a partial signature is just the s value, this corresponds to
|
||||
// the length of a scalar.
|
||||
MuSig2PartialSigSize = 32
|
||||
)
|
||||
|
||||
// MuSig2SessionID is a type for a session ID that is just a hash of the MuSig2
|
||||
// combined key and the local public nonces.
|
||||
type MuSig2SessionID [sha256.Size]byte
|
||||
|
||||
// MuSig2Signer is an interface that declares all methods that a MuSig2
|
||||
// compatible signer needs to implement.
|
||||
type MuSig2Signer interface {
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the
|
||||
// local key identified by the key locator. The complete list of all
|
||||
// public keys of all signing parties must be provided, including the
|
||||
// public key of the local signing key. If nonces of other parties are
|
||||
// already known, they can be submitted as well to reduce the number of
|
||||
// method calls necessary later on.
|
||||
MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey,
|
||||
*MuSig2Tweaks, [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo,
|
||||
error)
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other
|
||||
// signing participants for a session identified by its ID. This method
|
||||
// returns true once we have all nonces for all other signing
|
||||
// participants.
|
||||
MuSig2RegisterNonces(MuSig2SessionID,
|
||||
[][musig2.PubNonceSize]byte) (bool, error)
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key
|
||||
// that was specified when the session was created. This can only be
|
||||
// called when all public nonces of all participants are known and have
|
||||
// been registered with the session. If this node isn't responsible for
|
||||
// combining all the partial signatures, then the cleanup parameter
|
||||
// should be set, indicating that the session can be removed from memory
|
||||
// once the signature was produced.
|
||||
MuSig2Sign(MuSig2SessionID, [sha256.Size]byte,
|
||||
bool) (*musig2.PartialSignature, error)
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||
// local one, if it already exists. Once a partial signature of all
|
||||
// participants is registered, the final signature will be combined and
|
||||
// returned.
|
||||
MuSig2CombineSig(MuSig2SessionID,
|
||||
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error)
|
||||
}
|
||||
|
||||
// MuSig2SessionInfo is a struct for keeping track of a signing session
|
||||
// information in memory.
|
||||
type MuSig2SessionInfo struct {
|
||||
// SessionID is the wallet's internal unique ID of this session. The ID
|
||||
// is the hash over the combined public key and the local public nonces.
|
||||
SessionID [32]byte
|
||||
|
||||
// PublicNonce contains the public nonce of the local signer session.
|
||||
PublicNonce [musig2.PubNonceSize]byte
|
||||
|
||||
// CombinedKey is the combined public key with all tweaks applied to it.
|
||||
CombinedKey *btcec.PublicKey
|
||||
|
||||
// TaprootTweak indicates whether a taproot tweak (BIP-0086 or script
|
||||
// path) was used. The TaprootInternalKey will only be set if this is
|
||||
// set to true.
|
||||
TaprootTweak bool
|
||||
|
||||
// TaprootInternalKey is the raw combined public key without any tweaks
|
||||
// applied to it. This is only set if TaprootTweak is true.
|
||||
TaprootInternalKey *btcec.PublicKey
|
||||
|
||||
// HaveAllNonces indicates whether this session already has all nonces
|
||||
// of all other signing participants registered.
|
||||
HaveAllNonces bool
|
||||
|
||||
// HaveAllSigs indicates whether this session already has all partial
|
||||
// signatures of all other signing participants registered.
|
||||
HaveAllSigs bool
|
||||
}
|
||||
|
||||
// MuSig2Tweaks is a struct that contains all tweaks that can be applied to a
|
||||
// MuSig2 combined public key.
|
||||
type MuSig2Tweaks struct {
|
||||
// GenericTweaks is a list of normal tweaks to apply to the combined
|
||||
// public key (and to the private key when signing).
|
||||
GenericTweaks []musig2.KeyTweakDesc
|
||||
|
||||
// TaprootBIP0086Tweak indicates that the final key should use the
|
||||
// taproot tweak as defined in BIP 341, with the BIP 86 modification:
|
||||
// outputKey = internalKey + h_tapTweak(internalKey)*G.
|
||||
// In this case, the aggregated key before the tweak will be used as the
|
||||
// internal key. If this is set to true then TaprootTweak will be
|
||||
// ignored.
|
||||
TaprootBIP0086Tweak bool
|
||||
|
||||
// TaprootTweak specifies that the final key should use the taproot
|
||||
// tweak as defined in BIP 341:
|
||||
// outputKey = internalKey + h_tapTweak(internalKey || scriptRoot).
|
||||
// In this case, the aggregated key before the tweak will be used as the
|
||||
// internal key. Will be ignored if TaprootBIP0086Tweak is set to true.
|
||||
TaprootTweak []byte
|
||||
}
|
||||
|
||||
// HasTaprootTweak returns true if either a taproot BIP0086 tweak or a taproot
|
||||
// script root tweak is set.
|
||||
func (t *MuSig2Tweaks) HasTaprootTweak() bool {
|
||||
return t.TaprootBIP0086Tweak || len(t.TaprootTweak) > 0
|
||||
}
|
||||
|
||||
// ToContextOptions converts the tweak descriptor to context options.
|
||||
func (t *MuSig2Tweaks) ToContextOptions() []musig2.ContextOption {
|
||||
var tweakOpts []musig2.ContextOption
|
||||
if len(t.GenericTweaks) > 0 {
|
||||
tweakOpts = append(tweakOpts, musig2.WithTweakedContext(
|
||||
t.GenericTweaks...,
|
||||
))
|
||||
}
|
||||
|
||||
// The BIP0086 tweak and the taproot script tweak are mutually
|
||||
// exclusive.
|
||||
if t.TaprootBIP0086Tweak {
|
||||
tweakOpts = append(tweakOpts, musig2.WithBip86TweakCtx())
|
||||
} else if len(t.TaprootTweak) > 0 {
|
||||
tweakOpts = append(tweakOpts, musig2.WithTaprootTweakCtx(
|
||||
t.TaprootTweak,
|
||||
))
|
||||
}
|
||||
|
||||
return tweakOpts
|
||||
}
|
||||
|
||||
// MuSig2CombineKeys combines the given set of public keys into a single
|
||||
// combined MuSig2 combined public key, applying the given tweaks.
|
||||
func MuSig2CombineKeys(allSignerPubKeys []*btcec.PublicKey,
|
||||
tweaks *MuSig2Tweaks) (*musig2.AggregateKey, error) {
|
||||
|
||||
// Convert the tweak options into the appropriate MuSig2 API functional
|
||||
// options.
|
||||
var keyAggOpts []musig2.KeyAggOption
|
||||
switch {
|
||||
case tweaks.TaprootBIP0086Tweak:
|
||||
keyAggOpts = append(keyAggOpts, musig2.WithBIP86KeyTweak())
|
||||
case len(tweaks.TaprootTweak) > 0:
|
||||
keyAggOpts = append(keyAggOpts, musig2.WithTaprootKeyTweak(
|
||||
tweaks.TaprootTweak,
|
||||
))
|
||||
case len(tweaks.GenericTweaks) > 0:
|
||||
keyAggOpts = append(keyAggOpts, musig2.WithKeyTweaks(
|
||||
tweaks.GenericTweaks...,
|
||||
))
|
||||
}
|
||||
|
||||
// Then we'll use this information to compute the aggregated public key.
|
||||
combinedKey, _, _, err := musig2.AggregateKeys(
|
||||
allSignerPubKeys, true, keyAggOpts...,
|
||||
)
|
||||
return combinedKey, err
|
||||
}
|
||||
|
||||
// NewMuSig2SessionID returns the unique ID of a MuSig2 session by using the
|
||||
// combined key and the local public nonces and hashing that data.
|
||||
func NewMuSig2SessionID(combinedKey *btcec.PublicKey,
|
||||
publicNonces [musig2.PubNonceSize]byte) MuSig2SessionID {
|
||||
|
||||
// We hash the data to save some bytes in memory.
|
||||
hash := sha256.New()
|
||||
_, _ = hash.Write(combinedKey.SerializeCompressed())
|
||||
_, _ = hash.Write(publicNonces[:])
|
||||
|
||||
id := MuSig2SessionID{}
|
||||
copy(id[:], hash.Sum(nil))
|
||||
return id
|
||||
}
|
||||
|
||||
// SerializePartialSignature encodes the partial signature to a fixed size byte
|
||||
// array.
|
||||
func SerializePartialSignature(
|
||||
sig *musig2.PartialSignature) ([MuSig2PartialSigSize]byte, error) {
|
||||
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
result [MuSig2PartialSigSize]byte
|
||||
)
|
||||
if err := sig.Encode(&buf); err != nil {
|
||||
return result, fmt.Errorf("error encoding partial signature: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
if buf.Len() != MuSig2PartialSigSize {
|
||||
return result, fmt.Errorf("invalid partial signature length, "+
|
||||
"got %d wanted %d", buf.Len(), MuSig2PartialSigSize)
|
||||
}
|
||||
|
||||
copy(result[:], buf.Bytes())
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeserializePartialSignature decodes a partial signature from a byte slice.
|
||||
func DeserializePartialSignature(scalarBytes []byte) (*musig2.PartialSignature,
|
||||
error) {
|
||||
|
||||
if len(scalarBytes) != MuSig2PartialSigSize {
|
||||
return nil, fmt.Errorf("invalid partial signature length, got "+
|
||||
"%d wanted %d", len(scalarBytes), MuSig2PartialSigSize)
|
||||
}
|
||||
|
||||
sig := &musig2.PartialSignature{}
|
||||
if err := sig.Decode(bytes.NewReader(scalarBytes)); err != nil {
|
||||
return nil, fmt.Errorf("error decoding partial signature: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
return sig, nil
|
||||
}
|
|
@ -10,6 +10,11 @@ import (
|
|||
// Signer implementations such as hardware wallets, hardware tokens, HSM's, or
|
||||
// simply a regular wallet.
|
||||
type Signer interface {
|
||||
// MuSig2Signer is an embedded interface to make sure all our signers
|
||||
// also support MuSig2 signing, so we can forward calls to a remote
|
||||
// signer as well.
|
||||
MuSig2Signer
|
||||
|
||||
// SignOutputRaw generates a signature for the passed transaction
|
||||
// according to the data within the passed SignDescriptor.
|
||||
//
|
||||
|
|
|
@ -75,12 +75,12 @@ func TapscriptFullTree(internalKey *btcec.PublicKey,
|
|||
// key and revealed script.
|
||||
func TapscriptPartialReveal(internalKey *btcec.PublicKey,
|
||||
revealedLeaf txscript.TapLeaf,
|
||||
inclusionProof [32]byte) *waddrmgr.Tapscript {
|
||||
inclusionProof []byte) *waddrmgr.Tapscript {
|
||||
|
||||
controlBlock := &txscript.ControlBlock{
|
||||
InternalKey: internalKey,
|
||||
LeafVersion: txscript.BaseLeafVersion,
|
||||
InclusionProof: inclusionProof[:],
|
||||
InclusionProof: inclusionProof,
|
||||
}
|
||||
rootHash := controlBlock.RootHash(revealedLeaf.Script)
|
||||
tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash)
|
||||
|
|
|
@ -2,16 +2,20 @@ package input
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -130,6 +134,50 @@ func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor
|
|||
}
|
||||
}
|
||||
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||
// key identified by the key locator. The complete list of all public keys of
|
||||
// all signing parties must be provided, including the public key of the local
|
||||
// signing key. If nonces of other parties are already known, they can be
|
||||
// submitted as well to reduce the number of method calls necessary later on.
|
||||
func (m *MockSigner) MuSig2CreateSession(keychain.KeyLocator,
|
||||
[]*btcec.PublicKey, *MuSig2Tweaks,
|
||||
[][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||
// participants for a session identified by its ID. This method returns true
|
||||
// once we have all nonces for all other signing participants.
|
||||
func (m *MockSigner) MuSig2RegisterNonces(MuSig2SessionID,
|
||||
[][musig2.PubNonceSize]byte) (bool, error) {
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key
|
||||
// that was specified when the session was created. This can only be
|
||||
// called when all public nonces of all participants are known and have
|
||||
// been registered with the session. If this node isn't responsible for
|
||||
// combining all the partial signatures, then the cleanup parameter
|
||||
// should be set, indicating that the session can be removed from memory
|
||||
// once the signature was produced.
|
||||
func (m *MockSigner) MuSig2Sign(MuSig2SessionID,
|
||||
[sha256.Size]byte, bool) (*musig2.PartialSignature, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||
// local one, if it already exists. Once a partial signature of all
|
||||
// participants is registered, the final signature will be combined and
|
||||
// returned.
|
||||
func (m *MockSigner) MuSig2CombineSig(MuSig2SessionID,
|
||||
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error) {
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// findKey searches through all stored private keys and returns one
|
||||
// corresponding to the hashed pubkey if it can be found. The public key may
|
||||
// either correspond directly to the private key or to the private key with a
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -201,6 +201,176 @@ func local_request_Signer_DeriveSharedKey_0(ctx context.Context, marshaler runti
|
|||
|
||||
}
|
||||
|
||||
func request_Signer_MuSig2CombineKeys_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2CombineKeysRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.MuSig2CombineKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Signer_MuSig2CombineKeys_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2CombineKeysRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.MuSig2CombineKeys(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_Signer_MuSig2CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2SessionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.MuSig2CreateSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Signer_MuSig2CreateSession_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2SessionRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.MuSig2CreateSession(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_Signer_MuSig2RegisterNonces_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2RegisterNoncesRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.MuSig2RegisterNonces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Signer_MuSig2RegisterNonces_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2RegisterNoncesRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.MuSig2RegisterNonces(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_Signer_MuSig2Sign_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2SignRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.MuSig2Sign(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Signer_MuSig2Sign_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2SignRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.MuSig2Sign(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_Signer_MuSig2CombineSig_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2CombineSigRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.MuSig2CombineSig(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Signer_MuSig2CombineSig_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq MuSig2CombineSigRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||
if berr != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||
}
|
||||
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.MuSig2CombineSig(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterSignerHandlerServer registers the http handlers for service Signer to "mux".
|
||||
// UnaryRPC :call SignerServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
|
@ -322,6 +492,121 @@ func RegisterSignerHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser
|
|||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2CombineKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineKeys", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinekeys"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Signer_MuSig2CombineKeys_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2CombineKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CreateSession", runtime.WithHTTPPathPattern("/v2/signer/musig2/createsession"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Signer_MuSig2CreateSession_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2CreateSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2RegisterNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterNonces", runtime.WithHTTPPathPattern("/v2/signer/musig2/registernonces"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Signer_MuSig2RegisterNonces_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2RegisterNonces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2Sign", runtime.WithHTTPPathPattern("/v2/signer/musig2/sign"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Signer_MuSig2Sign_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2Sign_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2CombineSig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineSig", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinesig"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Signer_MuSig2CombineSig_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2CombineSig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -463,6 +748,106 @@ func RegisterSignerHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli
|
|||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2CombineKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineKeys", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinekeys"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Signer_MuSig2CombineKeys_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2CombineKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2CreateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CreateSession", runtime.WithHTTPPathPattern("/v2/signer/musig2/createsession"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Signer_MuSig2CreateSession_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2CreateSession_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2RegisterNonces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2RegisterNonces", runtime.WithHTTPPathPattern("/v2/signer/musig2/registernonces"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Signer_MuSig2RegisterNonces_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2RegisterNonces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2Sign_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2Sign", runtime.WithHTTPPathPattern("/v2/signer/musig2/sign"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Signer_MuSig2Sign_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2Sign_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_Signer_MuSig2CombineSig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2CombineSig", runtime.WithHTTPPathPattern("/v2/signer/musig2/combinesig"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Signer_MuSig2CombineSig_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Signer_MuSig2CombineSig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -476,6 +861,16 @@ var (
|
|||
pattern_Signer_VerifyMessage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "verifymessage"}, ""))
|
||||
|
||||
pattern_Signer_DeriveSharedKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "signer", "sharedkey"}, ""))
|
||||
|
||||
pattern_Signer_MuSig2CombineKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinekeys"}, ""))
|
||||
|
||||
pattern_Signer_MuSig2CreateSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "createsession"}, ""))
|
||||
|
||||
pattern_Signer_MuSig2RegisterNonces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "registernonces"}, ""))
|
||||
|
||||
pattern_Signer_MuSig2Sign_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "sign"}, ""))
|
||||
|
||||
pattern_Signer_MuSig2CombineSig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinesig"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -488,4 +883,14 @@ var (
|
|||
forward_Signer_VerifyMessage_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Signer_DeriveSharedKey_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Signer_MuSig2CombineKeys_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Signer_MuSig2CreateSession_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Signer_MuSig2RegisterNonces_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Signer_MuSig2Sign_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Signer_MuSig2CombineSig_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
|
|
|
@ -147,4 +147,129 @@ func RegisterSignerJSONCallbacks(registry map[string]func(ctx context.Context,
|
|||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["signrpc.Signer.MuSig2CombineKeys"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &MuSig2CombineKeysRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewSignerClient(conn)
|
||||
resp, err := client.MuSig2CombineKeys(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["signrpc.Signer.MuSig2CreateSession"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &MuSig2SessionRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewSignerClient(conn)
|
||||
resp, err := client.MuSig2CreateSession(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["signrpc.Signer.MuSig2RegisterNonces"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &MuSig2RegisterNoncesRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewSignerClient(conn)
|
||||
resp, err := client.MuSig2RegisterNonces(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["signrpc.Signer.MuSig2Sign"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &MuSig2SignRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewSignerClient(conn)
|
||||
resp, err := client.MuSig2Sign(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["signrpc.Signer.MuSig2CombineSig"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &MuSig2CombineSigRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewSignerClient(conn)
|
||||
resp, err := client.MuSig2CombineSig(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,76 @@ service Signer {
|
|||
hashed with sha256, resulting in the final key length of 256bit.
|
||||
*/
|
||||
rpc DeriveSharedKey (SharedKeyRequest) returns (SharedKeyResponse);
|
||||
|
||||
/*
|
||||
MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used
|
||||
to calculate the combined MuSig2 public key from a list of all participating
|
||||
signers' public keys. This RPC is completely stateless and deterministic and
|
||||
does not create any signing session. It can be used to determine the Taproot
|
||||
public key that should be put in an on-chain output once all public keys are
|
||||
known. A signing session is only needed later when that output should be
|
||||
_spent_ again.
|
||||
|
||||
NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
releases. Backward compatibility is not guaranteed!
|
||||
*/
|
||||
rpc MuSig2CombineKeys (MuSig2CombineKeysRequest)
|
||||
returns (MuSig2CombineKeysResponse);
|
||||
|
||||
/*
|
||||
MuSig2CreateSession (experimental!) creates a new MuSig2 signing session
|
||||
using the local key identified by the key locator. The complete list of all
|
||||
public keys of all signing parties must be provided, including the public
|
||||
key of the local signing key. If nonces of other parties are already known,
|
||||
they can be submitted as well to reduce the number of RPC calls necessary
|
||||
later on.
|
||||
|
||||
NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
releases. Backward compatibility is not guaranteed!
|
||||
*/
|
||||
rpc MuSig2CreateSession (MuSig2SessionRequest)
|
||||
returns (MuSig2SessionResponse);
|
||||
|
||||
/*
|
||||
MuSig2RegisterNonces (experimental!) registers one or more public nonces of
|
||||
other signing participants for a session identified by its ID. This RPC can
|
||||
be called multiple times until all nonces are registered.
|
||||
|
||||
NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
releases. Backward compatibility is not guaranteed!
|
||||
*/
|
||||
rpc MuSig2RegisterNonces (MuSig2RegisterNoncesRequest)
|
||||
returns (MuSig2RegisterNoncesResponse);
|
||||
|
||||
/*
|
||||
MuSig2Sign (experimental!) creates a partial signature using the local
|
||||
signing key that was specified when the session was created. This can only
|
||||
be called when all public nonces of all participants are known and have been
|
||||
registered with the session. If this node isn't responsible for combining
|
||||
all the partial signatures, then the cleanup flag should be set, indicating
|
||||
that the session can be removed from memory once the signature was produced.
|
||||
|
||||
NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
releases. Backward compatibility is not guaranteed!
|
||||
*/
|
||||
rpc MuSig2Sign (MuSig2SignRequest) returns (MuSig2SignResponse);
|
||||
|
||||
/*
|
||||
MuSig2CombineSig (experimental!) combines the given partial signature(s)
|
||||
with the local one, if it already exists. Once a partial signature of all
|
||||
participants is registered, the final signature will be combined and
|
||||
returned.
|
||||
|
||||
NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
releases. Backward compatibility is not guaranteed!
|
||||
*/
|
||||
rpc MuSig2CombineSig (MuSig2CombineSigRequest)
|
||||
returns (MuSig2CombineSigResponse);
|
||||
}
|
||||
|
||||
message KeyLocator {
|
||||
|
@ -265,3 +335,221 @@ message SharedKeyResponse {
|
|||
// The shared public key, hashed with sha256.
|
||||
bytes shared_key = 1;
|
||||
}
|
||||
|
||||
message TweakDesc {
|
||||
/*
|
||||
Tweak is the 32-byte value that will modify the public key.
|
||||
*/
|
||||
bytes tweak = 1;
|
||||
|
||||
/*
|
||||
Specifies if the target key should be converted to an x-only public key
|
||||
before tweaking. If true, then the public key will be mapped to an x-only
|
||||
key before the tweaking operation is applied.
|
||||
*/
|
||||
bool is_x_only = 2;
|
||||
}
|
||||
|
||||
message TaprootTweakDesc {
|
||||
/*
|
||||
The root hash of the tapscript tree if a script path is committed to. If
|
||||
the MuSig2 key put on chain doesn't also commit to a script path (BIP-0086
|
||||
key spend only), then this needs to be empty and the key_spend_only field
|
||||
below must be set to true. This is required because gRPC cannot
|
||||
differentiate between a zero-size byte slice and a nil byte slice (both
|
||||
would be serialized the same way). So the extra boolean is required.
|
||||
*/
|
||||
bytes script_root = 1;
|
||||
|
||||
/*
|
||||
Indicates that the above script_root is expected to be empty because this
|
||||
is a BIP-0086 key spend only commitment where only the internal key is
|
||||
committed to instead of also including a script root hash.
|
||||
*/
|
||||
bool key_spend_only = 2;
|
||||
}
|
||||
|
||||
message MuSig2CombineKeysRequest {
|
||||
/*
|
||||
A list of all public keys (serialized in 32-byte x-only format!)
|
||||
participating in the signing session. The list will always be sorted
|
||||
lexicographically internally. This must include the local key which is
|
||||
described by the above key_loc.
|
||||
*/
|
||||
repeated bytes all_signer_pubkeys = 1;
|
||||
|
||||
/*
|
||||
A series of optional generic tweaks to be applied to the the aggregated
|
||||
public key.
|
||||
*/
|
||||
repeated TweakDesc tweaks = 2;
|
||||
|
||||
/*
|
||||
An optional taproot specific tweak that must be specified if the MuSig2
|
||||
combined key will be used as the main taproot key of a taproot output
|
||||
on-chain.
|
||||
*/
|
||||
TaprootTweakDesc taproot_tweak = 3;
|
||||
}
|
||||
|
||||
message MuSig2CombineKeysResponse {
|
||||
/*
|
||||
The combined public key (in the 32-byte x-only format) with all tweaks
|
||||
applied to it. If a taproot tweak is specified, this corresponds to the
|
||||
taproot key that can be put into the on-chain output.
|
||||
*/
|
||||
bytes combined_key = 1;
|
||||
|
||||
/*
|
||||
The raw combined public key (in the 32-byte x-only format) before any tweaks
|
||||
are applied to it. If a taproot tweak is specified, this corresponds to the
|
||||
internal key that needs to be put into the witness if the script spend path
|
||||
is used.
|
||||
*/
|
||||
bytes taproot_internal_key = 2;
|
||||
}
|
||||
|
||||
message MuSig2SessionRequest {
|
||||
/*
|
||||
The key locator that identifies which key to use for signing.
|
||||
*/
|
||||
KeyLocator key_loc = 1;
|
||||
|
||||
/*
|
||||
A list of all public keys (serialized in 32-byte x-only format!)
|
||||
participating in the signing session. The list will always be sorted
|
||||
lexicographically internally. This must include the local key which is
|
||||
described by the above key_loc.
|
||||
*/
|
||||
repeated bytes all_signer_pubkeys = 2;
|
||||
|
||||
/*
|
||||
An optional list of all public nonces of other signing participants that
|
||||
might already be known.
|
||||
*/
|
||||
repeated bytes other_signer_public_nonces = 3;
|
||||
|
||||
/*
|
||||
A series of optional generic tweaks to be applied to the the aggregated
|
||||
public key.
|
||||
*/
|
||||
repeated TweakDesc tweaks = 4;
|
||||
|
||||
/*
|
||||
An optional taproot specific tweak that must be specified if the MuSig2
|
||||
combined key will be used as the main taproot key of a taproot output
|
||||
on-chain.
|
||||
*/
|
||||
TaprootTweakDesc taproot_tweak = 5;
|
||||
}
|
||||
|
||||
message MuSig2SessionResponse {
|
||||
/*
|
||||
The unique ID that represents this signing session. A session can be used
|
||||
for producing a signature a single time. If the signing fails for any
|
||||
reason, a new session with the same participants needs to be created.
|
||||
*/
|
||||
bytes session_id = 1;
|
||||
|
||||
/*
|
||||
The combined public key (in the 32-byte x-only format) with all tweaks
|
||||
applied to it. If a taproot tweak is specified, this corresponds to the
|
||||
taproot key that can be put into the on-chain output.
|
||||
*/
|
||||
bytes combined_key = 2;
|
||||
|
||||
/*
|
||||
The raw combined public key (in the 32-byte x-only format) before any tweaks
|
||||
are applied to it. If a taproot tweak is specified, this corresponds to the
|
||||
internal key that needs to be put into the witness if the script spend path
|
||||
is used.
|
||||
*/
|
||||
bytes taproot_internal_key = 3;
|
||||
|
||||
/*
|
||||
The two public nonces the local signer uses, combined into a single value
|
||||
of 66 bytes. Can be split into the two 33-byte points to get the individual
|
||||
nonces.
|
||||
*/
|
||||
bytes local_public_nonces = 4;
|
||||
|
||||
/*
|
||||
Indicates whether all nonces required to start the signing process are known
|
||||
now.
|
||||
*/
|
||||
bool have_all_nonces = 5;
|
||||
}
|
||||
|
||||
message MuSig2RegisterNoncesRequest {
|
||||
/*
|
||||
The unique ID of the signing session those nonces should be registered with.
|
||||
*/
|
||||
bytes session_id = 1;
|
||||
|
||||
/*
|
||||
A list of all public nonces of other signing participants that should be
|
||||
registered.
|
||||
*/
|
||||
repeated bytes other_signer_public_nonces = 3;
|
||||
}
|
||||
|
||||
message MuSig2RegisterNoncesResponse {
|
||||
/*
|
||||
Indicates whether all nonces required to start the signing process are known
|
||||
now.
|
||||
*/
|
||||
bool have_all_nonces = 1;
|
||||
}
|
||||
|
||||
message MuSig2SignRequest {
|
||||
/*
|
||||
The unique ID of the signing session to use for signing.
|
||||
*/
|
||||
bytes session_id = 1;
|
||||
|
||||
/*
|
||||
The 32-byte SHA256 digest of the message to sign.
|
||||
*/
|
||||
bytes message_digest = 2;
|
||||
|
||||
/*
|
||||
Cleanup indicates that after signing, the session state can be cleaned up,
|
||||
since another participant is going to be responsible for combining the
|
||||
partial signatures.
|
||||
*/
|
||||
bool cleanup = 3;
|
||||
}
|
||||
|
||||
message MuSig2SignResponse {
|
||||
/*
|
||||
The partial signature created by the local signer.
|
||||
*/
|
||||
bytes local_partial_signature = 1;
|
||||
}
|
||||
|
||||
message MuSig2CombineSigRequest {
|
||||
/*
|
||||
The unique ID of the signing session to combine the signatures for.
|
||||
*/
|
||||
bytes session_id = 1;
|
||||
|
||||
/*
|
||||
The list of all other participants' partial signatures to add to the current
|
||||
session.
|
||||
*/
|
||||
repeated bytes other_partial_signatures = 2;
|
||||
}
|
||||
|
||||
message MuSig2CombineSigResponse {
|
||||
/*
|
||||
Indicates whether all partial signatures required to create a final, full
|
||||
signature are known yet. If this is true, then the final_signature field is
|
||||
set, otherwise it is empty.
|
||||
*/
|
||||
bool have_all_signatures = 1;
|
||||
|
||||
/*
|
||||
The final, full signature that is valid for the combined public key.
|
||||
*/
|
||||
bytes final_signature = 2;
|
||||
}
|
|
@ -50,6 +50,176 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/v2/signer/musig2/combinekeys": {
|
||||
"post": {
|
||||
"summary": "MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used\nto calculate the combined MuSig2 public key from a list of all participating\nsigners' public keys. This RPC is completely stateless and deterministic and\ndoes not create any signing session. It can be used to determine the Taproot\npublic key that should be put in an on-chain output once all public keys are\nknown. A signing session is only needed later when that output should be\n_spent_ again.",
|
||||
"description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!",
|
||||
"operationId": "Signer_MuSig2CombineKeys",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2CombineKeysResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2CombineKeysRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Signer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/signer/musig2/combinesig": {
|
||||
"post": {
|
||||
"summary": "MuSig2CombineSig (experimental!) combines the given partial signature(s)\nwith the local one, if it already exists. Once a partial signature of all\nparticipants is registered, the final signature will be combined and\nreturned.",
|
||||
"description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!",
|
||||
"operationId": "Signer_MuSig2CombineSig",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2CombineSigResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2CombineSigRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Signer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/signer/musig2/createsession": {
|
||||
"post": {
|
||||
"summary": "MuSig2CreateSession (experimental!) creates a new MuSig2 signing session\nusing the local key identified by the key locator. The complete list of all\npublic keys of all signing parties must be provided, including the public\nkey of the local signing key. If nonces of other parties are already known,\nthey can be submitted as well to reduce the number of RPC calls necessary\nlater on.",
|
||||
"description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!",
|
||||
"operationId": "Signer_MuSig2CreateSession",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2SessionResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2SessionRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Signer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/signer/musig2/registernonces": {
|
||||
"post": {
|
||||
"summary": "MuSig2RegisterNonces (experimental!) registers one or more public nonces of\nother signing participants for a session identified by its ID. This RPC can\nbe called multiple times until all nonces are registered.",
|
||||
"description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!",
|
||||
"operationId": "Signer_MuSig2RegisterNonces",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2RegisterNoncesResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2RegisterNoncesRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Signer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/signer/musig2/sign": {
|
||||
"post": {
|
||||
"summary": "MuSig2Sign (experimental!) creates a partial signature using the local\nsigning key that was specified when the session was created. This can only\nbe called when all public nonces of all participants are known and have been\nregistered with the session. If this node isn't responsible for combining\nall the partial signatures, then the cleanup flag should be set, indicating\nthat the session can be removed from memory once the signature was produced.",
|
||||
"description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!",
|
||||
"operationId": "Signer_MuSig2Sign",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2SignResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/signrpcMuSig2SignRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Signer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/signer/sharedkey": {
|
||||
"post": {
|
||||
"summary": "DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key\nderivation between the ephemeral public key in the request and the node's\nkey specified in the key_desc parameter. Either a key locator or a raw\npublic key is expected in the key_desc, if neither is supplied, defaults to\nthe node's identity private key:\nP_shared = privKeyNode * ephemeralPubkey\nThe resulting shared public key is serialized in the compressed format and\nhashed with sha256, resulting in the final key length of 256bit.",
|
||||
|
@ -276,6 +446,198 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2CombineKeysRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all_signer_pubkeys": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "A list of all public keys (serialized in 32-byte x-only format!)\nparticipating in the signing session. The list will always be sorted\nlexicographically internally. This must include the local key which is\ndescribed by the above key_loc."
|
||||
},
|
||||
"tweaks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/signrpcTweakDesc"
|
||||
},
|
||||
"description": "A series of optional generic tweaks to be applied to the the aggregated\npublic key."
|
||||
},
|
||||
"taproot_tweak": {
|
||||
"$ref": "#/definitions/signrpcTaprootTweakDesc",
|
||||
"description": "An optional taproot specific tweak that must be specified if the MuSig2\ncombined key will be used as the main taproot key of a taproot output\non-chain."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2CombineKeysResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"combined_key": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The combined public key (in the 32-byte x-only format) with all tweaks\napplied to it. If a taproot tweak is specified, this corresponds to the\ntaproot key that can be put into the on-chain output."
|
||||
},
|
||||
"taproot_internal_key": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The raw combined public key (in the 32-byte x-only format) before any tweaks\nare applied to it. If a taproot tweak is specified, this corresponds to the\ninternal key that needs to be put into the witness if the script spend path\nis used."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2CombineSigRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The unique ID of the signing session to combine the signatures for."
|
||||
},
|
||||
"other_partial_signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "The list of all other participants' partial signatures to add to the current\nsession."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2CombineSigResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"have_all_signatures": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether all partial signatures required to create a final, full\nsignature are known yet. If this is true, then the final_signature field is\nset, otherwise it is empty."
|
||||
},
|
||||
"final_signature": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The final, full signature that is valid for the combined public key."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2RegisterNoncesRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The unique ID of the signing session those nonces should be registered with."
|
||||
},
|
||||
"other_signer_public_nonces": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "A list of all public nonces of other signing participants that should be\nregistered."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2RegisterNoncesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"have_all_nonces": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether all nonces required to start the signing process are known\nnow."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2SessionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key_loc": {
|
||||
"$ref": "#/definitions/signrpcKeyLocator",
|
||||
"description": "The key locator that identifies which key to use for signing."
|
||||
},
|
||||
"all_signer_pubkeys": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "A list of all public keys (serialized in 32-byte x-only format!)\nparticipating in the signing session. The list will always be sorted\nlexicographically internally. This must include the local key which is\ndescribed by the above key_loc."
|
||||
},
|
||||
"other_signer_public_nonces": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "An optional list of all public nonces of other signing participants that\nmight already be known."
|
||||
},
|
||||
"tweaks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/signrpcTweakDesc"
|
||||
},
|
||||
"description": "A series of optional generic tweaks to be applied to the the aggregated\npublic key."
|
||||
},
|
||||
"taproot_tweak": {
|
||||
"$ref": "#/definitions/signrpcTaprootTweakDesc",
|
||||
"description": "An optional taproot specific tweak that must be specified if the MuSig2\ncombined key will be used as the main taproot key of a taproot output\non-chain."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2SessionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The unique ID that represents this signing session. A session can be used\nfor producing a signature a single time. If the signing fails for any\nreason, a new session with the same participants needs to be created."
|
||||
},
|
||||
"combined_key": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The combined public key (in the 32-byte x-only format) with all tweaks\napplied to it. If a taproot tweak is specified, this corresponds to the\ntaproot key that can be put into the on-chain output."
|
||||
},
|
||||
"taproot_internal_key": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The raw combined public key (in the 32-byte x-only format) before any tweaks\nare applied to it. If a taproot tweak is specified, this corresponds to the\ninternal key that needs to be put into the witness if the script spend path\nis used."
|
||||
},
|
||||
"local_public_nonces": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The two public nonces the local signer uses, combined into a single value\nof 66 bytes. Can be split into the two 33-byte points to get the individual\nnonces."
|
||||
},
|
||||
"have_all_nonces": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether all nonces required to start the signing process are known\nnow."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2SignRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The unique ID of the signing session to use for signing."
|
||||
},
|
||||
"message_digest": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The 32-byte SHA256 digest of the message to sign."
|
||||
},
|
||||
"cleanup": {
|
||||
"type": "boolean",
|
||||
"description": "Cleanup indicates that after signing, the session state can be cleaned up,\nsince another participant is going to be responsible for combining the\npartial signatures."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcMuSig2SignResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"local_partial_signature": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The partial signature created by the local signer."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcSharedKeyRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -416,6 +778,34 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"signrpcTaprootTweakDesc": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"script_root": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "The root hash of the tapscript tree if a script path is committed to. If\nthe MuSig2 key put on chain doesn't also commit to a script path (BIP-0086\nkey spend only), then this needs to be empty and the key_spend_only field\nbelow must be set to true. This is required because gRPC cannot\ndifferentiate between a zero-size byte slice and a nil byte slice (both\nwould be serialized the same way). So the extra boolean is required."
|
||||
},
|
||||
"key_spend_only": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates that the above script_root is expected to be empty because this\nis a BIP-0086 key spend only commitment where only the internal key is\ncommitted to instead of also including a script root hash."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcTweakDesc": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tweak": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "Tweak is the 32-byte value that will modify the public key."
|
||||
},
|
||||
"is_x_only": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies if the target key should be converted to an x-only public key\nbefore tweaking. If true, then the public key will be mapped to an x-only\nkey before the tweaking operation is applied."
|
||||
}
|
||||
}
|
||||
},
|
||||
"signrpcTxOut": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -18,3 +18,18 @@ http:
|
|||
- selector: signrpc.Signer.DeriveSharedKey
|
||||
post: "/v2/signer/sharedkey"
|
||||
body: "*"
|
||||
- selector: signrpc.Signer.MuSig2CombineKeys
|
||||
post: "/v2/signer/musig2/combinekeys"
|
||||
body: "*"
|
||||
- selector: signrpc.Signer.MuSig2CreateSession
|
||||
post: "/v2/signer/musig2/createsession"
|
||||
body: "*"
|
||||
- selector: signrpc.Signer.MuSig2RegisterNonces
|
||||
post: "/v2/signer/musig2/registernonces"
|
||||
body: "*"
|
||||
- selector: signrpc.Signer.MuSig2Sign
|
||||
post: "/v2/signer/musig2/sign"
|
||||
body: "*"
|
||||
- selector: signrpc.Signer.MuSig2CombineSig
|
||||
post: "/v2/signer/musig2/combinesig"
|
||||
body: "*"
|
||||
|
|
|
@ -64,6 +64,62 @@ type SignerClient interface {
|
|||
//The resulting shared public key is serialized in the compressed format and
|
||||
//hashed with sha256, resulting in the final key length of 256bit.
|
||||
DeriveSharedKey(ctx context.Context, in *SharedKeyRequest, opts ...grpc.CallOption) (*SharedKeyResponse, error)
|
||||
//
|
||||
//MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used
|
||||
//to calculate the combined MuSig2 public key from a list of all participating
|
||||
//signers' public keys. This RPC is completely stateless and deterministic and
|
||||
//does not create any signing session. It can be used to determine the Taproot
|
||||
//public key that should be put in an on-chain output once all public keys are
|
||||
//known. A signing session is only needed later when that output should be
|
||||
//_spent_ again.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2CombineKeys(ctx context.Context, in *MuSig2CombineKeysRequest, opts ...grpc.CallOption) (*MuSig2CombineKeysResponse, error)
|
||||
//
|
||||
//MuSig2CreateSession (experimental!) creates a new MuSig2 signing session
|
||||
//using the local key identified by the key locator. The complete list of all
|
||||
//public keys of all signing parties must be provided, including the public
|
||||
//key of the local signing key. If nonces of other parties are already known,
|
||||
//they can be submitted as well to reduce the number of RPC calls necessary
|
||||
//later on.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2CreateSession(ctx context.Context, in *MuSig2SessionRequest, opts ...grpc.CallOption) (*MuSig2SessionResponse, error)
|
||||
//
|
||||
//MuSig2RegisterNonces (experimental!) registers one or more public nonces of
|
||||
//other signing participants for a session identified by its ID. This RPC can
|
||||
//be called multiple times until all nonces are registered.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2RegisterNonces(ctx context.Context, in *MuSig2RegisterNoncesRequest, opts ...grpc.CallOption) (*MuSig2RegisterNoncesResponse, error)
|
||||
//
|
||||
//MuSig2Sign (experimental!) creates a partial signature using the local
|
||||
//signing key that was specified when the session was created. This can only
|
||||
//be called when all public nonces of all participants are known and have been
|
||||
//registered with the session. If this node isn't responsible for combining
|
||||
//all the partial signatures, then the cleanup flag should be set, indicating
|
||||
//that the session can be removed from memory once the signature was produced.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2Sign(ctx context.Context, in *MuSig2SignRequest, opts ...grpc.CallOption) (*MuSig2SignResponse, error)
|
||||
//
|
||||
//MuSig2CombineSig (experimental!) combines the given partial signature(s)
|
||||
//with the local one, if it already exists. Once a partial signature of all
|
||||
//participants is registered, the final signature will be combined and
|
||||
//returned.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSigRequest, opts ...grpc.CallOption) (*MuSig2CombineSigResponse, error)
|
||||
}
|
||||
|
||||
type signerClient struct {
|
||||
|
@ -119,6 +175,51 @@ func (c *signerClient) DeriveSharedKey(ctx context.Context, in *SharedKeyRequest
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *signerClient) MuSig2CombineKeys(ctx context.Context, in *MuSig2CombineKeysRequest, opts ...grpc.CallOption) (*MuSig2CombineKeysResponse, error) {
|
||||
out := new(MuSig2CombineKeysResponse)
|
||||
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CombineKeys", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *signerClient) MuSig2CreateSession(ctx context.Context, in *MuSig2SessionRequest, opts ...grpc.CallOption) (*MuSig2SessionResponse, error) {
|
||||
out := new(MuSig2SessionResponse)
|
||||
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CreateSession", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *signerClient) MuSig2RegisterNonces(ctx context.Context, in *MuSig2RegisterNoncesRequest, opts ...grpc.CallOption) (*MuSig2RegisterNoncesResponse, error) {
|
||||
out := new(MuSig2RegisterNoncesResponse)
|
||||
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2RegisterNonces", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *signerClient) MuSig2Sign(ctx context.Context, in *MuSig2SignRequest, opts ...grpc.CallOption) (*MuSig2SignResponse, error) {
|
||||
out := new(MuSig2SignResponse)
|
||||
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2Sign", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *signerClient) MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSigRequest, opts ...grpc.CallOption) (*MuSig2CombineSigResponse, error) {
|
||||
out := new(MuSig2CombineSigResponse)
|
||||
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2CombineSig", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SignerServer is the server API for Signer service.
|
||||
// All implementations must embed UnimplementedSignerServer
|
||||
// for forward compatibility
|
||||
|
@ -169,6 +270,62 @@ type SignerServer interface {
|
|||
//The resulting shared public key is serialized in the compressed format and
|
||||
//hashed with sha256, resulting in the final key length of 256bit.
|
||||
DeriveSharedKey(context.Context, *SharedKeyRequest) (*SharedKeyResponse, error)
|
||||
//
|
||||
//MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used
|
||||
//to calculate the combined MuSig2 public key from a list of all participating
|
||||
//signers' public keys. This RPC is completely stateless and deterministic and
|
||||
//does not create any signing session. It can be used to determine the Taproot
|
||||
//public key that should be put in an on-chain output once all public keys are
|
||||
//known. A signing session is only needed later when that output should be
|
||||
//_spent_ again.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2CombineKeys(context.Context, *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error)
|
||||
//
|
||||
//MuSig2CreateSession (experimental!) creates a new MuSig2 signing session
|
||||
//using the local key identified by the key locator. The complete list of all
|
||||
//public keys of all signing parties must be provided, including the public
|
||||
//key of the local signing key. If nonces of other parties are already known,
|
||||
//they can be submitted as well to reduce the number of RPC calls necessary
|
||||
//later on.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2CreateSession(context.Context, *MuSig2SessionRequest) (*MuSig2SessionResponse, error)
|
||||
//
|
||||
//MuSig2RegisterNonces (experimental!) registers one or more public nonces of
|
||||
//other signing participants for a session identified by its ID. This RPC can
|
||||
//be called multiple times until all nonces are registered.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error)
|
||||
//
|
||||
//MuSig2Sign (experimental!) creates a partial signature using the local
|
||||
//signing key that was specified when the session was created. This can only
|
||||
//be called when all public nonces of all participants are known and have been
|
||||
//registered with the session. If this node isn't responsible for combining
|
||||
//all the partial signatures, then the cleanup flag should be set, indicating
|
||||
//that the session can be removed from memory once the signature was produced.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2Sign(context.Context, *MuSig2SignRequest) (*MuSig2SignResponse, error)
|
||||
//
|
||||
//MuSig2CombineSig (experimental!) combines the given partial signature(s)
|
||||
//with the local one, if it already exists. Once a partial signature of all
|
||||
//participants is registered, the final signature will be combined and
|
||||
//returned.
|
||||
//
|
||||
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
|
||||
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
|
||||
//releases. Backward compatibility is not guaranteed!
|
||||
MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error)
|
||||
mustEmbedUnimplementedSignerServer()
|
||||
}
|
||||
|
||||
|
@ -191,6 +348,21 @@ func (UnimplementedSignerServer) VerifyMessage(context.Context, *VerifyMessageRe
|
|||
func (UnimplementedSignerServer) DeriveSharedKey(context.Context, *SharedKeyRequest) (*SharedKeyResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeriveSharedKey not implemented")
|
||||
}
|
||||
func (UnimplementedSignerServer) MuSig2CombineKeys(context.Context, *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method MuSig2CombineKeys not implemented")
|
||||
}
|
||||
func (UnimplementedSignerServer) MuSig2CreateSession(context.Context, *MuSig2SessionRequest) (*MuSig2SessionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method MuSig2CreateSession not implemented")
|
||||
}
|
||||
func (UnimplementedSignerServer) MuSig2RegisterNonces(context.Context, *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method MuSig2RegisterNonces not implemented")
|
||||
}
|
||||
func (UnimplementedSignerServer) MuSig2Sign(context.Context, *MuSig2SignRequest) (*MuSig2SignResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method MuSig2Sign not implemented")
|
||||
}
|
||||
func (UnimplementedSignerServer) MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method MuSig2CombineSig not implemented")
|
||||
}
|
||||
func (UnimplementedSignerServer) mustEmbedUnimplementedSignerServer() {}
|
||||
|
||||
// UnsafeSignerServer may be embedded to opt out of forward compatibility for this service.
|
||||
|
@ -294,6 +466,96 @@ func _Signer_DeriveSharedKey_Handler(srv interface{}, ctx context.Context, dec f
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Signer_MuSig2CombineKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MuSig2CombineKeysRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SignerServer).MuSig2CombineKeys(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/signrpc.Signer/MuSig2CombineKeys",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SignerServer).MuSig2CombineKeys(ctx, req.(*MuSig2CombineKeysRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Signer_MuSig2CreateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MuSig2SessionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SignerServer).MuSig2CreateSession(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/signrpc.Signer/MuSig2CreateSession",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SignerServer).MuSig2CreateSession(ctx, req.(*MuSig2SessionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Signer_MuSig2RegisterNonces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MuSig2RegisterNoncesRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SignerServer).MuSig2RegisterNonces(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/signrpc.Signer/MuSig2RegisterNonces",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SignerServer).MuSig2RegisterNonces(ctx, req.(*MuSig2RegisterNoncesRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Signer_MuSig2Sign_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MuSig2SignRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SignerServer).MuSig2Sign(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/signrpc.Signer/MuSig2Sign",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SignerServer).MuSig2Sign(ctx, req.(*MuSig2SignRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Signer_MuSig2CombineSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MuSig2CombineSigRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SignerServer).MuSig2CombineSig(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/signrpc.Signer/MuSig2CombineSig",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SignerServer).MuSig2CombineSig(ctx, req.(*MuSig2CombineSigRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Signer_ServiceDesc is the grpc.ServiceDesc for Signer service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
|
@ -321,6 +583,26 @@ var Signer_ServiceDesc = grpc.ServiceDesc{
|
|||
MethodName: "DeriveSharedKey",
|
||||
Handler: _Signer_DeriveSharedKey_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "MuSig2CombineKeys",
|
||||
Handler: _Signer_MuSig2CombineKeys_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "MuSig2CreateSession",
|
||||
Handler: _Signer_MuSig2CreateSession_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "MuSig2RegisterNonces",
|
||||
Handler: _Signer_MuSig2RegisterNonces_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "MuSig2Sign",
|
||||
Handler: _Signer_MuSig2Sign_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "MuSig2CombineSig",
|
||||
Handler: _Signer_MuSig2CombineSig_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "signrpc/signer.proto",
|
||||
|
|
|
@ -6,12 +6,15 @@ package signrpc
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
@ -69,6 +72,26 @@ var (
|
|||
Entity: "signer",
|
||||
Action: "generate",
|
||||
}},
|
||||
"/signrpc.Signer/MuSig2CombineKeys": {{
|
||||
Entity: "signer",
|
||||
Action: "read",
|
||||
}},
|
||||
"/signrpc.Signer/MuSig2CreateSession": {{
|
||||
Entity: "signer",
|
||||
Action: "generate",
|
||||
}},
|
||||
"/signrpc.Signer/MuSig2RegisterNonces": {{
|
||||
Entity: "signer",
|
||||
Action: "generate",
|
||||
}},
|
||||
"/signrpc.Signer/MuSig2Sign": {{
|
||||
Entity: "signer",
|
||||
Action: "generate",
|
||||
}},
|
||||
"/signrpc.Signer/MuSig2CombineSig": {{
|
||||
Entity: "signer",
|
||||
Action: "generate",
|
||||
}},
|
||||
}
|
||||
|
||||
// DefaultSignerMacFilename is the default name of the signer macaroon
|
||||
|
@ -673,6 +696,250 @@ func (s *Server) DeriveSharedKey(_ context.Context, in *SharedKeyRequest) (
|
|||
return &SharedKeyResponse{SharedKey: sharedKeyHash[:]}, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineKeys combines the given set of public keys into a single
|
||||
// combined MuSig2 combined public key, applying the given tweaks.
|
||||
func (s *Server) MuSig2CombineKeys(_ context.Context,
|
||||
in *MuSig2CombineKeysRequest) (*MuSig2CombineKeysResponse, error) {
|
||||
|
||||
// Parse the public keys of all signing participants. This must also
|
||||
// include our own, local key.
|
||||
allSignerPubKeys := make([]*btcec.PublicKey, len(in.AllSignerPubkeys))
|
||||
if len(in.AllSignerPubkeys) < 2 {
|
||||
return nil, fmt.Errorf("need at least two signing public keys")
|
||||
}
|
||||
|
||||
for idx, pubKeyBytes := range in.AllSignerPubkeys {
|
||||
pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing signer public "+
|
||||
"key %d: %v", idx, err)
|
||||
}
|
||||
allSignerPubKeys[idx] = pubKey
|
||||
}
|
||||
|
||||
// Are there any tweaks to apply to the combined public key?
|
||||
tweaks, err := UnmarshalTweaks(in.Tweaks, in.TaprootTweak)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling tweak options: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// Combine the keys now without creating a session in memory.
|
||||
combinedKey, err := input.MuSig2CombineKeys(allSignerPubKeys, tweaks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error combining keys: %v", err)
|
||||
}
|
||||
|
||||
var internalKeyBytes []byte
|
||||
if combinedKey.PreTweakedKey != nil {
|
||||
internalKeyBytes = schnorr.SerializePubKey(
|
||||
combinedKey.PreTweakedKey,
|
||||
)
|
||||
}
|
||||
|
||||
return &MuSig2CombineKeysResponse{
|
||||
CombinedKey: schnorr.SerializePubKey(
|
||||
combinedKey.FinalKey,
|
||||
),
|
||||
TaprootInternalKey: internalKeyBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||
// key identified by the key locator. The complete list of all public keys of
|
||||
// all signing parties must be provided, including the public key of the local
|
||||
// signing key. If nonces of other parties are already known, they can be
|
||||
// submitted as well to reduce the number of RPC calls necessary later on.
|
||||
func (s *Server) MuSig2CreateSession(_ context.Context,
|
||||
in *MuSig2SessionRequest) (*MuSig2SessionResponse, error) {
|
||||
|
||||
// A key locator is always mandatory.
|
||||
if in.KeyLoc == nil {
|
||||
return nil, fmt.Errorf("missing key_loc")
|
||||
}
|
||||
keyLoc := keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(in.KeyLoc.KeyFamily),
|
||||
Index: uint32(in.KeyLoc.KeyIndex),
|
||||
}
|
||||
|
||||
// Parse the public keys of all signing participants. This must also
|
||||
// include our own, local key.
|
||||
allSignerPubKeys := make([]*btcec.PublicKey, len(in.AllSignerPubkeys))
|
||||
if len(in.AllSignerPubkeys) < 2 {
|
||||
return nil, fmt.Errorf("need at least two signing public keys")
|
||||
}
|
||||
|
||||
for idx, pubKeyBytes := range in.AllSignerPubkeys {
|
||||
pubKey, err := schnorr.ParsePubKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing signer public "+
|
||||
"key %d: %v", idx, err)
|
||||
}
|
||||
allSignerPubKeys[idx] = pubKey
|
||||
}
|
||||
|
||||
// We participate a nonce ourselves, so we can't have more nonces than
|
||||
// the total number of participants minus ourselves.
|
||||
maxNonces := len(in.AllSignerPubkeys) - 1
|
||||
if len(in.OtherSignerPublicNonces) > maxNonces {
|
||||
return nil, fmt.Errorf("too many other signer public nonces, "+
|
||||
"got %d but expected a maximum of %d",
|
||||
len(in.OtherSignerPublicNonces), maxNonces)
|
||||
}
|
||||
|
||||
// Parse all other nonces we might already know.
|
||||
otherSignerNonces, err := parseMuSig2PublicNonces(
|
||||
in.OtherSignerPublicNonces, true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing other nonces: %v", err)
|
||||
}
|
||||
|
||||
// Are there any tweaks to apply to the combined public key?
|
||||
tweaks, err := UnmarshalTweaks(in.Tweaks, in.TaprootTweak)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling tweak options: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// Register the session with the internal wallet/signer now.
|
||||
session, err := s.cfg.Signer.MuSig2CreateSession(
|
||||
keyLoc, allSignerPubKeys, tweaks, otherSignerNonces,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error registering session: %v", err)
|
||||
}
|
||||
|
||||
var internalKeyBytes []byte
|
||||
if session.TaprootTweak {
|
||||
internalKeyBytes = schnorr.SerializePubKey(
|
||||
session.TaprootInternalKey,
|
||||
)
|
||||
}
|
||||
|
||||
return &MuSig2SessionResponse{
|
||||
SessionId: session.SessionID[:],
|
||||
CombinedKey: schnorr.SerializePubKey(
|
||||
session.CombinedKey,
|
||||
),
|
||||
TaprootInternalKey: internalKeyBytes,
|
||||
LocalPublicNonces: session.PublicNonce[:],
|
||||
HaveAllNonces: session.HaveAllNonces,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||
// participants for a session identified by its ID.
|
||||
func (s *Server) MuSig2RegisterNonces(_ context.Context,
|
||||
in *MuSig2RegisterNoncesRequest) (*MuSig2RegisterNoncesResponse, error) {
|
||||
|
||||
// Check session ID length.
|
||||
sessionID, err := parseMuSig2SessionID(in.SessionId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing session ID: %v", err)
|
||||
}
|
||||
|
||||
// Parse the other signing participants' nonces. We can't validate the
|
||||
// number of nonces here because we don't have access to the session in
|
||||
// this context. But the signer will be able to make sure we don't
|
||||
// register more nonces than there are signers (which would mean
|
||||
// something is wrong in the signing setup). But we want at least a
|
||||
// single nonce for each call.
|
||||
otherSignerNonces, err := parseMuSig2PublicNonces(
|
||||
in.OtherSignerPublicNonces, false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing other nonces: %v", err)
|
||||
}
|
||||
|
||||
// Register the nonces now.
|
||||
haveAllNonces, err := s.cfg.Signer.MuSig2RegisterNonces(
|
||||
sessionID, otherSignerNonces,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error registering nonces: %v", err)
|
||||
}
|
||||
|
||||
return &MuSig2RegisterNoncesResponse{HaveAllNonces: haveAllNonces}, nil
|
||||
}
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key that was
|
||||
// specified when the session was created. This can only be called when all
|
||||
// public nonces of all participants are known and have been registered with
|
||||
// the session. If this node isn't responsible for combining all the partial
|
||||
// signatures, then the cleanup flag should be set, indicating that the session
|
||||
// can be removed from memory once the signature was produced.
|
||||
func (s *Server) MuSig2Sign(_ context.Context,
|
||||
in *MuSig2SignRequest) (*MuSig2SignResponse, error) {
|
||||
|
||||
// Check session ID length.
|
||||
sessionID, err := parseMuSig2SessionID(in.SessionId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing session ID: %v", err)
|
||||
}
|
||||
|
||||
// Schnorr signatures only work reliably if the message is 32 bytes.
|
||||
msg := [sha256.Size]byte{}
|
||||
if len(in.MessageDigest) != sha256.Size {
|
||||
return nil, fmt.Errorf("invalid message digest size, got %d "+
|
||||
"but expected %d", len(in.MessageDigest), sha256.Size)
|
||||
}
|
||||
copy(msg[:], in.MessageDigest)
|
||||
|
||||
// Create our own partial signature with the local signing key.
|
||||
partialSig, err := s.cfg.Signer.MuSig2Sign(sessionID, msg, in.Cleanup)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error signing: %v", err)
|
||||
}
|
||||
|
||||
serializedPartialSig, err := input.SerializePartialSignature(partialSig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error serializing sig: %v", err)
|
||||
}
|
||||
|
||||
return &MuSig2SignResponse{
|
||||
LocalPartialSignature: serializedPartialSig[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the local one,
|
||||
// if it already exists. Once a partial signature of all participants is
|
||||
// registered, the final signature will be combined and returned.
|
||||
func (s *Server) MuSig2CombineSig(_ context.Context,
|
||||
in *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error) {
|
||||
|
||||
// Check session ID length.
|
||||
sessionID, err := parseMuSig2SessionID(in.SessionId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing session ID: %v", err)
|
||||
}
|
||||
|
||||
// Parse all other signatures. This can be called multiple times, so we
|
||||
// can't really sanity check how many we already have vs. how many the
|
||||
// user supplied in this call.
|
||||
partialSigs, err := parseMuSig2PartialSignatures(
|
||||
in.OtherPartialSignatures,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing partial signatures: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// Combine the signatures now, potentially getting the final, full
|
||||
// signature if we've already got all partial ones.
|
||||
finalSig, haveAllSigs, err := s.cfg.Signer.MuSig2CombineSig(
|
||||
sessionID, partialSigs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error combining signatures: %v", err)
|
||||
}
|
||||
|
||||
return &MuSig2CombineSigResponse{
|
||||
HaveAllSignatures: haveAllSigs,
|
||||
FinalSignature: finalSig.Serialize(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseRawKeyBytes checks that the provided raw public key is valid and returns
|
||||
// the public key. A nil public key is returned if the length of the rawKeyBytes
|
||||
// is zero.
|
||||
|
@ -695,3 +962,111 @@ func parseRawKeyBytes(rawKeyBytes []byte) (*btcec.PublicKey, error) {
|
|||
"specified")
|
||||
}
|
||||
}
|
||||
|
||||
// parseMuSig2SessionID parses a MuSig2 session ID from a raw byte slice.
|
||||
func parseMuSig2SessionID(rawID []byte) (input.MuSig2SessionID, error) {
|
||||
sessionID := input.MuSig2SessionID{}
|
||||
|
||||
// The session ID must be exact in its length.
|
||||
if len(rawID) != sha256.Size {
|
||||
return sessionID, fmt.Errorf("invalid session ID size, got "+
|
||||
"%d but expected %d", len(rawID), sha256.Size)
|
||||
}
|
||||
copy(sessionID[:], rawID)
|
||||
|
||||
return sessionID, nil
|
||||
}
|
||||
|
||||
// parseMuSig2PublicNonces sanity checks and parses the other signers' public
|
||||
// nonces.
|
||||
func parseMuSig2PublicNonces(pubNonces [][]byte,
|
||||
emptyAllowed bool) ([][musig2.PubNonceSize]byte, error) {
|
||||
|
||||
// For some calls the nonces are optional while for others it doesn't
|
||||
// make any sense to not specify them (for example for the explicit
|
||||
// nonce registration call there should be at least one nonce).
|
||||
if !emptyAllowed && len(pubNonces) == 0 {
|
||||
return nil, fmt.Errorf("at least one other signer public " +
|
||||
"nonce is required")
|
||||
}
|
||||
|
||||
// Parse all other nonces. This can be called multiple times, so we
|
||||
// can't really sanity check how many we already have vs. how many the
|
||||
// user supplied in this call.
|
||||
otherSignerNonces := make([][musig2.PubNonceSize]byte, len(pubNonces))
|
||||
for idx, otherNonceBytes := range pubNonces {
|
||||
if len(otherNonceBytes) != musig2.PubNonceSize {
|
||||
return nil, fmt.Errorf("invalid public nonce at "+
|
||||
"index %d: invalid length, got %d but "+
|
||||
"expected %d", idx, len(otherNonceBytes),
|
||||
musig2.PubNonceSize)
|
||||
}
|
||||
copy(otherSignerNonces[idx][:], otherNonceBytes)
|
||||
}
|
||||
|
||||
return otherSignerNonces, nil
|
||||
}
|
||||
|
||||
// parseMuSig2PartialSignatures sanity checks and parses the other signers'
|
||||
// partial signatures.
|
||||
func parseMuSig2PartialSignatures(
|
||||
partialSignatures [][]byte) ([]*musig2.PartialSignature, error) {
|
||||
|
||||
// We always want at least one partial signature.
|
||||
if len(partialSignatures) == 0 {
|
||||
return nil, fmt.Errorf("at least one partial signature is " +
|
||||
"required")
|
||||
}
|
||||
|
||||
parsedPartialSigs := make(
|
||||
[]*musig2.PartialSignature, len(partialSignatures),
|
||||
)
|
||||
for idx, otherPartialSigBytes := range partialSignatures {
|
||||
sig, err := input.DeserializePartialSignature(
|
||||
otherPartialSigBytes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid partial signature at "+
|
||||
"index %d: %v", idx, err)
|
||||
}
|
||||
|
||||
parsedPartialSigs[idx] = sig
|
||||
}
|
||||
|
||||
return parsedPartialSigs, nil
|
||||
}
|
||||
|
||||
// UnmarshalTweaks parses the RPC tweak descriptions into their native
|
||||
// counterpart.
|
||||
func UnmarshalTweaks(rpcTweaks []*TweakDesc,
|
||||
taprootTweak *TaprootTweakDesc) (*input.MuSig2Tweaks, error) {
|
||||
|
||||
// Parse the generic tweaks first.
|
||||
tweaks := &input.MuSig2Tweaks{
|
||||
GenericTweaks: make([]musig2.KeyTweakDesc, len(rpcTweaks)),
|
||||
}
|
||||
for idx, rpcTweak := range rpcTweaks {
|
||||
if len(rpcTweak.Tweak) == 0 {
|
||||
return nil, fmt.Errorf("tweak cannot be empty")
|
||||
}
|
||||
|
||||
copy(tweaks.GenericTweaks[idx].Tweak[:], rpcTweak.Tweak)
|
||||
tweaks.GenericTweaks[idx].IsXOnly = rpcTweak.IsXOnly
|
||||
}
|
||||
|
||||
// Now parse the taproot specific tweak.
|
||||
if taprootTweak != nil {
|
||||
if taprootTweak.KeySpendOnly {
|
||||
tweaks.TaprootBIP0086Tweak = true
|
||||
} else {
|
||||
if len(taprootTweak.ScriptRoot) == 0 {
|
||||
return nil, fmt.Errorf("script root cannot " +
|
||||
"be empty for non-keyspend")
|
||||
}
|
||||
|
||||
tweaks.TaprootTweak = taprootTweak.ScriptRoot
|
||||
}
|
||||
}
|
||||
|
||||
return tweaks, nil
|
||||
}
|
||||
|
|
|
@ -114,6 +114,32 @@ func testRemoteSigner(net *lntest.NetworkHarness, t *harnessTest) {
|
|||
fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
|
||||
runSignOutputRaw(tt, net, wo)
|
||||
},
|
||||
}, {
|
||||
name: "taproot",
|
||||
sendCoins: true,
|
||||
fn: func(tt *harnessTest, wo, carol *lntest.HarnessNode) {
|
||||
ctxt, cancel := context.WithTimeout(
|
||||
ctxb, 3*defaultTimeout,
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
// TODO(guggero): Fix remote taproot signing by adding
|
||||
// the required fields to PSBT.
|
||||
// testTaprootComputeInputScriptKeySpendBip86(
|
||||
// ctxt, tt, wo, net,
|
||||
// )
|
||||
// testTaprootSignOutputRawScriptSpend(ctxt, tt, wo, net)
|
||||
// testTaprootSignOutputRawKeySpendBip86(
|
||||
// ctxt, tt, wo, net,
|
||||
// )
|
||||
// testTaprootSignOutputRawKeySpendRootHash(
|
||||
// ctxt, tt, wo, net,
|
||||
// )
|
||||
testTaprootMuSig2KeySpendRootHash(ctxt, tt, wo, net)
|
||||
testTaprootMuSig2ScriptSpend(ctxt, tt, wo, net)
|
||||
testTaprootMuSig2KeySpendBip86(ctxt, tt, wo, net)
|
||||
testTaprootMuSig2CombinedLeafKeySpend(ctxt, tt, wo, net)
|
||||
},
|
||||
}}
|
||||
|
||||
for _, st := range subTests {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,13 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
@ -47,6 +50,50 @@ func (d *DummySigner) ComputeInputScript(tx *wire.MsgTx,
|
|||
return &input.Script{}, nil
|
||||
}
|
||||
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||
// key identified by the key locator. The complete list of all public keys of
|
||||
// all signing parties must be provided, including the public key of the local
|
||||
// signing key. If nonces of other parties are already known, they can be
|
||||
// submitted as well to reduce the number of method calls necessary later on.
|
||||
func (d *DummySigner) MuSig2CreateSession(keychain.KeyLocator,
|
||||
[]*btcec.PublicKey, *input.MuSig2Tweaks,
|
||||
[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||
// participants for a session identified by its ID. This method returns true
|
||||
// once we have all nonces for all other signing participants.
|
||||
func (d *DummySigner) MuSig2RegisterNonces(input.MuSig2SessionID,
|
||||
[][musig2.PubNonceSize]byte) (bool, error) {
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key
|
||||
// that was specified when the session was created. This can only be
|
||||
// called when all public nonces of all participants are known and have
|
||||
// been registered with the session. If this node isn't responsible for
|
||||
// combining all the partial signatures, then the cleanup parameter
|
||||
// should be set, indicating that the session can be removed from memory
|
||||
// once the signature was produced.
|
||||
func (d *DummySigner) MuSig2Sign(input.MuSig2SessionID,
|
||||
[sha256.Size]byte, bool) (*musig2.PartialSignature, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||
// local one, if it already exists. Once a partial signature of all
|
||||
// participants is registered, the final signature will be combined and
|
||||
// returned.
|
||||
func (d *DummySigner) MuSig2CombineSig(input.MuSig2SessionID,
|
||||
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error) {
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// SingleSigner is an implementation of the Signer interface that signs
|
||||
// everything with a single private key.
|
||||
type SingleSigner struct {
|
||||
|
@ -136,3 +183,47 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator,
|
|||
}
|
||||
return ecdsa.Sign(s.Privkey, digest), nil
|
||||
}
|
||||
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||
// key identified by the key locator. The complete list of all public keys of
|
||||
// all signing parties must be provided, including the public key of the local
|
||||
// signing key. If nonces of other parties are already known, they can be
|
||||
// submitted as well to reduce the number of method calls necessary later on.
|
||||
func (s *SingleSigner) MuSig2CreateSession(keychain.KeyLocator,
|
||||
[]*btcec.PublicKey, *input.MuSig2Tweaks,
|
||||
[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||
// participants for a session identified by its ID. This method returns true
|
||||
// once we have all nonces for all other signing participants.
|
||||
func (s *SingleSigner) MuSig2RegisterNonces(input.MuSig2SessionID,
|
||||
[][musig2.PubNonceSize]byte) (bool, error) {
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key
|
||||
// that was specified when the session was created. This can only be
|
||||
// called when all public nonces of all participants are known and have
|
||||
// been registered with the session. If this node isn't responsible for
|
||||
// combining all the partial signatures, then the cleanup parameter
|
||||
// should be set, indicating that the session can be removed from memory
|
||||
// once the signature was produced.
|
||||
func (s *SingleSigner) MuSig2Sign(input.MuSig2SessionID,
|
||||
[sha256.Size]byte, bool) (*musig2.PartialSignature, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||
// local one, if it already exists. Once a partial signature of all
|
||||
// participants is registered, the final signature will be combined and
|
||||
// returned.
|
||||
func (s *SingleSigner) MuSig2CombineSig(input.MuSig2SessionID,
|
||||
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error) {
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
"github.com/lightningnetwork/lnd/blockcache"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
|
@ -98,6 +99,9 @@ type BtcWallet struct {
|
|||
chainKeyScope waddrmgr.KeyScope
|
||||
|
||||
blockCache *blockcache.BlockCache
|
||||
|
||||
musig2Sessions map[input.MuSig2SessionID]*muSig2State
|
||||
musig2SessionsMtx sync.Mutex
|
||||
}
|
||||
|
||||
// A compile time check to ensure that BtcWallet implements the
|
||||
|
@ -160,13 +164,14 @@ func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) {
|
|||
}
|
||||
|
||||
return &BtcWallet{
|
||||
cfg: &cfg,
|
||||
wallet: wallet,
|
||||
db: wallet.Database(),
|
||||
chain: cfg.ChainSource,
|
||||
netParams: cfg.NetParams,
|
||||
chainKeyScope: chainKeyScope,
|
||||
blockCache: blockCache,
|
||||
cfg: &cfg,
|
||||
wallet: wallet,
|
||||
db: wallet.Database(),
|
||||
chain: cfg.ChainSource,
|
||||
netParams: cfg.NetParams,
|
||||
chainKeyScope: chainKeyScope,
|
||||
blockCache: blockCache,
|
||||
musig2Sessions: make(map[input.MuSig2SessionID]*muSig2State),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package btcwallet
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
@ -451,6 +453,257 @@ func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// muSig2State is a struct that holds on to the internal signing session state
|
||||
// of a MuSig2 session.
|
||||
type muSig2State struct {
|
||||
// MuSig2SessionInfo is the associated meta information of the signing
|
||||
// session.
|
||||
input.MuSig2SessionInfo
|
||||
|
||||
// context is the signing context responsible for keeping track of the
|
||||
// public keys involved in the signing process.
|
||||
context *musig2.Context
|
||||
|
||||
// session is the signing session responsible for keeping track of the
|
||||
// nonces and partial signatures involved in the signing process.
|
||||
session *musig2.Session
|
||||
}
|
||||
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||
// key identified by the key locator. The complete list of all public keys of
|
||||
// all signing parties must be provided, including the public key of the local
|
||||
// signing key. If nonces of other parties are already known, they can be
|
||||
// submitted as well to reduce the number of method calls necessary later on.
|
||||
func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator,
|
||||
allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks,
|
||||
otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
|
||||
error) {
|
||||
|
||||
// We need to derive the private key for signing. In the remote signing
|
||||
// setup, this whole RPC call will be forwarded to the signing
|
||||
// instance, which requires it to be stateful.
|
||||
privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
|
||||
KeyLocator: keyLoc,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error deriving private key: %v", err)
|
||||
}
|
||||
|
||||
// The context keeps track of all signing keys and our local key.
|
||||
allOpts := append(
|
||||
[]musig2.ContextOption{
|
||||
musig2.WithKnownSigners(allSignerPubKeys),
|
||||
},
|
||||
tweaks.ToContextOptions()...,
|
||||
)
|
||||
musigContext, err := musig2.NewContext(privKey, true, allOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating MuSig2 signing "+
|
||||
"context: %v", err)
|
||||
}
|
||||
|
||||
// The session keeps track of the own and other nonces.
|
||||
musigSession, err := musigContext.NewSession()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating MuSig2 signing "+
|
||||
"session: %v", err)
|
||||
}
|
||||
|
||||
// Add all nonces we might've learned so far.
|
||||
haveAllNonces := false
|
||||
for _, otherSignerNonce := range otherSignerNonces {
|
||||
haveAllNonces, err = musigSession.RegisterPubNonce(
|
||||
otherSignerNonce,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error registering other "+
|
||||
"signer public nonce: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Register the new session.
|
||||
combinedKey, err := musigContext.CombinedKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting combined key: %v", err)
|
||||
}
|
||||
session := &muSig2State{
|
||||
MuSig2SessionInfo: input.MuSig2SessionInfo{
|
||||
SessionID: input.NewMuSig2SessionID(
|
||||
combinedKey, musigSession.PublicNonce(),
|
||||
),
|
||||
PublicNonce: musigSession.PublicNonce(),
|
||||
CombinedKey: combinedKey,
|
||||
TaprootTweak: tweaks.HasTaprootTweak(),
|
||||
HaveAllNonces: haveAllNonces,
|
||||
},
|
||||
context: musigContext,
|
||||
session: musigSession,
|
||||
}
|
||||
|
||||
// The internal key is only calculated if we are using a taproot tweak
|
||||
// and need to know it for a potential script spend.
|
||||
if tweaks.HasTaprootTweak() {
|
||||
internalKey, err := musigContext.TaprootInternalKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting internal key: %v",
|
||||
err)
|
||||
}
|
||||
session.TaprootInternalKey = internalKey
|
||||
}
|
||||
|
||||
// Since we generate new nonces for every session, there is no way that
|
||||
// a session with the same ID already exists. So even if we call the API
|
||||
// twice with the same signers, we still get a new ID.
|
||||
b.musig2SessionsMtx.Lock()
|
||||
b.musig2Sessions[session.SessionID] = session
|
||||
b.musig2SessionsMtx.Unlock()
|
||||
|
||||
return &session.MuSig2SessionInfo, nil
|
||||
}
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||
// participants for a session identified by its ID. This method returns true
|
||||
// once we have all nonces for all other signing participants.
|
||||
func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
|
||||
otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) {
|
||||
|
||||
// We hold the lock during the whole operation, we don't want any
|
||||
// interference with calls that might come through in parallel for the
|
||||
// same session.
|
||||
b.musig2SessionsMtx.Lock()
|
||||
defer b.musig2SessionsMtx.Unlock()
|
||||
|
||||
session, ok := b.musig2Sessions[sessionID]
|
||||
if !ok {
|
||||
return false, fmt.Errorf("session with ID %x not found",
|
||||
sessionID[:])
|
||||
}
|
||||
|
||||
// Make sure we don't exceed the number of expected nonces as that would
|
||||
// indicate something is wrong with the signing setup.
|
||||
if session.HaveAllNonces {
|
||||
return true, fmt.Errorf("already have all nonces")
|
||||
}
|
||||
|
||||
numSigners := len(session.context.SigningKeys())
|
||||
remainingNonces := numSigners - session.session.NumRegisteredNonces()
|
||||
if len(otherSignerNonces) > remainingNonces {
|
||||
return false, fmt.Errorf("only %d other nonces remaining but "+
|
||||
"trying to register %d more", remainingNonces,
|
||||
len(otherSignerNonces))
|
||||
}
|
||||
|
||||
// Add all nonces we've learned so far.
|
||||
var err error
|
||||
for _, otherSignerNonce := range otherSignerNonces {
|
||||
session.HaveAllNonces, err = session.session.RegisterPubNonce(
|
||||
otherSignerNonce,
|
||||
)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error registering other "+
|
||||
"signer public nonce: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return session.HaveAllNonces, nil
|
||||
}
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key
|
||||
// that was specified when the session was created. This can only be
|
||||
// called when all public nonces of all participants are known and have
|
||||
// been registered with the session. If this node isn't responsible for
|
||||
// combining all the partial signatures, then the cleanup parameter
|
||||
// should be set, indicating that the session can be removed from memory
|
||||
// once the signature was produced.
|
||||
func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID,
|
||||
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
|
||||
|
||||
// We hold the lock during the whole operation, we don't want any
|
||||
// interference with calls that might come through in parallel for the
|
||||
// same session.
|
||||
b.musig2SessionsMtx.Lock()
|
||||
defer b.musig2SessionsMtx.Unlock()
|
||||
|
||||
session, ok := b.musig2Sessions[sessionID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session with ID %x not found",
|
||||
sessionID[:])
|
||||
}
|
||||
|
||||
// We can only sign once we have all other signer's nonces.
|
||||
if !session.HaveAllNonces {
|
||||
return nil, fmt.Errorf("only have %d of %d required nonces",
|
||||
session.session.NumRegisteredNonces(),
|
||||
len(session.context.SigningKeys()))
|
||||
}
|
||||
|
||||
// Create our own partial signature with the local signing key.
|
||||
partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error signing with local key: %v", err)
|
||||
}
|
||||
|
||||
// Clean up our local state if requested.
|
||||
if cleanUp {
|
||||
delete(b.musig2Sessions, sessionID)
|
||||
}
|
||||
|
||||
return partialSig, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||
// local one, if it already exists. Once a partial signature of all
|
||||
// participants is registered, the final signature will be combined and
|
||||
// returned.
|
||||
func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID,
|
||||
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
|
||||
error) {
|
||||
|
||||
// We hold the lock during the whole operation, we don't want any
|
||||
// interference with calls that might come through in parallel for the
|
||||
// same session.
|
||||
b.musig2SessionsMtx.Lock()
|
||||
defer b.musig2SessionsMtx.Unlock()
|
||||
|
||||
session, ok := b.musig2Sessions[sessionID]
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("session with ID %x not found",
|
||||
sessionID[:])
|
||||
}
|
||||
|
||||
// Make sure we don't exceed the number of expected partial signatures
|
||||
// as that would indicate something is wrong with the signing setup.
|
||||
if session.HaveAllSigs {
|
||||
return nil, true, fmt.Errorf("already have all partial" +
|
||||
"signatures")
|
||||
}
|
||||
|
||||
// Add all sigs we got so far.
|
||||
var (
|
||||
finalSig *schnorr.Signature
|
||||
err error
|
||||
)
|
||||
for _, otherPartialSig := range partialSigs {
|
||||
session.HaveAllSigs, err = session.session.CombineSig(
|
||||
otherPartialSig,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error combining "+
|
||||
"partial signature: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// If we have all partial signatures, we should be able to get the
|
||||
// complete signature now. We also remove this session from memory since
|
||||
// there is nothing more left to do.
|
||||
if session.HaveAllSigs {
|
||||
finalSig = session.session.FinalSig()
|
||||
delete(b.musig2Sessions, sessionID)
|
||||
}
|
||||
|
||||
return finalSig, session.HaveAllSigs, nil
|
||||
}
|
||||
|
||||
// A compile time check to ensure that BtcWallet implements the Signer
|
||||
// interface.
|
||||
var _ input.Signer = (*BtcWallet)(nil)
|
||||
|
|
|
@ -273,8 +273,9 @@ func TestScriptImport(t *testing.T) {
|
|||
// Now, as a last test, make sure that when we try adding an address
|
||||
// with partial script reveal, we get an error that the address already
|
||||
// exists.
|
||||
inclusionProof := leaf2.TapHash()
|
||||
tapscript2 := input.TapscriptPartialReveal(
|
||||
testPubKey, leaf1, leaf2.TapHash(),
|
||||
testPubKey, leaf1, inclusionProof[:],
|
||||
)
|
||||
_, err = w.ImportTaprootScript(scope, tapscript2)
|
||||
require.Error(t, err)
|
||||
|
|
|
@ -3,6 +3,7 @@ package rpcwallet
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -11,6 +12,8 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
|
@ -537,7 +540,7 @@ func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx,
|
|||
//
|
||||
// NOTE: This method will ignore any tweak parameters set within the
|
||||
// passed SignDescriptor as it assumes a set of typical script
|
||||
// templates (p2wkh, np2wkh, etc).
|
||||
// templates (p2wkh, np2wkh, BIP0086 p2tr, etc).
|
||||
//
|
||||
// NOTE: This method is part of the input.Signer interface.
|
||||
func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
|
||||
|
@ -571,6 +574,214 @@ func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||
// key identified by the key locator. The complete list of all public keys of
|
||||
// all signing parties must be provided, including the public key of the local
|
||||
// signing key. If nonces of other parties are already known, they can be
|
||||
// submitted as well to reduce the number of method calls necessary later on.
|
||||
func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator,
|
||||
pubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks,
|
||||
otherNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
|
||||
error) {
|
||||
|
||||
// We need to serialize all data for the RPC call. We can do that by
|
||||
// putting everything directly into the request struct.
|
||||
req := &signrpc.MuSig2SessionRequest{
|
||||
KeyLoc: &signrpc.KeyLocator{
|
||||
KeyFamily: int32(keyLoc.Family),
|
||||
KeyIndex: int32(keyLoc.Index),
|
||||
},
|
||||
AllSignerPubkeys: make([][]byte, len(pubKeys)),
|
||||
Tweaks: make(
|
||||
[]*signrpc.TweakDesc, len(tweaks.GenericTweaks),
|
||||
),
|
||||
OtherSignerPublicNonces: make([][]byte, len(otherNonces)),
|
||||
}
|
||||
for idx, pubKey := range pubKeys {
|
||||
req.AllSignerPubkeys[idx] = schnorr.SerializePubKey(pubKey)
|
||||
}
|
||||
for idx, genericTweak := range tweaks.GenericTweaks {
|
||||
req.Tweaks[idx] = &signrpc.TweakDesc{
|
||||
Tweak: genericTweak.Tweak[:],
|
||||
IsXOnly: genericTweak.IsXOnly,
|
||||
}
|
||||
}
|
||||
for idx, nonce := range otherNonces {
|
||||
req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
|
||||
copy(req.OtherSignerPublicNonces[idx], nonce[:])
|
||||
}
|
||||
if tweaks.HasTaprootTweak() {
|
||||
req.TaprootTweak = &signrpc.TaprootTweakDesc{
|
||||
KeySpendOnly: tweaks.TaprootBIP0086Tweak,
|
||||
ScriptRoot: tweaks.TaprootTweak,
|
||||
}
|
||||
}
|
||||
|
||||
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
||||
defer cancel()
|
||||
|
||||
resp, err := r.signerClient.MuSig2CreateSession(ctxt, req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error creating MuSig2 session in remote "+
|
||||
"signer instance: %v", err)
|
||||
|
||||
// Log as critical as we should shut down if there is no signer.
|
||||
log.Criticalf("RPC signer error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// De-Serialize all the info back into our native struct.
|
||||
info := &input.MuSig2SessionInfo{
|
||||
TaprootTweak: tweaks.HasTaprootTweak(),
|
||||
HaveAllNonces: resp.HaveAllNonces,
|
||||
}
|
||||
copy(info.SessionID[:], resp.SessionId)
|
||||
copy(info.PublicNonce[:], resp.LocalPublicNonces)
|
||||
|
||||
info.CombinedKey, err = schnorr.ParsePubKey(resp.CombinedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing combined key: %v", err)
|
||||
}
|
||||
|
||||
if tweaks.HasTaprootTweak() {
|
||||
info.TaprootInternalKey, err = schnorr.ParsePubKey(
|
||||
resp.TaprootInternalKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing internal key: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||
// participants for a session identified by its ID. This method returns true
|
||||
// once we have all nonces for all other signing participants.
|
||||
func (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
|
||||
pubNonces [][musig2.PubNonceSize]byte) (bool, error) {
|
||||
|
||||
// We need to serialize all data for the RPC call. We can do that by
|
||||
// putting everything directly into the request struct.
|
||||
req := &signrpc.MuSig2RegisterNoncesRequest{
|
||||
SessionId: sessionID[:],
|
||||
OtherSignerPublicNonces: make([][]byte, len(pubNonces)),
|
||||
}
|
||||
for idx, nonce := range pubNonces {
|
||||
req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
|
||||
copy(req.OtherSignerPublicNonces[idx], nonce[:])
|
||||
}
|
||||
|
||||
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
||||
defer cancel()
|
||||
|
||||
resp, err := r.signerClient.MuSig2RegisterNonces(ctxt, req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error registering MuSig2 nonces in remote "+
|
||||
"signer instance: %v", err)
|
||||
|
||||
// Log as critical as we should shut down if there is no signer.
|
||||
log.Criticalf("RPC signer error: %v", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return resp.HaveAllNonces, nil
|
||||
}
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key
|
||||
// that was specified when the session was created. This can only be
|
||||
// called when all public nonces of all participants are known and have
|
||||
// been registered with the session. If this node isn't responsible for
|
||||
// combining all the partial signatures, then the cleanup parameter
|
||||
// should be set, indicating that the session can be removed from memory
|
||||
// once the signature was produced.
|
||||
func (r *RPCKeyRing) MuSig2Sign(sessionID input.MuSig2SessionID,
|
||||
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
|
||||
|
||||
// We need to serialize all data for the RPC call. We can do that by
|
||||
// putting everything directly into the request struct.
|
||||
req := &signrpc.MuSig2SignRequest{
|
||||
SessionId: sessionID[:],
|
||||
MessageDigest: msg[:],
|
||||
Cleanup: cleanUp,
|
||||
}
|
||||
|
||||
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
||||
defer cancel()
|
||||
|
||||
resp, err := r.signerClient.MuSig2Sign(ctxt, req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error signing MuSig2 session in remote "+
|
||||
"signer instance: %v", err)
|
||||
|
||||
// Log as critical as we should shut down if there is no signer.
|
||||
log.Criticalf("RPC signer error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
partialSig, err := input.DeserializePartialSignature(
|
||||
resp.LocalPartialSignature,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing partial signature from "+
|
||||
"remote signer: %v", err)
|
||||
}
|
||||
|
||||
return partialSig, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||
// local one, if it already exists. Once a partial signature of all
|
||||
// participants is registered, the final signature will be combined and
|
||||
// returned.
|
||||
func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID,
|
||||
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
|
||||
error) {
|
||||
|
||||
// We need to serialize all data for the RPC call. We can do that by
|
||||
// putting everything directly into the request struct.
|
||||
req := &signrpc.MuSig2CombineSigRequest{
|
||||
SessionId: sessionID[:],
|
||||
OtherPartialSignatures: make([][]byte, len(partialSigs)),
|
||||
}
|
||||
for idx, partialSig := range partialSigs {
|
||||
rawSig, err := input.SerializePartialSignature(partialSig)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error serializing "+
|
||||
"partial signature: %v", err)
|
||||
}
|
||||
req.OtherPartialSignatures[idx] = rawSig[:]
|
||||
}
|
||||
|
||||
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
||||
defer cancel()
|
||||
|
||||
resp, err := r.signerClient.MuSig2CombineSig(ctxt, req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error combining MuSig2 signatures in remote "+
|
||||
"signer instance: %v", err)
|
||||
|
||||
// Log as critical as we should shut down if there is no signer.
|
||||
log.Criticalf("RPC signer error: %v", err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// The final signature is only available when we have all the other
|
||||
// partial signatures from all participants.
|
||||
if !resp.HaveAllSignatures {
|
||||
return nil, resp.HaveAllSignatures, nil
|
||||
}
|
||||
|
||||
finalSig, err := schnorr.ParseSignature(resp.FinalSignature)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("error parsing final signature: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
return finalSig, resp.HaveAllSignatures, nil
|
||||
}
|
||||
|
||||
// remoteSign signs the input specified in signDesc of the given transaction tx
|
||||
// using the remote signing instance.
|
||||
func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.17.3-buster
|
||||
FROM golang:1.18.0-buster
|
||||
|
||||
RUN apt-get update && apt-get install -y git
|
||||
ENV GOCACHE=/tmp/build/.cache
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package wtmock
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
|
@ -61,6 +64,50 @@ func (s *MockSigner) ComputeInputScript(tx *wire.MsgTx,
|
|||
panic("not implemented")
|
||||
}
|
||||
|
||||
// MuSig2CreateSession creates a new MuSig2 signing session using the local
|
||||
// key identified by the key locator. The complete list of all public keys of
|
||||
// all signing parties must be provided, including the public key of the local
|
||||
// signing key. If nonces of other parties are already known, they can be
|
||||
// submitted as well to reduce the number of method calls necessary later on.
|
||||
func (s *MockSigner) MuSig2CreateSession(keychain.KeyLocator,
|
||||
[]*btcec.PublicKey, *input.MuSig2Tweaks,
|
||||
[][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2RegisterNonces registers one or more public nonces of other signing
|
||||
// participants for a session identified by its ID. This method returns true
|
||||
// once we have all nonces for all other signing participants.
|
||||
func (s *MockSigner) MuSig2RegisterNonces(input.MuSig2SessionID,
|
||||
[][musig2.PubNonceSize]byte) (bool, error) {
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// MuSig2Sign creates a partial signature using the local signing key
|
||||
// that was specified when the session was created. This can only be
|
||||
// called when all public nonces of all participants are known and have
|
||||
// been registered with the session. If this node isn't responsible for
|
||||
// combining all the partial signatures, then the cleanup parameter
|
||||
// should be set, indicating that the session can be removed from memory
|
||||
// once the signature was produced.
|
||||
func (s *MockSigner) MuSig2Sign(input.MuSig2SessionID,
|
||||
[sha256.Size]byte, bool) (*musig2.PartialSignature, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MuSig2CombineSig combines the given partial signature(s) with the
|
||||
// local one, if it already exists. Once a partial signature of all
|
||||
// participants is registered, the final signature will be combined and
|
||||
// returned.
|
||||
func (s *MockSigner) MuSig2CombineSig(input.MuSig2SessionID,
|
||||
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error) {
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// AddPrivKey records the passed privKey in the MockSigner's registry of keys it
|
||||
// can sign with in the future. A unique key locator is returned, allowing the
|
||||
// caller to sign with this key when presented via an input.SignDescriptor.
|
||||
|
|
Loading…
Add table
Reference in a new issue