Merge pull request #4517 from Crypt-iQ/2659_keyloc

multi: store KeyLocator in OpenChannel, use ECDH instead of DerivePrivKey
This commit is contained in:
Olaoluwa Osuntokun 2021-03-05 16:12:49 -08:00 committed by GitHub
commit 3c14e3d71d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 484 additions and 58 deletions

@ -124,19 +124,34 @@ type Single struct {
func NewSingle(channel *channeldb.OpenChannel, func NewSingle(channel *channeldb.OpenChannel,
nodeAddrs []net.Addr) Single { nodeAddrs []net.Addr) Single {
// TODO(roasbeef): update after we start to store the KeyLoc for var shaChainRootDesc keychain.KeyDescriptor
// shachain root
// We'll need to obtain the shachain root which is derived directly // If the channel has a populated RevocationKeyLocator, then we can
// from a private key in our keychain. // just store that instead of the public key.
var b bytes.Buffer if channel.RevocationKeyLocator.Family == keychain.KeyFamilyRevocationRoot {
channel.RevocationProducer.Encode(&b) // Can't return an error. shaChainRootDesc = keychain.KeyDescriptor{
KeyLocator: channel.RevocationKeyLocator,
}
} else {
// If the RevocationKeyLocator is not populated, then we'll need
// to obtain a public point for the shachain root and store that.
// This is the legacy scheme.
var b bytes.Buffer
_ = channel.RevocationProducer.Encode(&b) // Can't return an error.
// Once we have the root, we'll make a public key from it, such that // Once we have the root, we'll make a public key from it, such that
// the backups plaintext don't carry any private information. When we // the backups plaintext don't carry any private information. When
// go to recover, we'll present this in order to derive the private // we go to recover, we'll present this in order to derive the
// key. // private key.
_, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes()) _, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes())
shaChainRootDesc = keychain.KeyDescriptor{
PubKey: shaChainPoint,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
},
}
}
// If a channel is unconfirmed, the block height of the ShortChannelID // If a channel is unconfirmed, the block height of the ShortChannelID
// is zero. This will lead to problems when trying to restore that // is zero. This will lead to problems when trying to restore that
@ -149,21 +164,16 @@ func NewSingle(channel *channeldb.OpenChannel,
} }
single := Single{ single := Single{
IsInitiator: channel.IsInitiator, IsInitiator: channel.IsInitiator,
ChainHash: channel.ChainHash, ChainHash: channel.ChainHash,
FundingOutpoint: channel.FundingOutpoint, FundingOutpoint: channel.FundingOutpoint,
ShortChannelID: chanID, ShortChannelID: chanID,
RemoteNodePub: channel.IdentityPub, RemoteNodePub: channel.IdentityPub,
Addresses: nodeAddrs, Addresses: nodeAddrs,
Capacity: channel.Capacity, Capacity: channel.Capacity,
LocalChanCfg: channel.LocalChanCfg, LocalChanCfg: channel.LocalChanCfg,
RemoteChanCfg: channel.RemoteChanCfg, RemoteChanCfg: channel.RemoteChanCfg,
ShaChainRootDesc: keychain.KeyDescriptor{ ShaChainRootDesc: shaChainRootDesc,
PubKey: shaChainPoint,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
},
},
} }
switch { switch {

@ -194,6 +194,10 @@ const (
// A tlv type definition used to serialize an outpoint's indexStatus // A tlv type definition used to serialize an outpoint's indexStatus
// for use in the outpoint index. // for use in the outpoint index.
indexStatusType tlv.Type = 0 indexStatusType tlv.Type = 0
// A tlv type definition used to serialize and deserialize a KeyLocator
// from the database.
keyLocType tlv.Type = 1
) )
// indexStatus is an enum-like type that describes what state the // indexStatus is an enum-like type that describes what state the
@ -719,6 +723,11 @@ type OpenChannel struct {
// was a revocation (true) or a commitment signature (false). // was a revocation (true) or a commitment signature (false).
LastWasRevoke bool LastWasRevoke bool
// RevocationKeyLocator stores the KeyLocator information that we will
// need to derive the shachain root for this channel. This allows us to
// have private key isolation from lnd.
RevocationKeyLocator keychain.KeyLocator
// TODO(roasbeef): eww // TODO(roasbeef): eww
Db *DB Db *DB
@ -3286,6 +3295,20 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
return err return err
} }
// Write the RevocationKeyLocator as the first entry in a tlv stream.
keyLocRecord := MakeKeyLocRecord(
keyLocType, &channel.RevocationKeyLocator,
)
tlvStream, err := tlv.NewStream(keyLocRecord)
if err != nil {
return err
}
if err := tlvStream.Encode(&w); err != nil {
return err
}
if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil { if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
return err return err
} }
@ -3475,6 +3498,16 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
} }
} }
keyLocRecord := MakeKeyLocRecord(keyLocType, &channel.RevocationKeyLocator)
tlvStream, err := tlv.NewStream(keyLocRecord)
if err != nil {
return err
}
if err := tlvStream.Decode(r); err != nil {
return err
}
channel.Packager = NewChannelPackager(channel.ShortChannelID) channel.Packager = NewChannelPackager(channel.ShortChannelID)
// Finally, read the optional shutdown scripts. // Finally, read the optional shutdown scripts.
@ -3657,3 +3690,38 @@ func storeThawHeight(chanBucket kvdb.RwBucket, height uint32) error {
func deleteThawHeight(chanBucket kvdb.RwBucket) error { func deleteThawHeight(chanBucket kvdb.RwBucket) error {
return chanBucket.Delete(frozenChanKey) return chanBucket.Delete(frozenChanKey)
} }
// EKeyLocator is an encoder for keychain.KeyLocator.
func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*keychain.KeyLocator); ok {
err := tlv.EUint32T(w, uint32(v.Family), buf)
if err != nil {
return err
}
return tlv.EUint32T(w, v.Index, buf)
}
return tlv.NewTypeForEncodingErr(val, "keychain.KeyLocator")
}
// DKeyLocator is a decoder for keychain.KeyLocator.
func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*keychain.KeyLocator); ok {
var family uint32
err := tlv.DUint32(r, &family, buf, 4)
if err != nil {
return err
}
v.Family = keychain.KeyFamily(family)
return tlv.DUint32(r, &v.Index, buf, 4)
}
return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
}
// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
// Type and the EKeyLocator and DKeyLocator functions. The size will always be
// 8 as KeyFamily is uint32 and the Index is uint32.
func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
}

