multi: refactor SignDigestCompact into SignMessageCompact

To make it possible to use a remote lnrpc server as a signer for our
wallet, we need to change our main interface to sign the message instead
of the message's digest. Otherwise we'd need to alter the
lnrpc.SignMessage RPC to accept a digest instead of only the message
which has security implications.
This commit is contained in:
Oliver Gugger
2021-09-23 16:54:27 +02:00
parent 02757f6735
commit 8b7c88537c
17 changed files with 2407 additions and 2373 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/zpay32" "github.com/lightningnetwork/lnd/zpay32"
) )
@@ -31,9 +32,11 @@ func Fuzz_encode(data []byte) int {
// Then, initialize the testMessageSigner so we can encode out // Then, initialize the testMessageSigner so we can encode out
// invoices with this private key. // invoices with this private key.
testMessageSigner := zpay32.MessageSigner{ testMessageSigner := zpay32.MessageSigner{
SignCompact: func(hash []byte) ([]byte, error) { SignCompact: func(msg []byte) ([]byte, error) {
sig, err := btcec.SignCompact(btcec.S256(), hash := chainhash.HashB(msg)
testPrivKey, hash, true) sig, err := btcec.SignCompact(
btcec.S256(), testPrivKey, hash, true,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't sign the "+ return nil, fmt.Errorf("can't sign the "+
"message: %v", err) "message: %v", err)

View File

@@ -14,6 +14,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/clock"
@@ -80,8 +81,11 @@ var (
testNetParams = &chaincfg.MainNetParams testNetParams = &chaincfg.MainNetParams
testMessageSigner = zpay32.MessageSigner{ testMessageSigner = zpay32.MessageSigner{
SignCompact: func(hash []byte) ([]byte, error) { SignCompact: func(msg []byte) ([]byte, error) {
sig, err := btcec.SignCompact(btcec.S256(), testPrivKey, hash, true) hash := chainhash.HashB(msg)
sig, err := btcec.SignCompact(
btcec.S256(), testPrivKey, hash, true,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't sign the message: %v", err) return nil, fmt.Errorf("can't sign the message: %v", err)
} }

View File

@@ -394,7 +394,7 @@ func (b *BtcWalletKeyRing) ECDH(keyDesc KeyDescriptor,
// SignMessage signs the given message, single or double SHA256 hashing it // SignMessage signs the given message, single or double SHA256 hashing it
// first, with the private key described in the key descriptor. // first, with the private key described in the key descriptor.
// //
// NOTE: This is part of the keychain.DigestSignerRing interface. // NOTE: This is part of the keychain.MessageSignerRing interface.
func (b *BtcWalletKeyRing) SignMessage(keyDesc KeyDescriptor, func (b *BtcWalletKeyRing) SignMessage(keyDesc KeyDescriptor,
msg []byte, doubleHash bool) (*btcec.Signature, error) { msg []byte, doubleHash bool) (*btcec.Signature, error) {
@@ -412,17 +412,24 @@ func (b *BtcWalletKeyRing) SignMessage(keyDesc KeyDescriptor,
return privKey.Sign(digest) return privKey.Sign(digest)
} }
// SignDigestCompact signs the given SHA256 message digest with the private key // SignMessageCompact signs the given message, single or double SHA256 hashing
// described in the key descriptor and returns the signature in the compact, // it first, with the private key described in the key descriptor and returns
// public key recoverable format. // the signature in the compact, public key recoverable format.
// //
// NOTE: This is part of the keychain.DigestSignerRing interface. // NOTE: This is part of the keychain.MessageSignerRing interface.
func (b *BtcWalletKeyRing) SignDigestCompact(keyDesc KeyDescriptor, func (b *BtcWalletKeyRing) SignMessageCompact(keyDesc KeyDescriptor,
digest [32]byte) ([]byte, error) { msg []byte, doubleHash bool) ([]byte, error) {
privKey, err := b.DerivePrivKey(keyDesc) privKey, err := b.DerivePrivKey(keyDesc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return btcec.SignCompact(btcec.S256(), privKey, digest[:], true)
var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return btcec.SignCompact(btcec.S256(), privKey, digest, true)
} }

View File

@@ -178,7 +178,7 @@ type SecretKeyRing interface {
ECDHRing ECDHRing
DigestSignerRing MessageSignerRing
// DerivePrivKey attempts to derive the private key that corresponds to // DerivePrivKey attempts to derive the private key that corresponds to
// the passed key descriptor. If the public key is set, then this // the passed key descriptor. If the public key is set, then this
@@ -188,24 +188,26 @@ type SecretKeyRing interface {
DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error) DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error)
} }
// DigestSignerRing is an interface that abstracts away basic low-level ECDSA // MessageSignerRing is an interface that abstracts away basic low-level ECDSA
// signing on keys within a key ring. // signing on keys within a key ring.
type DigestSignerRing interface { type MessageSignerRing interface {
// SignMessage signs the given message, single or double SHA256 hashing // SignMessage signs the given message, single or double SHA256 hashing
// it first, with the private key described in the key descriptor. // it first, with the private key described in the key descriptor.
SignMessage(keyDesc KeyDescriptor, message []byte, SignMessage(keyDesc KeyDescriptor, message []byte,
doubleHash bool) (*btcec.Signature, error) doubleHash bool) (*btcec.Signature, error)
// SignDigestCompact signs the given SHA256 message digest with the // SignMessageCompact signs the given message, single or double SHA256
// private key described in the key descriptor and returns the signature // hashing it first, with the private key described in the key
// in the compact, public key recoverable format. // descriptor and returns the signature in the compact, public key
SignDigestCompact(keyDesc KeyDescriptor, digest [32]byte) ([]byte, error) // recoverable format.
SignMessageCompact(keyDesc KeyDescriptor, message []byte,
doubleHash bool) ([]byte, error)
} }
// SingleKeyDigestSigner is an abstraction interface that hides the // SingleKeyMessageSigner is an abstraction interface that hides the
// implementation of the low-level ECDSA signing operations by wrapping a // implementation of the low-level ECDSA signing operations by wrapping a
// single, specific private key. // single, specific private key.
type SingleKeyDigestSigner interface { type SingleKeyMessageSigner interface {
// PubKey returns the public key of the wrapped private key. // PubKey returns the public key of the wrapped private key.
PubKey() *btcec.PublicKey PubKey() *btcec.PublicKey
@@ -213,10 +215,10 @@ type SingleKeyDigestSigner interface {
// it first, with the wrapped private key. // it first, with the wrapped private key.
SignMessage(message []byte, doubleHash bool) (*btcec.Signature, error) SignMessage(message []byte, doubleHash bool) (*btcec.Signature, error)
// SignDigestCompact signs the given SHA256 message digest with the // SignMessageCompact signs the given message, single or double SHA256
// wrapped private key and returns the signature in the compact, public // hashing it first, with the wrapped private key and returns the
// key recoverable format. // signature in the compact, public key recoverable format.
SignDigestCompact(digest [32]byte) ([]byte, error) SignMessageCompact(message []byte, doubleHash bool) ([]byte, error)
} }
// ECDHRing is an interface that abstracts away basic low-level ECDH shared key // ECDHRing is an interface that abstracts away basic low-level ECDH shared key

View File

@@ -5,45 +5,45 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
) )
func NewPubKeyDigestSigner(keyDesc KeyDescriptor, func NewPubKeyMessageSigner(keyDesc KeyDescriptor,
signer DigestSignerRing) *PubKeyDigestSigner { signer MessageSignerRing) *PubKeyMessageSigner {
return &PubKeyDigestSigner{ return &PubKeyMessageSigner{
keyDesc: keyDesc, keyDesc: keyDesc,
digestSigner: signer, digestSigner: signer,
} }
} }
type PubKeyDigestSigner struct { type PubKeyMessageSigner struct {
keyDesc KeyDescriptor keyDesc KeyDescriptor
digestSigner DigestSignerRing digestSigner MessageSignerRing
} }
func (p *PubKeyDigestSigner) PubKey() *btcec.PublicKey { func (p *PubKeyMessageSigner) PubKey() *btcec.PublicKey {
return p.keyDesc.PubKey return p.keyDesc.PubKey
} }
func (p *PubKeyDigestSigner) SignMessage(message []byte, func (p *PubKeyMessageSigner) SignMessage(message []byte,
doubleHash bool) (*btcec.Signature, error) { doubleHash bool) (*btcec.Signature, error) {
return p.digestSigner.SignMessage(p.keyDesc, message, doubleHash) return p.digestSigner.SignMessage(p.keyDesc, message, doubleHash)
} }
func (p *PubKeyDigestSigner) SignDigestCompact(digest [32]byte) ([]byte, func (p *PubKeyMessageSigner) SignMessageCompact(msg []byte,
error) { doubleHash bool) ([]byte, error) {
return p.digestSigner.SignDigestCompact(p.keyDesc, digest) return p.digestSigner.SignMessageCompact(p.keyDesc, msg, doubleHash)
} }
type PrivKeyDigestSigner struct { type PrivKeyMessageSigner struct {
PrivKey *btcec.PrivateKey PrivKey *btcec.PrivateKey
} }
func (p *PrivKeyDigestSigner) PubKey() *btcec.PublicKey { func (p *PrivKeyMessageSigner) PubKey() *btcec.PublicKey {
return p.PrivKey.PubKey() return p.PrivKey.PubKey()
} }
func (p *PrivKeyDigestSigner) SignMessage(msg []byte, func (p *PrivKeyMessageSigner) SignMessage(msg []byte,
doubleHash bool) (*btcec.Signature, error) { doubleHash bool) (*btcec.Signature, error) {
var digest []byte var digest []byte
@@ -55,11 +55,17 @@ func (p *PrivKeyDigestSigner) SignMessage(msg []byte,
return p.PrivKey.Sign(digest) return p.PrivKey.Sign(digest)
} }
func (p *PrivKeyDigestSigner) SignDigestCompact(digest [32]byte) ([]byte, func (p *PrivKeyMessageSigner) SignMessageCompact(msg []byte,
error) { doubleHash bool) ([]byte, error) {
return btcec.SignCompact(btcec.S256(), p.PrivKey, digest[:], true) var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return btcec.SignCompact(btcec.S256(), p.PrivKey, digest, true)
} }
var _ SingleKeyDigestSigner = (*PubKeyDigestSigner)(nil) var _ SingleKeyMessageSigner = (*PubKeyMessageSigner)(nil)
var _ SingleKeyDigestSigner = (*PrivKeyDigestSigner)(nil) var _ SingleKeyMessageSigner = (*PrivKeyMessageSigner)(nil)

View File

@@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@@ -426,14 +425,11 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
return nil, nil, err return nil, nil, err
} }
payReqString, err := payReq.Encode( payReqString, err := payReq.Encode(zpay32.MessageSigner{
zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) {
SignCompact: func(msg []byte) ([]byte, error) { return cfg.NodeSigner.SignMessageCompact(msg, false)
hash := chainhash.HashB(msg)
return cfg.NodeSigner.SignDigestCompact(hash)
},
}, },
) })
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1086,6 +1086,12 @@ message SignMessageRequest {
base64. base64.
*/ */
bytes msg = 1; bytes msg = 1;
/*
Instead of the default double-SHA256 hashing of the message before signing,
only use one round of hashing instead.
*/
bool single_hash = 2;
} }
message SignMessageResponse { message SignMessageResponse {
// The signature for the given message // The signature for the given message

View File

@@ -6187,6 +6187,10 @@
"type": "string", "type": "string",
"format": "byte", "format": "byte",
"description": "The message to be signed. When using REST, this field must be encoded as\nbase64." "description": "The message to be signed. When using REST, this field must be encoded as\nbase64."
},
"single_hash": {
"type": "boolean",
"description": "Instead of the default double-SHA256 hashing of the message before signing,\nonly use one round of hashing instead."
} }
} }
}, },

