• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.3
/aezeed/cipherseed.go
1
package aezeed
2

3
import (
4
        "bytes"
5
        "crypto/rand"
6
        "encoding/binary"
7
        "hash/crc32"
8
        "io"
9
        "time"
10

11
        "github.com/Yawning/aez"
12
        "github.com/kkdai/bstream"
13
        "golang.org/x/crypto/scrypt"
14
)
15

16
const (
17
        // CipherSeedVersion is the current version of the aezeed scheme as
18
        // defined in this package. This version indicates the following
19
        // parameters for the deciphered cipher seed: a 1 byte version, 2 bytes
20
        // for the Bitcoin Days Genesis timestamp, and 16 bytes for entropy. It
21
        // also governs how the cipher seed should be enciphered. In this
22
        // version we take the deciphered seed, create a 5 byte salt, use that
23
        // with an optional passphrase to generate a 32-byte key (via scrypt),
24
        // then encipher with aez (using the salt and version as AD). The final
25
        // enciphered seed is: version || ciphertext || salt.
26
        CipherSeedVersion uint8 = 0
27

28
        // DecipheredCipherSeedSize is the size of the plaintext seed resulting
29
        // from deciphering the cipher seed. The size consists of the
30
        // following:
31
        //
32
        //  * 1 byte version || 2 bytes timestamp || 16 bytes of entropy.
33
        //
34
        // The version is used by wallets to know how to re-derive relevant
35
        // addresses, the 2 byte timestamp a BDG (Bitcoin Days Genesis) offset,
36
        // and finally, the 16 bytes to be used to generate the HD wallet seed.
37
        DecipheredCipherSeedSize = 19
38

39
        // EncipheredCipherSeedSize is the size of the fully encoded+enciphered
40
        // cipher seed. We first obtain the enciphered plaintext seed by
41
        // carrying out the enciphering as governed in the current version. We
42
        // then take that enciphered seed (now 19+4=23 bytes due to ciphertext
43
        // expansion, essentially a checksum) and prepend a version, then
44
        // append the salt, and then take a checksum of everything. The
45
        // checksum allows us to verify that the user input the correct set of
46
        // words, then we can verify the passphrase due to the internal MAC
47
        // equiv.  The final breakdown is:
48
        //
49
        //  * 1 byte version || 23 byte enciphered seed || 5 byte salt || 4 byte checksum
50
        //
51
        // With CipherSeedVersion we encipher as follows: we use
52
        // scrypt(n=32768, r=8, p=1) to derive a 32-byte key from an optional
53
        // user passphrase. We then encipher the plaintext seed using a value
54
        // of tau (with aez) of 8-bytes (so essentially a 32-bit MAC).  When
55
        // enciphering, we include the version and scrypt salt as the AD.  This
56
        // gives us a total of 33 bytes. These 33 bytes fit cleanly into 24
57
        // mnemonic words.
58
        EncipheredCipherSeedSize = 33
59

60
        // CipherTextExpansion is the number of bytes that will be added as
61
        // redundancy for the enciphering scheme implemented by aez. This can
62
        // be seen as the size of the equivalent MAC.
63
        CipherTextExpansion = 4
64

65
        // EntropySize is the number of bytes of entropy we'll use to generate
66
        // the seed.
67
        EntropySize = 16
68

69
        // NumMnemonicWords is the number of words that an encoded cipher seed
70
        // will result in.
71
        NumMnemonicWords = 24
72

73
        // SaltSize is the size of the salt we'll generate to use with scrypt
74
        // to generate a key for use within aez from the user's passphrase. The
75
        // role of the salt is to make the creation of rainbow tables
76
        // infeasible.
77
        SaltSize = 5
78

79
        // adSize is the size of the encoded associated data that will be
80
        // passed into aez when enciphering and deciphering the seed. The AD
81
        // itself (associated data) is just the cipher seed version and salt.
82
        adSize = 6
83

84
        // checkSumSize is the size of the checksum applied to the final
85
        // encoded ciphertext.
86
        checkSumSize = 4
87

88
        // keyLen is the size of the key that we'll use for encryption with
89
        // aez.
90
        keyLen = 32
91

92
        // BitsPerWord is the number of bits each word in the wordlist encodes.
93
        // We encode our mnemonic using 24 words, so 264 bits (33 bytes).
94
        BitsPerWord = 11
95

96
        // saltOffset is the index within an enciphered cipher seed that marks
97
        // the start of the salt.
98
        saltOffset = EncipheredCipherSeedSize - checkSumSize - SaltSize
99

100
        // checkSumSize is the index within an enciphered cipher seed that
101
        // marks the start of the checksum.
102
        checkSumOffset = EncipheredCipherSeedSize - checkSumSize
103
)
104