@ -8,6 +8,8 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/stretchr/testify/require"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
@ -50,6 +52,9 @@ var (
IP: net.ParseIP("127.0.0.1"), IP: net.ParseIP("127.0.0.1"),
Port: 18555, Port: 18555,
} }
// keyLocIndex is the KeyLocator Index we use for TestKeyLocatorEncoding.
keyLocIndex = uint32(2049)
) )
// testChannelParams is a struct which details the specifics of how a channel // testChannelParams is a struct which details the specifics of how a channel
@ -1587,3 +1592,33 @@ func TestHasChanStatus(t *testing.T) {
}) })
} }
} }
// TestKeyLocatorEncoding tests that we are able to serialize a given
// keychain.KeyLocator. After successfully encoding, we check that the decode
// output arrives at the same initial KeyLocator.
func TestKeyLocatorEncoding(t *testing.T) {
keyLoc := keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
Index: keyLocIndex,
}
// First, we'll encode the KeyLocator into a buffer.
var (
b bytes.Buffer
buf [8]byte
)
err := EKeyLocator(&b, &keyLoc, &buf)
require.NoError(t, err, "unable to encode key locator")
// Next, we'll attempt to decode the bytes into a new KeyLocator.
r := bytes.NewReader(b.Bytes())
var decodedKeyLoc keychain.KeyLocator
err = DKeyLocator(r, &decodedKeyLoc, &buf, 8)
require.NoError(t, err, "unable to decode key locator")
// Finally, we'll compare that the original KeyLocator and the decoded
// version are equal.
require.Equal(t, keyLoc, decodedKeyLoc)
}

