Skip to content

Commit 500abe5

Browse files
authored
feat(ironfish): Add lock to wallet (#5270)
* feat(ironfish): Add lock to wallet * test(ironfish): Add test for no accounts
1 parent 67b47b2 commit 500abe5

File tree

6 files changed

+427
-0
lines changed

6 files changed

+427
-0
lines changed

ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7078,5 +7078,125 @@
70787078
"sequence": 1
70797079
}
70807080
}
7081+
],
7082+
"Wallet lock does nothing if the wallet is decrypted": [
7083+
{
7084+
"value": {
7085+
"encrypted": false,
7086+
"version": 4,
7087+
"id": "8af0e641-fc15-4a89-9768-83cf64abeeeb",
7088+
"name": "A",
7089+
"spendingKey": "237de7df9ed8a722d3bf4328d9739a38475db3bd5f362d17f28910b5e39c9bd9",
7090+
"viewKey": "c3f792f13567a87d7c0b04118aa366ec44923b531a49d52cf52a4e5de607c48b97e719eaeea93b8e2bae4ee0ea644769fc648a19e6e3b3daeee3720055901fd6",
7091+
"incomingViewKey": "ee50b5c9a64f8608b812c8bfcd28ab4f1dbc647dbff1a9da0fbba8eec7eab701",
7092+
"outgoingViewKey": "7d18c8f25ac2e37eae0172130d7c10d04a15e90b7b61d8c622fc8734e71d6548",
7093+
"publicAddress": "901e29364e8bc674a3055462243e0de8679c063ca7edb22c8ecadf21c8529809",
7094+
"createdAt": {
7095+
"hash": {
7096+
"type": "Buffer",
7097+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7098+
},
7099+
"sequence": 1
7100+
},
7101+
"scanningEnabled": true,
7102+
"proofAuthorizingKey": "019b4ea6bf29fdbf550e308aefc0334c6ae78e73b7df201866700b11df8a5003"
7103+
},
7104+
"head": {
7105+
"hash": {
7106+
"type": "Buffer",
7107+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7108+
},
7109+
"sequence": 1
7110+
}
7111+
},
7112+
{
7113+
"value": {
7114+
"encrypted": false,
7115+
"version": 4,
7116+
"id": "24f155b2-d22c-4572-960a-cb4ee61b6e61",
7117+
"name": "B",
7118+
"spendingKey": "2af84f62d481371999754e429f659c0c0ac7fd1af7c2c28a0dd0409f0d9dfbdb",
7119+
"viewKey": "7fef125b2a47015c9ce0f08155a0f014b1e700c2903c0a3226d9d8695098456850139aec9d0e43304c6fe1ef9ff2861d433ca376d8ad0f33c3cd610c72810ebd",
7120+
"incomingViewKey": "4e732036137814a2fba27fa409b98f3244a79b00dc29d13dacd27aed47d90604",
7121+
"outgoingViewKey": "5547943b200ede2c93710d96bcea2f5aec9982e606450d8ee6a4711a080a6df7",
7122+
"publicAddress": "bc843e766ba2c59ad68b23edcb12deb833ef878f855c21fdfab8810f62457ec5",
7123+
"createdAt": {
7124+
"hash": {
7125+
"type": "Buffer",
7126+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7127+
},
7128+
"sequence": 1
7129+
},
7130+
"scanningEnabled": true,
7131+
"proofAuthorizingKey": "7f543a79769a544ca8050ae64370d2da59e6c87d163133d5da38572d0ff2b009"
7132+
},
7133+
"head": {
7134+
"hash": {
7135+
"type": "Buffer",
7136+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7137+
},
7138+
"sequence": 1
7139+
}
7140+
}
7141+
],
7142+
"Wallet lock clears decrypted accounts if the wallet is encrypted": [
7143+
{
7144+
"value": {
7145+
"encrypted": false,
7146+
"version": 4,
7147+
"id": "4531af42-9639-47b3-afef-ce575774946e",
7148+
"name": "A",
7149+
"spendingKey": "e50fa8a0307352ca6577427a64abd6196180520d7c0df0c003e7a35d8deed329",
7150+
"viewKey": "bf55a55c74df77f2d96d23bc35d7974977514d0b4a3e78c26a827b547e96c52e43e9f623029f41f241a6a92e6fca6877c363098eda2279b721706ae060e44d64",
7151+
"incomingViewKey": "b2053da877ec8db680dcb5ac943e10315a65e8fb3965f10531f6c718fa57c505",
7152+
"outgoingViewKey": "877c69e7bc0a77bc675420a639ab446bd760870abaf2af3deb076d7fc87fe159",
7153+
"publicAddress": "64785cec540aa64f8723a07132a6ce7d03bdd35461cf9262a1fa6451a00fe41c",
7154+
"createdAt": {
7155+
"hash": {
7156+
"type": "Buffer",
7157+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7158+
},
7159+
"sequence": 1
7160+
},
7161+
"scanningEnabled": true,
7162+
"proofAuthorizingKey": "75e533bdc944c8a07f56526147faabf6624b71651e13f3ed625184dd24c0e707"
7163+
},
7164+
"head": {
7165+
"hash": {
7166+
"type": "Buffer",
7167+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7168+
},
7169+
"sequence": 1
7170+
}
7171+
},
7172+
{
7173+
"value": {
7174+
"encrypted": false,
7175+
"version": 4,
7176+
"id": "b85c7b21-6f6a-482a-bfd6-320ef989d30a",
7177+
"name": "B",
7178+
"spendingKey": "b5882a9f81ecc018d06b5dd857fcc25f3fcbf4116df81fcb421e15084960569b",
7179+
"viewKey": "49a836adf8e591451cff38acf5aff4a753cd54951cbe116ec58054141a09d95335ba0a48bb2b4f19a753c8900aca17c3c89c649a076bc99aea71bc7cafdb4eef",
7180+
"incomingViewKey": "df437af36f0edb32c899086e455a54c10799d4a587c74ab23436907484b49703",
7181+
"outgoingViewKey": "54fbf88518b8de5d45fb691b9076f770ad40ae3d1928c28dc67be28889fa4a70",
7182+
"publicAddress": "62d4847dbbc77bd2c447e1c550159a937da0d558cb6e694e38f1ec0e9114520d",
7183+
"createdAt": {
7184+
"hash": {
7185+
"type": "Buffer",
7186+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7187+
},
7188+
"sequence": 1
7189+
},
7190+
"scanningEnabled": true,
7191+
"proofAuthorizingKey": "bea22593c255a3863cddf2b5edee06e67b8398e0eb785e590f3db9575ce45c00"
7192+
},
7193+
"head": {
7194+
"hash": {
7195+
"type": "Buffer",
7196+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
7197+
},
7198+
"sequence": 1
7199+
}
7200+
}
70817201
]
70827202
}

