From dbbf84a1b127ba3607c41508ff057d1dc7846307 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 16 Nov 2023 14:39:48 +0200 Subject: [PATCH] multi: use MessageSignerRing where needed In this commit, we pass the MessageSignerRing around in places where Schnorr signing will be needed to sign Gossip 1.75 messages. --- discovery/gossiper.go | 10 ++--- keychain/derivation.go | 6 +++ keychain/signer.go | 33 +++++++++++++++++ lnrpc/invoicesrpc/addinvoice.go | 2 +- lntest/mock/signer.go | 65 +++++++++++++++++++++++++++++++++ netann/chan_status_manager.go | 3 +- netann/channel_update.go | 6 +-- netann/channel_update_test.go | 4 +- netann/node_signer.go | 49 ++++++++++++++++++++++--- rpcserver.go | 2 +- 10 files changed, 161 insertions(+), 19 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 4a7929614..9d97ffabb 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -262,14 +262,14 @@ type Config struct { // use to determine which messages need to be resent for a given peer. MessageStore GossipMessageStore - // AnnSigner is an instance of the MessageSigner interface which will - // be used to manually sign any outgoing channel updates. The signer - // implementation should be backed by the public key of the backing - // Lightning node. + // AnnSigner is an instance of the MessageSignerRing interface which + // will be used to manually sign any outgoing channel updates. The + // signer implementation should be backed by the public key of the + // backing Lightning node. // // TODO(roasbeef): extract ann crafting + sign from fundingMgr into // here? - AnnSigner lnwallet.MessageSigner + AnnSigner keychain.MessageSignerRing // ScidCloser is an instance of ClosedChannelTracker that helps the // gossiper cut down on spam channel announcements for already closed diff --git a/keychain/derivation.go b/keychain/derivation.go index 2b1c43444..050058ca6 100644 --- a/keychain/derivation.go +++ b/keychain/derivation.go @@ -262,6 +262,12 @@ type SingleKeyMessageSigner interface { // hashing it first, with the wrapped private key and returns the // signature in the compact, public key recoverable format. SignMessageCompact(message []byte, doubleHash bool) ([]byte, error) + + // SignMessageSchnorr signs the given message, single or double SHA256 + // hashing it first, with the private key described in the key locator + // and the optional Taproot tweak applied to the private key. + SignMessageSchnorr(keyLoc KeyLocator, msg []byte, doubleHash bool, + taprootTweak, tag []byte) (*schnorr.Signature, error) } // ECDHRing is an interface that abstracts away basic low-level ECDH shared key diff --git a/keychain/signer.go b/keychain/signer.go index 9605e72ec..6fd856f7f 100644 --- a/keychain/signer.go +++ b/keychain/signer.go @@ -3,7 +3,9 @@ package keychain 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/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" ) func NewPubKeyMessageSigner(pubKey *btcec.PublicKey, keyLoc KeyLocator, @@ -42,6 +44,14 @@ func (p *PubKeyMessageSigner) SignMessageCompact(msg []byte, return p.digestSigner.SignMessageCompact(p.keyLoc, msg, doubleHash) } +func (p *PubKeyMessageSigner) SignMessageSchnorr(keyLoc KeyLocator, msg []byte, + doubleHash bool, taprootTweak, tag []byte) (*schnorr.Signature, error) { + + return p.digestSigner.SignMessageSchnorr( + keyLoc, msg, doubleHash, taprootTweak, tag, + ) +} + func NewPrivKeyMessageSigner(privKey *btcec.PrivateKey, keyLoc KeyLocator) *PrivKeyMessageSigner { @@ -88,5 +98,28 @@ func (p *PrivKeyMessageSigner) SignMessageCompact(msg []byte, return ecdsa.SignCompact(p.privKey, digest, true) } +func (p *PrivKeyMessageSigner) SignMessageSchnorr(_ KeyLocator, msg []byte, + doubleHash bool, taprootTweak, tag []byte) (*schnorr.Signature, error) { + + // If a tag was provided, we need to take the tagged hash of the input. + var digest []byte + switch { + case len(tag) > 0: + taggedHash := chainhash.TaggedHash(tag, msg) + digest = taggedHash[:] + case doubleHash: + digest = chainhash.DoubleHashB(msg) + default: + digest = chainhash.HashB(msg) + } + + privKey := p.privKey + if len(taprootTweak) > 0 { + privKey = txscript.TweakTaprootPrivKey(*privKey, taprootTweak) + } + + return schnorr.Sign(privKey, digest) +} + var _ SingleKeyMessageSigner = (*PubKeyMessageSigner)(nil) var _ SingleKeyMessageSigner = (*PrivKeyMessageSigner)(nil) diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index a5d199b8e..fbd56d7c7 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -571,7 +571,7 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, // key is used to sign the invoice so that the sender // can derive the true pub key of the recipient. if !blind { - return cfg.NodeSigner.SignMessageCompact( + return cfg.NodeSigner.SignMessageCompactNoKeyLoc( //nolint:lll msg, false, ) } diff --git a/lntest/mock/signer.go b/lntest/mock/signer.go index 1d30204ea..a1853f353 100644 --- a/lntest/mock/signer.go +++ b/lntest/mock/signer.go @@ -205,3 +205,68 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator, } return ecdsa.Sign(s.Privkey, digest), nil } + +// SignMessageCompact signs the given message, single or double SHA256 hashing +// it first, with the private key described in the key locator and returns the +// signature in the compact, public key recoverable format. +// +// NOTE: This is part of the keychain.MessageSignerRing interface. +func (s *SingleSigner) SignMessageCompact(keyLoc keychain.KeyLocator, + msg []byte, doubleHash bool) ([]byte, error) { + + mockKeyLoc := s.KeyLoc + if s.KeyLoc.IsEmpty() { + mockKeyLoc = idKeyLoc + } + + if keyLoc != mockKeyLoc { + return nil, fmt.Errorf("unknown public key") + } + + var digest []byte + if doubleHash { + digest = chainhash.DoubleHashB(msg) + } else { + digest = chainhash.HashB(msg) + } + + return ecdsa.SignCompact(s.Privkey, digest, true) +} + +// SignMessageSchnorr signs the given message, single or double SHA256 hashing +// it first, with the private key described in the key locator and the optional +// Taproot tweak applied to the private key. +// +// NOTE: this is part of the keychain.MessageSignerRing interface. +func (s *SingleSigner) SignMessageSchnorr(keyLoc keychain.KeyLocator, + msg []byte, doubleHash bool, taprootTweak, tag []byte) ( + *schnorr.Signature, error) { + + mockKeyLoc := s.KeyLoc + if s.KeyLoc.IsEmpty() { + mockKeyLoc = idKeyLoc + } + + if keyLoc != mockKeyLoc { + return nil, fmt.Errorf("unknown public key") + } + + privKey := s.Privkey + if len(taprootTweak) > 0 { + privKey = txscript.TweakTaprootPrivKey(*privKey, taprootTweak) + } + + // If a tag was provided, we need to take the tagged hash of the input. + var digest []byte + switch { + case len(tag) > 0: + taggedHash := chainhash.TaggedHash(tag, msg) + digest = taggedHash[:] + case doubleHash: + digest = chainhash.DoubleHashB(msg) + default: + digest = chainhash.HashB(msg) + } + + return schnorr.Sign(privKey, digest) +} diff --git a/netann/chan_status_manager.go b/netann/chan_status_manager.go index d927a8100..d829b53dd 100644 --- a/netann/chan_status_manager.go +++ b/netann/chan_status_manager.go @@ -10,7 +10,6 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) @@ -50,7 +49,7 @@ type ChanStatusConfig struct { OurKeyLoc keychain.KeyLocator // MessageSigner signs messages that validate under OurPubKey. - MessageSigner lnwallet.MessageSigner + MessageSigner keychain.MessageSignerRing // BestBlockView gives access to the current best block. BestBlockView chainntnfs.BestBlockView diff --git a/netann/channel_update.go b/netann/channel_update.go index 902b8a176..2085a8c43 100644 --- a/netann/channel_update.go +++ b/netann/channel_update.go @@ -9,7 +9,6 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) @@ -57,8 +56,9 @@ func ChanUpdSetTimestamp(update *lnwire.ChannelUpdate1) { // monotonically increase from the prior. // // NOTE: This method modifies the given update. -func SignChannelUpdate(signer lnwallet.MessageSigner, keyLoc keychain.KeyLocator, - update *lnwire.ChannelUpdate1, mods ...ChannelUpdateModifier) error { +func SignChannelUpdate(signer keychain.MessageSignerRing, + keyLoc keychain.KeyLocator, update *lnwire.ChannelUpdate1, + mods ...ChannelUpdateModifier) error { // Apply the requested changes to the channel update. for _, modifier := range mods { diff --git a/netann/channel_update_test.go b/netann/channel_update_test.go index 689b48cab..1bb313823 100644 --- a/netann/channel_update_test.go +++ b/netann/channel_update_test.go @@ -14,6 +14,8 @@ import ( ) type mockSigner struct { + keychain.MessageSignerRing + err error } @@ -43,7 +45,7 @@ type updateDisableTest struct { startEnabled bool disable bool startTime time.Time - signer lnwallet.MessageSigner + signer keychain.MessageSignerRing expErr error } diff --git a/netann/node_signer.go b/netann/node_signer.go index 4cb8cea01..aeb95882f 100644 --- a/netann/node_signer.go +++ b/netann/node_signer.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lnwallet" ) // NodeSigner is an implementation of the MessageSigner interface backed by the @@ -43,15 +43,52 @@ func (n *NodeSigner) SignMessage(keyLoc keychain.KeyLocator, return sig, nil } -// SignMessageCompact signs a single or double sha256 digest of the msg +// SignMessageCompactNoKeyLoc signs a single or double sha256 digest of the msg // parameter under the resident node's private key. The returned signature is a -// pubkey-recoverable signature. -func (n *NodeSigner) SignMessageCompact(msg []byte, doubleHash bool) ([]byte, - error) { +// pubkey-recoverable signature. No key locator is required for this since the +// NodeSigner already has the key to sign with. +func (n *NodeSigner) SignMessageCompactNoKeyLoc(msg []byte, doubleHash bool) ( + []byte, error) { return n.keySigner.SignMessageCompact(msg, doubleHash) } +// SignMessageCompact signs the given message, single or double SHA256 hashing +// it first, with the private key described in the key locator and returns the +// signature in the compact, public key recoverable format. +// +// NOTE: this is part of the keychain.MessageSignerRing interface. +func (n *NodeSigner) SignMessageCompact(keyLoc keychain.KeyLocator, msg []byte, + doubleHash bool) ([]byte, error) { + + // If this isn't our identity public key, then we'll exit early with an + // error as we can't sign with this key. + if keyLoc != n.keySigner.KeyLocator() { + return nil, fmt.Errorf("unknown public key locator") + } + + return n.SignMessageCompactNoKeyLoc(msg, doubleHash) +} + +// SignMessageSchnorr signs the given message, single or double SHA256 hashing +// it first, with the private key described in the key locator and the optional +// Taproot tweak applied to the private key. +// +// NOTE: this is part of the keychain.MessageSignerRing interface. +func (n *NodeSigner) SignMessageSchnorr(keyLoc keychain.KeyLocator, msg []byte, + doubleHash bool, taprootTweak, tag []byte) (*schnorr.Signature, error) { + + // If this isn't our identity public key, then we'll exit early with an + // error as we can't sign with this key. + if keyLoc != n.keySigner.KeyLocator() { + return nil, fmt.Errorf("unknown public key locator") + } + + return n.keySigner.SignMessageSchnorr( + keyLoc, msg, doubleHash, taprootTweak, tag, + ) +} + // A compile time check to ensure that NodeSigner implements the MessageSigner // interface. -var _ lnwallet.MessageSigner = (*NodeSigner)(nil) +var _ keychain.MessageSignerRing = (*NodeSigner)(nil) diff --git a/rpcserver.go b/rpcserver.go index 2396db13e..dfa6de53c 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1671,7 +1671,7 @@ func (r *rpcServer) SignMessage(_ context.Context, } in.Msg = append(signedMsgPrefix, in.Msg...) - sigBytes, err := r.server.nodeSigner.SignMessageCompact( + sigBytes, err := r.server.nodeSigner.SignMessageCompactNoKeyLoc( in.Msg, !in.SingleHash, ) if err != nil {