github.com/decred/dcrlnd@v0.7.6/aezeed/cipherseed_test.go (about) 1 package aezeed 2 3 import ( 4 "bytes" 5 "math/rand" 6 "testing" 7 "testing/quick" 8 "time" 9 ) 10 11 // TestVector defines the values that are used to create a fully initialized 12 // aezeed mnemonic seed and the expected values that should be calculated. 13 type TestVector struct { 14 version uint8 15 time time.Time 16 entropy [EntropySize]byte 17 salt [saltSize]byte 18 password []byte 19 expectedMnemonic [NumMnemonicWords]string 20 expectedBirthday uint16 21 } 22 23 var ( 24 testEntropy = [EntropySize]byte{ 25 0x81, 0xb6, 0x37, 0xd8, 26 0x63, 0x59, 0xe6, 0x96, 27 0x0d, 0xe7, 0x95, 0xe4, 28 0x1e, 0x0b, 0x4c, 0xfd, 29 } 30 testSalt = [saltSize]byte{ 31 0x73, 0x61, 0x6c, 0x74, 0x31, // equal to "salt1" 32 } 33 version0TestVectors = []TestVector{ 34 { 35 version: 0, 36 time: DecredGenesisDate, 37 entropy: testEntropy, 38 salt: testSalt, 39 password: []byte{}, 40 expectedMnemonic: [NumMnemonicWords]string{ 41 "ability", "liquid", "travel", "stem", "barely", "drastic", 42 "pact", "cupboard", "apple", "thrive", "morning", "oak", 43 "feature", "tissue", "couch", "old", "math", "inform", 44 "success", "suggest", "drink", "motion", "know", "royal", 45 }, 46 expectedBirthday: 0, 47 }, 48 { 49 version: 0, 50 time: time.Unix(1521799345, 0), // 03/23/2018 @ 10:02am (UTC) 51 entropy: testEntropy, 52 salt: testSalt, 53 password: []byte("!very_safe_55345_password*"), 54 expectedMnemonic: [NumMnemonicWords]string{ 55 "absorb", "life", "portion", "oxygen", "stock", "pottery", 56 "soup", "build", "ugly", "jeans", "small", "uniform", 57 "letter", "voyage", "spirit", "age", "fury", "entire", 58 "success", "suggest", "drink", "office", "view", "dizzy", 59 }, 60 expectedBirthday: 773, 61 }, 62 } 63 ) 64 65 func assertCipherSeedEqual(t *testing.T, cipherSeed *CipherSeed, 66 cipherSeed2 *CipherSeed) { 67 68 if cipherSeed.InternalVersion != cipherSeed2.InternalVersion { 69 t.Fatalf("mismatched versions: expected %v, got %v", 70 cipherSeed.InternalVersion, cipherSeed2.InternalVersion) 71 } 72 if cipherSeed.Birthday != cipherSeed2.Birthday { 73 t.Fatalf("mismatched birthday: expected %v, got %v", 74 cipherSeed.Birthday, cipherSeed2.Birthday) 75 } 76 if cipherSeed.Entropy != cipherSeed2.Entropy { 77 t.Fatalf("mismatched versions: expected %x, got %x", 78 cipherSeed.Entropy[:], cipherSeed2.Entropy[:]) 79 } 80 } 81 82 // TestAezeedVersion0TestVectors tests some fixed test vector values against 83 // the expected mnemonic words. 84 func TestAezeedVersion0TestVectors(t *testing.T) { 85 t.Parallel() 86 87 // To minimize the number of tests that need to be run, 88 // go through all test vectors in the same test and also check 89 // the birthday calculation while we're at it. 90 for _, v := range version0TestVectors { 91 // First, we create new cipher seed with the given values 92 // from the test vector. 93 cipherSeed, err := New(v.version, &v.entropy, v.time) 94 if err != nil { 95 t.Fatalf("unable to create seed: %v", err) 96 } 97 98 // Then we need to set the salt to the pre-defined value, otherwise 99 // we'll end up with randomness in our mnemonics. 100 cipherSeed.salt = testSalt 101 102 // Now that the seed has been created, we'll attempt to convert it to a 103 // valid mnemonic. 104 mnemonic, err := cipherSeed.ToMnemonic(v.password) 105 if err != nil { 106 t.Fatalf("unable to create mnemonic: %v", err) 107 } 108 109 // Finally we compare the generated mnemonic and birthday to the 110 // expected value. 111 if mnemonic != v.expectedMnemonic { 112 t.Fatalf("mismatched mnemonic: expected %s, got %s", 113 v.expectedMnemonic, mnemonic) 114 } 115 if cipherSeed.Birthday != v.expectedBirthday { 116 t.Fatalf("mismatched birthday: expected %v, got %v", 117 v.expectedBirthday, cipherSeed.Birthday) 118 } 119 } 120 } 121 122 // TestEmptyPassphraseDerivation tests that the aezeed scheme is able to derive 123 // a proper mnemonic, and decipher that mnemonic when the user uses an empty 124 // passphrase. 125 func TestEmptyPassphraseDerivation(t *testing.T) { 126 t.Parallel() 127 128 // Our empty passphrase... 129 pass := []byte{} 130 131 // We'll now create a new cipher seed with an internal version of zero 132 // to simulate a wallet that just adopted the scheme. 133 cipherSeed, err := New(0, &testEntropy, time.Now()) 134 if err != nil { 135 t.Fatalf("unable to create seed: %v", err) 136 } 137 138 // Now that the seed has been created, we'll attempt to convert it to a 139 // valid mnemonic. 140 mnemonic, err := cipherSeed.ToMnemonic(pass) 141 if err != nil { 142 t.Fatalf("unable to create mnemonic: %v", err) 143 } 144 145 // Next, we'll try to decrypt the mnemonic with the passphrase that we 146 // used. 147 cipherSeed2, err := mnemonic.ToCipherSeed(pass) 148 if err != nil { 149 t.Fatalf("unable to decrypt mnemonic: %v", err) 150 } 151 152 // Finally, we'll ensure that the uncovered cipher seed matches 153 // precisely. 154 assertCipherSeedEqual(t, cipherSeed, cipherSeed2) 155 } 156 157 // TestManualEntropyGeneration tests that if the user doesn't provide a source 158 // of entropy, then we do so ourselves. 159 func TestManualEntropyGeneration(t *testing.T) { 160 t.Parallel() 161 162 // Our empty passphrase... 163 pass := []byte{} 164 165 // We'll now create a new cipher seed with an internal version of zero 166 // to simulate a wallet that just adopted the scheme. 167 cipherSeed, err := New(0, nil, time.Now()) 168 if err != nil { 169 t.Fatalf("unable to create seed: %v", err) 170 } 171 172 // Now that the seed has been created, we'll attempt to convert it to a 173 // valid mnemonic. 174 mnemonic, err := cipherSeed.ToMnemonic(pass) 175 if err != nil { 176 t.Fatalf("unable to create mnemonic: %v", err) 177 } 178 179 // Next, we'll try to decrypt the mnemonic with the passphrase that we 180 // used. 181 cipherSeed2, err := mnemonic.ToCipherSeed(pass) 182 if err != nil { 183 t.Fatalf("unable to decrypt mnemonic: %v", err) 184 } 185 186 // Finally, we'll ensure that the uncovered cipher seed matches 187 // precisely. 188 assertCipherSeedEqual(t, cipherSeed, cipherSeed2) 189 } 190 191 // TestInvalidPassphraseRejection tests if a caller attempts to use the 192 // incorrect passprhase for an enciphered seed, then the proper error is 193 // returned. 194 func TestInvalidPassphraseRejection(t *testing.T) { 195 t.Parallel() 196 197 // First, we'll generate a new cipher seed with a test passphrase. 198 pass := []byte("test") 199 cipherSeed, err := New(0, &testEntropy, time.Now()) 200 if err != nil { 201 t.Fatalf("unable to create seed: %v", err) 202 } 203 204 // Now that we have our cipher seed, we'll encipher it and request a 205 // mnemonic that we can use to recover later. 206 mnemonic, err := cipherSeed.ToMnemonic(pass) 207 if err != nil { 208 t.Fatalf("unable to create mnemonic: %v", err) 209 } 210 211 // If we try to decipher with the wrong passphrase, we should get the 212 // proper error. 213 wrongPass := []byte("kek") 214 if _, err := mnemonic.ToCipherSeed(wrongPass); err != ErrInvalidPass { 215 t.Fatalf("expected ErrInvalidPass, instead got %v", err) 216 } 217 } 218 219 // TestRawEncipherDecipher tests that callers are able to use the raw methods 220 // to map between ciphertext and the raw plaintext deciphered seed. 221 func TestRawEncipherDecipher(t *testing.T) { 222 t.Parallel() 223 224 // First, we'll generate a new cipher seed with a test passphrase. 225 pass := []byte("test") 226 cipherSeed, err := New(0, &testEntropy, time.Now()) 227 if err != nil { 228 t.Fatalf("unable to create seed: %v", err) 229 } 230 231 // With the cipherseed obtained, we'll now use the raw encipher method 232 // to obtain our final cipher text. 233 cipherText, err := cipherSeed.Encipher(pass) 234 if err != nil { 235 t.Fatalf("unable to encipher seed: %v", err) 236 } 237 238 mnemonic, err := cipherTextToMnemonic(cipherText) 239 if err != nil { 240 t.Fatalf("unable to create mnemonic: %v", err) 241 } 242 243 // Now that we have the ciphertext (mapped to the mnemonic), we'll 244 // attempt to decipher it raw using the user's passphrase. 245 plainSeedBytes, err := mnemonic.Decipher(pass) 246 if err != nil { 247 t.Fatalf("unable to decipher: %v", err) 248 } 249 250 // If we deserialize the plaintext seed bytes, it should exactly match 251 // the original cipher seed. 252 var newSeed CipherSeed 253 err = newSeed.decode(bytes.NewReader(plainSeedBytes[:])) 254 if err != nil { 255 t.Fatalf("unable to decode cipher seed: %v", err) 256 } 257 258 assertCipherSeedEqual(t, cipherSeed, &newSeed) 259 } 260 261 // TestInvalidExternalVersion tests that if we present a ciphertext with the 262 // incorrect version to decipherCipherSeed, then it fails with the expected 263 // error. 264 func TestInvalidExternalVersion(t *testing.T) { 265 t.Parallel() 266 267 // First, we'll generate a new cipher seed. 268 cipherSeed, err := New(0, &testEntropy, time.Now()) 269 if err != nil { 270 t.Fatalf("unable to create seed: %v", err) 271 } 272 273 // With the cipherseed obtained, we'll now use the raw encipher method 274 // to obtain our final cipher text. 275 pass := []byte("newpasswhodis") 276 cipherText, err := cipherSeed.Encipher(pass) 277 if err != nil { 278 t.Fatalf("unable to encipher seed: %v", err) 279 } 280 281 // Now that we have the cipher text, we'll modify the first byte to be 282 // an invalid version. 283 cipherText[0] = 44 284 285 // With the version swapped, if we try to decipher it, (no matter the 286 // passphrase), it should fail. 287 _, err = decipherCipherSeed(cipherText, []byte("kek")) 288 if err != ErrIncorrectVersion { 289 t.Fatalf("wrong error: expected ErrIncorrectVersion, "+ 290 "got %v", err) 291 } 292 } 293 294 // TestChangePassphrase tests that we're able to generate a cipher seed, then 295 // change the password. If we attempt to decipher the new enciphered seed, then 296 // we should get the exact same seed back. 297 func TestChangePassphrase(t *testing.T) { 298 t.Parallel() 299 300 // First, we'll generate a new cipher seed with a test passphrase. 301 pass := []byte("test") 302 cipherSeed, err := New(0, &testEntropy, time.Now()) 303 if err != nil { 304 t.Fatalf("unable to create seed: %v", err) 305 } 306 307 // Now that we have our cipher seed, we'll encipher it and request a 308 // mnemonic that we can use to recover later. 309 mnemonic, err := cipherSeed.ToMnemonic(pass) 310 if err != nil { 311 t.Fatalf("unable to create mnemonic: %v", err) 312 } 313 314 // Now that have the mnemonic, we'll attempt to re-encipher the 315 // passphrase in order to get a brand new mnemonic. 316 newPass := []byte("strongerpassyeh!") 317 newmnemonic, err := mnemonic.ChangePass(pass, newPass) 318 if err != nil { 319 t.Fatalf("unable to change passphrase: %v", err) 320 } 321 322 // We'll now attempt to decipher the new mnemonic using the new 323 // passphrase to arrive at (what should be) the original cipher seed. 324 newCipherSeed, err := newmnemonic.ToCipherSeed(newPass) 325 if err != nil { 326 t.Fatalf("unable to decipher cipher seed: %v", err) 327 } 328 329 // Now that we have the cipher seed, we'll verify that the plaintext 330 // seed matches *identically*. 331 assertCipherSeedEqual(t, cipherSeed, newCipherSeed) 332 } 333 334 // TestChangePassphraseWrongPass tests that if we have a valid enciphered 335 // cipherseed, but then try to change the password with the *wrong* password, 336 // then we get an error. 337 func TestChangePassphraseWrongPass(t *testing.T) { 338 t.Parallel() 339 340 // First, we'll generate a new cipher seed with a test passphrase. 341 pass := []byte("test") 342 cipherSeed, err := New(0, &testEntropy, time.Now()) 343 if err != nil { 344 t.Fatalf("unable to create seed: %v", err) 345 } 346 347 // Now that we have our cipher seed, we'll encipher it and request a 348 // mnemonic that we can use to recover later. 349 mnemonic, err := cipherSeed.ToMnemonic(pass) 350 if err != nil { 351 t.Fatalf("unable to create mnemonic: %v", err) 352 } 353 354 // Now that have the mnemonic, we'll attempt to re-encipher the 355 // passphrase in order to get a brand new mnemonic. However, we'll be 356 // using the *wrong* passphrase. This should result in an 357 // ErrInvalidPass error. 358 wrongPass := []byte("kek") 359 newPass := []byte("strongerpassyeh!") 360 _, err = mnemonic.ChangePass(wrongPass, newPass) 361 if err != ErrInvalidPass { 362 t.Fatalf("expected ErrInvalidPass, instead got %v", err) 363 } 364 } 365 366 // TestMnemonicEncoding uses quickcheck like property based testing to ensure 367 // that we're always able to fully recover the original byte stream encoded 368 // into the mnemonic phrase. 369 func TestMnemonicEncoding(t *testing.T) { 370 t.Parallel() 371 372 // mainScenario is the main driver of our property based test. We'll 373 // ensure that given a random byte string of length 33 bytes, if we 374 // convert that to the mnemonic, then we should be able to reverse the 375 // conversion. 376 mainScenario := func(cipherSeedBytes [EncipheredCipherSeedSize]byte) bool { 377 mnemonic, err := cipherTextToMnemonic(cipherSeedBytes) 378 if err != nil { 379 t.Fatalf("unable to map cipher text: %v", err) 380 return false 381 } 382 383 newCipher := mnemonicToCipherText(&mnemonic) 384 385 if newCipher != cipherSeedBytes { 386 t.Fatalf("cipherseed doesn't match: expected %v, got %v", 387 cipherSeedBytes, newCipher) 388 return false 389 } 390 391 return true 392 } 393 394 if err := quick.Check(mainScenario, nil); err != nil { 395 t.Fatalf("fuzz check failed: %v", err) 396 } 397 } 398 399 // TestEncipherDecipher is a property-based test that ensures that given a 400 // version, entropy, and birthday, then we're able to map that to a cipherseed 401 // mnemonic, then back to the original plaintext cipher seed. 402 func TestEncipherDecipher(t *testing.T) { 403 t.Parallel() 404 405 // mainScenario is the main driver of our property based test. We'll 406 // ensure that given a random seed tuple (internal version, entropy, 407 // and birthday) we're able to convert that to a valid cipher seed. 408 // Additionally, we should be able to decipher the final mnemonic, and 409 // recover the original cipherseed. 410 mainScenario := func(version uint8, entropy [EntropySize]byte, 411 nowInt int64, pass [20]byte) bool { 412 413 now := time.Unix(nowInt, 0) 414 415 cipherSeed, err := New(version, &entropy, now) 416 if err != nil { 417 t.Fatalf("unable to map cipher text: %v", err) 418 return false 419 } 420 421 mnemonic, err := cipherSeed.ToMnemonic(pass[:]) 422 if err != nil { 423 t.Fatalf("unable to generate mnemonic: %v", err) 424 return false 425 } 426 427 cipherSeed2, err := mnemonic.ToCipherSeed(pass[:]) 428 if err != nil { 429 t.Fatalf("unable to decrypt cipher seed: %v", err) 430 return false 431 } 432 433 if cipherSeed.InternalVersion != cipherSeed2.InternalVersion { 434 t.Fatalf("mismatched versions: expected %v, got %v", 435 cipherSeed.InternalVersion, cipherSeed2.InternalVersion) 436 return false 437 } 438 if cipherSeed.Birthday != cipherSeed2.Birthday { 439 t.Fatalf("mismatched birthday: expected %v, got %v", 440 cipherSeed.Birthday, cipherSeed2.Birthday) 441 return false 442 } 443 if cipherSeed.Entropy != cipherSeed2.Entropy { 444 t.Fatalf("mismatched versions: expected %x, got %x", 445 cipherSeed.Entropy[:], cipherSeed2.Entropy[:]) 446 return false 447 } 448 449 return true 450 } 451 452 if err := quick.Check(mainScenario, nil); err != nil { 453 t.Fatalf("fuzz check failed: %v", err) 454 } 455 } 456 457 // TestSeedEncodeDecode tests that we're able to reverse the encoding of an 458 // arbitrary raw seed. 459 func TestSeedEncodeDecode(t *testing.T) { 460 // mainScenario is the primary driver of our property-based test. We'll 461 // ensure that given a random cipher seed, we can encode it an decode 462 // it precisely. 463 mainScenario := func(version uint8, nowInt int64, 464 entropy [EntropySize]byte) bool { 465 466 now := time.Unix(nowInt, 0) 467 seed := CipherSeed{ 468 InternalVersion: version, 469 Birthday: uint16(now.Sub(DecredGenesisDate) / (time.Hour * 24)), 470 Entropy: entropy, 471 } 472 473 var b bytes.Buffer 474 if err := seed.encode(&b); err != nil { 475 t.Fatalf("unable to encode: %v", err) 476 return false 477 } 478 479 var newSeed CipherSeed 480 if err := newSeed.decode(&b); err != nil { 481 t.Fatalf("unable to decode: %v", err) 482 return false 483 } 484 485 if seed.InternalVersion != newSeed.InternalVersion { 486 t.Fatalf("mismatched versions: expected %v, got %v", 487 seed.InternalVersion, newSeed.InternalVersion) 488 return false 489 } 490 if seed.Birthday != newSeed.Birthday { 491 t.Fatalf("mismatched birthday: expected %v, got %v", 492 seed.Birthday, newSeed.Birthday) 493 return false 494 } 495 if seed.Entropy != newSeed.Entropy { 496 t.Fatalf("mismatched versions: expected %x, got %x", 497 seed.Entropy[:], newSeed.Entropy[:]) 498 return false 499 } 500 501 return true 502 } 503 504 if err := quick.Check(mainScenario, nil); err != nil { 505 t.Fatalf("fuzz check failed: %v", err) 506 } 507 } 508 509 // TestDecipherUnknownMnenomicWord tests that if we obtain a mnemonic, the 510 // modify one of the words to not be within the word list, then it's detected 511 // when we attempt to map it back to the original cipher seed. 512 func TestDecipherUnknownMnenomicWord(t *testing.T) { 513 t.Parallel() 514 515 // First, we'll create a new cipher seed with "test" ass a password. 516 pass := []byte("test") 517 cipherSeed, err := New(0, &testEntropy, time.Now()) 518 if err != nil { 519 t.Fatalf("unable to create seed: %v", err) 520 } 521 522 // Now that we have our cipher seed, we'll encipher it and request a 523 // mnemonic that we can use to recover later. 524 mnemonic, err := cipherSeed.ToMnemonic(pass) 525 if err != nil { 526 t.Fatalf("unable to create mnemonic: %v", err) 527 } 528 529 // Before we attempt to decrypt the cipher seed, we'll mutate one of 530 // the word so it isn't actually in our final word list. 531 randIndex := rand.Int31n(int32(len(mnemonic))) 532 mnemonic[randIndex] = "kek" 533 534 // If we attempt to map back to the original cipher seed now, then we 535 // should get ErrUnknownMnenomicWord. 536 _, err = mnemonic.ToCipherSeed(pass) 537 if err == nil { 538 t.Fatalf("expected ErrUnknownMnenomicWord error") 539 } 540 541 wordErr, ok := err.(ErrUnknownMnenomicWord) 542 if !ok { 543 t.Fatalf("expected ErrUnknownMnenomicWord instead got %T", err) 544 } 545 546 if wordErr.Word != "kek" { 547 t.Fatalf("word mismatch: expected %v, got %v", "kek", wordErr.Word) 548 } 549 if int32(wordErr.Index) != randIndex { 550 t.Fatalf("wrong index detected: expected %v, got %v", 551 randIndex, wordErr.Index) 552 } 553 554 // If the mnemonic includes a word that is not in the englishList 555 // it fails, even when it is a substring of a valid word 556 // Example: `heart` is in the list, `hear` is not 557 mnemonic[randIndex] = "hear" 558 559 // If we attempt to map back to the original cipher seed now, then we 560 // should get ErrUnknownMnenomicWord. 561 _, err = mnemonic.ToCipherSeed(pass) 562 if err == nil { 563 t.Fatalf("expected ErrUnknownMnenomicWord error") 564 } 565 _, ok = err.(ErrUnknownMnenomicWord) 566 if !ok { 567 t.Fatalf("expected ErrUnknownMnenomicWord instead got %T", err) 568 } 569 } 570 571 // TestDecipherIncorrectMnemonic tests that if we obtain a cipherseed, but then 572 // swap out words, then checksum fails. 573 func TestDecipherIncorrectMnemonic(t *testing.T) { 574 // First, we'll create a new cipher seed with "test" ass a password. 575 pass := []byte("test") 576 cipherSeed, err := New(0, &testEntropy, time.Now()) 577 if err != nil { 578 t.Fatalf("unable to create seed: %v", err) 579 } 580 581 // Now that we have our cipher seed, we'll encipher it and request a 582 // mnemonic that we can use to recover later. 583 mnemonic, err := cipherSeed.ToMnemonic(pass) 584 if err != nil { 585 t.Fatalf("unable to create mnemonic: %v", err) 586 } 587 588 // We'll now swap out two words from the mnemonic, which should trigger 589 // a checksum failure. 590 swapIndex1 := 9 591 swapIndex2 := 13 592 mnemonic[swapIndex1], mnemonic[swapIndex2] = mnemonic[swapIndex2], mnemonic[swapIndex1] 593 594 // If we attempt to decrypt now, we should get a checksum failure. 595 // If we attempt to map back to the original cipher seed now, then we 596 // should get ErrUnknownMnenomicWord. 597 _, err = mnemonic.ToCipherSeed(pass) 598 if err != ErrIncorrectMnemonic { 599 t.Fatalf("expected ErrIncorrectMnemonic error") 600 } 601 } 602 603 // TODO(roasbeef): add test failure checksum fail is modified, new error 604 605 func init() { 606 // For the purposes of our test, we'll crank down the scrypt params a 607 // bit. 608 scryptN = 16 609 scryptR = 8 610 scryptP = 1 611 }