View File

@@ -54,9 +54,15 @@ func (s *SecretKeyRing) SignMessage(_ keychain.KeyDescriptor,
return s.RootKey.Sign(digest) return s.RootKey.Sign(digest)
} }
// SignDigestCompact signs the passed digest. // SignMessageCompact signs the passed message.
func (s *SecretKeyRing) SignDigestCompact(_ keychain.KeyDescriptor, func (s *SecretKeyRing) SignMessageCompact(_ keychain.KeyDescriptor,
digest [32]byte) ([]byte, error) { msg []byte, doubleHash bool) ([]byte, error) {
return btcec.SignCompact(btcec.S256(), s.RootKey, digest[:], true) var digest []byte
if doubleHash {
digest = chainhash.DoubleHashB(msg)
} else {
digest = chainhash.HashB(msg)
}
return btcec.SignCompact(btcec.S256(), s.RootKey, digest, true)
} }

View File

@@ -310,7 +310,7 @@ func newManagerCfg(t *testing.T, numChannels int,
if err != nil { if err != nil {
t.Fatalf("unable to generate key pair: %v", err) t.Fatalf("unable to generate key pair: %v", err)
} }
privKeySigner := &keychain.PrivKeyDigestSigner{PrivKey: privKey} privKeySigner := &keychain.PrivKeyMessageSigner{PrivKey: privKey}
graph := newMockGraph( graph := newMockGraph(
t, numChannels, startEnabled, startEnabled, privKey.PubKey(), t, numChannels, startEnabled, startEnabled, privKey.PubKey(),

View File

@@ -32,7 +32,7 @@ var _ lnwallet.MessageSigner = (*mockSigner)(nil)
var ( var (
privKey, _ = btcec.NewPrivateKey(btcec.S256()) privKey, _ = btcec.NewPrivateKey(btcec.S256())
privKeySigner = &keychain.PrivKeyDigestSigner{PrivKey: privKey} privKeySigner = &keychain.PrivKeyMessageSigner{PrivKey: privKey}
pubKey = privKey.PubKey() pubKey = privKey.PubKey()

View File

@@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
@@ -13,12 +12,12 @@ import (
// NodeSigner is an implementation of the MessageSigner interface backed by the // NodeSigner is an implementation of the MessageSigner interface backed by the
// identity private key of running lnd node. // identity private key of running lnd node.
type NodeSigner struct { type NodeSigner struct {
keySigner keychain.SingleKeyDigestSigner keySigner keychain.SingleKeyMessageSigner
} }
// NewNodeSigner creates a new instance of the NodeSigner backed by the target // NewNodeSigner creates a new instance of the NodeSigner backed by the target
// private key. // private key.
func NewNodeSigner(keySigner keychain.SingleKeyDigestSigner) *NodeSigner { func NewNodeSigner(keySigner keychain.SingleKeyMessageSigner) *NodeSigner {
return &NodeSigner{ return &NodeSigner{
keySigner: keySigner, keySigner: keySigner,
} }
@@ -45,29 +44,13 @@ func (n *NodeSigner) SignMessage(pubKey *btcec.PublicKey,
return sig, nil return sig, nil
} }
// SignCompact signs a double-sha256 digest of the msg parameter under the // SignMessageCompact signs a single or double sha256 digest of the msg
// resident node's private key. The returned signature is a pubkey-recoverable // parameter under the resident node's private key. The returned signature is a
// signature. // pubkey-recoverable signature.
func (n *NodeSigner) SignCompact(msg []byte) ([]byte, error) { func (n *NodeSigner) SignMessageCompact(msg []byte, doubleHash bool) ([]byte,
// We'll sign the dsha256 of the target message. error) {
digest := chainhash.DoubleHashB(msg)
return n.SignDigestCompact(digest) return n.keySigner.SignMessageCompact(msg, doubleHash)
}
// SignDigestCompact signs the provided message digest under the resident
// node's private key. The returned signature is a pubkey-recoverable signature.
func (n *NodeSigner) SignDigestCompact(hash []byte) ([]byte, error) {
var digest [32]byte
copy(digest[:], hash)
// keychain.SignDigestCompact returns a pubkey-recoverable signature.
sig, err := n.keySigner.SignDigestCompact(digest)
if err != nil {
return nil, fmt.Errorf("can't sign the hash: %v", err)
}
return sig, nil
} }
// A compile time check to ensure that NodeSigner implements the MessageSigner // A compile time check to ensure that NodeSigner implements the MessageSigner

View File

@@ -61,7 +61,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes( aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
btcec.S256(), channels.AlicesPrivKey, btcec.S256(), channels.AlicesPrivKey,
) )
aliceKeySigner := &keychain.PrivKeyDigestSigner{PrivKey: aliceKeyPriv} aliceKeySigner := &keychain.PrivKeyMessageSigner{PrivKey: aliceKeyPriv}
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes( bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
btcec.S256(), channels.BobsPrivKey, btcec.S256(), channels.BobsPrivKey,
) )

View File

@@ -1522,7 +1522,7 @@ var (
// SignMessage signs a message with the resident node's private key. The // SignMessage signs a message with the resident node's private key. The
// returned signature string is zbase32 encoded and pubkey recoverable, meaning // returned signature string is zbase32 encoded and pubkey recoverable, meaning
// that only the message digest and signature are needed for verification. // that only the message digest and signature are needed for verification.
func (r *rpcServer) SignMessage(ctx context.Context, func (r *rpcServer) SignMessage(_ context.Context,
in *lnrpc.SignMessageRequest) (*lnrpc.SignMessageResponse, error) { in *lnrpc.SignMessageRequest) (*lnrpc.SignMessageResponse, error) {
if in.Msg == nil { if in.Msg == nil {
@@ -1530,7 +1530,9 @@ func (r *rpcServer) SignMessage(ctx context.Context,
} }
in.Msg = append(signedMsgPrefix, in.Msg...) in.Msg = append(signedMsgPrefix, in.Msg...)
sigBytes, err := r.server.nodeSigner.SignCompact(in.Msg) sigBytes, err := r.server.nodeSigner.SignMessageCompact(
in.Msg, !in.SingleHash,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -451,7 +451,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
var ( var (
err error err error
nodeKeyECDH = keychain.NewPubKeyECDH(*nodeKeyDesc, cc.KeyRing) nodeKeyECDH = keychain.NewPubKeyECDH(*nodeKeyDesc, cc.KeyRing)
nodeKeySigner = keychain.NewPubKeyDigestSigner( nodeKeySigner = keychain.NewPubKeyMessageSigner(
*nodeKeyDesc, cc.KeyRing, *nodeKeyDesc, cc.KeyRing,
) )
) )

View File

@@ -102,8 +102,10 @@ var (
testMessageSigner = MessageSigner{ testMessageSigner = MessageSigner{
SignCompact: func(msg []byte) ([]byte, error) { SignCompact: func(msg []byte) ([]byte, error) {
sig, err := btcec.SignCompact(btcec.S256(), hash := chainhash.HashB(msg)
testPrivKey, chainhash.HashB(msg), true) sig, err := btcec.SignCompact(
btcec.S256(), testPrivKey, hash, true,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't sign the "+ return nil, fmt.Errorf("can't sign the "+
"message: %v", err) "message: %v", err)