105
var (
106
        // Below at the default scrypt parameters that are tied to cipher seed
107
        // version zero.
108
        scryptN = 32768
109
        scryptR = 8
110
        scryptP = 1
111

112
        // crcTable is a table that presents the polynomial we'll use for
113
        // computing our checksum.
114
        crcTable = crc32.MakeTable(crc32.Castagnoli)
115

116
        // defaultPassphrase is the default passphrase that will be used for
117
        // encryption in the case that the user chooses not to specify their
118
        // own passphrase.
119
        defaultPassphrase = []byte("aezeed")
120
)
121

122
var (
123
        // BitcoinGenesisDate is the timestamp of Bitcoin's genesis block.
124
        // We'll use this value in order to create a compact birthday for the
125
        // seed. The birthday will be interested as the number of days since
126
        // the genesis date. We refer to this time period as ABE (after Bitcoin
127
        // era).
128
        BitcoinGenesisDate = time.Unix(1231006505, 0)
129
)
130

131
// SeedOptions is a type that holds options that configure the generation of a
132
// new cipher seed.
133
type SeedOptions struct {
134
        // randomnessSource is the source of randomness that is used to generate
135
        // the salt that is used for encrypting the seed.
136
        randomnessSource io.Reader
137
}
138

139
// DefaultOptions returns the default seed options.
140
func DefaultOptions() *SeedOptions {
3✔
141
        return &SeedOptions{
3✔
142
                randomnessSource: rand.Reader,
3✔
143
        }
3✔
144
}
3✔
145

146
// SeedOptionModifier is a function signature for modifying the default
147
// SeedOptions.
148
type SeedOptionModifier func(*SeedOptions)
149

150
// WithRandomnessSource returns an option modifier that replaces the default
151
// randomness source with the given reader.
UNCOV
152
func WithRandomnessSource(src io.Reader) SeedOptionModifier {
×
UNCOV
153
        return func(opts *SeedOptions) {
×
UNCOV
154
                opts.randomnessSource = src
×
UNCOV
155
        }
×
156
}
157