@ -48,20 +48,7 @@ type chanDBRestorer struct {
func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) ( func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) (
*channeldb.ChannelShell, error) { *channeldb.ChannelShell, error) {
// First, we'll also need to obtain the private key for the shachain var err error
// root from the encoded public key.
//
// TODO(roasbeef): now adds req for hardware signers to impl
// shachain...
privKey, err := c.secretKeys.DerivePrivKey(backup.ShaChainRootDesc)
if err != nil {
return nil, fmt.Errorf("unable to derive shachain root key: %v", err)
}
revRoot, err := chainhash.NewHash(privKey.Serialize())
if err != nil {
return nil, err
}
shaChainProducer := shachain.NewRevocationProducer(*revRoot)
// Each of the keys in our local channel config only have their // Each of the keys in our local channel config only have their
// locators populate, so we'll re-derive the raw key now as we'll need // locators populate, so we'll re-derive the raw key now as we'll need
@ -97,6 +84,53 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) (
return nil, fmt.Errorf("unable to derive htlc key: %v", err) return nil, fmt.Errorf("unable to derive htlc key: %v", err)
} }
// The shachain root that seeds RevocationProducer for this channel.
// It currently has two possible formats.
var revRoot *chainhash.Hash
// If the PubKey field is non-nil, then this shachain root is using the
// legacy non-ECDH scheme.
if backup.ShaChainRootDesc.PubKey != nil {
ltndLog.Debugf("Using legacy revocation producer format for "+
"channel point %v", backup.FundingOutpoint)
// Obtain the private key for the shachain root from the
// encoded public key.
privKey, err := c.secretKeys.DerivePrivKey(
backup.ShaChainRootDesc,
)
if err != nil {
return nil, fmt.Errorf("could not derive private key "+
"for legacy channel revocation root format: "+
"%v", err)
}
revRoot, err = chainhash.NewHash(privKey.Serialize())
if err != nil {
return nil, err
}
} else {
ltndLog.Debugf("Using new ECDH revocation producer format "+
"for channel point %v", backup.FundingOutpoint)
// This is the scheme in which the shachain root is derived via
// an ECDH operation on the private key of ShaChainRootDesc and
// our public multisig key.
ecdh, err := c.secretKeys.ECDH(
backup.ShaChainRootDesc,
backup.LocalChanCfg.MultiSigKey.PubKey,
)
if err != nil {
return nil, fmt.Errorf("unable to derive shachain "+
"root: %v", err)
}
ch := chainhash.Hash(ecdh)
revRoot = &ch
}
shaChainProducer := shachain.NewRevocationProducer(*revRoot)
var chanType channeldb.ChannelType var chanType channeldb.ChannelType
switch backup.Version { switch backup.Version {
@ -119,8 +153,8 @@ func (c *chanDBRestorer) openChannelShell(backup chanbackup.Single) (
return nil, fmt.Errorf("unknown Single version: %v", err) return nil, fmt.Errorf("unknown Single version: %v", err)
} }
ltndLog.Infof("SCB Recovery: created channel shell for ChannelPoint(%v), "+ ltndLog.Infof("SCB Recovery: created channel shell for ChannelPoint"+
"chan_type=%v", backup.FundingOutpoint, chanType) "(%v), chan_type=%v", backup.FundingOutpoint, chanType)
chanShell := channeldb.ChannelShell{ chanShell := channeldb.ChannelShell{
NodeAddrs: backup.Addresses, NodeAddrs: backup.Addresses,

@ -16,6 +16,7 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -356,6 +357,37 @@ func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) {
) )
}, },
}, },
// Restore by also creating a channel with the legacy revocation
// producer format to make sure old SCBs can still be recovered.
{
name: "old revocation producer format",
initiator: true,
legacyRevocation: true,
restoreMethod: func(oldNode *lntest.HarnessNode,
backupFilePath string,
mnemonic []string) (nodeRestorer, error) {
// For this restoration method, we'll grab the
// current multi-channel backup from the old
// node, and use it to restore a new node
// within the closure.
req := &lnrpc.ChanBackupExportRequest{}
chanBackup, err := oldNode.ExportAllChannelBackups(
ctxb, req,
)
require.NoError(t.t, err)
multi := chanBackup.MultiChanBackup.MultiChanBackup
// In our nodeRestorer function, we'll restore
// the node from seed, then manually recover the
// channel backup.
return chanRestoreViaRPC(
net, password, mnemonic, multi,
)
},
},
} }
// TODO(roasbeef): online vs offline close? // TODO(roasbeef): online vs offline close?
@ -765,6 +797,10 @@ type chanRestoreTestCase struct {
// used for the channels created in the test. // used for the channels created in the test.
anchorCommit bool anchorCommit bool
// legacyRevocation signals if a channel with the legacy revocation
// producer format should also be created before restoring.
legacyRevocation bool
// restoreMethod takes an old node, then returns a function // restoreMethod takes an old node, then returns a function
// closure that'll return the same node, but with its state // closure that'll return the same node, but with its state
// restored via a custom method. We use this to abstract away // restored via a custom method. We use this to abstract away
@ -867,6 +903,13 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
t.Fatalf("channel backup not updated in time: %v", err) t.Fatalf("channel backup not updated in time: %v", err)
} }
// Also create channels with the legacy revocation producer format if
// requested.
case testCase.legacyRevocation:
createLegacyRevocationChannel(
net, t, chanAmt, pushAmt, from, to,
)
default: default:
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPoint := openChannelAndAssert( chanPoint := openChannelAndAssert(
@ -1068,6 +1111,117 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
) )
} }
// createLegacyRevocationChannel creates a single channel using the legacy
// revocation producer format by using PSBT to signal a special pending channel
// ID.
func createLegacyRevocationChannel(net *lntest.NetworkHarness, t *harnessTest,
chanAmt, pushAmt btcutil.Amount, from, to *lntest.HarnessNode) {
ctxb := context.Background()
// We'll signal to the wallet that we also want to create a channel with
// the legacy revocation producer format that relies on deriving a
// private key from the key ring. This is only available during itests
// to make sure we don't hard depend on the DerivePrivKey method of the
// key ring. We can signal the wallet by setting a custom pending
// channel ID. To be able to do that, we need to set a funding shim
// which is easiest by using PSBT funding. The ID is the hex
// representation of the string "legacy-revocation".
itestLegacyFormatChanID := [32]byte{
0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x2d, 0x72, 0x65, 0x76,
0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
}
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
openChannelReq := lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
FundingShim: &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_PsbtShim{
PsbtShim: &lnrpc.PsbtShim{
PendingChanId: itestLegacyFormatChanID[:],
},
},
},
}
chanUpdates, tempPsbt, err := openChannelPsbt(
ctxt, from, to, openChannelReq,
)
require.NoError(t.t, err)
// Fund the PSBT by using the source node's wallet.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Psbt{
Psbt: tempPsbt,
},
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
SatPerVbyte: 2,
},
}
fundResp, err := from.WalletKitClient.FundPsbt(ctxt, fundReq)
require.NoError(t.t, err)
// We have a PSBT that has no witness data yet, which is exactly what we
// need for the next step of verifying the PSBT with the funding intents.
_, err = from.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
PsbtVerify: &lnrpc.FundingPsbtVerify{
PendingChanId: itestLegacyFormatChanID[:],
FundedPsbt: fundResp.FundedPsbt,
},
},
})
require.NoError(t.t, err)
// Now we'll ask the source node's wallet to sign the PSBT so we can
// finish the funding flow.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt,
}
finalizeRes, err := from.WalletKitClient.FinalizePsbt(
ctxt, finalizeReq,
)
require.NoError(t.t, err)
// We've signed our PSBT now, let's pass it to the intent again.
_, err = from.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
PendingChanId: itestLegacyFormatChanID[:],
SignedPsbt: finalizeRes.SignedPsbt,
},
},
})
require.NoError(t.t, err)
// Consume the "channel pending" update. This waits until the funding
// transaction was fully compiled.
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
updateResp, err := receiveChanUpdate(ctxt, chanUpdates)
require.NoError(t.t, err)
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
require.True(t.t, ok)
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: upd.ChanPending.Txid,
},
OutputIndex: upd.ChanPending.OutputIndex,
}
_ = mineBlocks(t, net, 6, 1)
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
err = from.WaitForNetworkChannelOpen(ctxt, chanPoint)
require.NoError(t.t, err)
err = to.WaitForNetworkChannelOpen(ctxt, chanPoint)
require.NoError(t.t, err)
}
// chanRestoreViaRPC is a helper test method that returns a nodeRestorer // chanRestoreViaRPC is a helper test method that returns a nodeRestorer
// instance which will restore the target node from a password+seed, then // instance which will restore the target node from a password+seed, then
// trigger a SCB restore using the RPC interface. // trigger a SCB restore using the RPC interface.

