Skip to content

Commit acc10bd

Browse files
authored
Merge pull request #94 from hyperledger/generic-wallet
Add support for using the FileSystem Wallet without using EthAddress
2 parents d5f269b + f456e81 commit acc10bd

5 files changed

Lines changed: 295 additions & 123 deletions

File tree

pkg/fswallet/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
package fswallet
1818

1919
import (
20+
"context"
21+
2022
"github.com/hyperledger/firefly-common/pkg/config"
23+
"github.com/hyperledger/firefly-signer/pkg/keystorev3"
2124
)
2225

2326
const (
@@ -61,6 +64,12 @@ type Config struct {
6164
Metadata MetadataConfig
6265
}
6366

67+
type ConfigGeneric struct {
68+
Config
69+
WalletFileValidator func(ctx context.Context, addrString string, kv3 keystorev3.WalletFile) error
70+
AddressValidator func(ctx context.Context, addrString string) (string, error)
71+
}
72+
6473
type FilenamesConfig struct {
6574
PrimaryMatchRegex string
6675
PrimaryExt string

pkg/fswallet/fslistener_test.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
"github.com/stretchr/testify/assert"
3232
)
3333

34-
func newEmptyWalletTestDir(t *testing.T, init bool) (context.Context, *fsWallet, chan ethtypes.Address0xHex, func()) {
34+
func newEmptyWalletTestDir(t *testing.T, init bool) (context.Context, *walletEthAddr, chan ethtypes.Address0xHex, func()) {
3535
config.RootConfigReset()
3636
logrus.SetLevel(logrus.TraceLevel)
3737

@@ -50,7 +50,7 @@ func newEmptyWalletTestDir(t *testing.T, init bool) (context.Context, *fsWallet,
5050
assert.NoError(t, err)
5151
}
5252

53-
return ctx, ff.(*fsWallet), listener, func() {
53+
return ctx, ff.(*walletEthAddr), listener, func() {
5454
ff.Close()
5555
}
5656
}
@@ -67,13 +67,14 @@ func TestFileListener(t *testing.T) {
6767
testPWFIle, err := os.ReadFile("../../test/keystore_toml/1f185718734552d08278aa70f804580bab5fd2b4.pwd")
6868
assert.NoError(t, err)
6969

70-
err = os.WriteFile(path.Join(f.conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.pwd"), testPWFIle, 0644)
70+
conf := &f.gw.(*fsWallet).conf
71+
err = os.WriteFile(path.Join(conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.pwd"), testPWFIle, 0644)
7172
assert.NoError(t, err)
7273

7374
testKeyFIle, err := os.ReadFile("../../test/keystore_toml/1f185718734552d08278aa70f804580bab5fd2b4.key.json")
7475
assert.NoError(t, err)
7576

76-
err = os.WriteFile(path.Join(f.conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.key.json"), testKeyFIle, 0644)
77+
err = os.WriteFile(path.Join(conf.Path, "1f185718734552d08278aa70f804580bab5fd2b4.key.json"), testKeyFIle, 0644)
7778
assert.NoError(t, err)
7879

7980
newAddr1 := <-listener1
@@ -93,15 +94,16 @@ func TestFileListenerStartFail(t *testing.T) {
9394
ctx, f, _, done := newEmptyWalletTestDir(t, false)
9495
defer done()
9596

96-
os.RemoveAll(f.conf.Path)
97+
conf := &f.gw.(*fsWallet).conf
98+
os.RemoveAll(conf.Path)
9799
err := f.Initialize(ctx)
98100
assert.Regexp(t, "FF22060", err)
99101

100102
}
101103

102104
func TestFileListenerRemoveDirWhileListening(t *testing.T) {
103105

104-
ctx, f, _, done := newEmptyWalletTestDir(t, true)
106+
ctx, ew, _, done := newEmptyWalletTestDir(t, true)
105107
defer done()
106108

107109
errs := make(chan error, 1)
@@ -111,6 +113,7 @@ func TestFileListenerRemoveDirWhileListening(t *testing.T) {
111113
time.Sleep(10 * time.Millisecond)
112114
cancelCtx()
113115
}()
116+
f := ew.gw.(*fsWallet)
114117
f.fsListenerLoop(ctx, func() {}, make(chan fsnotify.Event), errs)
115118

116119
}

pkg/fswallet/fswallet.go

Lines changed: 62 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,33 @@ import (
3232
"github.com/hyperledger/firefly-common/pkg/i18n"
3333
"github.com/hyperledger/firefly-common/pkg/log"
3434
"github.com/hyperledger/firefly-signer/internal/signermsgs"
35-
"github.com/hyperledger/firefly-signer/pkg/eip712"
36-
"github.com/hyperledger/firefly-signer/pkg/ethsigner"
37-
"github.com/hyperledger/firefly-signer/pkg/ethtypes"
3835
"github.com/hyperledger/firefly-signer/pkg/keystorev3"
39-
"github.com/hyperledger/firefly-signer/pkg/secp256k1"
4036
"github.com/karlseguin/ccache"
4137
"github.com/pelletier/go-toml"
4238
"gopkg.in/yaml.v2"
4339
)
4440

45-
type SyncAddressCallback func(context.Context, ethtypes.Address0xHex) error
41+
type SyncCallback func(context.Context, string) error
4642

4743
// Wallet is a directory containing a set of KeystoreV3 files, conforming
4844
// to the ethsigner.Wallet interface and providing notifications when new
4945
// keys are added to the wallet (via FS listener).
50-
type Wallet interface {
51-
ethsigner.WalletTypedData
52-
GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex) (keystorev3.WalletFile, error)
53-
SetSyncAddressCallback(SyncAddressCallback)
54-
AddListener(listener chan<- ethtypes.Address0xHex)
46+
type WalletGeneric interface {
47+
Initialize(ctx context.Context) error
48+
Refresh(ctx context.Context) error
49+
Close() error
50+
51+
GetAccounts(ctx context.Context) ([]string, error)
52+
GetWalletFile(ctx context.Context, addr string) (keystorev3.WalletFile, error)
53+
SetSyncCallback(SyncCallback)
54+
AddListener(listener chan<- string)
5555
}
5656

57-
func NewFilesystemWallet(ctx context.Context, conf *Config, initialListeners ...chan<- ethtypes.Address0xHex) (ww Wallet, err error) {
57+
func NewFilesystemWalletGeneric(ctx context.Context, conf *ConfigGeneric, initialListeners ...chan<- string) (ww WalletGeneric, err error) {
5858
w := &fsWallet{
5959
conf: *conf,
6060
listeners: initialListeners,
61-
addressToFileMap: make(map[ethtypes.Address0xHex]string),
61+
addressToFileMap: make(map[string]string),
6262
}
6363
w.signerCache = ccache.New(
6464
// We use a LRU cache with a size-aware max
@@ -96,39 +96,23 @@ func goTemplateFromConfig(ctx context.Context, name string, templateStr string)
9696
}
9797

9898
type fsWallet struct {
99-
conf Config
99+
conf ConfigGeneric
100100
signerCache *ccache.Cache
101101
signerCacheTTL time.Duration
102102
metadataKeyFileProperty *template.Template
103103
metadataPasswordFileProperty *template.Template
104104
primaryMatchRegex *regexp.Regexp
105-
syncAddressCallback SyncAddressCallback
105+
syncCallback SyncCallback
106106

107107
mux sync.Mutex
108-
addressToFileMap map[ethtypes.Address0xHex]string // map for lookup to filename
109-
addressList []*ethtypes.Address0xHex // ordered list in filename at startup, then notification order
110-
listeners []chan<- ethtypes.Address0xHex
108+
addressToFileMap map[string]string // map for lookup to filename
109+
addressList []string // ordered list in filename at startup, then notification order
110+
listeners []chan<- string
111111
fsListenerCancel context.CancelFunc
112112
fsListenerStarted chan error
113113
fsListenerDone chan struct{}
114114
}
115115

116-
func (w *fsWallet) Sign(ctx context.Context, txn *ethsigner.Transaction, chainID int64) ([]byte, error) {
117-
keypair, err := w.getSignerForJSONAccount(ctx, txn.From)
118-
if err != nil {
119-
return nil, err
120-
}
121-
return txn.Sign(keypair, chainID)
122-
}
123-
124-
func (w *fsWallet) SignTypedDataV4(ctx context.Context, from ethtypes.Address0xHex, payload *eip712.TypedData) (*ethsigner.EIP712Result, error) {
125-
keypair, err := w.getSignerForAddr(ctx, from)
126-
if err != nil {
127-
return nil, err
128-
}
129-
return ethsigner.SignTypedDataV4(ctx, keypair, payload)
130-
}
131-
132116
func (w *fsWallet) Initialize(ctx context.Context) error {
133117
// Run a get accounts pass, to check all is ok
134118
lCtx, lCancel := context.WithCancel(log.WithLogField(ctx, "fswallet", w.conf.Path))
@@ -144,7 +128,7 @@ func (w *fsWallet) Initialize(ctx context.Context) error {
144128
}
145129

146130
// Asynchronously listen for all addresses as they are detected - during startup, or after startup
147-
func (w *fsWallet) AddListener(listener chan<- ethtypes.Address0xHex) {
131+
func (w *fsWallet) AddListener(listener chan<- string) {
148132
w.mux.Lock()
149133
defer w.mux.Unlock()
150134
w.listeners = append(w.listeners, listener)
@@ -162,47 +146,54 @@ func (w *fsWallet) AddListener(listener chan<- ethtypes.Address0xHex) {
162146
// will be called concurrently. However, it is guaranteed to be called in-line with the
163147
// initialize/refresh so you know once that calls returns all new keys detected by it have
164148
// driven the callback.
165-
func (w *fsWallet) SetSyncAddressCallback(callback SyncAddressCallback) {
166-
w.syncAddressCallback = callback
149+
func (w *fsWallet) SetSyncCallback(callback SyncCallback) {
150+
w.syncCallback = callback
167151
}
168152

169153
// GetAccounts returns the currently cached list of known addresses
170-
func (w *fsWallet) GetAccounts(_ context.Context) ([]*ethtypes.Address0xHex, error) {
154+
func (w *fsWallet) GetAccounts(_ context.Context) ([]string, error) {
171155
w.mux.Lock()
172156
defer w.mux.Unlock()
173-
accounts := make([]*ethtypes.Address0xHex, len(w.addressList))
157+
accounts := make([]string, len(w.addressList))
174158
copy(accounts, w.addressList)
175159
return accounts, nil
176160
}
177161

178-
func (w *fsWallet) matchFilename(ctx context.Context, f fs.FileInfo) *ethtypes.Address0xHex {
162+
func (w *fsWallet) matchFilename(ctx context.Context, f fs.FileInfo) string {
179163
if f.IsDir() {
180164
log.L(ctx).Tracef("Ignoring '%s/%s: directory", w.conf.Path, f.Name())
181-
return nil
165+
return ""
182166
}
183167
if w.primaryMatchRegex != nil {
184168
match := w.primaryMatchRegex.FindStringSubmatch(f.Name())
185169
if match == nil {
186170
log.L(ctx).Tracef("Ignoring '%s/%s': does not match regexp", w.conf.Path, f.Name())
187-
return nil
171+
return ""
188172
}
189-
addr, err := ethtypes.NewAddress(match[1]) // safe due to SubexpNames() length check
190-
if err != nil {
191-
log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), match[1], err)
192-
return nil
173+
var err error
174+
addrString := match[1]
175+
if w.conf.AddressValidator != nil {
176+
addrString, err = w.conf.AddressValidator(ctx, addrString)
177+
if err != nil {
178+
log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), match[1], err)
179+
return ""
180+
}
193181
}
194-
return addr
182+
return addrString
195183
}
196184
if !strings.HasSuffix(f.Name(), w.conf.Filenames.PrimaryExt) {
197185
log.L(ctx).Tracef("Ignoring '%s/%s: does not match extension '%s'", w.conf.Path, f.Name(), w.conf.Filenames.PrimaryExt)
198186
}
199187
addrString := strings.TrimSuffix(f.Name(), w.conf.Filenames.PrimaryExt)
200-
addr, err := ethtypes.NewAddress(addrString)
201-
if err != nil {
202-
log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), addrString, err)
203-
return nil
188+
if w.conf.AddressValidator != nil {
189+
var err error
190+
addrString, err = w.conf.AddressValidator(ctx, addrString)
191+
if err != nil {
192+
log.L(ctx).Warnf("Ignoring '%s/%s': invalid address '%s': %s", w.conf.Path, f.Name(), addrString, err)
193+
return ""
194+
}
204195
}
205-
return addr
196+
return addrString
206197
}
207198

208199
func (w *fsWallet) Refresh(ctx context.Context) error {
@@ -221,16 +212,16 @@ func (w *fsWallet) Refresh(ctx context.Context) error {
221212
return w.notifyNewFiles(ctx, files...)
222213
}
223214

224-
func (w *fsWallet) processNewFiles(ctx context.Context, files ...fs.FileInfo) (listeners []chan<- ethtypes.Address0xHex, newAddresses []*ethtypes.Address0xHex) {
215+
func (w *fsWallet) processNewFiles(ctx context.Context, files ...fs.FileInfo) (listeners []chan<- string, newAddresses []string) {
225216
// Lock now we have the list
226217
w.mux.Lock()
227218
defer w.mux.Unlock()
228-
newAddresses = make([]*ethtypes.Address0xHex, 0)
219+
newAddresses = make([]string, 0)
229220
for _, f := range files {
230221
addr := w.matchFilename(ctx, f)
231-
if addr != nil {
232-
if existingFilename, exists := w.addressToFileMap[*addr]; existingFilename != f.Name() {
233-
w.addressToFileMap[*addr] = f.Name()
222+
if addr != "" {
223+
if existingFilename, exists := w.addressToFileMap[addr]; existingFilename != f.Name() {
224+
w.addressToFileMap[addr] = f.Name()
234225
if !exists {
235226
log.L(ctx).Debugf("Added address: %s (file=%s)", addr, f.Name())
236227
w.addressList = append(w.addressList, addr)
@@ -239,7 +230,7 @@ func (w *fsWallet) processNewFiles(ctx context.Context, files ...fs.FileInfo) (l
239230
}
240231
}
241232
}
242-
listeners = make([]chan<- ethtypes.Address0xHex, len(w.listeners))
233+
listeners = make([]chan<- string, len(w.listeners))
243234
copy(listeners, w.listeners)
244235
log.L(ctx).Debugf("Processed %d files. Found %d new addresses", len(files), len(newAddresses))
245236
return listeners, newAddresses
@@ -252,10 +243,10 @@ func (w *fsWallet) notifyNewFiles(ctx context.Context, files ...fs.FileInfo) err
252243

253244
if len(newAddresses) > 0 {
254245

255-
if w.syncAddressCallback != nil {
246+
if w.syncCallback != nil {
256247
// Sync callbacks are called here in-line, but outside the lock.
257248
for _, addr := range newAddresses {
258-
if err := w.syncAddressCallback(ctx, *addr); err != nil {
249+
if err := w.syncCallback(ctx, addr); err != nil {
259250
log.L(ctx).Errorf("sync listener returned error for address %s: %s", addr, err)
260251
return err
261252
}
@@ -267,7 +258,7 @@ func (w *fsWallet) notifyNewFiles(ctx context.Context, files ...fs.FileInfo) err
267258
go func() {
268259
for _, l := range listeners {
269260
for _, addr := range newAddresses {
270-
l <- *addr
261+
l <- addr
271262
}
272263
}
273264
}()
@@ -286,59 +277,37 @@ func (w *fsWallet) Close() error {
286277
return nil
287278
}
288279

289-
func (w *fsWallet) getSignerForJSONAccount(ctx context.Context, rawAddrJSON json.RawMessage) (*secp256k1.KeyPair, error) {
280+
func (w *fsWallet) GetWalletFile(ctx context.Context, addrString string) (keystorev3.WalletFile, error) {
290281

291-
// We require an ethereum address in the "from" field
292-
var from ethtypes.Address0xHex
293-
err := json.Unmarshal(rawAddrJSON, &from)
294-
if err != nil {
295-
return nil, err
296-
}
297-
return w.getSignerForAddr(ctx, from)
298-
}
299-
300-
func (w *fsWallet) getSignerForAddr(ctx context.Context, from ethtypes.Address0xHex) (*secp256k1.KeyPair, error) {
301-
302-
wf, err := w.GetWalletFile(ctx, from)
303-
if err != nil {
304-
return nil, err
305-
}
306-
return wf.KeyPair(), nil
307-
308-
}
309-
310-
func (w *fsWallet) GetWalletFile(ctx context.Context, addr ethtypes.Address0xHex) (keystorev3.WalletFile, error) {
311-
312-
addrString := addr.String()
313282
cached := w.signerCache.Get(addrString)
314283
if cached != nil {
315284
cached.Extend(w.signerCacheTTL)
316285
return cached.Value().(keystorev3.WalletFile), nil
317286
}
318287

319288
w.mux.Lock()
320-
primaryFilename, ok := w.addressToFileMap[addr]
289+
primaryFilename, ok := w.addressToFileMap[addrString]
321290
w.mux.Unlock()
322291
if !ok {
323-
return nil, i18n.NewError(ctx, signermsgs.MsgWalletNotAvailable, addr)
292+
return nil, i18n.NewError(ctx, signermsgs.MsgWalletNotAvailable, addrString)
324293
}
325294

326-
kv3, err := w.loadWalletFile(ctx, addr, path.Join(w.conf.Path, primaryFilename))
295+
kv3, err := w.loadWalletFile(ctx, addrString, path.Join(w.conf.Path, primaryFilename))
327296
if err != nil {
328297
return nil, err
329298
}
330299

331-
keypair := kv3.KeyPair()
332-
if keypair.Address != addr {
333-
return nil, i18n.NewError(ctx, signermsgs.MsgAddressMismatch, keypair.Address, addr)
300+
if w.conf.WalletFileValidator != nil {
301+
if err := w.conf.WalletFileValidator(ctx, addrString, kv3); err != nil {
302+
return nil, err
303+
}
334304
}
335305

336306
w.signerCache.Set(addrString, kv3, w.signerCacheTTL)
337307
return kv3, err
338-
339308
}
340309

341-
func (w *fsWallet) loadWalletFile(ctx context.Context, addr ethtypes.Address0xHex, primaryFilename string) (keystorev3.WalletFile, error) {
310+
func (w *fsWallet) loadWalletFile(ctx context.Context, addr string, primaryFilename string) (keystorev3.WalletFile, error) {
342311

343312
b, err := os.ReadFile(primaryFilename)
344313
if err != nil {
@@ -395,7 +364,7 @@ func (w *fsWallet) loadWalletFile(ctx context.Context, addr ethtypes.Address0xHe
395364

396365
}
397366

398-
func (w *fsWallet) getKeyAndPasswordFiles(ctx context.Context, addr ethtypes.Address0xHex, primaryFilename string, primaryFile []byte) (kf string, pf string, err error) {
367+
func (w *fsWallet) getKeyAndPasswordFiles(ctx context.Context, addr string, primaryFilename string, primaryFile []byte) (kf string, pf string, err error) {
399368
if strings.ToLower(w.conf.Metadata.Format) == "auto" {
400369
w.conf.Metadata.Format = strings.TrimPrefix(w.conf.Filenames.PrimaryExt, ".")
401370
}
@@ -414,7 +383,7 @@ func (w *fsWallet) getKeyAndPasswordFiles(ctx context.Context, addr ethtypes.Add
414383
if passwordPath == "" {
415384
passwordPath = w.conf.Path
416385
}
417-
passwordFilename := addr.String()
386+
passwordFilename := addr
418387
if !w.conf.Filenames.With0xPrefix {
419388
passwordFilename = strings.TrimPrefix(passwordFilename, "0x")
420389
}

0 commit comments

Comments
 (0)