Skip to content

Commit df7be6f

Browse files
authored
feat(ironfish): Require passphrase when creating encrypted account (#5357)
1 parent f6b340a commit df7be6f

File tree

5 files changed

+162
-5
lines changed

5 files changed

+162
-5
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ routes.register<typeof CreateAccountRequestSchema, CreateAccountResponse>(
3030
)
3131
}
3232

33-
const account = await context.wallet.createAccount(name)
33+
const account = await context.wallet.createAccount(name, {
34+
passphrase: request.data.passphrase,
35+
})
3436
if (context.wallet.nodeClient) {
3537
void context.wallet.scan()
3638
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { AssertHasRpcContext } from '../rpcContext'
1818
* Hence, we're adding a new createAccount endpoint and will eventually sunset the create endpoint.
1919
*/
2020

21-
export type CreateAccountRequest = { name: string; default?: boolean }
21+
export type CreateAccountRequest = { name: string; default?: boolean; passphrase?: string }
2222
export type CreateAccountResponse = {
2323
name: string
2424
publicAddress: string
@@ -29,6 +29,7 @@ export const CreateAccountRequestSchema: yup.ObjectSchema<CreateAccountRequest>
2929
.object({
3030
name: yup.string().defined(),
3131
default: yup.boolean().optional(),
32+
passphrase: yup.string().optional(),
3233
})
3334
.defined()
3435

@@ -48,7 +49,9 @@ routes.register<typeof CreateAccountRequestSchema, CreateAccountResponse>(
4849

4950
let account
5051
try {
51-
account = await context.wallet.createAccount(request.data.name)
52+
account = await context.wallet.createAccount(request.data.name, {
53+
passphrase: request.data.passphrase,
54+
})
5255
} catch (e) {
5356
if (e instanceof DuplicateAccountNameError) {
5457
throw new RpcValidationError(e.message, 400, RPC_ERROR_CODES.DUPLICATE_ACCOUNT_NAME)

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

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8010,5 +8010,98 @@
80108010
"sequence": 1
80118011
}
80128012
}
8013+
],
8014+
"Wallet createAccount should throw an error if the wallet is encrypted and no passphrase is provided": [
8015+
{
8016+
"value": {
8017+
"encrypted": false,
8018+
"version": 4,
8019+
"id": "1c6e07eb-c169-4b57-abf9-265edf76e1fa",
8020+
"name": "A",
8021+
"spendingKey": "d943511a50b8462f22b2dfd211af1cb259d35d863ba9bf951a0cbedd7e31da00",
8022+
"viewKey": "dd73ab5660d0d0a0e284cb8bf7ff12b426359088cf80e8b060ce004f4dbc37468a268af4b8d099aa7f1b28fe916a43f9d1d453c2c39998695f4bc9cbbff17f60",
8023+
"incomingViewKey": "dfae17b21ba416ec45f6d501ad18b357b0f8a74e0d5c634d8916f87214dec902",
8024+
"outgoingViewKey": "cbec4ca0886e0f2f9463b49f30d72f82c5b785c7f93735bfa873cd02447294dc",
8025+
"publicAddress": "c189195086f945f7eb47761b5c76186885a3ed80e0f4e004e0bec391d0a2d362",
8026+
"createdAt": {
8027+
"hash": {
8028+
"type": "Buffer",
8029+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
8030+
},
8031+
"sequence": 1
8032+
},
8033+
"scanningEnabled": true,
8034+
"proofAuthorizingKey": "58d69b65852dcbc009694caf64e01350a114c3dd3184a187dcb9836be4577d0e"
8035+
},
8036+
"head": {
8037+
"hash": {
8038+
"type": "Buffer",
8039+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
8040+
},
8041+
"sequence": 1
8042+
}
8043+
}
8044+
],
8045+
"Wallet createAccount should throw an error if the wallet is encrypted and an incorrect passphrase is provided": [
8046+
{
8047+
"value": {
8048+
"encrypted": false,
8049+
"version": 4,
8050+
"id": "bf34ed08-531f-426a-ad4e-04850e4ec30d",
8051+
"name": "A",
8052+
"spendingKey": "5e0dc425fe146cfae4ff69ae152befc60d63a43ae25e61dfec42f80f96ab3a66",
8053+
"viewKey": "65fb139017e246a2deab7dd168383e4d1e2199ec5dbdc3ff4818e22b7aeb265f58f220bf5059c259f99771b03ffb1e7f66162bc7d4191342b134632768f96b08",
8054+
"incomingViewKey": "e4763f3e32f92f949ae618837d88d3a8b4ebc78ee6edf66634a1f2e755ea3d03",
8055+
"outgoingViewKey": "b5c437917dc0e319f76be255520e59bbdaf84eb753321a6524014fabc469275a",
8056+
"publicAddress": "a9e24ca9cc0a6ac7ea1c0aa62fcc5219ceb79cb1534ddd4b1b406995baa985b7",
8057+
"createdAt": {
8058+
"hash": {
8059+
"type": "Buffer",
8060+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
8061+
},
8062+
"sequence": 1
8063+
},
8064+
"scanningEnabled": true,
8065+
"proofAuthorizingKey": "dfcf2e74f203fec5c6101bf1cab647d01c36864cd87b5eb9f2e21f554f7aa801"
8066+
},
8067+
"head": {
8068+
"hash": {
8069+
"type": "Buffer",
8070+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
8071+
},
8072+
"sequence": 1
8073+
}
8074+
}
8075+
],
8076+
"Wallet createAccount should save a new encrypted account with the correct passphrase": [
8077+
{
8078+
"value": {
8079+
"encrypted": false,
8080+
"version": 4,
8081+
"id": "4485f7db-a46d-4647-aa6b-fa076ea71a35",
8082+
"name": "A",
8083+
"spendingKey": "4e9af29010a71416b5430e32162d2fc62a0ad33b933067e7c96e585cb7643334",
8084+
"viewKey": "ef434cfa62b32cd927dae4a767c8f1f5c87029b85aa57e7df0d0aa025bb84471022e1c0c36d1acc95d916740d89dce5469169af479bd0862914b0cf8a1c9691b",
8085+
"incomingViewKey": "ebab5cdb70bde85d171a9db146ef8bb6bf99e60518c89ac53c122790a21e5701",
8086+
"outgoingViewKey": "a18924829aa89073d7b2e81982868c4c41cb3989914e26c6feb03b0372b2f419",
8087+
"publicAddress": "71bc93dd9eebf50402bfaedc63f302a61bdc95fef7630bd5526c259dd353542f",
8088+
"createdAt": {
8089+
"hash": {
8090+
"type": "Buffer",
8091+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
8092+
},
8093+
"sequence": 1
8094+
},
8095+
"scanningEnabled": true,
8096+
"proofAuthorizingKey": "acb375d98a7f82e9b72b9a8b9448d2db409ad4b089bc8296f1fc4e019888af0a"
8097+
},
8098+
"head": {
8099+
"hash": {
8100+
"type": "Buffer",
8101+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
8102+
},
8103+
"sequence": 1
8104+
}
8105+
}
80138106
]
80148107
}

ironfish/src/wallet/wallet.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '../testUtilities'
2323
import { AsyncUtils, BufferUtils, ORE_TO_IRON } from '../utils'
2424
import { Account, TransactionStatus, TransactionType } from '../wallet'
25+
import { EncryptedAccount } from './account/encryptedAccount'
2526
import {
2627
AccountDecryptionFailedError,
2728
DuplicateAccountNameError,
@@ -1053,6 +1054,51 @@ describe('Wallet', () => {
10531054
'Account name cannot be blank',
10541055
)
10551056
})
1057+
1058+
it('should throw an error if the wallet is encrypted and no passphrase is provided', async () => {
1059+
const { node } = await nodeTest.createSetup()
1060+
const passphrase = 'foo'
1061+
1062+
await useAccountFixture(node.wallet, 'A')
1063+
await node.wallet.encrypt(passphrase)
1064+
1065+
await expect(node.wallet.createAccount('B')).rejects.toThrow()
1066+
})
1067+
1068+
it('should throw an error if the wallet is encrypted and an incorrect passphrase is provided', async () => {
1069+
const { node } = await nodeTest.createSetup()
1070+
const passphrase = 'foo'
1071+
1072+
await useAccountFixture(node.wallet, 'A')
1073+
await node.wallet.encrypt(passphrase)
1074+
1075+
await expect(
1076+
node.wallet.createAccount('B', { passphrase: 'incorrect ' }),
1077+
).rejects.toThrow()
1078+
})
1079+
1080+
it('should save a new encrypted account with the correct passphrase', async () => {
1081+
const { node } = await nodeTest.createSetup()
1082+
const passphrase = 'foo'
1083+
1084+
await useAccountFixture(node.wallet, 'A')
1085+
await node.wallet.encrypt(passphrase)
1086+
1087+
const account = await node.wallet.createAccount('B', { passphrase })
1088+
1089+
const accountValue = await node.wallet.walletDb.accounts.get(account.id)
1090+
Assert.isNotUndefined(accountValue)
1091+
Assert.isTrue(accountValue.encrypted)
1092+
1093+
const encryptedAccount = new EncryptedAccount({
1094+
data: accountValue.data,
1095+
walletDb: node.wallet.walletDb,
1096+
})
1097+
const decryptedAccount = encryptedAccount.decrypt(passphrase)
1098+
1099+
expect(decryptedAccount.spendingKey).toEqual(account.spendingKey)
1100+
expect(decryptedAccount.name).toEqual(account.name)
1101+
})
10561102
})
10571103

