You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
loop/lndclient/signer_client.go

255 lines
7.2 KiB
Go

package lndclient
import (
"context"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"google.golang.org/grpc"
)
// SignerClient exposes sign functionality.
type SignerClient interface {
SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([][]byte, error)
// ComputeInputScript generates the proper input script for P2WPKH
// output and NP2WPKH outputs. This method only requires that the
// `Output`, `HashType`, `SigHashes` and `InputIndex` fields are
// populated within the sign descriptors.
ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([]*input.Script, error)
// SignMessage signs a message with the key specified in the key
// locator. The returned signature is fixed-size LN wire format encoded.
SignMessage(ctx context.Context, msg []byte,
locator keychain.KeyLocator) ([]byte, error)
// VerifyMessage verifies a signature over a message using the public
// key provided. The signature must be fixed-size LN wire format
// encoded.
VerifyMessage(ctx context.Context, msg, sig []byte, pubkey [33]byte) (
bool, error)
// DeriveSharedKey returns a shared secret key by performing
// Diffie-Hellman key derivation between the ephemeral public key and
// the key specified by the key locator (or the node's identity private
// key if no key locator is specified):
//
// P_shared = privKeyNode * ephemeralPubkey
//
// The resulting shared public key is serialized in the compressed
// format and hashed with SHA256, resulting in a final key length of 256
// bits.
DeriveSharedKey(ctx context.Context, ephemeralPubKey *btcec.PublicKey,
keyLocator *keychain.KeyLocator) ([32]byte, error)
}
type signerClient struct {
client signrpc.SignerClient
signerMac serializedMacaroon
}
func newSignerClient(conn *grpc.ClientConn,
signerMac serializedMacaroon) *signerClient {
return &signerClient{
client: signrpc.NewSignerClient(conn),
signerMac: signerMac,
}
}
func marshallSignDescriptors(signDescriptors []*input.SignDescriptor,
) []*signrpc.SignDescriptor {
rpcSignDescs := make([]*signrpc.SignDescriptor, len(signDescriptors))
for i, signDesc := range signDescriptors {
var keyBytes []byte
var keyLocator *signrpc.KeyLocator
if signDesc.KeyDesc.PubKey != nil {
keyBytes = signDesc.KeyDesc.PubKey.SerializeCompressed()
} else {
keyLocator = &signrpc.KeyLocator{
KeyFamily: int32(
signDesc.KeyDesc.KeyLocator.Family,
),
KeyIndex: int32(
signDesc.KeyDesc.KeyLocator.Index,
),
}
}
var doubleTweak []byte
if signDesc.DoubleTweak != nil {
doubleTweak = signDesc.DoubleTweak.Serialize()
}
rpcSignDescs[i] = &signrpc.SignDescriptor{
WitnessScript: signDesc.WitnessScript,
Output: &signrpc.TxOut{
PkScript: signDesc.Output.PkScript,
Value: signDesc.Output.Value,
},
Sighash: uint32(signDesc.HashType),
InputIndex: int32(signDesc.InputIndex),
KeyDesc: &signrpc.KeyDescriptor{
RawKeyBytes: keyBytes,
KeyLoc: keyLocator,
},
SingleTweak: signDesc.SingleTweak,
DoubleTweak: doubleTweak,
}
}
return rpcSignDescs
}
func (s *signerClient) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([][]byte, error) {
txRaw, err := swap.EncodeTx(tx)
if err != nil {
return nil, err
}
rpcSignDescs := marshallSignDescriptors(signDescriptors)
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.SignOutputRaw(rpcCtx,
&signrpc.SignReq{
RawTxBytes: txRaw,
SignDescs: rpcSignDescs,
},
)
if err != nil {
return nil, err
}
return resp.RawSigs, nil
}
// ComputeInputScript generates the proper input script for P2WPKH output and
// NP2WPKH outputs. This method only requires that the `Output`, `HashType`,
// `SigHashes` and `InputIndex` fields are populated within the sign
// descriptors.
func (s *signerClient) ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([]*input.Script, error) {
txRaw, err := swap.EncodeTx(tx)
if err != nil {
return nil, err
}
rpcSignDescs := marshallSignDescriptors(signDescriptors)
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.ComputeInputScript(
rpcCtx, &signrpc.SignReq{
RawTxBytes: txRaw,
SignDescs: rpcSignDescs,
},
)
if err != nil {
return nil, err
}
inputScripts := make([]*input.Script, 0, len(resp.InputScripts))
for _, inputScript := range resp.InputScripts {
inputScripts = append(inputScripts, &input.Script{
SigScript: inputScript.SigScript,
Witness: inputScript.Witness,
})
}
return inputScripts, 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 *signerClient) SignMessage(ctx context.Context, msg []byte,
locator keychain.KeyLocator) ([]byte, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcIn := &signrpc.SignMessageReq{
Msg: msg,
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(locator.Family),
KeyIndex: int32(locator.Index),
},
}
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.SignMessage(rpcCtx, rpcIn)
if err != nil {
return nil, err
}
return resp.Signature, 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 *signerClient) VerifyMessage(ctx context.Context, msg, sig []byte,
pubkey [33]byte) (bool, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcIn := &signrpc.VerifyMessageReq{
Msg: msg,
Signature: sig,
Pubkey: pubkey[:],
}
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.VerifyMessage(rpcCtx, rpcIn)
if err != nil {
return false, err
}
return resp.Valid, nil
}
// DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key
// derivation between the ephemeral public key and the key specified by the key
// locator (or the node's identity private key if no key locator is specified):
//
// P_shared = privKeyNode * ephemeralPubkey
//
// The resulting shared public key is serialized in the compressed format and
// hashed with SHA256, resulting in a final key length of 256 bits.
func (s *signerClient) DeriveSharedKey(ctx context.Context,
ephemeralPubKey *btcec.PublicKey,
keyLocator *keychain.KeyLocator) ([32]byte, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcIn := &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubKey.SerializeCompressed(),
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyLocator.Family),
KeyIndex: int32(keyLocator.Index),
},
}
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.DeriveSharedKey(rpcCtx, rpcIn)
if err != nil {
return [32]byte{}, err
}
var sharedKey [32]byte
copy(sharedKey[:], resp.SharedKey)
return sharedKey, nil
}