@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -161,6 +162,10 @@ type ChannelReservation struct {
chanFunder chanfunding.Assembler chanFunder chanfunding.Assembler
fundingIntent chanfunding.Intent fundingIntent chanfunding.Intent
// nextRevocationKeyLoc stores the key locator information for this
// channel.
nextRevocationKeyLoc keychain.KeyLocator
} }
// NewChannelReservation creates a new channel reservation. This function is // NewChannelReservation creates a new channel reservation. This function is

@ -0,0 +1,51 @@
// +build !rpctest
package lnwallet
import (
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/shachain"
)
// nextRevocationProducer creates a new revocation producer, deriving the
// revocation root by applying ECDH to a new key from our revocation root family
// and the multisig key we use for the channel.
func (l *LightningWallet) nextRevocationProducer(res *ChannelReservation,
keyRing keychain.KeyRing) (shachain.Producer, error) {
// Derive the next key in the revocation root family.
nextRevocationKeyDesc, err := keyRing.DeriveNextKey(
keychain.KeyFamilyRevocationRoot,
)
if err != nil {
return nil, err
}
// If the DeriveNextKey call returns the first key with Index 0, we need
// to re-derive the key as the keychain/btcwallet.go DerivePrivKey call
// special-cases Index 0.
if nextRevocationKeyDesc.Index == 0 {
nextRevocationKeyDesc, err = keyRing.DeriveNextKey(
keychain.KeyFamilyRevocationRoot,
)
if err != nil {
return nil, err
}
}
res.nextRevocationKeyLoc = nextRevocationKeyDesc.KeyLocator
// Perform an ECDH operation between the private key described in
// nextRevocationKeyDesc and our public multisig key. The result will be
// used to seed the revocation producer.
revRoot, err := l.ECDH(
nextRevocationKeyDesc, res.ourContribution.MultiSigKey.PubKey,
)
if err != nil {
return nil, err
}
// Once we have the root, we can then generate our shachain producer
// and from that generate the per-commitment point.
return shachain.NewRevocationProducer(revRoot), nil
}

