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

lightningnetwork / lnd / 14259349056

04 Apr 2025 06:24AM UTC coverage: 69.043% (+10.4%) from 58.637%
14259349056

Pull #9635

github

web-flow
Merge 6155d9bf5 into 6a3845b79
Pull Request #9635: aezeed : add error handling for unexpected plaintext size in decipherCiph…

1 of 6 new or added lines in 1 file covered. (16.67%)

26 existing lines in 7 files now uncovered.

133435 of 193265 relevant lines covered (69.04%)

22207.23 hits per line

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

88.04
/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 {
118✔
142
        return &SeedOptions{
118✔
143
                randomnessSource: rand.Reader,
118✔
144
        }
118✔
145
}
118✔
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.
153
func WithRandomnessSource(src io.Reader) SeedOptionModifier {
1✔
154
        return func(opts *SeedOptions) {
2✔
155
                opts.randomnessSource = src
1✔
156
        }
1✔
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) {
118✔
209

118✔
210
        opts := DefaultOptions()
118✔
211
        for _, modifier := range modifiers {
119✔
212
                modifier(opts)
1✔
213
        }
1✔
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
118✔
219
        if entropy == nil {
120✔
220
                if _, err := opts.randomnessSource.Read(seed[:]); err != nil {
2✔
221
                        return nil, err
×
222
                }
×
223
        } else {
116✔
224
                // Otherwise, we'll copy the set of bytes.
116✔
225
                copy(seed[:], entropy[:])
116✔
226
        }
116✔
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))
118✔
232

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

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

245
        return c, nil
118✔
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 {
221✔
251
        err := binary.Write(w, binary.BigEndian, c.InternalVersion)
221✔
252
        if err != nil {
221✔
253
                return err
×
254
        }
×
255

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

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

264
        return nil
221✔
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 {
211✔
270
        err := binary.Read(r, binary.BigEndian, &c.InternalVersion)
211✔
271
        if err != nil {
211✔
272
                return err
×
273
        }
×
274

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

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

283
        return nil
211✔
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 {
121✔
289
        var ad [adSize]byte
121✔
290
        ad[0] = version
121✔
291
        copy(ad[1:], salt[:])
121✔
292

121✔
293
        return ad
121✔
294
}
121✔
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 {
113✔
300
        var ad [adSize]byte
113✔
301
        ad[0] = encipheredSeed[0]
113✔
302

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

113✔
305
        return ad
113✔
306
}
113✔
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) {
121✔
314

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

121✔
317
        // If the passphrase wasn't provided, then we'll use the string
121✔
318
        // "aezeed" in place.
121✔
319
        passphrase := pass
121✔
320
        if len(passphrase) == 0 {
125✔
321
                passphrase = defaultPassphrase
4✔
322
        }
4✔
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(
121✔
327
                passphrase, c.salt[:], scryptN, scryptR, scryptP, keyLen,
121✔
328
        )
121✔
329
        if err != nil {
121✔
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
121✔
336
        if err := c.encode(&seedBytes); err != nil {
121✔
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)
121✔
344

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

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

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

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

121✔
368
        return cipherSeedBytes, nil
121✔
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) {
218✔
375

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

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

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

390
                words[i] = DefaultWordList[index]
5,163✔
391
        }
392

393
        return words, nil
218✔
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) {
117✔
400
        // First, we'll convert the valid seed triple into an aez cipher text
117✔
401
        // with our KDF salt appended to it.
117✔
402
        cipherText, err := c.encipher(pass)
117✔
403
        if err != nil {
117✔
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)
117✔
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) {
7✔
416

7✔
417
        return c.encipher(pass)
7✔
418
}
7✔
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 {
214✔
440
        var cipherText [EncipheredCipherSeedSize]byte
214✔
441

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

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

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

214✔
458
        return cipherText
214✔
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) {
116✔
466
        // First, we'll attempt to decipher the mnemonic by mapping back into
116✔
467
        // our byte slice and applying our deciphering scheme.
116✔
468
        plainSeed, salt, err := m.Decipher(pass)
116✔
469
        if err != nil {
122✔
470
                return nil, err
6✔
471
        }
6✔
472

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

482
        return &c, nil
110✔
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) {
115✔
490

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

115✔
496
        // Before we do anything, we'll ensure that the version is one that we
115✔
497
        // understand. Otherwise, we won't be able to decrypt, or even parse
115✔
498
        // the cipher seed.
115✔
499
        if cipherSeedBytes[0] != CipherSeedVersion {
116✔
500
                return plainSeed, salt, ErrIncorrectVersion
1✔
501
        }
1✔
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])
114✔
507
        cipherSeed := cipherSeedBytes[1:saltOffset]
114✔
508
        checksum := cipherSeedBytes[checkSumOffset:]
114✔
509

114✔
510
        // Before we perform any crypto operations, we'll re-create and verify
114✔
511
        // the checksum to ensure that the user input the proper set of words.
114✔
512
        freshChecksum := crc32.Checksum(
114✔
513
                cipherSeedBytes[:checkSumOffset], crcTable,
114✔
514
        )
114✔
515
        if freshChecksum != binary.BigEndian.Uint32(checksum) {
115✔
516
                return plainSeed, salt, ErrIncorrectMnemonic
1✔
517
        }
1✔
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)
113✔
522
        if err != nil {
113✔
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)
113✔
529

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

111✔
547
        return plainSeed, salt, nil
111✔
548

549
}
550

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

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

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

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

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

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

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

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

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