From 17fa8e1cdb6761c0aa4e9761ff1a41aac91f0924 Mon Sep 17 00:00:00 2001 From: JoeGruff Date: Fri, 7 Nov 2025 16:21:00 +0900 Subject: [PATCH] multi: Add seed passphrase. --- cgo/types.go | 1 + cgo/walletloader.go | 1 + dcr/loader.go | 51 +++++++--- dcr/params.go | 1 + dcr/wallet.go | 17 +++- dcr/walletdata.go | 29 ++++-- go.mod | 1 + go.sum | 2 + mnemonic/seed.go | 16 +++ mnemonic/seed_test.go | 225 ++++++++++++++++++++++++------------------ 10 files changed, 223 insertions(+), 121 deletions(-) diff --git a/cgo/types.go b/cgo/types.go index 5285943..5be3df2 100644 --- a/cgo/types.go +++ b/cgo/types.go @@ -166,6 +166,7 @@ type Config struct { Birthday int64 `json:"birthday"` Pass string `json:"pass"` Mnemonic string `json:"mnemonic"` + SeedPass string `json:"seedpass"` // If the wallet existed before but the db was deleted to reduce // storage, restore from the local encrypted seed using the provided // password. Also works for watching only wallets with no password. diff --git a/cgo/walletloader.go b/cgo/walletloader.go index 5e75582..cafa494 100644 --- a/cgo/walletloader.go +++ b/cgo/walletloader.go @@ -89,6 +89,7 @@ func createWallet(cConfig *C.char) *C.char { } recoveryConfig = &dcr.RecoveryCfg{ Seed: seed, + SeedPass: []byte(cfg.SeedPass), SeedType: seedType, Birthday: birthday, } diff --git a/dcr/loader.go b/dcr/loader.go index 7fe28ff..d79b25a 100644 --- a/dcr/loader.go +++ b/dcr/loader.go @@ -4,6 +4,7 @@ import ( "context" "encoding/binary" "encoding/hex" + "errors" "fmt" "os" "path/filepath" @@ -14,6 +15,7 @@ import ( "decred.org/dcrwallet/v4/wallet/udb" "github.com/decred/dcrd/crypto/blake256" "github.com/decred/dcrd/hdkeychain/v3" + "github.com/decred/libwallet/mnemonic" ) const ( @@ -49,10 +51,9 @@ func CreateWallet(ctx context.Context, params CreateWalletParams, recovery *Reco } var ( - seed []byte - tweakedSeed func() []byte - birthday time.Time - seedType SeedType + seed, seedPass, tweakedSeed []byte + birthday time.Time + seedType SeedType ) if recovery != nil { @@ -69,10 +70,20 @@ func CreateWallet(ctx context.Context, params CreateWalletParams, recovery *Reco if err != nil { return nil, fmt.Errorf("unable to decrypt wallet seed: %v", err) } + if len(wd.EncryptedSeedPassHex) != 0 { + encSeedPass, err := hex.DecodeString(wd.EncryptedSeedPassHex) + if err != nil { + return nil, fmt.Errorf("unable to decode encrypted seed pass: %v", err) + } + seedPass, err = DecryptData(encSeedPass, params.Pass) + if err != nil { + return nil, fmt.Errorf("unable to decrypt wallet seed pass: %v", err) + } + } birthday = time.Unix(wd.Birthday, 0) seedType = wd.SeedType } else { - seed, birthday, seedType = recovery.Seed, recovery.Birthday, recovery.SeedType + seed, seedPass, birthday, seedType = recovery.Seed, recovery.SeedPass, recovery.Birthday, recovery.SeedType } } else { seed, err = hdkeychain.GenerateSeed(entropyBytes) @@ -83,24 +94,36 @@ func CreateWallet(ctx context.Context, params CreateWalletParams, recovery *Reco // Seed type is default fifteen words. } - if seedType == STFifteenWords { - // Adjust seed to create the same wallet as dex. + switch seedType { + case STFifteenWords: + // Applying the seed pass to 15 word wallets breaks existing + // wallets when no pass is supplied. Also, the mnemonic cannot + // be used by other decred software if a passphrase is applied. + if len(seedPass) != 0 { + return nil, errors.New("seed passphrase cannot be used with 15 word mnemonics") + } + // Adjust seed to create the same wallet as bison wallet. b := make([]byte, len(seed)+4) copy(b, seed) binary.BigEndian.PutUint32(b[len(seed):], 42) ts := blake256.Sum256(b) - tweakedSeed = func() []byte { return ts[:] } - } else { - tweakedSeed = func() []byte { return seed } + tweakedSeed = ts[:] + case STTwelveWords, STTwentyFourWords: + words, err := mnemonic.GenerateMnemonic(seed) + if err != nil { + return nil, fmt.Errorf("unable to recreate seed mnemonic: %v", err) + } + // Apply even if password is null. + tweakedSeed = mnemonic.ApplyPassphrase(seed, seedPass, words) } - _, _, _, acctKeySLIP0044Priv, err := udb.HDKeysFromSeed(tweakedSeed(), chainParams) + _, _, _, acctKeySLIP0044Priv, err := udb.HDKeysFromSeed(tweakedSeed, chainParams) if err != nil { return nil, err } defer acctKeySLIP0044Priv.Zero() xpub := acctKeySLIP0044Priv.Neuter() - wd, err := saveWalletData(seed, xpub.String(), birthday, params.DataDir, params.Pass, seedType) + wd, err := saveWalletData(seed, seedPass, xpub.String(), birthday, params.DataDir, params.Pass, seedType) if err != nil { return nil, fmt.Errorf("saveWalletData error: %v", err) } @@ -130,7 +153,7 @@ func CreateWallet(ctx context.Context, params CreateWalletParams, recovery *Reco }() // Initialize the newly created database for the wallet before opening. - err = wallet.Create(ctx, db, nil, params.Pass, tweakedSeed(), chainParams) + err = wallet.Create(ctx, db, nil, params.Pass, tweakedSeed, chainParams) if err != nil { return nil, fmt.Errorf("wallet.Create error: %w", err) } @@ -208,7 +231,7 @@ func CreateWatchOnlyWallet(ctx context.Context, extendedPubKey string, params Cr return nil, fmt.Errorf("unable to parse extended key: %w", err) } - wd, err := saveWalletData(nil, xpub.String(), time.Time{}, params.DataDir, nil, 0) // password not required + wd, err := saveWalletData(nil, nil, xpub.String(), time.Time{}, params.DataDir, nil, 0) // password not required if err != nil { return nil, fmt.Errorf("saveWalletData error: %v", err) } diff --git a/dcr/params.go b/dcr/params.go index 806e715..7367be0 100644 --- a/dcr/params.go +++ b/dcr/params.go @@ -24,6 +24,7 @@ type CreateWalletParams struct { // RecoveryCfg is the information used to recover a wallet. type RecoveryCfg struct { Seed []byte + SeedPass []byte SeedType SeedType Birthday time.Time UseLocalSeed bool diff --git a/dcr/wallet.go b/dcr/wallet.go index 44c5ff3..17bbc88 100644 --- a/dcr/wallet.go +++ b/dcr/wallet.go @@ -87,15 +87,28 @@ func (w *Wallet) ReEncryptSeed(oldPass, newPass []byte) error { return err } + var seedPass []byte + if len(w.metaData.EncryptedSeedPassHex) != 0 { + encSeedPass, err := hex.DecodeString(w.metaData.EncryptedSeedPassHex) + if err != nil { + return fmt.Errorf("unable to decode encrypted seed pass: %v", err) + } + seedPass, err = DecryptData(encSeedPass, oldPass) + if err != nil { + return fmt.Errorf("unable to decrypt wallet seed pass: %v", err) + } + } + birthday := time.Unix(w.metaData.Birthday, 0) - updatedMetaData, err := saveWalletData(seed, w.metaData.DefaultAccountXPub, birthday, w.dir, newPass, w.metaData.SeedType) + updatedMetaData, err := saveWalletData(seed, seedPass, w.metaData.DefaultAccountXPub, birthday, w.dir, newPass, w.metaData.SeedType) if err != nil { return err } - // Update only the EncryptedSeedHex field since we've held the seedMtx lock + // Update only the EncryptedSeedHex and pass field since we've held the seedMtx lock // above. w.metaData.EncryptedSeedHex = updatedMetaData.EncryptedSeedHex + w.metaData.EncryptedSeedPassHex = updatedMetaData.EncryptedSeedPassHex return nil } diff --git a/dcr/walletdata.go b/dcr/walletdata.go index 2f1e258..12920e8 100644 --- a/dcr/walletdata.go +++ b/dcr/walletdata.go @@ -25,24 +25,35 @@ const ( const walletDataFileName = "walletdata.json" type walletData struct { - EncryptedSeedHex string `json:"encryptedseedhex,omitempty"` - SeedType SeedType `json:"seedtype,omitempty"` - DefaultAccountXPub string `json:"defaultaccountxpub,omitempty"` - Birthday int64 `json:"birthday,omitempty"` + EncryptedSeedHex string `json:"encryptedseedhex,omitempty"` + EncryptedSeedPassHex string `json:"encryptedseedpasshex,omitempty"` + SeedType SeedType `json:"seedtype,omitempty"` + DefaultAccountXPub string `json:"defaultaccountxpub,omitempty"` + Birthday int64 `json:"birthday,omitempty"` } -func saveWalletData(seed []byte, defaultAccountXPub string, birthday time.Time, dataDir string, walletPass []byte, seedType SeedType) (*walletData, error) { +func saveWalletData(seed, seedPass []byte, defaultAccountXPub string, birthday time.Time, dataDir string, walletPass []byte, seedType SeedType) (*walletData, error) { encSeed, err := EncryptData(seed, walletPass) if err != nil { return nil, fmt.Errorf("seed encryption error: %v", err) } + encSeedPassHex := "" + if len(seedPass) != 0 { + encSeedPass, err := EncryptData(seedPass, walletPass) + if err != nil { + return nil, fmt.Errorf("seed pass encryption error: %v", err) + } + encSeedPassHex = hex.EncodeToString(encSeedPass) + } + encSeedHex := hex.EncodeToString(encSeed) wd := &walletData{ - EncryptedSeedHex: encSeedHex, - DefaultAccountXPub: defaultAccountXPub, - Birthday: birthday.Unix(), - SeedType: seedType, + EncryptedSeedHex: encSeedHex, + EncryptedSeedPassHex: encSeedPassHex, + DefaultAccountXPub: defaultAccountXPub, + Birthday: birthday.Unix(), + SeedType: seedType, } file, err := json.MarshalIndent(wd, "", " ") if err != nil { diff --git a/go.mod b/go.mod index 49a2169..8d089c5 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/jrick/logrotate v1.1.2 github.com/kevinburke/nacl v0.0.0-20210405173606-cd9060f5f776 golang.org/x/crypto v0.33.0 + golang.org/x/text v0.22.0 ) require ( diff --git a/go.sum b/go.sum index f80ce84..a8a2e92 100644 --- a/go.sum +++ b/go.sum @@ -94,6 +94,8 @@ golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= diff --git a/mnemonic/seed.go b/mnemonic/seed.go index 69f1c49..8ec177d 100644 --- a/mnemonic/seed.go +++ b/mnemonic/seed.go @@ -2,11 +2,15 @@ package mnemonic import ( "crypto/sha256" + "crypto/sha512" "encoding/binary" "errors" "fmt" "sort" "strings" + + "golang.org/x/crypto/pbkdf2" + "golang.org/x/text/unicode/norm" ) // DecodeMnemonic decodes the entropy and checksum from mnemonic and validates @@ -132,3 +136,15 @@ func wordIndex(word string) (uint16, error) { } return uint16(i), nil } + +// ApplyPassphrase returns the seed after applying passphrase. Follows +// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed +func ApplyPassphrase(seed, pass []byte, words string) []byte { + const ( + iterations = 2048 + keyLen = 64 + ) + password := norm.NFC.String(words) + salt := "mnemonic" + norm.NFC.String(string(pass)) + return pbkdf2.Key([]byte(password), []byte(salt), iterations, keyLen, sha512.New) +} diff --git a/mnemonic/seed_test.go b/mnemonic/seed_test.go index 289ea98..fd3e844 100644 --- a/mnemonic/seed_test.go +++ b/mnemonic/seed_test.go @@ -55,104 +55,129 @@ func TestEncodeDecode(t *testing.T) { // TestBip39Vectors taken from https://github.com/trezor/python-mnemonic/blob/master/vectors.json func TestBip39Vectors(t *testing.T) { + pass := []byte("TREZOR") tests := []struct { - name, seed, mnemonic string + name, seed, mnemonic, seedWPass string }{{ - name: "ok 12 abandon", - seed: "00000000000000000000000000000000", - mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", - }, { - name: "ok 12 legal", - seed: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow", - }, { - name: "ok 12 letter", - seed: "80808080808080808080808080808080", - mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", - }, { - name: "ok 12 zoo", - seed: "ffffffffffffffffffffffffffffffff", - mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", - }, { - name: "ok 18 abandon", - seed: "000000000000000000000000000000000000000000000000", - mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", - }, { - name: "ok 18 legal", - seed: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - mnemonic: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", - }, { - name: "ok 18 letter", - seed: "808080808080808080808080808080808080808080808080", - mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", - }, { - name: "ok 18 zoo", - seed: "ffffffffffffffffffffffffffffffffffffffffffffffff", - mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", - }, { - name: "ok 24 abandon", - seed: "0000000000000000000000000000000000000000000000000000000000000000", - mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", - }, { - name: "ok 24 legal", - seed: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", - mnemonic: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", - }, { - name: "ok 24 letter", - seed: "8080808080808080808080808080808080808080808080808080808080808080", - mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", - }, { - name: "ok 24 zoo", - seed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", - }, { - name: "ok 12 ozone", - seed: "9e885d952ad362caeb4efe34a8e91bd2", - mnemonic: "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", - }, { - name: "ok 18 gravity", - seed: "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", - mnemonic: "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", - }, { - name: "ok 24 hamster", - seed: "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", - mnemonic: "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", - }, { - name: "ok 12 scheme", - seed: "c0ba5a8e914111210f2bd131f3d5e08d", - mnemonic: "scheme spot photo card baby mountain device kick cradle pact join borrow", - }, { - name: "ok 18 horn", - seed: "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", - mnemonic: "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", - }, { - name: "ok 24 panda", - seed: "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", - mnemonic: "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", - }, { - name: "ok 12 cat", - seed: "23db8160a31d3e0dca3688ed941adbf3", - mnemonic: "cat swing flag economy stadium alone churn speed unique patch report train", - }, { - name: "ok 18 light", - seed: "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", - mnemonic: "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", - }, { - name: "ok 24 all", - seed: "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", - mnemonic: "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", - }, { - name: "ok 12 vessel", - seed: "f30f8c1da665478f49b001d94c5fc452", - mnemonic: "vessel ladder alter error federal sibling chat ability sun glass valve picture", - }, { - name: "ok 18 scissors", - seed: "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", - mnemonic: "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", - }, { - name: "ok 24 void", - seed: "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", - mnemonic: "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", + name: "ok 12 abandon", + seed: "00000000000000000000000000000000", + mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + seedWPass: "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + }, { + name: "ok 12 legal", + seed: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + mnemonic: "legal winner thank year wave sausage worth useful legal winner thank yellow", + seedWPass: "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", + }, { + name: "ok 12 letter", + seed: "80808080808080808080808080808080", + mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + seedWPass: "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", + }, { + name: "ok 12 zoo", + seed: "ffffffffffffffffffffffffffffffff", + mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + seedWPass: "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", + }, { + name: "ok 18 abandon", + seed: "000000000000000000000000000000000000000000000000", + mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + seedWPass: "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", + }, { + name: "ok 18 legal", + seed: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + mnemonic: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + seedWPass: "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", + }, { + name: "ok 18 letter", + seed: "808080808080808080808080808080808080808080808080", + mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + seedWPass: "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", + }, { + name: "ok 18 zoo", + seed: "ffffffffffffffffffffffffffffffffffffffffffffffff", + mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + seedWPass: "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", + }, { + name: "ok 24 abandon", + seed: "0000000000000000000000000000000000000000000000000000000000000000", + mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + seedWPass: "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", + }, { + name: "ok 24 legal", + seed: "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + mnemonic: "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + seedWPass: "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", + }, { + name: "ok 24 letter", + seed: "8080808080808080808080808080808080808080808080808080808080808080", + mnemonic: "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + seedWPass: "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", + }, { + name: "ok 24 zoo", + seed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + mnemonic: "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + seedWPass: "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", + }, { + name: "ok 12 ozone", + seed: "9e885d952ad362caeb4efe34a8e91bd2", + mnemonic: "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", + seedWPass: "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028", + }, { + name: "ok 18 gravity", + seed: "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", + mnemonic: "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", + seedWPass: "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac", + }, { + name: "ok 24 hamster", + seed: "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", + mnemonic: "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", + seedWPass: "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440", + }, { + name: "ok 12 scheme", + seed: "c0ba5a8e914111210f2bd131f3d5e08d", + mnemonic: "scheme spot photo card baby mountain device kick cradle pact join borrow", + seedWPass: "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612", + }, { + name: "ok 18 horn", + seed: "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", + mnemonic: "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", + seedWPass: "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d", + }, { + name: "ok 24 panda", + seed: "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", + mnemonic: "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", + seedWPass: "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d", + }, { + name: "ok 12 cat", + seed: "23db8160a31d3e0dca3688ed941adbf3", + mnemonic: "cat swing flag economy stadium alone churn speed unique patch report train", + seedWPass: "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5", + }, { + name: "ok 18 light", + seed: "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", + mnemonic: "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", + seedWPass: "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02", + }, { + name: "ok 24 all", + seed: "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", + mnemonic: "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", + seedWPass: "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d", + }, { + name: "ok 12 vessel", + seed: "f30f8c1da665478f49b001d94c5fc452", + mnemonic: "vessel ladder alter error federal sibling chat ability sun glass valve picture", + seedWPass: "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f", + }, { + name: "ok 18 scissors", + seed: "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", + mnemonic: "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", + seedWPass: "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88", + }, { + name: "ok 24 void", + seed: "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", + mnemonic: "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", + seedWPass: "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998", }} for _, test := range tests { @@ -175,6 +200,14 @@ func TestBip39Vectors(t *testing.T) { if !bytes.Equal(seed, testSeed) { t.Fatalf("expected seed %x but got %x", testSeed, seed) } + testSeedWPass, err := hex.DecodeString(test.seedWPass) + if err != nil { + t.Fatal(err) + } + seedWPass := ApplyPassphrase(seed, pass, mnemonic) + if !bytes.Equal(seedWPass, testSeedWPass) { + t.Fatalf("expected seed with passphrase %x but got %x", testSeedWPass, seedWPass) + } }) } }