@ -0,0 +1,77 @@
// +build rpctest
package lnwallet
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/shachain"
)
// nextRevocationProducer creates a new revocation producer, deriving the
// revocation root by applying ECDH to a new key from our revocation root family
// and the multisig key we use for the channel.
func (l *LightningWallet) nextRevocationProducer(res *ChannelReservation,
keyRing keychain.KeyRing) (shachain.Producer, error) {
// Derive the next key in the revocation root family.
nextRevocationKeyDesc, err := keyRing.DeriveNextKey(
keychain.KeyFamilyRevocationRoot,
)
if err != nil {
return nil, err
}
// Within our itests, we want to make sure we can still restore channel
// backups created with the old revocation root derivation method. To
// create a channel in the legacy format during the test, we signal this
// by setting an explicit pending channel ID. The ID is the hex
// representation of the string "legacy-revocation".
itestLegacyFormatChanID := [32]byte{
0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x2d, 0x72, 0x65, 0x76,
0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
}
if res.pendingChanID == itestLegacyFormatChanID {
revocationRoot, err := l.DerivePrivKey(nextRevocationKeyDesc)
if err != nil {
return nil, err
}
// Once we have the root, we can then generate our shachain
// producer and from that generate the per-commitment point.
revRoot, err := chainhash.NewHash(revocationRoot.Serialize())
if err != nil {
return nil, err
}
return shachain.NewRevocationProducer(*revRoot), nil
}
// If the DeriveNextKey call returns the first key with Index 0, we need
// to re-derive the key as the keychain/btcwallet.go DerivePrivKey call
// special-cases Index 0.
if nextRevocationKeyDesc.Index == 0 {
nextRevocationKeyDesc, err = keyRing.DeriveNextKey(
keychain.KeyFamilyRevocationRoot,
)
if err != nil {
return nil, err
}
}
res.nextRevocationKeyLoc = nextRevocationKeyDesc.KeyLocator
// Perform an ECDH operation between the private key described in
// nextRevocationKeyDesc and our public multisig key. The result will be
// used to seed the revocation producer.
revRoot, err := l.ECDH(
nextRevocationKeyDesc, res.ourContribution.MultiSigKey.PubKey,
)
if err != nil {
return nil, err
}
// Once we have the root, we can then generate our shachain producer
// and from that generate the per-commitment point.
return shachain.NewRevocationProducer(revRoot), nil
}

