Skip to content

Commit 22db8b3

Browse files
authored
feat(ironfish): Require passphrase when renaming encrypted accounts (#5355)
1 parent df7be6f commit 22db8b3

File tree

5 files changed

+154
-5
lines changed

5 files changed

+154
-5
lines changed

ironfish/src/rpc/routes/wallet/rename.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ routes.register<typeof RenameAccountRequestSchema, RenameAccountResponse>(
2121
AssertHasRpcContext(request, context, 'wallet')
2222

2323
const account = getAccount(context.wallet, request.data.account)
24-
await account.setName(request.data.newName)
24+
await account.setName(request.data.newName, { passphrase: request.data.passphrase })
2525
request.end()
2626
},
2727
)

ironfish/src/rpc/routes/wallet/renameAccount.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import { routes } from '../router'
77
import { AssertHasRpcContext } from '../rpcContext'
88
import { getAccount } from './utils'
99

10-
export type RenameAccountRequest = { account: string; newName: string }
10+
export type RenameAccountRequest = { account: string; newName: string; passphrase?: string }
1111
export type RenameAccountResponse = undefined
1212

1313
export const RenameAccountRequestSchema: yup.ObjectSchema<RenameAccountRequest> = yup
1414
.object({
1515
account: yup.string().defined(),
1616
newName: yup.string().defined(),
17+
passphrase: yup.string().optional(),
1718
})
1819
.defined()
1920

@@ -28,7 +29,7 @@ routes.register<typeof RenameAccountRequestSchema, RenameAccountResponse>(
2829
AssertHasRpcContext(request, context, 'wallet')
2930

3031
const account = getAccount(context.wallet, request.data.account)
31-
await account.setName(request.data.newName)
32+
await account.setName(request.data.newName, { passphrase: request.data.passphrase })
3233
request.end()
3334
},
3435
)

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

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5903,5 +5903,98 @@
59035903
"sequence": 1
59045904
}
59055905
}
5906+
],
5907+
"Accounts setName should throw an error if the passphrase is missing and the wallet is encrypted": [
5908+
{
5909+
"value": {
5910+
"encrypted": false,
5911+
"version": 4,
5912+
"id": "6f0698b4-a99c-46aa-9391-59f0c55cd755",
5913+
"name": "accountA",
5914+
"spendingKey": "89001fcdef6bff7e9fd76d4ae6275bf6786afc2797eded9df094ae4a6894782d",
5915+
"viewKey": "389bc77ae499f3edc0dc445a732add6f36c275b260efe2c791cc515f6c2c0cd75f5b31e9b1f82edb0ee57b9304ece90d48f43c36d78660b04960b9692485d058",
5916+
"incomingViewKey": "fdd70ff012b4e48576bdd71207ebe1e3747811c77fa1a25862c3b869123ce007",
5917+
"outgoingViewKey": "9664b763a0418a476d072c715fcbbba58bcaf60cc951ba017195d75453131f11",
5918+
"publicAddress": "14a9bfb247dcf632f85ff79ebef222cc9ccf364f9b3e3e0ee39b75d68f80782a",
5919+
"createdAt": {
5920+
"hash": {
5921+
"type": "Buffer",
5922+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
5923+
},
5924+
"sequence": 1
5925+
},
5926+
"scanningEnabled": true,
5927+
"proofAuthorizingKey": "8c11ae2523136f4b11bada56d9bcab2b6591cc99cf7aede8a238c5fefd7fce0d"
5928+
},
5929+
"head": {
5930+
"hash": {
5931+
"type": "Buffer",
5932+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
5933+
},
5934+
"sequence": 1
5935+
}
5936+
}
5937+
],
5938+
"Accounts setName should throw an error if the passphrase is incorrect and the wallet is encrypted": [
5939+
{
5940+
"value": {
5941+
"encrypted": false,
5942+
"version": 4,
5943+
"id": "92b122e5-9f14-453b-a364-e20a7d107305",
5944+
"name": "accountA",
5945+
"spendingKey": "3d68ebfd3d600792fc94d583bbab97ab4d02b9f41aa2a6655e151e41d4a33d8d",
5946+
"viewKey": "51e699920432cf221351568ade21fb8af4110c7ad57ad90cc2664340d50d6f207a19eb846feb76588f467e4dce99d0da074ca680aca11d3d8c91bd47ae5d9081",
5947+
"incomingViewKey": "15f8cb20e3a7b494474f4c4737bc0403dbb5de6e532e2c83a4469cd7f95f5b02",
5948+
"outgoingViewKey": "fc1b2e0e85cca6ddaf657baf2c69c76b403afe1adadd73b794c5cb81724d240d",
5949+
"publicAddress": "899ea1e9be7202aedab0a41f6b9cf661ce20e41ba11c1ee9d15ac64d8c96a391",
5950+
"createdAt": {
5951+
"hash": {
5952+
"type": "Buffer",
5953+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
5954+
},
5955+
"sequence": 1
5956+
},
5957+
"scanningEnabled": true,
5958+
"proofAuthorizingKey": "d0d08a05953a50a0ce8b35e0c410b7dde77e3bf6099aaa4fc413de2096f7ef0a"
5959+
},
5960+
"head": {
5961+
"hash": {
5962+
"type": "Buffer",
5963+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
5964+
},
5965+
"sequence": 1
5966+
}
5967+
}
5968+
],
5969+
"Accounts setName should save the encrypted account if the passphrase is correct and the wallet is encrypted": [
5970+
{
5971+
"value": {
5972+
"encrypted": false,
5973+
"version": 4,
5974+
"id": "1dd7e196-ffed-4fe0-8dbd-c49f82bf44b7",
5975+
"name": "accountA",
5976+
"spendingKey": "71ec323628c95bd56df353ec444b8ceb3d463603e82a89d4ac3336bdf630993a",
5977+
"viewKey": "5219abc719ecb8954e77d89e60f4fc82588e8f619ba4da38b53ed0471aeccb200cc7cec29ca533c769c96213417f78ccfaf2ba3f12a4b948a0da242805eb044c",
5978+
"incomingViewKey": "d9d70e59490b44c078300a23376e4fc516b47a31231c77538d17740f399e3d00",
5979+
"outgoingViewKey": "e3eca4b31e0d4b48e27458db2eff35b4d713d84b0b07505fdc8d49b2e40ab69d",
5980+
"publicAddress": "fbc47fe75ef9534b28b95fd17288488b89a4b752191a323a3703520095e8c24b",
5981+
"createdAt": {
5982+
"hash": {
5983+
"type": "Buffer",
5984+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
5985+
},
5986+
"sequence": 1
5987+
},
5988+
"scanningEnabled": true,
5989+
"proofAuthorizingKey": "f335356ed8f77c4facebc2ad9377ab05e6d71ec83aa772df0c23e2733458a10c"
5990+
},
5991+
"head": {
5992+
"hash": {
5993+
"type": "Buffer",
5994+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
5995+
},
5996+
"sequence": 1
5997+
}
5998+
}
59065999
]
59076000
}