158
// CipherSeed is a fully decoded instance of the aezeed scheme. At a high
159
// level, the encoded cipher seed is the enciphering of: a version byte, a set
160
// of bytes for a timestamp, the entropy which will be used to directly
161
// construct the HD seed, and finally a checksum over the rest. This scheme was
162
// created as the widely used schemes in the space lack two critical traits: a
163
// version byte, and a birthday timestamp. The version allows us to modify the
164
// details of the scheme in the future, and the birthday gives wallets a limit
165
// of how far back in the chain they'll need to start scanning. We also add an
166
// external version to the enciphering plaintext seed. With this addition,
167
// seeds are able to be "upgraded" (to diff params, or entirely diff crypt),
168
// while maintaining the semantics of the plaintext seed.
169
//
170
// The core of the scheme is the usage of aez to carefully control the size of
171
// the final encrypted seed. With the current parameters, this scheme can be
172
// encoded using a 24 word mnemonic. We use 4 bytes of ciphertext expansion
173
// when enciphering the raw seed, giving us the equivalent of 40-bit MAC (as we
174
// check for a particular seed version). Using the external 4 byte checksum,
175
// we're able to ensure that the user input the correct set of words.  Finally,
176
// the password in the scheme is optional. If not specified, "aezeed" will be
177
// used as the password. Otherwise, the addition of the password means that
178
// users can encrypt the raw "plaintext" seed under distinct passwords to
179
// produce unique mnemonic phrases.
180
type CipherSeed struct {
181
        // InternalVersion is the version of the plaintext cipher seed. This is
182
        // to be used by wallets to determine if the seed version is compatible
183
        // with the derivation schemes they know.
184
        InternalVersion uint8
185

186
        // Birthday is the time that the seed was created. This is expressed as
187
        // the number of days since the timestamp in the Bitcoin genesis block.
188
        // We use days as seconds gives us wasted granularity. The oldest seed
189
        // that we can encode using this format is through the date 2188.
190
        Birthday uint16
191

192
        // Entropy is a set of bytes generated via a CSPRNG. This is the value
193
        // that should be used to directly generate the HD root, as defined
194
        // within BIP0032.
195
        Entropy [EntropySize]byte
196

197
        // salt is the salt that was used to generate the key from the user's
198
        // specified passphrase.
199
        salt [SaltSize]byte
200
}
201

202
// New generates a new CipherSeed instance from an optional source of entropy.
203
// If the entropy isn't provided, then a set of random bytes will be used in
204
// place. The final fixed argument should be the time at which the seed was
205
// created, followed by optional seed option modifiers.
206
func New(internalVersion uint8, entropy *[EntropySize]byte,
207
        now time.Time, modifiers ...SeedOptionModifier) (*CipherSeed, error) {
3✔
208

3✔
209
        opts := DefaultOptions()
3✔
210
        for _, modifier := range modifiers {
3✔
UNCOV
211
                modifier(opts)
×
UNCOV
212
        }
×
213

214
        // If a set of entropy wasn't provided, then we'll read a set of bytes
215
        // from the randomness source provided (which by default is the system's
216
        // CSPRNG).
217
        var seed [EntropySize]byte
3✔
218
        if entropy == nil {
3✔
UNCOV
219
                if _, err := opts.randomnessSource.Read(seed[:]); err != nil {
×
220
                        return nil, err
×
221
                }
×
222
        } else {
3✔
223
                // Otherwise, we'll copy the set of bytes.
3✔
224
                copy(seed[:], entropy[:])
3✔
225
        }
3✔
226

227
        // To compute our "birthday", we'll first use the current time, then
228
        // subtract that from the Bitcoin Genesis Date. We'll then convert that
229
        // value to days.
230
        birthday := uint16(now.Sub(BitcoinGenesisDate) / (time.Hour * 24))
3✔
231

3✔
232
        c := &CipherSeed{
3✔
233
                InternalVersion: internalVersion,
3✔
234
                Birthday:        birthday,
3✔
235
                Entropy:         seed,
3✔
236
        }
3✔
237

3✔
238
        // Next, we'll read a random salt that will be used with scrypt to
3✔
239
        // eventually derive our key.
3✔
240
        if _, err := opts.randomnessSource.Read(c.salt[:]); err != nil {
3✔
241
                return nil, err
×
242
        }
×
243

244
        return c, nil
3✔
245
}
246

247
// encode attempts to encode the target cipherSeed into the passed io.Writer
248
// instance.
249
func (c *CipherSeed) encode(w io.Writer) error {
3✔
250
        err := binary.Write(w, binary.BigEndian, c.InternalVersion)
3✔
251
        if err != nil {
3✔
252
                return err
×
253
        }
×
254

255
        if err := binary.Write(w, binary.BigEndian, c.Birthday); err != nil {
3✔
256
                return err
×
257
        }
×
258

259
        if _, err := w.Write(c.Entropy[:]); err != nil {
3✔
260
                return err
×
261
        }
×
262

263
        return nil
3✔
264
}
265