10581104
describe('removeAccount', () => {

ironfish/src/wallet/wallet.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ export class Wallet {
13351335

13361336
async createAccount(
13371337
name: string,
1338-
options: { createdAt?: HeadValue | null; setDefault?: boolean } = {
1338+
options: { createdAt?: HeadValue | null; setDefault?: boolean; passphrase?: string } = {
13391339
setDefault: false,
13401340
},
13411341
): Promise<Account> {
@@ -1379,7 +1379,20 @@ export class Wallet {
13791379
})
13801380

13811381
await this.walletDb.db.transaction(async (tx) => {
1382-
await this.walletDb.setAccount(account, tx)
1382+
const accountsEncrypted = await this.walletDb.accountsEncrypted(tx)
1383+
1384+
if (accountsEncrypted) {
1385+
Assert.isNotUndefined(options.passphrase)
1386+
const encryptedAccount = await this.walletDb.setEncryptedAccount(
1387+
account,
1388+
options.passphrase,
1389+
tx,
1390+
)
1391+
this.encryptedAccountById.set(account.id, encryptedAccount)
1392+
} else {
1393+
await this.walletDb.setAccount(account, tx)
1394+
}
1395+
13831396
await account.updateHead(createdAt, tx)
13841397
})
13851398

0 commit comments

Comments
 (0)