ironfish/src/wallet/account/account.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { AsyncUtils } from '../../utils/async'
2020
import { BalanceValue } from '../walletdb/balanceValue'
2121
import { Account } from './account'
22+
import { EncryptedAccount } from './encryptedAccount'
2223

2324
describe('Accounts', () => {
2425
const nodeTest = createNodeTest()
@@ -186,6 +187,49 @@ describe('Accounts', () => {
186187
await expect(account.setName('')).rejects.toThrow('Account name cannot be blank')
187188
await expect(account.setName(' ')).rejects.toThrow('Account name cannot be blank')
188189
})
190+
191+
it('should throw an error if the passphrase is missing and the wallet is encrypted', async () => {
192+
const { node } = nodeTest
193+
const passphrase = 'foo'
194+
195+
const account = await useAccountFixture(node.wallet, 'accountA')
196+
await node.wallet.encrypt(passphrase)
197+
198+
await expect(account.setName('B')).rejects.toThrow()
199+
})
200+
201+
it('should throw an error if the passphrase is incorrect and the wallet is encrypted', async () => {
202+
const { node } = nodeTest
203+
const passphrase = 'foo'
204+
205+
const account = await useAccountFixture(node.wallet, 'accountA')
206+
await node.wallet.encrypt(passphrase)
207+
208+
await expect(account.setName('B', { passphrase: 'incorrect ' })).rejects.toThrow()
209+
})
210+
211+
it('should save the encrypted account if the passphrase is correct and the wallet is encrypted', async () => {
212+
const { node } = nodeTest
213+
const passphrase = 'foo'
214+
const newName = 'B'
215+
216+
const account = await useAccountFixture(node.wallet, 'accountA')
217+
await node.wallet.encrypt(passphrase)
218+
219+
await account.setName(newName, { passphrase })
220+
221+
const accountValue = await node.wallet.walletDb.accounts.get(account.id)
222+
Assert.isNotUndefined(accountValue)
223+
Assert.isTrue(accountValue.encrypted)
224+
225+
const encryptedAccount = new EncryptedAccount({
226+
data: accountValue.data,
227+
walletDb: node.wallet.walletDb,
228+
})
229+
const decryptedAccount = encryptedAccount.decrypt(passphrase)
230+
231+
expect(decryptedAccount.name).toEqual(newName)
232+
})
189233
})
190234

191235
describe('loadPendingTransactions', () => {

ironfish/src/wallet/account/account.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,25 @@ export class Account {
127127
}
128128
}
129129

130-
async setName(name: string, tx?: IDatabaseTransaction): Promise<void> {
130+
async setName(
131+
name: string,
132+
options?: { passphrase?: string },
133+
tx?: IDatabaseTransaction,
134+
): Promise<void> {
131135
if (!name.trim()) {
132136
throw new Error('Account name cannot be blank')
133137
}
134138

139+
const walletEncrypted = await this.walletDb.accountsEncrypted(tx)
140+
135141
this.name = name
136142

137-
await this.walletDb.setAccount(this, tx)
143+
if (walletEncrypted) {
144+
Assert.isNotUndefined(options?.passphrase)
145+
await this.walletDb.setEncryptedAccount(this, options.passphrase, tx)
146+
} else {
147+
await this.walletDb.setAccount(this, tx)
148+
}
138149
}
139150

140151
async *getNotes(

0 commit comments

Comments
 (0)