mirror of
https://github.com/lightninglabs/loop
synced 2024-11-17 21:25:56 +00:00
255 lines
7.2 KiB
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
|
|
}
|