@ -1071,26 +1071,13 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation,
return err return err
} }
// With the above keys created, we'll also need to initialization our // With the above keys created, we'll also need to initialize our
// initial revocation tree state. // revocation tree state, and from that generate the per-commitment point.
nextRevocationKeyDesc, err := keyRing.DeriveNextKey( producer, err := l.nextRevocationProducer(reservation, keyRing)
keychain.KeyFamilyRevocationRoot,
)
if err != nil {
return err
}
revocationRoot, err := l.DerivePrivKey(nextRevocationKeyDesc)
if err != nil { if err != nil {
return err return err
} }
// Once we have the root, we can then generate our shachain producer
// and from that generate the per-commitment point.
revRoot, err := chainhash.NewHash(revocationRoot.Serialize())
if err != nil {
return err
}
producer := shachain.NewRevocationProducer(*revRoot)
firstPreimage, err := producer.AtIndex(0) firstPreimage, err := producer.AtIndex(0)
if err != nil { if err != nil {
return err return err
@ -1727,6 +1714,8 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs
res.partialState.RemoteShutdownScript = res.partialState.RemoteShutdownScript =
res.theirContribution.UpfrontShutdown res.theirContribution.UpfrontShutdown
res.partialState.RevocationKeyLocator = res.nextRevocationKeyLoc
// Add the complete funding transaction to the DB, in its open bucket // Add the complete funding transaction to the DB, in its open bucket
// which will be used for the lifetime of this channel. // which will be used for the lifetime of this channel.
nodeAddr := res.nodeAddr nodeAddr := res.nodeAddr
@ -1891,6 +1880,9 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
// which will be used for the lifetime of this channel. // which will be used for the lifetime of this channel.
chanState.LocalChanCfg = pendingReservation.ourContribution.toChanConfig() chanState.LocalChanCfg = pendingReservation.ourContribution.toChanConfig()
chanState.RemoteChanCfg = pendingReservation.theirContribution.toChanConfig() chanState.RemoteChanCfg = pendingReservation.theirContribution.toChanConfig()
chanState.RevocationKeyLocator = pendingReservation.nextRevocationKeyLoc
err = chanState.SyncPending(pendingReservation.nodeAddr, uint32(bestHeight)) err = chanState.SyncPending(pendingReservation.nodeAddr, uint32(bestHeight))
if err != nil { if err != nil {
req.err <- err req.err <- err