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

lightningnetwork / lnd / 14068647756

25 Mar 2025 07:40PM UTC coverage: 58.6% (-10.4%) from 69.049%
14068647756

Pull #9635

github

web-flow
Merge 68c074b7f into 0a2b33abe
Pull Request #9635: aezeed : add error handling for unexpected plaintext size in decipherCiph…

1 of 7 new or added lines in 1 file covered. (14.29%)

28085 existing lines in 450 files now uncovered.

97025 of 165572 relevant lines covered (58.6%)

1.82 hits per line

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

74.73
/aezeed/cipherseed.go
1
package aezeed
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

245
        return c, nil
3✔
246
}
247

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

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

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

264
        return nil
3✔
265
}
266

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

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

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

283
        return nil
3✔
284
}
285

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

3✔
293
        return ad
3✔
294
}
3✔
295

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

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

3✔
305
        return ad
3✔
306
}
3✔
307

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

393
        return words, nil
3✔
394
}
395

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

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

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

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

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

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

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

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

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

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

3✔
458
        return cipherText
3✔
459
}
460

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

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

482
        return &c, nil
3✔
483
}
484

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

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

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

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

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

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

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

3✔
530
        // With the key, we'll attempt to decrypt the plaintext. If the
3✔
531
        // ciphertext was altered, or the passphrase is incorrect, then we'll
3✔
532
        // error out.
3✔
533
        plainSeedBytes, ok := aez.Decrypt(
3✔
534
                key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil,
3✔
535
        )
3✔
536
        if !ok {
3✔
UNCOV
537
                return plainSeed, salt, ErrInvalidPass
×
UNCOV
538
        }
×
539
        if len(plainSeedBytes) != DecipheredCipherSeedSize {
3✔
NEW
540
                return plainSeed, salt, fmt.Errorf(
×
NEW
541
                        "unexpected plaintext size: got %d, want %d",
×
NEW
542
                        len(plainSeedBytes),
×
NEW
543
                        DecipheredCipherSeedSize,
×
NEW
544
                )
×
NEW
545
        }
×
546
        copy(plainSeed[:], plainSeedBytes)
3✔
547

3✔
548
        return plainSeed, salt, nil
3✔
549

550
}
551

552
// Decipher attempts to decipher the encoded mnemonic by first mapping to the
553
// original ciphertext, then applying our deciphering scheme. ErrInvalidPass
554
// will be returned if the passphrase is incorrect.
555
func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte,
556
        [SaltSize]byte, error) {
3✔
557

3✔
558
        // Before we attempt to map the mnemonic back to the original
3✔
559
        // ciphertext, we'll ensure that all the word are actually a part of
3✔
560
        // the current default word list.
3✔
561
        wordDict := make(map[string]struct{}, len(DefaultWordList))
3✔
562
        for _, word := range DefaultWordList {
6✔
563
                wordDict[word] = struct{}{}
3✔
564
        }
3✔
565

566
        for i, word := range m {
6✔
567
                if _, ok := wordDict[word]; !ok {
3✔
UNCOV
568
                        emptySeed := [DecipheredCipherSeedSize]byte{}
×
UNCOV
569
                        return emptySeed, [SaltSize]byte{},
×
UNCOV
570
                                ErrUnknownMnemonicWord{
×
UNCOV
571
                                        Word:  word,
×
UNCOV
572
                                        Index: uint8(i),
×
UNCOV
573
                                }
×
UNCOV
574
                }
×
575
        }
576

577
        // If the passphrase wasn't provided, then we'll use the string
578
        // "aezeed" in place.
579
        passphrase := pass
3✔
580
        if len(passphrase) == 0 {
3✔
UNCOV
581
                passphrase = defaultPassphrase
×
UNCOV
582
        }
×
583

584
        // Next, we'll map the mnemonic phrase back into the original cipher
585
        // text.
586
        cipherText := mnemonicToCipherText(m)
3✔
587

3✔
588
        // Finally, we'll attempt to decipher the enciphered seed. The result
3✔
589
        // will be the raw seed minus the ciphertext expansion, external
3✔
590
        // version, and salt.
3✔
591
        return decipherCipherSeed(cipherText, passphrase)
3✔
592
}
593

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

×
UNCOV
601
        // First, we'll try to decrypt the current mnemonic using the existing
×
UNCOV
602
        // passphrase. If this fails, then we can't proceed any further.
×
UNCOV
603
        cipherSeed, err := m.ToCipherSeed(oldPass)
×
UNCOV
604
        if err != nil {
×
UNCOV
605
                return newMnemonic, err
×
UNCOV
606
        }
×
607

608
        // If the deciphering was successful, then we'll now re-encipher using
609
        // the new user provided passphrase.
UNCOV
610
        return cipherSeed.ToMnemonic(newPass)
×
611
}
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