Skip to content

Commit 441540b

Browse files
committed
multi: Add 12 and 24 seed restoration.
1 parent f4d456d commit 441540b

9 files changed

Lines changed: 2338 additions & 31 deletions

File tree

cgo/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ type Config struct {
163163
Net string `json:"net"`
164164
DataDir string `json:"datadir"`
165165
// Only needed during creation.
166+
Birthday int64 `json:"birthday"`
166167
Pass string `json:"pass"`
167168
Mnemonic string `json:"mnemonic"`
168169
// If the wallet existed before but the db was deleted to reduce

cgo/walletloader.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import "C"
44
import (
55
"context"
66
"encoding/json"
7+
"strings"
78
"sync"
9+
"time"
810

9-
"decred.org/dcrdex/client/mnemonic"
11+
dexmnemonic "decred.org/dcrdex/client/mnemonic"
1012
"github.com/decred/libwallet/dcr"
13+
"github.com/decred/libwallet/mnemonic"
1114
"github.com/decred/slog"
1215
)
1316

@@ -59,12 +62,34 @@ func createWallet(cConfig *C.char) *C.char {
5962

6063
var recoveryConfig *dcr.RecoveryCfg
6164
if cfg.Mnemonic != "" {
62-
seed, birthday, err := mnemonic.DecodeMnemonic(cfg.Mnemonic)
65+
var (
66+
seed []byte
67+
birthday time.Time
68+
seedType dcr.SeedType
69+
err error
70+
)
71+
nWords := len(strings.Fields(cfg.Mnemonic))
72+
switch nWords {
73+
case 15:
74+
seed, birthday, err = dexmnemonic.DecodeMnemonic(cfg.Mnemonic)
75+
seedType = dcr.STFifteenWords
76+
case 12:
77+
seed, err = mnemonic.DecodeMnemonic(cfg.Mnemonic)
78+
birthday = time.Unix(cfg.Birthday, 0)
79+
seedType = dcr.STTwelveWords
80+
case 24:
81+
seed, err = mnemonic.DecodeMnemonic(cfg.Mnemonic)
82+
birthday = time.Unix(cfg.Birthday, 0)
83+
seedType = dcr.STTwentyFourWords
84+
default:
85+
return errCResponse("unknown mnemonic format. expected 12, 15, or 14 words, got %d", nWords)
86+
}
6387
if err != nil {
6488
return errCResponse("unable to decode wallet mnemonic: %v", err)
6589
}
6690
recoveryConfig = &dcr.RecoveryCfg{
6791
Seed: seed,
92+
SeedType: seedType,
6893
Birthday: birthday,
6994
}
7095
}

dcr/loader.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,15 @@ func CreateWallet(ctx context.Context, params CreateWalletParams, recovery *Reco
4848
return nil, fmt.Errorf("check new wallet data directory error: %w", err)
4949
}
5050

51-
var seed []byte
52-
var birthday time.Time
51+
var (
52+
seed, tweakedSeed []byte
53+
birthday time.Time
54+
seedType SeedType
55+
)
56+
5357
if recovery != nil {
5458
if recovery.UseLocalSeed {
55-
wd, err := RetreiveWalletData(params.DataDir)
59+
wd, err := retrieveWalletData(params.DataDir)
5660
if err != nil {
5761
return nil, fmt.Errorf("unable to get wallet data: %v", err)
5862
}
@@ -65,22 +69,29 @@ func CreateWallet(ctx context.Context, params CreateWalletParams, recovery *Reco
6569
return nil, fmt.Errorf("unable to decrypt wallet seed: %v", err)
6670
}
6771
birthday = time.Unix(wd.Birthday, 0)
72+
seedType = wd.SeedType
6873
} else {
69-
seed, birthday = recovery.Seed, recovery.Birthday
74+
seed, birthday, seedType = recovery.Seed, recovery.Birthday, recovery.SeedType
7075
}
7176
} else {
7277
seed, err = hdkeychain.GenerateSeed(entropyBytes)
7378
if err != nil {
7479
return nil, fmt.Errorf("unable to generate random seed: %v", err)
7580
}
7681
birthday = time.Now()
82+
// Seed type is default fifteen words.
7783
}
7884

79-
// Adjust seed to create the same wallet as dex.
80-
b := make([]byte, len(seed)+4)
81-
copy(b, seed)
82-
binary.BigEndian.PutUint32(b[len(seed):], 42)
83-
tweakedSeed := blake256.Sum256(b)
85+
if seedType == STFifteenWords {
86+
// Adjust seed to create the same wallet as dex.
87+
b := make([]byte, len(seed)+4)
88+
copy(b, seed)
89+
binary.BigEndian.PutUint32(b[len(seed):], 42)
90+
ts := blake256.Sum256(b)
91+
copy(tweakedSeed, ts[:])
92+
} else {
93+
copy(tweakedSeed, seed)
94+
}
8495

8596
_, _, _, acctKeySLIP0044Priv, err := udb.HDKeysFromSeed(tweakedSeed[:], chainParams)
8697
if err != nil {
@@ -89,9 +100,9 @@ func CreateWallet(ctx context.Context, params CreateWalletParams, recovery *Reco
89100
defer acctKeySLIP0044Priv.Zero()
90101
xpub := acctKeySLIP0044Priv.Neuter()
91102

92-
wd, err := SaveWalletData(seed, xpub.String(), birthday, params.DataDir, params.Pass)
103+
wd, err := saveWalletData(seed, xpub.String(), birthday, params.DataDir, params.Pass, seedType)
93104
if err != nil {
94-
return nil, fmt.Errorf("SaveWalletData error: %v", err)
105+
return nil, fmt.Errorf("saveWalletData error: %v", err)
95106
}
96107

97108
ctx, cancel := context.WithTimeout(ctx, time.Minute)
@@ -185,7 +196,7 @@ func CreateWatchOnlyWallet(ctx context.Context, extendedPubKey string, params Cr
185196
}
186197

187198
if useLocalSeed {
188-
wd, err := RetreiveWalletData(params.DataDir)
199+
wd, err := retrieveWalletData(params.DataDir)
189200
if err != nil {
190201
return nil, fmt.Errorf("unable to get wallet data: %v", err)
191202
}
@@ -197,9 +208,9 @@ func CreateWatchOnlyWallet(ctx context.Context, extendedPubKey string, params Cr
197208
return nil, fmt.Errorf("unable to parse extended key: %w", err)
198209
}
199210

200-
wd, err := SaveWalletData(nil, xpub.String(), time.Time{}, params.DataDir, nil) // password not required
211+
wd, err := saveWalletData(nil, xpub.String(), time.Time{}, params.DataDir, nil, 0) // password not required
201212
if err != nil {
202-
return nil, fmt.Errorf("SaveWalletData error: %v", err)
213+
return nil, fmt.Errorf("saveWalletData error: %v", err)
203214
}
204215

205216
ctx, cancel := context.WithTimeout(ctx, time.Minute)
@@ -266,7 +277,7 @@ func LoadWallet(ctx context.Context, params OpenWalletParams) (*Wallet, error) {
266277
return nil, fmt.Errorf("error parsing chain params: %w", err)
267278
}
268279

269-
wd, err := RetreiveWalletData(params.DataDir)
280+
wd, err := retrieveWalletData(params.DataDir)
270281
if err != nil {
271282
return nil, err
272283
}

dcr/params.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type CreateWalletParams struct {
2424
// RecoveryCfg is the information used to recover a wallet.
2525
type RecoveryCfg struct {
2626
Seed []byte
27+
SeedType SeedType
2728
Birthday time.Time
2829
UseLocalSeed bool
2930
NumExternalAddresses uint32

dcr/wallet.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ package dcr
33
import (
44
"context"
55
"encoding/hex"
6+
"errors"
67
"fmt"
78
"path/filepath"
89
"sync"
910
"time"
1011

11-
"decred.org/dcrdex/client/mnemonic"
12+
dexmnemonic "decred.org/dcrdex/client/mnemonic"
1213
"decred.org/dcrwallet/v4/spv"
1314
"decred.org/dcrwallet/v4/wallet"
1415
"github.com/decred/dcrd/chaincfg/v3"
16+
"github.com/decred/libwallet/mnemonic"
1517
"github.com/decred/slog"
1618
)
1719

@@ -46,10 +48,6 @@ func (w *Wallet) DecryptSeed(passphrase []byte) (string, error) {
4648
w.seedMtx.Lock()
4749
defer w.seedMtx.Unlock()
4850

49-
if w.metaData.EncryptedSeedHex == "" {
50-
return "", fmt.Errorf("seed has been verified")
51-
}
52-
5351
encryptedSeed, err := hex.DecodeString(w.metaData.EncryptedSeedHex)
5452
if err != nil {
5553
return "", fmt.Errorf("unable to decode encrypted hex seed: %v", err)
@@ -59,15 +57,23 @@ func (w *Wallet) DecryptSeed(passphrase []byte) (string, error) {
5957
if err != nil {
6058
return "", err
6159
}
62-
return mnemonic.GenerateMnemonic(seed, time.Unix(w.metaData.Birthday, 0))
60+
61+
switch len(seed) {
62+
case 18:
63+
return dexmnemonic.GenerateMnemonic(seed, time.Unix(w.metaData.Birthday, 0))
64+
case 16, 32:
65+
return mnemonic.GenerateMnemonic(seed)
66+
default:
67+
return "", fmt.Errorf("invalid saved seed length %d", len(seed))
68+
}
6369
}
6470

6571
func (w *Wallet) ReEncryptSeed(oldPass, newPass []byte) error {
6672
w.seedMtx.Lock()
6773
defer w.seedMtx.Unlock()
6874

6975
if w.metaData.EncryptedSeedHex == "" {
70-
return nil
76+
return errors.New("encrypted seed does not exist")
7177
}
7278

7379
encryptedSeed, err := hex.DecodeString(w.metaData.EncryptedSeedHex)
@@ -81,7 +87,7 @@ func (w *Wallet) ReEncryptSeed(oldPass, newPass []byte) error {
8187
}
8288

8389
birthday := time.Unix(w.metaData.Birthday, 0)
84-
updatedMetaData, err := SaveWalletData(seed, w.metaData.DefaultAccountXPub, birthday, w.dir, newPass)
90+
updatedMetaData, err := saveWalletData(seed, w.metaData.DefaultAccountXPub, birthday, w.dir, newPass, w.metaData.SeedType)
8591
if err != nil {
8692
return err
8793
}

dcr/walletdata.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,29 @@ import (
99
"time"
1010
)
1111

12+
// SeedType defines the type of seed used by the wallet. Currently all use bip39
13+
// words but they are different lengths.
14+
type SeedType int
15+
16+
const (
17+
// STFifteenWords encodes a birthday along with the seed. It is also
18+
// used by bison wallet and has a tweak that causes it to produce the
19+
// same wallet.
20+
STFifteenWords SeedType = iota // 0
21+
STTwelveWords // 1
22+
STTwentyFourWords // 2
23+
)
24+
1225
const walletDataFileName = "walletdata.json"
1326

1427
type walletData struct {
15-
EncryptedSeedHex string `json:"encryptedseedhex,omitempty"`
16-
DefaultAccountXPub string `json:"defaultaccountxpub,omitempty"`
17-
Birthday int64 `json:"birthday,omitempty"`
28+
EncryptedSeedHex string `json:"encryptedseedhex,omitempty"`
29+
SeedType SeedType `json:"seedtype,omitempty"`
30+
DefaultAccountXPub string `json:"defaultaccountxpub,omitempty"`
31+
Birthday int64 `json:"birthday,omitempty"`
1832
}
1933

20-
func SaveWalletData(seed []byte, defaultAccountXPub string, birthday time.Time, dataDir string, walletPass []byte) (*walletData, error) {
34+
func saveWalletData(seed []byte, defaultAccountXPub string, birthday time.Time, dataDir string, walletPass []byte, seedType SeedType) (*walletData, error) {
2135
encSeed, err := EncryptData(seed, walletPass)
2236
if err != nil {
2337
return nil, fmt.Errorf("seed encryption error: %v", err)
@@ -28,6 +42,7 @@ func SaveWalletData(seed []byte, defaultAccountXPub string, birthday time.Time,
2842
EncryptedSeedHex: encSeedHex,
2943
DefaultAccountXPub: defaultAccountXPub,
3044
Birthday: birthday.Unix(),
45+
SeedType: seedType,
3146
}
3247
file, err := json.MarshalIndent(wd, "", " ")
3348
if err != nil {
@@ -41,8 +56,8 @@ func SaveWalletData(seed []byte, defaultAccountXPub string, birthday time.Time,
4156
return wd, nil
4257
}
4358

44-
// RetreiveWalletData returns the wallet data from the data dir.
45-
func RetreiveWalletData(dataDir string) (*walletData, error) {
59+
// retrieveWalletData returns the wallet data from the data dir.
60+
func retrieveWalletData(dataDir string) (*walletData, error) {
4661
fp := filepath.Join(dataDir, walletDataFileName)
4762
b, err := os.ReadFile(fp)
4863
if err != nil {

0 commit comments

Comments
 (0)