266
// decode attempts to decode an encoded cipher seed instance into the target
267
// CipherSeed struct.
268
func (c *CipherSeed) decode(r io.Reader) error {
3✔
269
        err := binary.Read(r, binary.BigEndian, &c.InternalVersion)
3✔
270
        if err != nil {
3✔
271
                return err
×
272
        }
×
273

274
        if err := binary.Read(r, binary.BigEndian, &c.Birthday); err != nil {
3✔
275
                return err
×
276
        }
×
277

278
        if _, err := io.ReadFull(r, c.Entropy[:]); err != nil {
3✔
279
                return err
×
280
        }
×
281

282
        return nil
3✔
283
}
284

285
// encodeAD returns the fully encoded associated data for use when performing
286
// our current enciphering operation. The AD is: version || salt.
287
func encodeAD(version uint8, salt [SaltSize]byte) [adSize]byte {
3✔
288
        var ad [adSize]byte
3✔
289
        ad[0] = version
3✔
290
        copy(ad[1:], salt[:])
3✔
291

3✔
292
        return ad
3✔
293
}
3✔
294

295
// extractAD extracts an associated data from a fully encoded and enciphered
296
// cipher seed. This is to be used when attempting to decrypt an enciphered
297
// cipher seed.
298
func extractAD(encipheredSeed [EncipheredCipherSeedSize]byte) [adSize]byte {
3✔
299
        var ad [adSize]byte
3✔
300
        ad[0] = encipheredSeed[0]
3✔
301

3✔
302
        copy(ad[1:], encipheredSeed[saltOffset:checkSumOffset])
3✔
303

3✔
304
        return ad
3✔
305
}
3✔
306

307
// encipher takes a fully populated cipher seed instance, and enciphers the
308
// encoded seed, then appends a randomly generated seed used to stretch the
309
// passphrase out into an appropriate key, then computes a checksum over the
310
// preceding.
311
func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte,
312
        error) {
3✔
313

3✔
314
        var cipherSeedBytes [EncipheredCipherSeedSize]byte
3✔
315

3✔
316
        // If the passphrase wasn't provided, then we'll use the string
3✔
317
        // "aezeed" in place.
3✔
318
        passphrase := pass
3✔
319
        if len(passphrase) == 0 {
3✔
UNCOV
320
                passphrase = defaultPassphrase
×
UNCOV
321
        }
×
322

323
        // With our salt pre-generated, we'll now run the password through a
324
        // KDF to obtain the key we'll use for encryption.
325
        key, err := scrypt.Key(
3✔
326
                passphrase, c.salt[:], scryptN, scryptR, scryptP, keyLen,
3✔
327
        )
3✔
328
        if err != nil {
3✔
329
                return cipherSeedBytes, err
×
330
        }
×
331

332
        // Next, we'll encode the serialized plaintext cipher seed into a buffer
333
        // that we'll use for encryption.
334
        var seedBytes bytes.Buffer
3✔
335
        if err := c.encode(&seedBytes); err != nil {
3✔
336
                return cipherSeedBytes, err
×
337
        }
×
338

339
        // With our plaintext seed encoded, we'll now construct the AD that
340
        // will be passed to the encryption operation. This ensures to
341
        // authenticate both the salt and the external version.
342
        ad := encodeAD(CipherSeedVersion, c.salt)
3✔
343

3✔
344
        // With all items assembled, we'll now encipher the plaintext seed
3✔
345
        // with our AD, key, and MAC size.
3✔
346
        cipherSeed := seedBytes.Bytes()
3✔
347
        cipherText := aez.Encrypt(
3✔
348
                key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil,
3✔
349
        )
3✔
350

3✔
351
        // Finally, we'll pack the {version || ciphertext || salt || checksum}
3✔
352
        // seed into a byte slice for encoding as a mnemonic.
3✔
353
        cipherSeedBytes[0] = CipherSeedVersion
3✔
354
        copy(cipherSeedBytes[1:saltOffset], cipherText)
3✔
355
        copy(cipherSeedBytes[saltOffset:], c.salt[:])
3✔
356

3✔
357
        // With the seed mostly assembled, we'll now compute a checksum all the
3✔
358
        // contents.
3✔
359
        checkSum := crc32.Checksum(cipherSeedBytes[:checkSumOffset], crcTable)
3✔
360

3✔
361
        // With our checksum computed, we can finish encoding the full cipher
3✔
362
        // seed.
3✔
363
        var checkSumBytes [4]byte
3✔
364
        binary.BigEndian.PutUint32(checkSumBytes[:], checkSum)
3✔
365
        copy(cipherSeedBytes[checkSumOffset:], checkSumBytes[:])
3✔
366

3✔
367
        return cipherSeedBytes, nil
3✔
368
}
369

