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

lightningnetwork / lnd / 15736109134

18 Jun 2025 02:46PM UTC coverage: 58.197% (-10.1%) from 68.248%
15736109134

Pull #9752

github

web-flow
Merge d2634a68c into 31c74f20f
Pull Request #9752: routerrpc: reject payment to invoice that don't have payment secret or blinded paths

6 of 13 new or added lines in 2 files covered. (46.15%)

28331 existing lines in 455 files now uncovered.

97860 of 168153 relevant lines covered (58.2%)

1.81 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