ironfish/src/wallet/wallet.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2449,4 +2449,49 @@ describe('Wallet', () => {
24492449
expect(node.wallet.encryptedAccounts).toHaveLength(2)
24502450
})
24512451
})
2452+
2453+
describe('lock', () => {
2454+
it('does nothing if the wallet is decrypted', async () => {
2455+
const { node } = nodeTest
2456+
2457+
await useAccountFixture(node.wallet, 'A')
2458+
await useAccountFixture(node.wallet, 'B')
2459+
expect(node.wallet.accounts).toHaveLength(2)
2460+
expect(node.wallet.encryptedAccounts).toHaveLength(0)
2461+
2462+
await node.wallet.lock()
2463+
expect(node.wallet.accounts).toHaveLength(2)
2464+
expect(node.wallet.encryptedAccounts).toHaveLength(0)
2465+
})
2466+
2467+
it('clears decrypted accounts if the wallet is encrypted', async () => {
2468+
const { node } = nodeTest
2469+
const passphrase = 'foo'
2470+
2471+
await useAccountFixture(node.wallet, 'A')
2472+
await useAccountFixture(node.wallet, 'B')
2473+
2474+
// TODO(rohanjadvani)
2475+
// This is temporary for a unit test to keep PRs small.
2476+
// This will be refactored once unlock comes in a subsequent change.
2477+
// The goal is to mock an unlocked state by copying and setting
2478+
// decrypted accounts within the wallet.
2479+
const accountById = new Map(node.wallet.accountById.entries())
2480+
2481+
await node.wallet.encrypt(passphrase)
2482+
expect(node.wallet.accounts).toHaveLength(0)
2483+
expect(node.wallet.encryptedAccounts).toHaveLength(2)
2484+
2485+
// Mock unlock until the method is implemented
2486+
node.wallet.locked = false
2487+
for (const [k, v] of accountById.entries()) {
2488+
node.wallet.accountById.set(k, v)
2489+
}
2490+
expect(node.wallet.accounts).toHaveLength(2)
2491+
2492+
await node.wallet.lock()
2493+
expect(node.wallet.accounts).toHaveLength(0)
2494+
expect(node.wallet.locked).toBe(true)
2495+
})
2496+
})
24522497
})

ironfish/src/wallet/wallet.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export class Wallet {
110110
protected isStarted = false
111111
protected isOpen = false
112112
protected isSyncingTransactionGossip = false
113+
locked: boolean
113114
protected eventLoopTimeout: SetTimeoutToken | null = null
114115
private readonly createTransactionMutex: Mutex
115116
private readonly eventLoopAbortController: AbortController
@@ -144,6 +145,7 @@ export class Wallet {
144145
this.networkId = networkId
145146
this.nodeClient = nodeClient || null
146147
this.rebroadcastAfter = rebroadcastAfter ?? 10
148+
this.locked = false
147149
this.createTransactionMutex = new Mutex()
148150
this.eventLoopAbortController = new AbortController()
149151

@@ -220,9 +222,13 @@ export class Wallet {
220222
walletDb: this.walletDb,
221223
})
222224
this.encryptedAccountById.set(id, encryptedAccount)
225+
226+
this.locked = true
223227
} else {
224228
const account = new Account({ accountValue, walletDb: this.walletDb })
225229
this.accountById.set(account.id, account)
230+
231+
this.locked = false
226232
}
227233
}
228234

@@ -231,6 +237,7 @@ export class Wallet {
231237
}
232238

233239
private unload(): void {
240+
this.encryptedAccountById.clear()
234241
this.accountById.clear()
235242

236243
this.defaultAccount = null
@@ -1796,4 +1803,20 @@ export class Wallet {
17961803
unlock()
17971804
}
17981805
}
1806+
1807+
async lock(tx?: IDatabaseTransaction): Promise<void> {
1808+
const unlock = await this.createTransactionMutex.lock()
1809+
1810+
try {
1811+
const encrypted = await this.walletDb.accountsEncrypted(tx)
1812+
if (!encrypted) {
1813+
return
1814+
}
1815+
1816+
this.accountById.clear()
1817+
this.locked = true
1818+
} finally {
1819+
unlock()
1820+
}
1821+
}
17991822
}

0 commit comments

Comments
 (0)