mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
1335 lines
42 KiB
Go
1335 lines
42 KiB
Go
//go:build signrpc
|
|
// +build signrpc
|
|
|
|
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"
|
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/macaroons"
|
|
"google.golang.org/grpc"
|
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
|
)
|
|
|
|
const (
|
|
// subServerName is the name of the sub rpc server. We'll use this name
|
|
// to register ourselves, and we also require that the main
|
|
// SubServerConfigDispatcher instance recognize this as the name of the
|
|
// config file that we need.
|
|
subServerName = "SignRPC"
|
|
|
|
// BIP0340 is the prefix for BIP0340-related tagged hashes.
|
|
BIP0340 = "BIP0340"
|
|
)
|
|
|
|
var (
|
|
// macaroonOps are the set of capabilities that our minted macaroon (if
|
|
// it doesn't already exist) will have.
|
|
macaroonOps = []bakery.Op{
|
|
{
|
|
Entity: "signer",
|
|
Action: "generate",
|
|
},
|
|
{
|
|
Entity: "signer",
|
|
Action: "read",
|
|
},
|
|
}
|
|
|
|
// macPermissions maps RPC calls to the permissions they require.
|
|
macPermissions = map[string][]bakery.Op{
|
|
"/signrpc.Signer/SignOutputRaw": {{
|
|
Entity: "signer",
|
|
Action: "generate",
|
|
}},
|
|
"/signrpc.Signer/ComputeInputScript": {{
|
|
Entity: "signer",
|
|
Action: "generate",
|
|
}},
|
|
"/signrpc.Signer/SignMessage": {{
|
|
Entity: "signer",
|
|
Action: "generate",
|
|
}},
|
|
"/signrpc.Signer/VerifyMessage": {{
|
|
Entity: "signer",
|
|
Action: "read",
|
|
}},
|
|
"/signrpc.Signer/DeriveSharedKey": {{
|
|
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",
|
|
}},
|
|
"/signrpc.Signer/MuSig2Cleanup": {{
|
|
Entity: "signer",
|
|
Action: "generate",
|
|
}},
|
|
}
|
|
|
|
// DefaultSignerMacFilename is the default name of the signer macaroon
|
|
// that we expect to find via a file handle within the main
|
|
// configuration file in this package.
|
|
DefaultSignerMacFilename = "signer.macaroon"
|
|
)
|
|
|
|
// ServerShell is a shell struct holding a reference to the actual sub-server.
|
|
// It is used to register the gRPC sub-server with the root server before we
|
|
// have the necessary dependencies to populate the actual sub-server.
|
|
type ServerShell struct {
|
|
SignerServer
|
|
}
|
|
|
|
// Server is a sub-server of the main RPC server: the signer RPC. This sub RPC
|
|
// server allows external callers to access the full signing capabilities of
|
|
// lnd. This allows callers to create custom protocols, external to lnd, even
|
|
// backed by multiple distinct lnd across independent failure domains.
|
|
type Server struct {
|
|
// Required by the grpc-gateway/v2 library for forward compatibility.
|
|
UnimplementedSignerServer
|
|
|
|
cfg *Config
|
|
}
|
|
|
|
// A compile time check to ensure that Server fully implements the SignerServer
|
|
// gRPC service.
|
|
var _ SignerServer = (*Server)(nil)
|
|
|
|
// New returns a new instance of the signrpc Signer sub-server. We also return
|
|
// the set of permissions for the macaroons that we may create within this
|
|
// method. If the macaroons we need aren't found in the filepath, then we'll
|
|
// create them on start up. If we're unable to locate, or create the macaroons
|
|
// we need, then we'll return with an error.
|
|
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
|
// If the path of the signer macaroon wasn't generated, then we'll
|
|
// assume that it's found at the default network directory.
|
|
if cfg.SignerMacPath == "" {
|
|
cfg.SignerMacPath = filepath.Join(
|
|
cfg.NetworkDir, DefaultSignerMacFilename,
|
|
)
|
|
}
|
|
|
|
// Now that we know the full path of the signer macaroon, we can check
|
|
// to see if we need to create it or not. If stateless_init is set
|
|
// then we don't write the macaroons.
|
|
macFilePath := cfg.SignerMacPath
|
|
if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
|
|
!lnrpc.FileExists(macFilePath) {
|
|
|
|
log.Infof("Making macaroons for Signer RPC Server at: %v",
|
|
macFilePath)
|
|
|
|
// At this point, we know that the signer macaroon doesn't yet,
|
|
// exist, so we need to create it with the help of the main
|
|
// macaroon service.
|
|
signerMac, err := cfg.MacService.NewMacaroon(
|
|
context.Background(), macaroons.DefaultRootKeyID,
|
|
macaroonOps...,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
signerMacBytes, err := signerMac.M().MarshalBinary()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
err = ioutil.WriteFile(macFilePath, signerMacBytes, 0644)
|
|
if err != nil {
|
|
_ = os.Remove(macFilePath)
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
signerServer := &Server{
|
|
cfg: cfg,
|
|
}
|
|
|
|
return signerServer, macPermissions, nil
|
|
}
|
|
|
|
// Start launches any helper goroutines required for the rpcServer to function.
|
|
//
|
|
// NOTE: This is part of the lnrpc.SubServer interface.
|
|
func (s *Server) Start() error {
|
|
return nil
|
|
}
|
|
|
|
// Stop signals any active goroutines for a graceful closure.
|
|
//
|
|
// NOTE: This is part of the lnrpc.SubServer interface.
|
|
func (s *Server) Stop() error {
|
|
return nil
|
|
}
|
|
|
|
// Name returns a unique string representation of the sub-server. This can be
|
|
// used to identify the sub-server and also de-duplicate them.
|
|
//
|
|
// NOTE: This is part of the lnrpc.SubServer interface.
|
|
func (s *Server) Name() string {
|
|
return subServerName
|
|
}
|
|
|
|
// RegisterWithRootServer will be called by the root gRPC server to direct a
|
|
// sub RPC server to register itself with the main gRPC root server. Until this
|
|
// is called, each sub-server won't be able to have
|
|
// requests routed towards it.
|
|
//
|
|
// NOTE: This is part of the lnrpc.GrpcHandler interface.
|
|
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
|
|
// We make sure that we register it with the main gRPC server to ensure
|
|
// all our methods are routed properly.
|
|
RegisterSignerServer(grpcServer, r)
|
|
|
|
log.Debugf("Signer RPC server successfully register with root gRPC " +
|
|
"server")
|
|
|
|
return nil
|
|
}
|
|
|
|
// RegisterWithRestServer will be called by the root REST mux to direct a sub
|
|
// RPC server to register itself with the main REST mux server. Until this is
|
|
// called, each sub-server won't be able to have requests routed towards it.
|
|
//
|
|
// NOTE: This is part of the lnrpc.GrpcHandler interface.
|
|
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
|
|
mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
|
|
|
|
// We make sure that we register it with the main REST server to ensure
|
|
// all our methods are routed properly.
|
|
err := RegisterSignerHandlerFromEndpoint(ctx, mux, dest, opts)
|
|
if err != nil {
|
|
log.Errorf("Could not register Signer REST server "+
|
|
"with root REST server: %v", err)
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Signer REST server successfully registered with " +
|
|
"root REST server")
|
|
return nil
|
|
}
|
|
|
|
// CreateSubServer populates the subserver's dependencies using the passed
|
|
// SubServerConfigDispatcher. This method should fully initialize the
|
|
// sub-server instance, making it ready for action. It returns the macaroon
|
|
// permissions that the sub-server wishes to pass on to the root server for all
|
|
// methods routed towards it.
|
|
//
|
|
// NOTE: This is part of the lnrpc.GrpcHandler interface.
|
|
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
|
|
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
|
|
|
|
subServer, macPermissions, err := createNewSubServer(configRegistry)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
r.SignerServer = subServer
|
|
return subServer, macPermissions, nil
|
|
}
|
|
|
|
// SignOutputRaw generates a signature for the passed transaction according to
|
|
// the data within the passed SignReq. If we're unable to find the keys that
|
|
// correspond to the KeyLocators in the SignReq then we'll return an error.
|
|
// Additionally, if the user doesn't provide the set of required parameters, or
|
|
// provides an invalid transaction, then we'll return with an error.
|
|
//
|
|
// NOTE: The resulting signature should be void of a sighash byte.
|
|
func (s *Server) SignOutputRaw(_ context.Context, in *SignReq) (*SignResp,
|
|
error) {
|
|
|
|
switch {
|
|
// If the client doesn't specify a transaction, then there's nothing to
|
|
// sign, so we'll exit early.
|
|
case len(in.RawTxBytes) == 0:
|
|
return nil, fmt.Errorf("a transaction to sign MUST be " +
|
|
"passed in")
|
|
|
|
// If the client doesn't tell us *how* to sign the transaction, then we
|
|
// can't sign anything, so we'll exit early.
|
|
case len(in.SignDescs) == 0:
|
|
return nil, fmt.Errorf("at least one SignDescs MUST be " +
|
|
"passed in")
|
|
}
|
|
|
|
// Now that we know we have an actual transaction to decode, we'll
|
|
// deserialize it into something that we can properly utilize.
|
|
var (
|
|
txToSign wire.MsgTx
|
|
err error
|
|
)
|
|
txReader := bytes.NewReader(in.RawTxBytes)
|
|
if err := txToSign.Deserialize(txReader); err != nil {
|
|
return nil, fmt.Errorf("unable to decode tx: %w", err)
|
|
}
|
|
|
|
var (
|
|
sigHashCache = input.NewTxSigHashesV0Only(&txToSign)
|
|
prevOutputFetcher = txscript.NewMultiPrevOutFetcher(nil)
|
|
)
|
|
|
|
// If we're spending one or more SegWit v1 (Taproot) inputs, then we
|
|
// need the full UTXO information available.
|
|
if len(in.PrevOutputs) > 0 {
|
|
if len(in.PrevOutputs) != len(txToSign.TxIn) {
|
|
return nil, fmt.Errorf("provided previous outputs " +
|
|
"doesn't match number of transaction inputs")
|
|
}
|
|
|
|
// Add all previous inputs to our sighash prev out fetcher so we
|
|
// can calculate the sighash correctly.
|
|
for idx, txIn := range txToSign.TxIn {
|
|
prevOutputFetcher.AddPrevOut(
|
|
txIn.PreviousOutPoint, &wire.TxOut{
|
|
Value: in.PrevOutputs[idx].Value,
|
|
PkScript: in.PrevOutputs[idx].PkScript,
|
|
},
|
|
)
|
|
}
|
|
sigHashCache = txscript.NewTxSigHashes(
|
|
&txToSign, prevOutputFetcher,
|
|
)
|
|
}
|
|
|
|
log.Debugf("Generating sigs for %v inputs: ", len(in.SignDescs))
|
|
|
|
// With the transaction deserialized, we'll now convert sign descs so
|
|
// we can feed it into the actual signer.
|
|
signDescs := make([]*input.SignDescriptor, 0, len(in.SignDescs))
|
|
for _, signDesc := range in.SignDescs {
|
|
keyDesc := signDesc.KeyDesc
|
|
|
|
// The caller can either specify the key using the raw pubkey,
|
|
// or the description of the key. We'll still attempt to parse
|
|
// both if both were provided however, to ensure the underlying
|
|
// SignOutputRaw has as much information as possible.
|
|
var (
|
|
targetPubKey *btcec.PublicKey
|
|
keyLoc keychain.KeyLocator
|
|
)
|
|
|
|
// If this method doesn't return nil, then we know that user is
|
|
// attempting to include a raw serialized pub key.
|
|
if keyDesc.GetRawKeyBytes() != nil {
|
|
targetPubKey, err = parseRawKeyBytes(
|
|
keyDesc.GetRawKeyBytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Similarly, if they specified a key locator, then we'll parse
|
|
// that as well.
|
|
if keyDesc.GetKeyLoc() != nil {
|
|
protoLoc := keyDesc.GetKeyLoc()
|
|
keyLoc = keychain.KeyLocator{
|
|
Family: keychain.KeyFamily(
|
|
protoLoc.KeyFamily,
|
|
),
|
|
Index: uint32(protoLoc.KeyIndex),
|
|
}
|
|
}
|
|
|
|
// Check what sign method was selected by the user so, we know
|
|
// exactly what we're expecting and can prevent some of the more
|
|
// obvious usage errors.
|
|
signMethod, err := UnmarshalSignMethod(signDesc.SignMethod)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to unmarshal sign "+
|
|
"method: %v", err)
|
|
}
|
|
if !signMethod.PkScriptCompatible(signDesc.Output.PkScript) {
|
|
return nil, fmt.Errorf("selected sign method %v is "+
|
|
"not compatible with given pk script %x",
|
|
signMethod, signDesc.Output.PkScript)
|
|
}
|
|
|
|
// Perform input validation according to the sign method. Not
|
|
// all methods require the same fields to be provided.
|
|
switch signMethod {
|
|
case input.WitnessV0SignMethod:
|
|
// If a witness script isn't passed, then we can't
|
|
// proceed, as in the p2wsh case, we can't properly
|
|
// generate the sighash. A P2WKH doesn't need a witness
|
|
// script. But SignOutputRaw still needs to know the PK
|
|
// script that was used for the output. We'll send it in
|
|
// the WitnessScript field, the SignOutputRaw RPC will
|
|
// know what to do with it when creating the sighash.
|
|
if len(signDesc.WitnessScript) == 0 {
|
|
return nil, fmt.Errorf("witness script MUST " +
|
|
"be specified for segwit v0 sign " +
|
|
"method")
|
|
}
|
|
|
|
case input.TaprootKeySpendBIP0086SignMethod:
|
|
if len(signDesc.TapTweak) > 0 {
|
|
return nil, fmt.Errorf("tap tweak must be " +
|
|
"empty for BIP0086 key spend")
|
|
}
|
|
|
|
case input.TaprootKeySpendSignMethod:
|
|
if len(signDesc.TapTweak) != sha256.Size {
|
|
return nil, fmt.Errorf("tap tweak must be " +
|
|
"specified for key spend with root " +
|
|
"hash")
|
|
}
|
|
|
|
case input.TaprootScriptSpendSignMethod:
|
|
if len(signDesc.WitnessScript) == 0 {
|
|
return nil, fmt.Errorf("witness script MUST " +
|
|
"be specified for taproot script " +
|
|
"spend method")
|
|
}
|
|
}
|
|
|
|
// If the users provided a double tweak, then we'll need to
|
|
// parse that out now to ensure their input is properly signed.
|
|
var tweakPrivKey *btcec.PrivateKey
|
|
if len(signDesc.DoubleTweak) != 0 {
|
|
tweakPrivKey, _ = btcec.PrivKeyFromBytes(
|
|
signDesc.DoubleTweak,
|
|
)
|
|
}
|
|
|
|
// Finally, with verification and parsing complete, we can
|
|
// construct the final sign descriptor to generate the proper
|
|
// signature for this input.
|
|
signDescs = append(signDescs, &input.SignDescriptor{
|
|
KeyDesc: keychain.KeyDescriptor{
|
|
KeyLocator: keyLoc,
|
|
PubKey: targetPubKey,
|
|
},
|
|
SingleTweak: signDesc.SingleTweak,
|
|
DoubleTweak: tweakPrivKey,
|
|
TapTweak: signDesc.TapTweak,
|
|
WitnessScript: signDesc.WitnessScript,
|
|
SignMethod: signMethod,
|
|
Output: &wire.TxOut{
|
|
Value: signDesc.Output.Value,
|
|
PkScript: signDesc.Output.PkScript,
|
|
},
|
|
HashType: txscript.SigHashType(signDesc.Sighash),
|
|
SigHashes: sigHashCache,
|
|
InputIndex: int(signDesc.InputIndex),
|
|
PrevOutputFetcher: prevOutputFetcher,
|
|
})
|
|
|
|
// Are we trying to sign for a Taproot output? Then we need all
|
|
// previous outputs being declared, otherwise we'd run into a
|
|
// panic later on.
|
|
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
|
|
for idx, txIn := range txToSign.TxIn {
|
|
utxo := prevOutputFetcher.FetchPrevOutput(
|
|
txIn.PreviousOutPoint,
|
|
)
|
|
if utxo == nil {
|
|
return nil, fmt.Errorf("error signing "+
|
|
"taproot output, transaction "+
|
|
"input %d is missing its "+
|
|
"previous outpoint information",
|
|
idx)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we've mapped all the proper sign descriptors, we can
|
|
// request signatures for each of them, passing in the transaction to
|
|
// be signed.
|
|
numSigs := len(in.SignDescs)
|
|
resp := &SignResp{
|
|
RawSigs: make([][]byte, numSigs),
|
|
}
|
|
for i, signDesc := range signDescs {
|
|
sig, err := s.cfg.Signer.SignOutputRaw(&txToSign, signDesc)
|
|
if err != nil {
|
|
log.Errorf("unable to generate sig for input "+
|
|
"#%v: %v", i, err)
|
|
|
|
return nil, err
|
|
}
|
|
|
|
resp.RawSigs[i] = sig.Serialize()
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// ComputeInputScript generates a complete InputIndex for the passed
|
|
// transaction with the signature as defined within the passed SignDescriptor.
|
|
// This method should be capable of generating the proper input script for both
|
|
// regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh
|
|
// output.
|
|
//
|
|
// Note that when using this method to sign inputs belonging to the wallet, the
|
|
// only items of the SignDescriptor that need to be populated are pkScript in
|
|
// the TxOut field, the value in that same field, and finally the input index.
|
|
func (s *Server) ComputeInputScript(ctx context.Context,
|
|
in *SignReq) (*InputScriptResp, error) {
|
|
|
|
switch {
|
|
// If the client doesn't specify a transaction, then there's nothing to
|
|
// sign, so we'll exit early.
|
|
case len(in.RawTxBytes) == 0:
|
|
return nil, fmt.Errorf("a transaction to sign MUST be " +
|
|
"passed in")
|
|
|
|
// If the client doesn't tell us *how* to sign the transaction, then we
|
|
// can't sign anything, so we'll exit early.
|
|
case len(in.SignDescs) == 0:
|
|
return nil, fmt.Errorf("at least one SignDescs MUST be " +
|
|
"passed in")
|
|
}
|
|
|
|
// Now that we know we have an actual transaction to decode, we'll
|
|
// deserialize it into something that we can properly utilize.
|
|
var txToSign wire.MsgTx
|
|
txReader := bytes.NewReader(in.RawTxBytes)
|
|
if err := txToSign.Deserialize(txReader); err != nil {
|
|
return nil, fmt.Errorf("unable to decode tx: %w", err)
|
|
}
|
|
|
|
var (
|
|
sigHashCache = input.NewTxSigHashesV0Only(&txToSign)
|
|
prevOutputFetcher = txscript.NewMultiPrevOutFetcher(nil)
|
|
)
|
|
|
|
// If we're spending one or more SegWit v1 (Taproot) inputs, then we
|
|
// need the full UTXO information available.
|
|
if len(in.PrevOutputs) > 0 {
|
|
if len(in.PrevOutputs) != len(txToSign.TxIn) {
|
|
return nil, fmt.Errorf("provided previous outputs " +
|
|
"doesn't match number of transaction inputs")
|
|
}
|
|
|
|
// Add all previous inputs to our sighash prev out fetcher so we
|
|
// can calculate the sighash correctly.
|
|
for idx, txIn := range txToSign.TxIn {
|
|
prevOutputFetcher.AddPrevOut(
|
|
txIn.PreviousOutPoint, &wire.TxOut{
|
|
Value: in.PrevOutputs[idx].Value,
|
|
PkScript: in.PrevOutputs[idx].PkScript,
|
|
},
|
|
)
|
|
}
|
|
sigHashCache = txscript.NewTxSigHashes(
|
|
&txToSign, prevOutputFetcher,
|
|
)
|
|
}
|
|
|
|
signDescs := make([]*input.SignDescriptor, 0, len(in.SignDescs))
|
|
for _, signDesc := range in.SignDescs {
|
|
// For this method, the only fields that we care about are the
|
|
// hash type, and the information concerning the output as we
|
|
// only know how to provide full witnesses for outputs that we
|
|
// solely control.
|
|
signDescs = append(signDescs, &input.SignDescriptor{
|
|
Output: &wire.TxOut{
|
|
Value: signDesc.Output.Value,
|
|
PkScript: signDesc.Output.PkScript,
|
|
},
|
|
HashType: txscript.SigHashType(signDesc.Sighash),
|
|
SigHashes: sigHashCache,
|
|
PrevOutputFetcher: prevOutputFetcher,
|
|
InputIndex: int(signDesc.InputIndex),
|
|
})
|
|
}
|
|
|
|
// With all of our signDescs assembled, we can now generate a valid
|
|
// input script for each of them, and collate the responses to return
|
|
// back to the caller.
|
|
numWitnesses := len(in.SignDescs)
|
|
resp := &InputScriptResp{
|
|
InputScripts: make([]*InputScript, numWitnesses),
|
|
}
|
|
for i, signDesc := range signDescs {
|
|
inputScript, err := s.cfg.Signer.ComputeInputScript(
|
|
&txToSign, signDesc,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp.InputScripts[i] = &InputScript{
|
|
Witness: inputScript.Witness,
|
|
SigScript: inputScript.SigScript,
|
|
}
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// SignMessage signs a message with the key specified in the key locator. The
|
|
// returned signature is fixed-size LN wire format encoded.
|
|
func (s *Server) SignMessage(_ context.Context,
|
|
in *SignMessageReq) (*SignMessageResp, error) {
|
|
|
|
if in.Msg == nil {
|
|
return nil, fmt.Errorf("a message to sign MUST be passed in")
|
|
}
|
|
if in.KeyLoc == nil {
|
|
return nil, fmt.Errorf("a key locator MUST be passed in")
|
|
}
|
|
if in.SchnorrSig && in.CompactSig {
|
|
return nil, fmt.Errorf("compact format can not be used for " +
|
|
"Schnorr signatures")
|
|
}
|
|
if !in.SchnorrSig && len(in.Tag) > 0 {
|
|
return nil, fmt.Errorf("tag can only be used when the " +
|
|
"Schnorr signature option is set")
|
|
}
|
|
if bytes.HasPrefix(in.Tag, []byte(BIP0340)) {
|
|
return nil, fmt.Errorf("tag cannot have BIP0340 prefix")
|
|
}
|
|
if bytes.HasPrefix(in.Tag, chainhash.TagTapSighash) {
|
|
return nil, fmt.Errorf("tag cannot be TapSighash")
|
|
}
|
|
if in.DoubleHash && len(in.Tag) > 0 {
|
|
return nil, fmt.Errorf("double hash and tag can't be set at " +
|
|
"the same time")
|
|
}
|
|
|
|
// Describe the private key we'll be using for signing.
|
|
keyLocator := keychain.KeyLocator{
|
|
Family: keychain.KeyFamily(in.KeyLoc.KeyFamily),
|
|
Index: uint32(in.KeyLoc.KeyIndex),
|
|
}
|
|
|
|
// Use the schnorr signature algorithm to sign the message.
|
|
if in.SchnorrSig {
|
|
sig, err := s.cfg.KeyRing.SignMessageSchnorr(
|
|
keyLocator, in.Msg, in.DoubleHash,
|
|
in.SchnorrSigTapTweak, in.Tag,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't sign the hash: %w", err)
|
|
}
|
|
|
|
sigParsed, err := schnorr.ParseSignature(sig.Serialize())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't parse Schnorr "+
|
|
"signature: %v", err)
|
|
}
|
|
|
|
return &SignMessageResp{
|
|
Signature: sigParsed.Serialize(),
|
|
}, nil
|
|
}
|
|
|
|
// To allow a watch-only wallet to forward the SignMessageCompact to an
|
|
// endpoint that doesn't add the message prefix, we allow this RPC to
|
|
// also return the compact signature format instead of adding a flag to
|
|
// the lnrpc.SignMessage call that removes the message prefix.
|
|
if in.CompactSig {
|
|
sigBytes, err := s.cfg.KeyRing.SignMessageCompact(
|
|
keyLocator, in.Msg, in.DoubleHash,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't sign the hash: %w", err)
|
|
}
|
|
|
|
return &SignMessageResp{
|
|
Signature: sigBytes,
|
|
}, nil
|
|
}
|
|
|
|
// Create the raw ECDSA signature first and convert it to the final wire
|
|
// format after.
|
|
sig, err := s.cfg.KeyRing.SignMessage(
|
|
keyLocator, in.Msg, in.DoubleHash,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't sign the hash: %w", err)
|
|
}
|
|
wireSig, err := lnwire.NewSigFromSignature(sig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't convert to wire format: %w", err)
|
|
}
|
|
return &SignMessageResp{
|
|
Signature: wireSig.ToSignatureBytes(),
|
|
}, nil
|
|
}
|
|
|
|
// VerifyMessage verifies a signature over a message using the public key
|
|
// provided. The signature must be fixed-size LN wire format encoded.
|
|
func (s *Server) VerifyMessage(_ context.Context,
|
|
in *VerifyMessageReq) (*VerifyMessageResp, error) {
|
|
|
|
if in.Msg == nil {
|
|
return nil, fmt.Errorf("a message to verify MUST be passed in")
|
|
}
|
|
if in.Signature == nil {
|
|
return nil, fmt.Errorf("a signature to verify MUST be passed " +
|
|
"in")
|
|
}
|
|
if in.Pubkey == nil {
|
|
return nil, fmt.Errorf("a pubkey to verify MUST be passed in")
|
|
}
|
|
if !in.IsSchnorrSig && len(in.Tag) > 0 {
|
|
return nil, fmt.Errorf("tag can only be used when the " +
|
|
"Schnorr signature option is set")
|
|
}
|
|
|
|
// We allow for Schnorr signatures to be verified.
|
|
if in.IsSchnorrSig {
|
|
// We expect the public key to be in the BIP-340 32-byte format
|
|
// for Schnorr signatures.
|
|
pubkey, err := schnorr.ParsePubKey(in.Pubkey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to parse pubkey: %w",
|
|
err)
|
|
}
|
|
|
|
sigParsed, err := schnorr.ParseSignature(in.Signature)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't parse Schnorr "+
|
|
"signature: %w", err)
|
|
}
|
|
|
|
var digest []byte
|
|
if len(in.Tag) == 0 {
|
|
digest = chainhash.HashB(in.Msg)
|
|
} else {
|
|
taggedHash := chainhash.TaggedHash(in.Tag, in.Msg)
|
|
digest = taggedHash[:]
|
|
}
|
|
valid := sigParsed.Verify(digest, pubkey)
|
|
|
|
return &VerifyMessageResp{
|
|
Valid: valid,
|
|
}, nil
|
|
}
|
|
|
|
pubkey, err := btcec.ParsePubKey(in.Pubkey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to parse pubkey: %w", err)
|
|
}
|
|
|
|
// The signature must be fixed-size LN wire format encoded.
|
|
wireSig, err := lnwire.NewSigFromECDSARawSignature(in.Signature)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode signature: %w", err)
|
|
}
|
|
sig, err := wireSig.ToSignature()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert from wire format: %w",
|
|
err)
|
|
}
|
|
|
|
// The signature is over the sha256 hash of the message.
|
|
digest := chainhash.HashB(in.Msg)
|
|
valid := sig.Verify(digest, pubkey)
|
|
return &VerifyMessageResp{
|
|
Valid: valid,
|
|
}, nil
|
|
}
|
|
|
|
// DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key
|
|
// derivation between the ephemeral public key in the request and the node's
|
|
// key specified in the key_desc parameter. Either a key locator or a raw public
|
|
// key is expected in the key_desc, if neither is supplied, defaults to the
|
|
// node's identity private key. The old key_loc parameter in the request
|
|
// shouldn't be used anymore.
|
|
// The resulting shared public key is serialized in the compressed format and
|
|
// hashed with sha256, resulting in the final key length of 256bit.
|
|
func (s *Server) DeriveSharedKey(_ context.Context, in *SharedKeyRequest) (
|
|
*SharedKeyResponse, error) {
|
|
|
|
// Check that EphemeralPubkey is valid.
|
|
ephemeralPubkey, err := parseRawKeyBytes(in.EphemeralPubkey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error in ephemeral pubkey: %w", err)
|
|
}
|
|
if ephemeralPubkey == nil {
|
|
return nil, fmt.Errorf("must provide ephemeral pubkey")
|
|
}
|
|
|
|
// Check for backward compatibility. The caller either specifies the old
|
|
// key_loc field, or the new key_desc field, but not both.
|
|
if in.KeyDesc != nil && in.KeyLoc != nil {
|
|
return nil, fmt.Errorf("use either key_desc or key_loc")
|
|
}
|
|
|
|
// When key_desc is used, the key_desc.key_loc is expected as the caller
|
|
// needs to specify the KeyFamily.
|
|
if in.KeyDesc != nil && in.KeyDesc.KeyLoc == nil {
|
|
return nil, fmt.Errorf("when setting key_desc the field " +
|
|
"key_desc.key_loc must also be set")
|
|
}
|
|
|
|
// We extract two params, rawKeyBytes and keyLoc. Notice their initial
|
|
// values will be overwritten if not using the deprecated RPC param.
|
|
var rawKeyBytes []byte
|
|
keyLoc := in.KeyLoc
|
|
if in.KeyDesc != nil {
|
|
keyLoc = in.KeyDesc.GetKeyLoc()
|
|
rawKeyBytes = in.KeyDesc.GetRawKeyBytes()
|
|
}
|
|
|
|
// When no keyLoc is supplied, defaults to the node's identity private
|
|
// key.
|
|
if keyLoc == nil {
|
|
keyLoc = &KeyLocator{
|
|
KeyFamily: int32(keychain.KeyFamilyNodeKey),
|
|
KeyIndex: 0,
|
|
}
|
|
}
|
|
|
|
// Check the caller is using either the key index or the raw public key
|
|
// to perform the ECDH, we can't have both.
|
|
if rawKeyBytes != nil && keyLoc.KeyIndex != 0 {
|
|
return nil, fmt.Errorf("use either raw_key_bytes or key_index")
|
|
}
|
|
|
|
// Check the raw public key is valid. Notice that if the rawKeyBytes is
|
|
// empty, the parseRawKeyBytes won't return an error, a nil
|
|
// *btcec.PublicKey is returned instead.
|
|
pk, err := parseRawKeyBytes(rawKeyBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error in raw pubkey: %w", err)
|
|
}
|
|
|
|
// Create a key descriptor. When the KeyIndex is not specified, it uses
|
|
// the empty value 0, and when the raw public key is not specified, the
|
|
// pk is nil.
|
|
keyDescriptor := keychain.KeyDescriptor{
|
|
KeyLocator: keychain.KeyLocator{
|
|
Family: keychain.KeyFamily(keyLoc.KeyFamily),
|
|
Index: uint32(keyLoc.KeyIndex),
|
|
},
|
|
PubKey: pk,
|
|
}
|
|
|
|
// Derive the shared key using ECDH and hashing the serialized
|
|
// compressed shared point.
|
|
sharedKeyHash, err := s.cfg.KeyRing.ECDH(keyDescriptor, ephemeralPubkey)
|
|
if err != nil {
|
|
err := fmt.Errorf("unable to derive shared key: %w", err)
|
|
log.Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
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) {
|
|
|
|
// Check the now mandatory version first. We made the version mandatory,
|
|
// so we don't get unexpected/undefined behavior for old clients that
|
|
// don't specify the version. Since this API is still declared to be
|
|
// experimental this should be the approach that leads to the least
|
|
// amount of unexpected behavior.
|
|
version, err := UnmarshalMuSig2Version(in.Version)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing version: %w", err)
|
|
}
|
|
|
|
// Parse the public keys of all signing participants. This must also
|
|
// include our own, local key.
|
|
allSignerPubKeys, err := input.MuSig2ParsePubKeys(
|
|
version, in.AllSignerPubkeys,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing all signer public "+
|
|
"keys: %w", 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: %w",
|
|
err)
|
|
}
|
|
|
|
// Combine the keys now without creating a session in memory.
|
|
combinedKey, err := input.MuSig2CombineKeys(
|
|
version, allSignerPubKeys, true, tweaks,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error combining keys: %w", err)
|
|
}
|
|
|
|
var internalKeyBytes []byte
|
|
if combinedKey.PreTweakedKey != nil {
|
|
internalKeyBytes = schnorr.SerializePubKey(
|
|
combinedKey.PreTweakedKey,
|
|
)
|
|
}
|
|
|
|
return &MuSig2CombineKeysResponse{
|
|
CombinedKey: schnorr.SerializePubKey(
|
|
combinedKey.FinalKey,
|
|
),
|
|
TaprootInternalKey: internalKeyBytes,
|
|
Version: in.Version,
|
|
}, nil
|
|
}
|
|
|
|
// secNonceToPubNonce takes our two secret nonces, and produces their two
|
|
// corresponding EC points, serialized in compressed format.
|
|
//
|
|
// NOTE: This was copied from btcsuite/btcec/musig2/nonces.go.
|
|
func secNonceToPubNonce(secNonce [musig2.SecNonceSize]byte,
|
|
) [musig2.PubNonceSize]byte {
|
|
|
|
var k1Mod, k2Mod btcec.ModNScalar
|
|
k1Mod.SetByteSlice(secNonce[:btcec.PrivKeyBytesLen])
|
|
k2Mod.SetByteSlice(secNonce[btcec.PrivKeyBytesLen:])
|
|
|
|
var r1, r2 btcec.JacobianPoint
|
|
btcec.ScalarBaseMultNonConst(&k1Mod, &r1)
|
|
btcec.ScalarBaseMultNonConst(&k2Mod, &r2)
|
|
|
|
// Next, we'll convert the key in jacobian format to a normal public
|
|
// key expressed in affine coordinates.
|
|
r1.ToAffine()
|
|
r2.ToAffine()
|
|
r1Pub := btcec.NewPublicKey(&r1.X, &r1.Y)
|
|
r2Pub := btcec.NewPublicKey(&r2.X, &r2.Y)
|
|
|
|
var pubNonce [musig2.PubNonceSize]byte
|
|
|
|
// The public nonces are serialized as: R1 || R2, where both keys are
|
|
// serialized in compressed format.
|
|
copy(pubNonce[:], r1Pub.SerializeCompressed())
|
|
copy(
|
|
pubNonce[btcec.PubKeyBytesLenCompressed:],
|
|
r2Pub.SerializeCompressed(),
|
|
)
|
|
|
|
return pubNonce
|
|
}
|
|
|
|
// 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) {
|
|
|
|
// Check the now mandatory version first. We made the version mandatory,
|
|
// so we don't get unexpected/undefined behavior for old clients that
|
|
// don't specify the version. Since this API is still declared to be
|
|
// experimental this should be the approach that leads to the least
|
|
// amount of unexpected behavior.
|
|
version, err := UnmarshalMuSig2Version(in.Version)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing version: %w", err)
|
|
}
|
|
|
|
// 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, err := input.MuSig2ParsePubKeys(
|
|
version, in.AllSignerPubkeys,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing all signer public "+
|
|
"keys: %w", err)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
var localNonces *musig2.Nonces
|
|
|
|
// If the pre generated local nonces were specified, then check to make
|
|
// sure they're the correct size and format.
|
|
nonceLen := len(in.PregeneratedLocalNonce)
|
|
switch {
|
|
case nonceLen != 0 && nonceLen != musig2.SecNonceSize:
|
|
return nil, fmt.Errorf("local nonces must be %v bytes, "+
|
|
"instead was %v", musig2.SecNonceSize, nonceLen)
|
|
|
|
case nonceLen == musig2.SecNonceSize:
|
|
var secNonce [musig2.SecNonceSize]byte
|
|
copy(secNonce[:], in.PregeneratedLocalNonce)
|
|
|
|
localNonces = &musig2.Nonces{
|
|
SecNonce: secNonce,
|
|
PubNonce: secNonceToPubNonce(secNonce),
|
|
}
|
|
}
|
|
|
|
// 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: %w", 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: %w",
|
|
err)
|
|
}
|
|
|
|
// Register the session with the internal wallet/signer now.
|
|
session, err := s.cfg.Signer.MuSig2CreateSession(
|
|
version, keyLoc, allSignerPubKeys, tweaks, otherSignerNonces,
|
|
localNonces,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error registering session: %w", 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,
|
|
Version: in.Version,
|
|
}, 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: %w", 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: %w", err)
|
|
}
|
|
|
|
// Register the nonces now.
|
|
haveAllNonces, err := s.cfg.Signer.MuSig2RegisterNonces(
|
|
sessionID, otherSignerNonces,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error registering nonces: %w", 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: %w", 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: %w", err)
|
|
}
|
|
|
|
serializedPartialSig, err := input.SerializePartialSignature(partialSig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error serializing sig: %w", 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: %w", 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: %w",
|
|
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: %w", err)
|
|
}
|
|
|
|
resp := &MuSig2CombineSigResponse{
|
|
HaveAllSignatures: haveAllSigs,
|
|
}
|
|
|
|
if haveAllSigs {
|
|
resp.FinalSignature = finalSig.Serialize()
|
|
}
|
|
|
|
return resp, err
|
|
}
|
|
|
|
// MuSig2Cleanup removes a session from memory to free up resources.
|
|
func (s *Server) MuSig2Cleanup(_ context.Context,
|
|
in *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error) {
|
|
|
|
// Check session ID length.
|
|
sessionID, err := parseMuSig2SessionID(in.SessionId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing session ID: %w", err)
|
|
}
|
|
|
|
err = s.cfg.Signer.MuSig2Cleanup(sessionID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error cleaning up session: %w", err)
|
|
}
|
|
|
|
return &MuSig2CleanupResponse{}, 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.
|
|
func parseRawKeyBytes(rawKeyBytes []byte) (*btcec.PublicKey, error) {
|
|
switch {
|
|
case len(rawKeyBytes) == 33:
|
|
// If a proper raw key was provided, then we'll attempt
|
|
// to decode and parse it.
|
|
return btcec.ParsePubKey(rawKeyBytes)
|
|
|
|
case len(rawKeyBytes) == 0:
|
|
// No key is provided, return nil.
|
|
return nil, nil
|
|
|
|
default:
|
|
// If the user provided a raw key, but it's of the
|
|
// wrong length, then we'll return with an error.
|
|
return nil, fmt.Errorf("pubkey must be " +
|
|
"serialized in compressed format if " +
|
|
"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
|
|
}
|
|
|
|
// UnmarshalSignMethod parses the RPC sign method into the native counterpart.
|
|
func UnmarshalSignMethod(rpcSignMethod SignMethod) (input.SignMethod, error) {
|
|
switch rpcSignMethod {
|
|
case SignMethod_SIGN_METHOD_WITNESS_V0:
|
|
return input.WitnessV0SignMethod, nil
|
|
|
|
case SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086:
|
|
return input.TaprootKeySpendBIP0086SignMethod, nil
|
|
|
|
case SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND:
|
|
return input.TaprootKeySpendSignMethod, nil
|
|
|
|
case SignMethod_SIGN_METHOD_TAPROOT_SCRIPT_SPEND:
|
|
return input.TaprootScriptSpendSignMethod, nil
|
|
|
|
default:
|
|
return 0, fmt.Errorf("unknown RPC sign method <%d>",
|
|
rpcSignMethod)
|
|
}
|
|
}
|