370
// cipherTextToMnemonic converts the aez ciphertext appended with the salt to a
371
// 24-word mnemonic pass phrase.
372
func cipherTextToMnemonic(cipherText [EncipheredCipherSeedSize]byte) (Mnemonic,
373
        error) {
3✔
374

3✔
375
        var words [NumMnemonicWords]string
3✔
376

3✔
377
        // First, we'll convert the ciphertext itself into a bitstream for easy
3✔
378
        // manipulation.
3✔
379
        cipherBits := bstream.NewBStreamReader(cipherText[:])
3✔
380

3✔
381
        // With our bitstream obtained, we'll read 11 bits at a time, then use
3✔
382
        // that to index into our word list to obtain the next word.
3✔
383
        for i := 0; i < NumMnemonicWords; i++ {
6✔
384
                index, err := cipherBits.ReadBits(BitsPerWord)
3✔
385
                if err != nil {
3✔
386
                        return Mnemonic{}, err
×
387
                }
×
388

389
                words[i] = DefaultWordList[index]
3✔
390
        }
391

392
        return words, nil
3✔
393
}
394

395
// ToMnemonic maps the final enciphered cipher seed to a human-readable 24-word
396
// mnemonic phrase. The password is optional, as if it isn't specified aezeed
397
// will be used in its place.
398
func (c *CipherSeed) ToMnemonic(pass []byte) (Mnemonic, error) {
3✔
399
        // First, we'll convert the valid seed triple into an aez cipher text
3✔
400
        // with our KDF salt appended to it.
3✔
401
        cipherText, err := c.encipher(pass)
3✔
402
        if err != nil {
3✔
403
                return Mnemonic{}, err
×
404
        }
×
405

406
        // Now that we have our cipher text, we'll convert it into a mnemonic
407
        // phrase.
408
        return cipherTextToMnemonic(cipherText)
3✔
409
}
410

411
// Encipher maps the cipher seed to an aez ciphertext using an optional
412
// passphrase.
413
func (c *CipherSeed) Encipher(pass []byte) ([EncipheredCipherSeedSize]byte,
414
        error) {
3✔
415

3✔
416
        return c.encipher(pass)
3✔
417
}
3✔
418

419
// BirthdayTime returns the cipher seed's internal birthday format as a native
420
// golang Time struct.
421
func (c *CipherSeed) BirthdayTime() time.Time {
3✔
422
        offset := time.Duration(c.Birthday) * 24 * time.Hour
3✔
423
        return BitcoinGenesisDate.Add(offset)
3✔
424
}
3✔
425

426
// Mnemonic is a 24-word passphrase as of cipher seed version zero. This
427
// passphrase encodes an encrypted seed triple (version, birthday, entropy).
428
// Additionally, we also encode the salt used with scrypt to derive the key
429
// that the cipher text is encrypted with, and the version which tells us how
430
// to decipher the seed.
431
type Mnemonic [NumMnemonicWords]string
432

