-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
multi.go
191 lines (161 loc) · 6.29 KB
/
multi.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package chanbackup
import (
"bytes"
"fmt"
"io"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnencrypt"
"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
// NilMultiSizePacked is the size of a "nil" packed Multi (45 bytes).
// This consists of the 24 byte chacha nonce, the 16 byte MAC, one byte
// for the version, and 4 bytes to signal zero entries.
NilMultiSizePacked = 24 + 16 + 1 + 4
)
// 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.
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate encrypt key %w", err)
}
return e.EncryptPayloadToWriter(multiBackupBuffer.Bytes(), w)
}
// 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.
e, err := lnencrypt.KeyRingEncrypter(keyRing)
if err != nil {
return fmt.Errorf("unable to generate encrypt key %w", err)
}
plaintextBackup, err := e.DecryptPayloadFromReader(r)
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