lnd.xprv/chanbackup/single.go
Olaoluwa Osuntokun c6ee42d3e2
chanbackup: create new Single version for tweakless commitment chans
In this commit, we create a new Single version for channels that use the
tweakless commitment scheme. When recovering from an SCB into an open
channel shell, we'll now check this field and use it to determine the
proper channel type. Otherwise, we may attempt to sweep the on chain
funds using the commitment point, when it goes directly to our key, or
the other way around.
2019-09-25 18:26:20 -07:00

488 lines
15 KiB
Go

package chanbackup
import (
"bytes"
"fmt"
"io"
"net"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
)
// SingleBackupVersion denotes the version of the single static channel backup.
// Based on this version, we know how to pack/unpack serialized versions of the
// backup.
type SingleBackupVersion byte
const (
// DefaultSingleVersion is the default version of the single channel
// backup. The serialized version of this static channel backup is
// simply: version || SCB. Where SCB is the known format of the
// version.
DefaultSingleVersion = 0
// TweaklessCommitVersion is the second SCB version. This version
// implicitly denotes that this channel uses the new tweakless commit
// format.
TweaklessCommitVersion = 1
)
// Single is a static description of an existing channel that can be used for
// the purposes of backing up. The fields in this struct allow a node to
// recover the settled funds within a channel in the case of partial or
// complete data loss. We provide the network address that we last used to
// connect to the peer as well, in case the node stops advertising the IP on
// the network for whatever reason.
//
// TODO(roasbeef): suffix version into struct?
type Single struct {
// Version is the version that should be observed when attempting to
// pack the single backup.
Version SingleBackupVersion
// IsInitiator is true if we were the initiator of the channel, and
// false otherwise. We'll need to know this information in order to
// properly re-derive the state hint information.
IsInitiator bool
// ChainHash is a hash which represents the blockchain that this
// channel will be opened within. This value is typically the genesis
// hash. In the case that the original chain went through a contentious
// hard-fork, then this value will be tweaked using the unique fork
// point on each branch.
ChainHash chainhash.Hash
// FundingOutpoint is the outpoint of the final funding transaction.
// This value uniquely and globally identities the channel within the
// target blockchain as specified by the chain hash parameter.
FundingOutpoint wire.OutPoint
// ShortChannelID encodes the exact location in the chain in which the
// channel was initially confirmed. This includes: the block height,
// transaction index, and the output within the target transaction.
ShortChannelID lnwire.ShortChannelID
// RemoteNodePub is the identity public key of the remote node this
// channel has been established with.
RemoteNodePub *btcec.PublicKey
// Addresses is a list of IP address in which either we were able to
// reach the node over in the past, OR we received an incoming
// authenticated connection for the stored identity public key.
Addresses []net.Addr
// Capacity is the size of the original channel.
Capacity btcutil.Amount
// LocalChanCfg is our local channel configuration. It contains all the
// information we need to re-derive the keys we used within the
// channel. Most importantly, it allows to derive the base public
// that's used to deriving the key used within the non-delayed
// pay-to-self output on the commitment transaction for a node. With
// this information, we can re-derive the private key needed to sweep
// the funds on-chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
LocalChanCfg channeldb.ChannelConfig
// RemoteChanCfg is the remote channel confirmation. We store this as
// well since we'll need some of their keys to re-derive things like
// the state hint obfuscator which will allow us to recognize the state
// their broadcast on chain.
//
// NOTE: Of the items in the ChannelConstraints, we only write the CSV
// delay.
RemoteChanCfg channeldb.ChannelConfig
// ShaChainRootDesc describes how to derive the private key that was
// used as the shachain root for this channel.
ShaChainRootDesc keychain.KeyDescriptor
}
// NewSingle creates a new static channel backup based on an existing open
// channel. We also pass in the set of addresses that we used in the past to
// connect to the channel peer.
func NewSingle(channel *channeldb.OpenChannel,
nodeAddrs []net.Addr) Single {
// TODO(roasbeef): update after we start to store the KeyLoc for
// shachain root
// We'll need to obtain the shachain root which is derived directly
// from a private key in our keychain.
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
// the backups plaintext don't carry any private information. When we
// go to recover, we'll present this in order to derive the private
// key.
_, shaChainPoint := btcec.PrivKeyFromBytes(btcec.S256(), b.Bytes())
single := Single{
IsInitiator: channel.IsInitiator,
ChainHash: channel.ChainHash,
FundingOutpoint: channel.FundingOutpoint,
ShortChannelID: channel.ShortChannelID,
RemoteNodePub: channel.IdentityPub,
Addresses: nodeAddrs,
Capacity: channel.Capacity,
LocalChanCfg: channel.LocalChanCfg,
RemoteChanCfg: channel.RemoteChanCfg,
ShaChainRootDesc: keychain.KeyDescriptor{
PubKey: shaChainPoint,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
},
},
}
if channel.ChanType.IsTweakless() {
single.Version = TweaklessCommitVersion
} else {
single.Version = DefaultSingleVersion
}
return single
}
// Serialize attempts to write out the serialized version of the target
// StaticChannelBackup into the passed io.Writer.
func (s *Single) Serialize(w io.Writer) error {
// Check to ensure that we'll only attempt to serialize a version that
// we're aware of.
switch s.Version {
case DefaultSingleVersion:
case TweaklessCommitVersion:
default:
return fmt.Errorf("unable to serialize w/ unknown "+
"version: %v", s.Version)
}
// If the sha chain root has specified a public key (which is
// optional), then we'll encode it now.
var shaChainPub [33]byte
if s.ShaChainRootDesc.PubKey != nil {
copy(
shaChainPub[:],
s.ShaChainRootDesc.PubKey.SerializeCompressed(),
)
}
// First we gather the SCB as is into a temporary buffer so we can
// determine the total length. Before we write out the serialized SCB,
// we write the length which allows us to skip any Singles that we
// don't know of when decoding a multi.
var singleBytes bytes.Buffer
if err := lnwire.WriteElements(
&singleBytes,
s.IsInitiator,
s.ChainHash[:],
s.FundingOutpoint,
s.ShortChannelID,
s.RemoteNodePub,
s.Addresses,
s.Capacity,
s.LocalChanCfg.CsvDelay,
// We only need to write out the KeyLocator portion of the
// local channel config.
uint32(s.LocalChanCfg.MultiSigKey.Family),
s.LocalChanCfg.MultiSigKey.Index,
uint32(s.LocalChanCfg.RevocationBasePoint.Family),
s.LocalChanCfg.RevocationBasePoint.Index,
uint32(s.LocalChanCfg.PaymentBasePoint.Family),
s.LocalChanCfg.PaymentBasePoint.Index,
uint32(s.LocalChanCfg.DelayBasePoint.Family),
s.LocalChanCfg.DelayBasePoint.Index,
uint32(s.LocalChanCfg.HtlcBasePoint.Family),
s.LocalChanCfg.HtlcBasePoint.Index,
s.RemoteChanCfg.CsvDelay,
// We only need to write out the raw pubkey for the remote
// channel config.
s.RemoteChanCfg.MultiSigKey.PubKey,
s.RemoteChanCfg.RevocationBasePoint.PubKey,
s.RemoteChanCfg.PaymentBasePoint.PubKey,
s.RemoteChanCfg.DelayBasePoint.PubKey,
s.RemoteChanCfg.HtlcBasePoint.PubKey,
shaChainPub[:],
uint32(s.ShaChainRootDesc.KeyLocator.Family),
s.ShaChainRootDesc.KeyLocator.Index,
); err != nil {
return err
}
return lnwire.WriteElements(
w,
byte(s.Version),
uint16(len(singleBytes.Bytes())),
singleBytes.Bytes(),
)
}
// PackToWriter is similar to the Serialize method, but takes the operation a
// step further by encryption the raw bytes of the static channel back up. For
// encryption we use the chacah20poly1305 AEAD cipher with a 24 byte nonce and
// 32-byte key size. We use a 24-byte nonce, as we can't ensure that we have a
// global counter to use as a sequence number for nonces, and want to ensure
// that we're able to decrypt these blobs without any additional context. We
// derive the key that we use for encryption via a SHA2 operation of the with
// the golden keychain.KeyFamilyStaticBackup base encryption key. We then take
// the serialized resulting shared secret point, and hash it using sha256 to
// obtain the key that we'll use for encryption. When using the AEAD, we pass
// the nonce as associated data such that we'll be able to package the two
// together for storage. Before writing out the encrypted payload, we prepend
// the nonce to the final blob.
func (s *Single) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
// First, we'll serialize the SCB (StaticChannelBackup) into a
// temporary buffer so we can store it in a temporary place before we
// go to encrypt the entire thing.
var rawBytes bytes.Buffer
if err := s.Serialize(&rawBytes); err != nil {
return err
}
// Finally, we'll encrypt the raw serialized SCB (using the nonce as
// associated data), and write out the ciphertext prepend with the
// nonce that we used to the passed io.Reader.
return encryptPayloadToWriter(rawBytes, w, keyRing)
}
// readLocalKeyDesc reads a KeyDescriptor encoded within an unpacked Single.
// For local KeyDescs, we only write out the KeyLocator information as we can
// re-derive the pubkey from it.
func readLocalKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var keyDesc keychain.KeyDescriptor
var keyFam uint32
if err := lnwire.ReadElements(r, &keyFam); err != nil {
return keyDesc, err
}
keyDesc.Family = keychain.KeyFamily(keyFam)
if err := lnwire.ReadElements(r, &keyDesc.Index); err != nil {
return keyDesc, err
}
return keyDesc, nil
}
// readRemoteKeyDesc reads a remote KeyDescriptor encoded within an unpacked
// Single. For remote KeyDescs, we write out only the PubKey since we don't
// actually have the KeyLocator data.
func readRemoteKeyDesc(r io.Reader) (keychain.KeyDescriptor, error) {
var (
keyDesc keychain.KeyDescriptor
pub [33]byte
)
_, err := io.ReadFull(r, pub[:])
if err != nil {
return keychain.KeyDescriptor{}, err
}
keyDesc.PubKey, err = btcec.ParsePubKey(pub[:], btcec.S256())
if err != nil {
return keychain.KeyDescriptor{}, err
}
keyDesc.PubKey.Curve = nil
return keyDesc, nil
}
// Deserialize attempts to read the raw plaintext serialized SCB from the
// passed io.Reader. If the method is successful, then the target
// StaticChannelBackup will be fully populated.
func (s *Single) Deserialize(r io.Reader) error {
// First, we'll need to read the version of this single-back up so we
// can know how to unpack each of the SCB.
var version byte
err := lnwire.ReadElements(r, &version)
if err != nil {
return err
}
s.Version = SingleBackupVersion(version)
switch s.Version {
case DefaultSingleVersion:
case TweaklessCommitVersion:
default:
return fmt.Errorf("unable to de-serialize w/ unknown "+
"version: %v", s.Version)
}
var length uint16
if err := lnwire.ReadElements(r, &length); err != nil {
return err
}
err = lnwire.ReadElements(
r, &s.IsInitiator, s.ChainHash[:], &s.FundingOutpoint,
&s.ShortChannelID, &s.RemoteNodePub, &s.Addresses, &s.Capacity,
)
if err != nil {
return err
}
err = lnwire.ReadElements(r, &s.LocalChanCfg.CsvDelay)
if err != nil {
return err
}
s.LocalChanCfg.MultiSigKey, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.RevocationBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.PaymentBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.DelayBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
s.LocalChanCfg.HtlcBasePoint, err = readLocalKeyDesc(r)
if err != nil {
return err
}
err = lnwire.ReadElements(r, &s.RemoteChanCfg.CsvDelay)
if err != nil {
return err
}
s.RemoteChanCfg.MultiSigKey, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.RevocationBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.PaymentBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.DelayBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
s.RemoteChanCfg.HtlcBasePoint, err = readRemoteKeyDesc(r)
if err != nil {
return err
}
// Finally, we'll parse out the ShaChainRootDesc.
var (
shaChainPub [33]byte
zeroPub [33]byte
)
if err := lnwire.ReadElements(r, shaChainPub[:]); err != nil {
return err
}
// Since this field is optional, we'll check to see if the pubkey has
// been specified or not.
if !bytes.Equal(shaChainPub[:], zeroPub[:]) {
s.ShaChainRootDesc.PubKey, err = btcec.ParsePubKey(
shaChainPub[:], btcec.S256(),
)
if err != nil {
return err
}
}
var shaKeyFam uint32
if err := lnwire.ReadElements(r, &shaKeyFam); err != nil {
return err
}
s.ShaChainRootDesc.KeyLocator.Family = keychain.KeyFamily(shaKeyFam)
return lnwire.ReadElements(r, &s.ShaChainRootDesc.KeyLocator.Index)
}
// UnpackFromReader is similar to Deserialize method, but it expects the passed
// io.Reader to contain an encrypt SCB. Refer to the SerializeAndEncrypt method
// for details w.r.t the encryption scheme used. If we're unable to decrypt the
// payload for whatever reason (wrong key, wrong nonce, etc), then this method
// will return an error.
func (s *Single) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
plaintext, err := decryptPayloadFromReader(r, keyRing)
if err != nil {
return err
}
// Finally, we'll pack the bytes into a reader to we can deserialize
// the plaintext bytes of the SCB.
backupReader := bytes.NewReader(plaintext)
return s.Deserialize(backupReader)
}
// PackStaticChanBackups accepts a set of existing open channels, and a
// keychain.KeyRing, and returns a map of outpoints to the serialized+encrypted
// static channel backups. The passed keyRing should be backed by the users
// root HD seed in order to ensure full determinism.
func PackStaticChanBackups(backups []Single,
keyRing keychain.KeyRing) (map[wire.OutPoint][]byte, error) {
packedBackups := make(map[wire.OutPoint][]byte)
for _, chanBackup := range backups {
chanPoint := chanBackup.FundingOutpoint
var b bytes.Buffer
err := chanBackup.PackToWriter(&b, keyRing)
if err != nil {
return nil, fmt.Errorf("unable to pack chan backup "+
"for %v: %v", chanPoint, err)
}
packedBackups[chanPoint] = b.Bytes()
}
return packedBackups, nil
}
// PackedSingles represents a series of fully packed SCBs. This may be the
// combination of a series of individual SCBs in order to batch their
// unpacking.
type PackedSingles [][]byte
// Unpack attempts to decrypt the passed set of encrypted SCBs and deserialize
// each one into a new SCB struct. The passed keyRing should be backed by the
// same HD seed as was used to encrypt the set of backups in the first place.
// If we're unable to decrypt any of the back ups, then we'll return an error.
func (p PackedSingles) Unpack(keyRing keychain.KeyRing) ([]Single, error) {
backups := make([]Single, len(p))
for i, encryptedBackup := range p {
var backup Single
backupReader := bytes.NewReader(encryptedBackup)
err := backup.UnpackFromReader(backupReader, keyRing)
if err != nil {
return nil, err
}
backups[i] = backup
}
return backups, nil
}
// TODO(roasbeef): make codec package?