github.com/decred/dcrlnd@v0.7.6/chanbackup/multi.go (about)

     1  package chanbackup
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/decred/dcrlnd/keychain"
     9  	"github.com/decred/dcrlnd/lnwire"
    10  )
    11  
    12  // MultiBackupVersion denotes the version of the multi channel static channel
    13  // backup. Based on this version, we know how to encode/decode packed/unpacked
    14  // versions of multi backups.
    15  type MultiBackupVersion byte
    16  
    17  const (
    18  	// DefaultMultiVersion is the default version of the multi channel
    19  	// backup. The serialized format for this version is simply: version ||
    20  	// numBackups || SCBs...
    21  	DefaultMultiVersion = 0
    22  
    23  	// NilMultiSizePacked is the size of a "nil" packed Multi (45 bytes).
    24  	// This consists of the 24 byte chacha nonce, the 16 byte MAC, one byte
    25  	// for the version, and 4 bytes to signal zero entries.
    26  	NilMultiSizePacked = 24 + 16 + 1 + 4
    27  )
    28  
    29  // Multi is a form of static channel backup that is amenable to being
    30  // serialized in a single file. Rather than a series of ciphertexts, a
    31  // multi-chan backup is a single ciphertext of all static channel backups
    32  // concatenated. This form factor gives users a single blob that they can use
    33  // to safely copy/obtain at anytime to backup their channels.
    34  type Multi struct {
    35  	// Version is the version that should be observed when attempting to
    36  	// pack the multi backup.
    37  	Version MultiBackupVersion
    38  
    39  	// StaticBackups is the set of single channel backups that this multi
    40  	// backup is comprised of.
    41  	StaticBackups []Single
    42  }
    43  
    44  // PackToWriter packs (encrypts+serializes) the target set of static channel
    45  // backups into a single AEAD ciphertext into the passed io.Writer. This is the
    46  // opposite of UnpackFromReader. The plaintext form of a multi-chan backup is
    47  // the following: a 4 byte integer denoting the number of serialized static
    48  // channel backups serialized, a series of serialized static channel backups
    49  // concatenated. To pack this payload, we then apply our chacha20 AEAD to the
    50  // entire payload, using the 24-byte nonce as associated data.
    51  func (m Multi) PackToWriter(w io.Writer, keyRing keychain.KeyRing) error {
    52  	// The only version that we know how to pack atm is version 0. Attempts
    53  	// to pack any other version will result in an error.
    54  	switch m.Version {
    55  	case DefaultMultiVersion:
    56  		break
    57  
    58  	default:
    59  		return fmt.Errorf("unable to pack unknown multi-version "+
    60  			"of %v", m.Version)
    61  	}
    62  
    63  	var multiBackupBuffer bytes.Buffer
    64  
    65  	// First, we'll write out the version of this multi channel baackup.
    66  	err := lnwire.WriteElements(&multiBackupBuffer, byte(m.Version))
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	// Now that we've written out the version of this multi-pack format,
    72  	// we'll now write the total number of backups to expect after this
    73  	// point.
    74  	numBackups := uint32(len(m.StaticBackups))
    75  	err = lnwire.WriteElements(&multiBackupBuffer, numBackups)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	// Next, we'll serialize the raw plaintext version of each of the
    81  	// backup into the intermediate buffer.
    82  	for _, chanBackup := range m.StaticBackups {
    83  		err := chanBackup.Serialize(&multiBackupBuffer)
    84  		if err != nil {
    85  			return fmt.Errorf("unable to serialize backup "+
    86  				"for %v: %v", chanBackup.FundingOutpoint, err)
    87  		}
    88  	}
    89  
    90  	// With the plaintext multi backup assembled, we'll now encrypt it
    91  	// directly to the passed writer.
    92  	return encryptPayloadToWriter(multiBackupBuffer, w, keyRing)
    93  }
    94  
    95  // UnpackFromReader attempts to unpack (decrypt+deserialize) a packed
    96  // multi-chan backup form the passed io.Reader. If we're unable to decrypt the
    97  // any portion of the multi-chan backup, an error will be returned.
    98  func (m *Multi) UnpackFromReader(r io.Reader, keyRing keychain.KeyRing) error {
    99  	// We'll attempt to read the entire packed backup, and also decrypt it
   100  	// using the passed key ring which is expected to be able to derive the
   101  	// encryption keys.
   102  	plaintextBackup, err := decryptPayloadFromReader(r, keyRing)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	backupReader := bytes.NewReader(plaintextBackup)
   107  
   108  	// Now that we've decrypted the payload successfully, we can parse out
   109  	// each of the individual static channel backups.
   110  
   111  	// First, we'll need to read the version of this multi-back up so we
   112  	// can know how to unpack each of the individual SCB's.
   113  	var multiVersion byte
   114  	err = lnwire.ReadElements(backupReader, &multiVersion)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	m.Version = MultiBackupVersion(multiVersion)
   120  	switch m.Version {
   121  
   122  	// The default version is simply a set of serialized SCB's with the
   123  	// number of total SCB's prepended to the front of the byte slice.
   124  	case DefaultMultiVersion:
   125  		// First, we'll need to read out the total number of backups
   126  		// that've been serialized into this multi-chan backup. Each
   127  		// backup is the same size, so we can continue until we've
   128  		// parsed out everything.
   129  		var numBackups uint32
   130  		err = lnwire.ReadElements(backupReader, &numBackups)
   131  		if err != nil {
   132  			return err
   133  		}
   134  
   135  		// We'll continue to parse out each backup until we've read all
   136  		// that was indicated from the length prefix.
   137  		for ; numBackups != 0; numBackups-- {
   138  			// Attempt to parse out the net static channel backup,
   139  			// if it's been malformed, then we'll return with an
   140  			// error
   141  			var chanBackup Single
   142  			err := chanBackup.Deserialize(backupReader)
   143  			if err != nil {
   144  				return err
   145  			}
   146  
   147  			// Collect the next valid chan backup into the main
   148  			// multi backup slice.
   149  			m.StaticBackups = append(m.StaticBackups, chanBackup)
   150  		}
   151  
   152  	default:
   153  		return fmt.Errorf("unable to unpack unknown multi-version "+
   154  			"of %v", multiVersion)
   155  	}
   156  
   157  	return nil
   158  }
   159  
   160  // TODO(roasbeef): new key ring interface?
   161  //  * just returns key given params?
   162  
   163  // PackedMulti represents a raw fully packed (serialized+encrypted)
   164  // multi-channel static channel backup.
   165  type PackedMulti []byte
   166  
   167  // Unpack attempts to unpack (decrypt+desrialize) the target packed
   168  // multi-channel back up. If we're unable to fully unpack this back, then an
   169  // error will be returned.
   170  func (p *PackedMulti) Unpack(keyRing keychain.KeyRing) (*Multi, error) {
   171  	var m Multi
   172  
   173  	packedReader := bytes.NewReader(*p)
   174  	if err := m.UnpackFromReader(packedReader, keyRing); err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	return &m, nil
   179  }
   180  
   181  // TODO(roasbsef): fuzz parsing