Skip to content

Commit

Permalink
feat(wallet): upgrade wallet (#1491)
Browse files Browse the repository at this point in the history
Co-authored-by: mj <[email protected]>
  • Loading branch information
akbariandev and mj authored Aug 29, 2024
1 parent d30aa03 commit 0fd3ac2
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 234 deletions.
2 changes: 1 addition & 1 deletion cmd/wallet/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func buildImportPrivateKeyCmd(parentCmd *cobra.Command) {
cmd.FatalErrorCheck(err)

password := getPassword(wlt, *passOpt)
err = wlt.ImportPrivateKey(password, prv)
err = wlt.ImportBLSPrivateKey(password, prv)
cmd.FatalErrorCheck(err)

err = wlt.Save()
Expand Down
13 changes: 12 additions & 1 deletion wallet/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var (
ErrHistoryExists = errors.New("transaction already exists")
)

// CRCNotMatchError describes an error in which the wallet CRC is not macthed.
// CRCNotMatchError describes an error in which the wallet CRC is not matched.
type CRCNotMatchError struct {
Expected uint32
Got uint32
Expand All @@ -36,3 +36,14 @@ type ExitsError struct {
func (e ExitsError) Error() string {
return fmt.Sprintf("a wallet exists at: %s", e.Path)
}

// UnsupportedVersionError indicates the wallet version is incompatible with the software's supported version.
type UnsupportedVersionError struct {
WalletVersion int
SupportedVersion int
}

func (e UnsupportedVersionError) Error() string {
return fmt.Sprintf("wallet version %d is not supported, latest supported version is %d",
e.WalletVersion, e.SupportedVersion)
}
119 changes: 110 additions & 9 deletions wallet/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ package wallet

import (
"encoding/json"
"fmt"
"hash/crc32"
"time"

"github.com/google/uuid"
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/crypto/bls"
blshdkeychain "github.com/pactus-project/pactus/crypto/bls/hdkeychain"
"github.com/pactus-project/pactus/genesis"
"github.com/pactus-project/pactus/util"
"github.com/pactus-project/pactus/util/logger"
"github.com/pactus-project/pactus/wallet/addresspath"
"github.com/pactus-project/pactus/wallet/vault"
)

type store struct {
const (
Version1 = 1 // initial version
Version2 = 2 // supporting Ed25519

VersionLatest = Version2
)

type Store struct {
Version int `json:"version"`
UUID uuid.UUID `json:"uuid"`
CreatedAt time.Time `json:"created_at"`
Expand All @@ -20,18 +34,22 @@ type store struct {
History history `json:"history"`
}

func (s *store) ToBytes() ([]byte, error) {
func FromBytes(data []byte) (*Store, error) {
s := new(Store)
if err := json.Unmarshal(data, s); err != nil {
return nil, err
}

return s, nil
}

func (s *Store) ToBytes() ([]byte, error) {
s.VaultCRC = s.calcVaultCRC()

return json.MarshalIndent(s, " ", " ")
}

func (s *store) Save(bs []byte) error {
err := json.Unmarshal(bs, s)
if err != nil {
return err
}

func (s *Store) ValidateCRC() error {
crc := s.calcVaultCRC()
if s.VaultCRC != crc {
return CRCNotMatchError{
Expand All @@ -43,7 +61,90 @@ func (s *store) Save(bs []byte) error {
return nil
}

func (s *store) calcVaultCRC() uint32 {
func (s *Store) UpgradeWallet(walletPath string) error {
oldVersion := s.Version
switch oldVersion {
case Version1:
if err := s.setPublicKeys(); err != nil {
return err
}

case Version2:
// Current version
return nil

default:
return UnsupportedVersionError{
WalletVersion: s.Version,
SupportedVersion: VersionLatest,
}
}

s.VaultCRC = s.calcVaultCRC()
s.Version = Version2

bs, err := s.ToBytes()
if err != nil {
return err
}

err = util.WriteFile(walletPath, bs)
if err != nil {
return err
}
logger.Info(fmt.Sprintf("wallet upgraded from version %d to version %d",
oldVersion, VersionLatest))

return nil
}

func (s *Store) setPublicKeys() error {
for addrKey, info := range s.Vault.Addresses {
if info.PublicKey != "" {
continue
}

// Some old wallet doesn't have public key for all addresses.
addr, err := crypto.AddressFromString(info.Address)
if err != nil {
return err
}

var xPub string
if addr.IsAccountAddress() {
xPub = s.Vault.Purposes.PurposeBLS.XPubAccount
} else if addr.IsValidatorAddress() {
xPub = s.Vault.Purposes.PurposeBLS.XPubValidator
}

ext, err := blshdkeychain.NewKeyFromString(xPub)
if err != nil {
return err
}

p, err := addresspath.FromString(info.Path)
if err != nil {
return err
}

extendedKey, err := ext.Derive(p.AddressIndex())
if err != nil {
return err
}

blsPubKey, err := bls.PublicKeyFromBytes(extendedKey.RawPublicKey())
if err != nil {
return err
}

info.PublicKey = blsPubKey.String()
s.Vault.Addresses[addrKey] = info
}

return nil
}

func (s *Store) calcVaultCRC() uint32 {
d, err := json.Marshal(s.Vault)
if err != nil {
return 0
Expand Down
38 changes: 38 additions & 0 deletions wallet/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package wallet

import (
"testing"

"github.com/pactus-project/pactus/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestUpgradeWallet(t *testing.T) {
// password is: "password"
data, err := util.ReadFile("./testdata/wallet_version_1")
require.NoError(t, err)

tempPath := util.TempFilePath()
err = util.WriteFile(tempPath, data)
require.NoError(t, err)

wlt, err := Open(tempPath, true)
require.NoError(t, err)

assert.Equal(t, 4, wlt.AddressCount())
assert.Equal(t, VersionLatest, wlt.store.Version)

infos := wlt.AddressInfos()
for _, info := range infos {
assert.NotEmpty(t, info.PublicKey)
}
}

func TestUnsupportedWallet(t *testing.T) {
_, err := Open("./testdata/unsupported_wallet", true)
require.ErrorIs(t, err, UnsupportedVersionError{
WalletVersion: 3,
SupportedVersion: VersionLatest,
})
}
3 changes: 3 additions & 0 deletions wallet/testdata/unsupported_wallet
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": 3
}
59 changes: 59 additions & 0 deletions wallet/testdata/wallet_version_1
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"version": 1,
"uuid": "44156117-268a-49f0-a906-400659b7a051",
"created_at": "2024-08-27T16:45:08Z",
"network": 0,
"crc": 885152871,
"vault": {
"type": 1,
"coin_type": 21888,
"addresses": {
"pc1p4xuja689hg2434yhr32clhn97x6afw58qlrcyd": {
"address": "pc1p4xuja689hg2434yhr32clhn97x6afw58qlrcyd",
"public_key": "public1p3wmdecume03kehtcaks95jjyem2m7pev0da2yx7t0gws66nkgp7vaah5sdd5gv0s4d34y0nqxch0cqq7fnsy9v46kum7e46gx9dua4sss7ne57m5c776h0e9dt0dw8hv24uushps9arv0zk8dc2xe0k7pgk588tf",
"label": "Imported Validator Address 1",
"path": "m/65535'/21888'/1'/0'"
},
"pc1pjneygutecly9gtandrdt8j36v8g4fl42k4y5xp": {
"address": "pc1pjneygutecly9gtandrdt8j36v8g4fl42k4y5xp",
"public_key": "",
"label": "test-2",
"path": "m/12381'/21888'/1'/0"
},
"pc1z0m0vw8sjfgv7f2zgq2hfxutg8rwn7gpffhe8tf": {
"address": "pc1z0m0vw8sjfgv7f2zgq2hfxutg8rwn7gpffhe8tf",
"public_key": "",
"label": "test-1",
"path": "m/12381'/21888'/2'/0"
},
"pc1z4xuja689hg2434yhr32clhn97x6afw58a5n9ns": {
"address": "pc1z4xuja689hg2434yhr32clhn97x6afw58a5n9ns",
"public_key": "public1p3wmdecume03kehtcaks95jjyem2m7pev0da2yx7t0gws66nkgp7vaah5sdd5gv0s4d34y0nqxch0cqq7fnsy9v46kum7e46gx9dua4sss7ne57m5c776h0e9dt0dw8hv24uushps9arv0zk8dc2xe0k7pgk588tf",
"label": "Imported Reward Address 1",
"path": "m/65535'/21888'/2'/0'"
}
},
"encrypter": {
"method": "ARGON2ID-AES_256_CTR-MACV1",
"params": {
"iterations": "3",
"memory": "65536",
"parallelism": "4"
}
},
"key_store": "hlkOogftrz0kU1xqwIF4Vgj5/fuo+6FNSUWK+cVD20Fvrp1KUUNQTDex9F5SC/EMJfoJhwH5ofMek+j9bDYJL53BX5wO3w/ASqm/+j23cm6VJ40IMh+q74OX3pNOTMvSd9ZDYO0aPEA4rnKLyBMHX2L82r0DaAfLQ8zNV+ngx+SUCBPy+cpvzyoHdvYDlwf1YdSjjrbBfLaXa53Odg38Zgp9eieJb3gcq51jtM5GOOpXb1YR9jByYBb/7cn75/1WoEkojRCcylK/+/SYdCnGhsXbpVPu4/70LaPKDa+JNBytq1Kg",
"purposes": {
"purpose_bls": {
"xpub_account": "xpublic1pqdwnqqyqsp2spqqpqqqgqgxj6mlduay8ase6hkefwa2lk2esz0gn2yu59tnk3w2tgnlfk5hleuqxpqdlt5q2f207qrwq9gasqscpckjv8hggc57n044gvml7u05cpzu8ra0zq2s3x3mak6xhtzga27tdx5pypnqxhlkf9aedptzcsd8l3q6m7ch6g4lphzsdv20fvc3dsu7mlj8kdy3n5fyllg07wecmpr8y6nsr95zlz",
"xpub_validator": "xpublic1pqdwnqqyqsp2spqqzqqqgqgx4n38q9n6qtvgrnx5r8atntrfnj4thwj6my5tx8tfe6ex6p89y7qqxptrfzgmnu62rut65lddgxl5nujdn82tusz5w2wqpjqpdpgplj0zx3gaaz9wv93yqcls6clepehlvhsvdp2ndwphhmag3j3hgsfxxz434hduxvqq4n8njxh4x9mnal2wma4sw5kp6y680tkut62gk6trfjscmsdumc",
"next_account_index": 1,
"next_validator_index": 1
}
}
},
"history": {
"transactions": null,
"activities": null,
"pendings": null
}
}
Loading

0 comments on commit 0fd3ac2

Please sign in to comment.