Skip to content

Commit 4e59dea

Browse files
authored
feat(ironfish): Add wallet/encrypt (#5318)
1 parent 698e819 commit 4e59dea

File tree

8 files changed

+225
-1
lines changed

8 files changed

+225
-1
lines changed

ironfish/src/rpc/adapters/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum RPC_ERROR_CODES {
1414
DUPLICATE_ACCOUNT_NAME = 'duplicate-account-name',
1515
IMPORT_ACCOUNT_NAME_REQUIRED = 'import-account-name-required',
1616
MULTISIG_SECRET_NOT_FOUND = 'multisig-secret-not-found',
17+
WALLET_ALREADY_ENCRYPTED = 'wallet-already-encrypted',
1718
}
1819

1920
/**

ironfish/src/rpc/clients/client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ import {
178178
DeleteTransactionRequest,
179179
DeleteTransactionResponse,
180180
} from '../routes/wallet/deleteTransaction'
181+
import { EncryptWalletRequest, EncryptWalletResponse } from '../routes/wallet/encrypt'
181182

182183
export abstract class RpcClient {
183184
abstract close(): void
@@ -640,6 +641,15 @@ export abstract class RpcClient {
640641
params,
641642
).waitForEnd()
642643
},
644+
645+
encrypt: (
646+
params: EncryptWalletRequest,
647+
): Promise<RpcResponseEnded<EncryptWalletResponse>> => {
648+
return this.request<EncryptWalletResponse>(
649+
`${ApiNamespace.wallet}/encrypt`,
650+
params,
651+
).waitForEnd()
652+
},
643653
}
644654

645655
mempool = {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{
2+
"Route wallet/encrypt encrypts accounts": [
3+
{
4+
"value": {
5+
"encrypted": false,
6+
"version": 4,
7+
"id": "86003dbd-7d93-472f-879c-21a81fcb4fcc",
8+
"name": "A",
9+
"spendingKey": "810407957cd5d03dc263291acaf61883e3fcd5d0f6d9d33c71e727d02be4980b",
10+
"viewKey": "ee2a5899b2bcf104f3e0c96383ced37dc96a750b616b6111cbbbdb42cd81df6cffdba56303ef07be834051ade514bb55b8fc33e9b5f1f6d8a65d207fa354dca0",
11+
"incomingViewKey": "ae71cad0dab16916dc266816a2eec97e6a56880278a744739f9129697b524401",
12+
"outgoingViewKey": "0cd5582c6a4cc98748957a315e96e87eacad49dd5fc4d1a3fa3e329e5710186b",
13+
"publicAddress": "550e4bab2719759b3ba48eef4164addefb5ab1ea709827a8d2f242a62da4b9c6",
14+
"createdAt": {
15+
"hash": {
16+
"type": "Buffer",
17+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
18+
},
19+
"sequence": 1
20+
},
21+
"scanningEnabled": true,
22+
"proofAuthorizingKey": "d68f9ab66a90fb80ee341323ed38eb09fe0ac2fad9c16834f0c41160e6dbf304"
23+
},
24+
"head": {
25+
"hash": {
26+
"type": "Buffer",
27+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
28+
},
29+
"sequence": 1
30+
}
31+
},
32+
{
33+
"value": {
34+
"encrypted": false,
35+
"version": 4,
36+
"id": "8f86ead0-93f8-4bd5-9775-2935c1a1e1ed",
37+
"name": "B",
38+
"spendingKey": "acc89c2e183b12b9183f2feaa5d8e1528834da3a18baecddd6ee2d96a8bac2f6",
39+
"viewKey": "a62b094ac064211bfcba30354b4feff5bc4aedd982064952b6724483f7c09c9738f4efe5b87424211600edc193bc07c89be7e8548d5615cc10d5d572faeeed3f",
40+
"incomingViewKey": "02979895d4a3b7219f000100ec029a9919bb2f23c4d7fb1f7b14eef0241b6104",
41+
"outgoingViewKey": "9c1282d1758dec787ced4ada55735ad1cef92535ca80e38c72dadd55230bde85",
42+
"publicAddress": "31ddaa091da2f2384de1355b5861cf6a4458d05e9bcee77e8dce3c62ecf0724e",
43+
"createdAt": {
44+
"hash": {
45+
"type": "Buffer",
46+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
47+
},
48+
"sequence": 1
49+
},
50+
"scanningEnabled": true,
51+
"proofAuthorizingKey": "9a2f0550b162e559e07df44c4517eb1c8b37930af68b2768d5a6349db8db960d"
52+
},
53+
"head": {
54+
"hash": {
55+
"type": "Buffer",
56+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
57+
},
58+
"sequence": 1
59+
}
60+
}
61+
],
62+
"Route wallet/encrypt throws if wallet is encrypted": [
63+
{
64+
"value": {
65+
"encrypted": false,
66+
"version": 4,
67+
"id": "e7b0cdf4-ebf7-4647-ac4a-970c161b8f67",
68+
"name": "A",
69+
"spendingKey": "c8698709f51e8b8b27366ac47773a84e103286a04f95ed5c74861fcc5b14f1e8",
70+
"viewKey": "d660a67d58a6f26435ca6993a4013de10d56a6607b67c0e94fdfb4e7d2392e99b8058e548f657fa72d9bc41ece07dbbd07811444f334b96d824a3029b640cb3c",
71+
"incomingViewKey": "e628ba4f58057825e21dfeefb5b652db4c8c4dead57958d46062261c8188a000",
72+
"outgoingViewKey": "aed6efb86774d5292dd6b6851d6011fb2d76648694748baa1685b93cce2e0980",
73+
"publicAddress": "867706d9da5632afc0cfdf7044c759d9b05579781c3c1743500aa42d6ede1c18",
74+
"createdAt": {
75+
"hash": {
76+
"type": "Buffer",
77+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
78+
},
79+
"sequence": 1
80+
},
81+
"scanningEnabled": true,
82+
"proofAuthorizingKey": "547970bb590fde11a35a47cb39cab5f7df609987ea53e566095db73763fa2903"
83+
},
84+
"head": {
85+
"hash": {
86+
"type": "Buffer",
87+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
88+
},
89+
"sequence": 1
90+
}
91+
},
92+
{
93+
"value": {
94+
"encrypted": false,
95+
"version": 4,
96+
"id": "b637868f-b7a1-466c-b14e-05f3fc493ad5",
97+
"name": "B",
98+
"spendingKey": "8a80e1564e85b27a572b52b32334e9a26dd917e8054afe9c3df65ab502b5d268",
99+
"viewKey": "a85b813148406d81c5efa836026f9c946e3544de3e11316c9ba352b186c8c9730c89e1faf4863c5c6c85d1bad6774c43cf3d6ed6a5442d218cda74cdad36bc16",
100+
"incomingViewKey": "857e9d97a89b07ffb5eef5774ea6e3a808e2eba90d375be8fa023addb1d50605",
101+
"outgoingViewKey": "5d2d37392995f438b03d7d83842b6565799991ed23438c51a615f3baecc465ac",
102+
"publicAddress": "410c313cec38ce1ab3805104802c133c7353ff1b2584aa71c556215c2caabcee",
103+
"createdAt": {
104+
"hash": {
105+
"type": "Buffer",
106+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
107+
},
108+
"sequence": 1
109+
},
110+
"scanningEnabled": true,
111+
"proofAuthorizingKey": "5686d627e789c93c8dfd0f2546e099873a167b65834a4007b3b03728d01d260e"
112+
},
113+
"head": {
114+
"hash": {
115+
"type": "Buffer",
116+
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
117+
},
118+
"sequence": 1
119+
}
120+
}
121+
]
122+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4+
import { useAccountFixture } from '../../../testUtilities'
5+
import { createRouteTest } from '../../../testUtilities/routeTest'
6+
import { RPC_ERROR_CODES } from '../../adapters/errors'
7+
8+
describe('Route wallet/encrypt', () => {
9+
const routeTest = createRouteTest()
10+
11+
it('encrypts accounts', async () => {
12+
await useAccountFixture(routeTest.node.wallet, 'A')
13+
await useAccountFixture(routeTest.node.wallet, 'B')
14+
15+
await routeTest.client.wallet.encrypt({ passphrase: 'foobar' })
16+
17+
const status = await routeTest.client.wallet.getAccountsStatus()
18+
19+
expect(status.content.encrypted).toBe(true)
20+
expect(status.content.locked).toBe(true)
21+
})
22+
23+
it('throws if wallet is encrypted', async () => {
24+
await useAccountFixture(routeTest.node.wallet, 'A')
25+
await useAccountFixture(routeTest.node.wallet, 'B')
26+
27+
await routeTest.client.wallet.encrypt({ passphrase: 'foobar' })
28+
29+
await expect(routeTest.client.wallet.encrypt({ passphrase: 'foobar' })).rejects.toThrow(
30+
expect.objectContaining({
31+
message: expect.any(String),
32+
status: 400,
33+
code: RPC_ERROR_CODES.WALLET_ALREADY_ENCRYPTED,
34+
}),
35+
)
36+
})
37+
})
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4+
import * as yup from 'yup'
5+
import { RPC_ERROR_CODES, RpcValidationError } from '../../adapters/errors'
6+
import { ApiNamespace } from '../namespaces'
7+
import { routes } from '../router'
8+
import { AssertHasRpcContext } from '../rpcContext'
9+
10+
export type EncryptWalletRequest = { passphrase: string }
11+
export type EncryptWalletResponse = undefined
12+
13+
export const EncryptWalletRequestSchema: yup.ObjectSchema<EncryptWalletRequest> = yup
14+
.object({
15+
passphrase: yup.string().defined(),
16+
})
17+
.defined()
18+
19+
export const EncryptWalletResponseSchema: yup.MixedSchema<EncryptWalletResponse> = yup
20+
.mixed()
21+
.oneOf([undefined] as const)
22+
23+
routes.register<typeof EncryptWalletRequestSchema, EncryptWalletResponse>(
24+
`${ApiNamespace.wallet}/encrypt`,
25+
EncryptWalletRequestSchema,
26+
async (request, context): Promise<void> => {
27+
AssertHasRpcContext(request, context, 'wallet')
28+
29+
const encrypted = await context.wallet.accountsEncrypted()
30+
if (encrypted) {
31+
throw new RpcValidationError(
32+
'Wallet is already encrypted',
33+
400,
34+
RPC_ERROR_CODES.WALLET_ALREADY_ENCRYPTED,
35+
)
36+
}
37+
38+
await context.wallet.encrypt(request.data.passphrase)
39+
request.end()
40+
},
41+
)

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export type GetAccountsStatusRequest = Record<string, never> | undefined
1212

1313
export type GetAccountsStatusResponse = {
1414
accounts: RpcAccountStatus[]
15+
encrypted: boolean
16+
locked: boolean
1517
}
1618

1719
export const GetAccountsStatusRequestSchema: yup.ObjectSchema<GetAccountsStatusRequest> = yup
@@ -22,6 +24,8 @@ export const GetAccountsStatusRequestSchema: yup.ObjectSchema<GetAccountsStatusR
2224
export const GetAccountsStatusResponseSchema: yup.ObjectSchema<GetAccountsStatusResponse> = yup
2325
.object({
2426
accounts: yup.array(RpcAccountStatusSchema).defined(),
27+
encrypted: yup.boolean().defined(),
28+
locked: yup.boolean().defined(),
2529
})
2630
.defined()
2731

@@ -35,6 +39,10 @@ routes.register<typeof GetAccountsStatusRequestSchema, GetAccountsStatusResponse
3539
node.wallet.accounts.map((account) => serializeRpcAccountStatus(node.wallet, account)),
3640
)
3741

38-
request.end({ accounts })
42+
request.end({
43+
accounts,
44+
encrypted: await node.wallet.accountsEncrypted(),
45+
locked: node.wallet.locked,
46+
})
3947
},
4048
)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export * from './createAccount'
1111
export * from './createTransaction'
1212
export * from './deleteTransaction'
1313
export * from './estimateFeeRates'
14+
export * from './encrypt'
1415
export * from './exportAccount'
1516
export * from './getAccountNotesStream'
1617
export * from './getAccountStatus'

ironfish/src/wallet/wallet.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,10 @@ export class Wallet {
18341834
})
18351835
}
18361836

1837+
async accountsEncrypted(): Promise<boolean> {
1838+
return this.walletDb.accountsEncrypted()
1839+
}
1840+
18371841
async encrypt(passphrase: string, tx?: IDatabaseTransaction): Promise<void> {
18381842
const unlock = await this.createTransactionMutex.lock()
18391843

0 commit comments

Comments
 (0)