lnd.xprv/chanbackup/multi.go
Olaoluwa Osuntokun 71df4b0545
chanbackup: introduce Multi, a multi-channel backup
In this commit, we introduce the Multi sturct. Multi is a series of
static channel backups. This type of backup can contains ALL the channel
backup state in a single packed blob. This is suitable for storing on
your file system, cloud storage, etc. Systems will be in place within
lnd to ensure that one can easily obtain the latest version of the Multi
for the node, and also that it will be kept up to date if channel state
changes.
2019-01-23 18:11:27 -08:00

177 lines
5.8 KiB
Go

package chanbackup
import (
"bytes"
"fmt"
"io"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
)
// MultiBackupVersion denotes the version of the multi channel static channel
// backup. Based on this version, we know how to encode/decode packed/unpacked
// versions of multi backups.
type MultiBackupVersion byte
const (
// DefaultMultiVersion is the default version of the multi channel
// backup. The serialized format for this version is simply: version ||
// numBackups || SCBs...
DefaultMultiVersion = 0
)
// Multi is a form of static channel backup that is amenable to being
// serialized in a single file. Rather than a series of ciphertexts, a
// multi-chan backup is a single ciphertext of all static channel backups
// concatenated. This form factor gives users a single blob that they can use
// to safely copy/obtain at anytime to backup their channels.
type Multi struct {
// Version is the version that should be observed when attempting to
// pack the multi backup.
Version MultiBackupVersion
// StaticBackups is the set of single channel backups that this multi
// backup is comprised of.
StaticBackups []Single
}
// PackToWriter packs (encrypts+serializes) the target set of static channel
// backups into a single AEAD ciphertext into the passed io.Writer. This is the
// opposite of UnpackFromReader. The plaintext form of a multi-chan backup is
// the following: a 4 byte integer denoting the number of serialized static
// channel backups serialized, a series of serialized static channel backups
// concatenated. To pack this payload, we then apply our chacha20 AEAD to the
// entire payload, using the 24-byte nonce as associated data.
func (m Multi) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
// The only version that we know how to pack atm is version 0. Attempts
// to pack any other version will result in an error.
switch m.Version {
case DefaultMultiVersion:
break
default:
return fmt.Errorf("unable to pack unknown multi-version "+
"of %v", m.Version)
}
var multiBackupBuffer bytes.Buffer
// First, we'll write out the version of this multi channel baackup.
err := lnwire.WriteElements(&multiBackupBuffer, byte(m.Version))
if err != nil {
return err
}
// Now that we've written out the version of this multi-pack format,
// we'll now write the total number of backups to expect after this
// point.
numBackups := uint32(len(m.StaticBackups))
err = lnwire.WriteElements(&multiBackupBuffer, numBackups)
if err != nil {
return err
}
// Next, we'll serialize the raw plaintext version of each of the
// backup into the intermediate buffer.
for _, chanBackup := range m.StaticBackups {
err := chanBackup.Serialize(&multiBackupBuffer)
if err != nil {
return fmt.Errorf("unable to serialize backup "+
"for %v: %v", chanBackup.FundingOutpoint, err)
}
}
// With the plaintext multi backup assembled, we'll now encrypt it
// directly to the passed writer.
return encryptPayloadToWriter(multiBackupBuffer, w, keyRing)
}
// UnpackFromReader attempts to unpack (decrypt+deserialize) a packed
// multi-chan backup form the passed io.Reader. If we're unable to decrypt the
// any portion of the multi-chan backup, an error will be returned.
func (m *Multi) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
// We'll attempt to read the entire packed backup, and also decrypt it
// using the passed key ring which is expected to be able to derive the
// encryption keys.
plaintextBackup, err := decryptPayloadFromReader(r, keyRing)
if err != nil {
return err
}
backupReader := bytes.NewReader(plaintextBackup)
// Now that we've decrypted the payload successfully, we can parse out
// each of the individual static channel backups.
// First, we'll need to read the version of this multi-back up so we
// can know how to unpack each of the individual SCB's.
var multiVersion byte
err = lnwire.ReadElements(backupReader, &multiVersion)
if err != nil {
return err
}
m.Version = MultiBackupVersion(multiVersion)
switch m.Version {
// The default version is simply a set of serialized SCB's with the
// number of total SCB's prepended to the front of the byte slice.
case DefaultMultiVersion:
// First, we'll need to read out the total number of backups
// that've been serialized into this multi-chan backup. Each
// backup is the same size, so we can continue until we've
// parsed out everything.
var numBackups uint32
err = lnwire.ReadElements(backupReader, &numBackups)
if err != nil {
return err
}
// We'll continue to parse out each backup until we've read all
// that was indicated from the length prefix.
for ; numBackups != 0; numBackups-- {
// Attempt to parse out the net static channel backup,
// if it's been malformed, then we'll return with an
// error
var chanBackup Single
err := chanBackup.Deserialize(backupReader)
if err != nil {
return err
}
// Collect the next valid chan backup into the main
// multi backup slice.
m.StaticBackups = append(m.StaticBackups, chanBackup)
}
default:
return fmt.Errorf("unable to unpack unknown multi-version "+
"of %v", multiVersion)
}
return nil
}
// TODO(roasbeef): new key ring interface?
// * just returns key given params?
// PackedMulti represents a raw fully packed (serialized+encrypted)
// multi-channel static channel backup.
type PackedMulti []byte
// Unpack attempts to unpack (decrypt+desrialize) the target packed
// multi-channel back up. If we're unable to fully unpack this back, then an
// error will be returned.
func (p *PackedMulti) Unpack(keyRing keychain.KeyRing) (*Multi, error) {
var m Multi
packedReader := bytes.NewReader(*p)
if err := m.UnpackFromReader(packedReader, keyRing); err != nil {
return nil, err
}
return &m, nil
}
// TODO(roasbsef): fuzz parsing