347 lines
12 KiB
Go
347 lines
12 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/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 defautl version of the single channel
|
||
|
// backup. The seralized version of this static channel backup is
|
||
|
// simply: version || SCB. Where SCB is the known format of the
|
||
|
// version.
|
||
|
DefaultSingleVersion = 0
|
||
|
)
|
||
|
|
||
|
// 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
|
||
|
|
||
|
// 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
|
||
|
|
||
|
// CsvDelay is the local CSV delay used within the channel. We may need
|
||
|
// this value to reconstruct our script to recover the funds on-chain
|
||
|
// after a force close.
|
||
|
CsvDelay uint16
|
||
|
|
||
|
// PaymentBasePoint describes how to derive 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.
|
||
|
PaymentBasePoint keychain.KeyLocator
|
||
|
|
||
|
// 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 {
|
||
|
|
||
|
chanCfg := channel.LocalChanCfg
|
||
|
|
||
|
// 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())
|
||
|
|
||
|
return Single{
|
||
|
ChainHash: channel.ChainHash,
|
||
|
FundingOutpoint: channel.FundingOutpoint,
|
||
|
ShortChannelID: channel.ShortChannelID,
|
||
|
RemoteNodePub: channel.IdentityPub,
|
||
|
Addresses: nodeAddrs,
|
||
|
CsvDelay: chanCfg.CsvDelay,
|
||
|
PaymentBasePoint: chanCfg.PaymentBasePoint.KeyLocator,
|
||
|
ShaChainRootDesc: keychain.KeyDescriptor{
|
||
|
PubKey: shaChainPoint,
|
||
|
KeyLocator: keychain.KeyLocator{
|
||
|
Family: keychain.KeyFamilyRevocationRoot,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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:
|
||
|
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.ChainHash[:],
|
||
|
s.FundingOutpoint,
|
||
|
s.ShortChannelID,
|
||
|
s.RemoteNodePub,
|
||
|
s.Addresses,
|
||
|
s.CsvDelay,
|
||
|
uint32(s.PaymentBasePoint.Family),
|
||
|
s.PaymentBasePoint.Index,
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
// 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:
|
||
|
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.ChainHash[:], &s.FundingOutpoint, &s.ShortChannelID,
|
||
|
&s.RemoteNodePub, &s.Addresses, &s.CsvDelay,
|
||
|
)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var keyFam uint32
|
||
|
if err := lnwire.ReadElements(r, &keyFam); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
s.PaymentBasePoint.Family = keychain.KeyFamily(keyFam)
|
||
|
|
||
|
err = lnwire.ReadElements(r, &s.PaymentBasePoint.Index)
|
||
|
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
|
||
|
// ben 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?
|