433
// mnemonicToCipherText converts a 24-word mnemonic phrase into a 33 byte
434
// cipher text.
435
//
436
// NOTE: This assumes that all words have already been checked to be amongst
437
// our word list.
438
func mnemonicToCipherText(mnemonic *Mnemonic) [EncipheredCipherSeedSize]byte {
3✔
439
        var cipherText [EncipheredCipherSeedSize]byte
3✔
440

3✔
441
        // We'll now perform the reverse mapping to that of
3✔
442
        // cipherTextToMnemonic: we'll get the index of the word, then write
3✔
443
        // out that index to the bit stream.
3✔
444
        cipherBits := bstream.NewBStreamWriter(EncipheredCipherSeedSize)
3✔
445
        for _, word := range mnemonic {
6✔
446
                // Using the reverse word map, we'll locate the index of this
3✔
447
                // word within the word list.
3✔
448
                index := uint64(ReverseWordMap[word])
3✔
449

3✔
450
                // With the index located, we'll now write this out to the
3✔
451
                // bitstream, appending to what's already there.
3✔
452
                cipherBits.WriteBits(index, BitsPerWord)
3✔
453
        }
3✔
454

455
        copy(cipherText[:], cipherBits.Bytes())
3✔
456

3✔
457
        return cipherText
3✔
458
}
459

460
// ToCipherSeed attempts to map the mnemonic to the original cipher text byte
461
// slice. Then we'll attempt to decrypt the ciphertext using aez with the
462
// passed passphrase, using the last 5 bytes of the ciphertext as a salt for
463
// the KDF.
464
func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) {
3✔
465
        // First, we'll attempt to decipher the mnemonic by mapping back into
3✔
466
        // our byte slice and applying our deciphering scheme.
3✔
467
        plainSeed, salt, err := m.Decipher(pass)
3✔
468
        if err != nil {
3✔
UNCOV
469
                return nil, err
×
UNCOV
470
        }
×
471

472
        // If decryption was successful, then we'll decode into a fresh
473
        // CipherSeed struct.
474
        c := CipherSeed{
3✔
475
                salt: salt,
3✔
476
        }
3✔
477
        if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil {
3✔
478
                return nil, err
×
479
        }
×
480

481
        return &c, nil
3✔
482
}
483

484
// decipherCipherSeed attempts to decipher the passed cipher seed ciphertext
485
// using the passed passphrase. This function is the opposite of
486
// the encipher method.
487
func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
488
        pass []byte) ([DecipheredCipherSeedSize]byte, [SaltSize]byte, error) {
3✔
489

3✔
490
        var (
3✔
491
                plainSeed [DecipheredCipherSeedSize]byte
3✔
492
                salt      [SaltSize]byte
3✔
493
        )
3✔
494

3✔
495
        // Before we do anything, we'll ensure that the version is one that we
3✔
496
        // understand. Otherwise, we won't be able to decrypt, or even parse
3✔
497
        // the cipher seed.
3✔
498
        if cipherSeedBytes[0] != CipherSeedVersion {
3✔
UNCOV
499
                return plainSeed, salt, ErrIncorrectVersion
×
UNCOV
500
        }
×
501

502
        // Next, we'll slice off the salt from the pass cipher seed, then
503
        // snip off the end of the cipher seed, ignoring the version, and
504
        // finally the checksum.
505
        copy(salt[:], cipherSeedBytes[saltOffset:saltOffset+SaltSize])
3✔
506
        cipherSeed := cipherSeedBytes[1:saltOffset]
3✔
507
        checksum := cipherSeedBytes[checkSumOffset:]
3✔
508

3✔
509
        // Before we perform any crypto operations, we'll re-create and verify
3✔
510
        // the checksum to ensure that the user input the proper set of words.
3✔
511
        freshChecksum := crc32.Checksum(
3✔
512
                cipherSeedBytes[:checkSumOffset], crcTable,
3✔
513
        )
3✔
514
        if freshChecksum != binary.BigEndian.Uint32(checksum) {
3✔
UNCOV
515
                return plainSeed, salt, ErrIncorrectMnemonic
×
UNCOV
516
        }
×
517

518
        // With the salt separated from the cipher text, we'll now obtain the
519
        // key used for encryption.
520
        key, err := scrypt.Key(pass, salt[:], scryptN, scryptR, scryptP, keyLen)
3✔
521
        if err != nil {
3✔
522
                return plainSeed, salt, err
×
523
        }
×
524

525
        // We'll also extract the AD that will be required to properly pass the
526
        // MAC check.
527
        ad := extractAD(cipherSeedBytes)
3✔
528

3✔
529
        // With the key, we'll attempt to decrypt the plaintext. If the
3✔
530
        // ciphertext was altered, or the passphrase is incorrect, then we'll
3✔
531
        // error out.
3✔
532
        plainSeedBytes, ok := aez.Decrypt(
3✔
533
                key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil,
3✔
534
        )
3✔
535
        if !ok {
3✔
UNCOV
536
                return plainSeed, salt, ErrInvalidPass
×
UNCOV
537
        }
×
538
        copy(plainSeed[:], plainSeedBytes)
3✔
539

3✔
540
        return plainSeed, salt, nil
3✔
541

542
}
543

544
// Decipher attempts to decipher the encoded mnemonic by first mapping to the
545
// original ciphertext, then applying our deciphering scheme. ErrInvalidPass
546
// will be returned if the passphrase is incorrect.
547
func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte,
548
        [SaltSize]byte, error) {
3✔
549

3✔
550
        // Before we attempt to map the mnemonic back to the original
3✔
551
        // ciphertext, we'll ensure that all the word are actually a part of
3✔
552
        // the current default word list.
3✔
553
        wordDict := make(map[string]struct{}, len(DefaultWordList))
3✔
554
        for _, word := range DefaultWordList {
6✔
555
                wordDict[word] = struct{}{}
3✔
556
        }
3✔
557

558
        for i, word := range m {
6✔
559
                if _, ok := wordDict[word]; !ok {
3✔
UNCOV
560
                        emptySeed := [DecipheredCipherSeedSize]byte{}
×
UNCOV
561
                        return emptySeed, [SaltSize]byte{},
×
UNCOV
562
                                ErrUnknownMnemonicWord{
×
UNCOV
563
                                        Word:  word,
×
UNCOV
564
                                        Index: uint8(i),
×
UNCOV
565
                                }
×
UNCOV
566
                }
×
567
        }
568

569
        // If the passphrase wasn't provided, then we'll use the string
570
        // "aezeed" in place.
571
        passphrase := pass
3✔
572
        if len(passphrase) == 0 {
3✔
UNCOV
573
                passphrase = defaultPassphrase
×
UNCOV
574
        }
×
575

576
        // Next, we'll map the mnemonic phrase back into the original cipher
577
        // text.
578
        cipherText := mnemonicToCipherText(m)
3✔
579

3✔
580
        // Finally, we'll attempt to decipher the enciphered seed. The result
3✔
581
        // will be the raw seed minus the ciphertext expansion, external
3✔
582
        // version, and salt.
3✔
583
        return decipherCipherSeed(cipherText, passphrase)
3✔
584
}
585

586
// ChangePass takes an existing mnemonic, and passphrase for said mnemonic and
587
// re-enciphers the plaintext cipher seed into a brand-new mnemonic. This can
588
// be used to allow users to re-encrypt the same seed with multiple pass
589
// phrases, or just change the passphrase on an existing seed.
UNCOV
590
func (m *Mnemonic) ChangePass(oldPass, newPass []byte) (Mnemonic, error) {
×
UNCOV
591
        var newMnemonic Mnemonic
×
UNCOV
592

×
UNCOV
593
        // First, we'll try to decrypt the current mnemonic using the existing
×
UNCOV
594
        // passphrase. If this fails, then we can't proceed any further.
×
UNCOV
595
        cipherSeed, err := m.ToCipherSeed(oldPass)
×
UNCOV
596
        if err != nil {
×
UNCOV
597
                return newMnemonic, err
×
UNCOV
598
        }
×
599

600
        // If the deciphering was successful, then we'll now re-encipher using
601
        // the new user provided passphrase.
UNCOV
602
        return cipherSeed.ToMnemonic(newPass)
×
603
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc