Skip to content

Commit

Permalink
feat(ironfish): Add wallet/encrypt (#5318)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohanjadvani authored Aug 20, 2024
1 parent 698e819 commit 4e59dea
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 1 deletion.
1 change: 1 addition & 0 deletions ironfish/src/rpc/adapters/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum RPC_ERROR_CODES {
DUPLICATE_ACCOUNT_NAME = 'duplicate-account-name',
IMPORT_ACCOUNT_NAME_REQUIRED = 'import-account-name-required',
MULTISIG_SECRET_NOT_FOUND = 'multisig-secret-not-found',
WALLET_ALREADY_ENCRYPTED = 'wallet-already-encrypted',
}

/**
Expand Down
10 changes: 10 additions & 0 deletions ironfish/src/rpc/clients/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ import {
DeleteTransactionRequest,
DeleteTransactionResponse,
} from '../routes/wallet/deleteTransaction'
import { EncryptWalletRequest, EncryptWalletResponse } from '../routes/wallet/encrypt'

export abstract class RpcClient {
abstract close(): void
Expand Down Expand Up @@ -640,6 +641,15 @@ export abstract class RpcClient {
params,
).waitForEnd()
},

encrypt: (
params: EncryptWalletRequest,
): Promise<RpcResponseEnded<EncryptWalletResponse>> => {
return this.request<EncryptWalletResponse>(
`${ApiNamespace.wallet}/encrypt`,
params,
).waitForEnd()
},
}

mempool = {
Expand Down
122 changes: 122 additions & 0 deletions ironfish/src/rpc/routes/wallet/__fixtures__/encrypt.test.ts.fixture
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"Route wallet/encrypt encrypts accounts": [
{
"value": {
"encrypted": false,
"version": 4,
"id": "86003dbd-7d93-472f-879c-21a81fcb4fcc",
"name": "A",
"spendingKey": "810407957cd5d03dc263291acaf61883e3fcd5d0f6d9d33c71e727d02be4980b",
"viewKey": "ee2a5899b2bcf104f3e0c96383ced37dc96a750b616b6111cbbbdb42cd81df6cffdba56303ef07be834051ade514bb55b8fc33e9b5f1f6d8a65d207fa354dca0",
"incomingViewKey": "ae71cad0dab16916dc266816a2eec97e6a56880278a744739f9129697b524401",
"outgoingViewKey": "0cd5582c6a4cc98748957a315e96e87eacad49dd5fc4d1a3fa3e329e5710186b",
"publicAddress": "550e4bab2719759b3ba48eef4164addefb5ab1ea709827a8d2f242a62da4b9c6",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "d68f9ab66a90fb80ee341323ed38eb09fe0ac2fad9c16834f0c41160e6dbf304"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
},
{
"value": {
"encrypted": false,
"version": 4,
"id": "8f86ead0-93f8-4bd5-9775-2935c1a1e1ed",
"name": "B",
"spendingKey": "acc89c2e183b12b9183f2feaa5d8e1528834da3a18baecddd6ee2d96a8bac2f6",
"viewKey": "a62b094ac064211bfcba30354b4feff5bc4aedd982064952b6724483f7c09c9738f4efe5b87424211600edc193bc07c89be7e8548d5615cc10d5d572faeeed3f",
"incomingViewKey": "02979895d4a3b7219f000100ec029a9919bb2f23c4d7fb1f7b14eef0241b6104",
"outgoingViewKey": "9c1282d1758dec787ced4ada55735ad1cef92535ca80e38c72dadd55230bde85",
"publicAddress": "31ddaa091da2f2384de1355b5861cf6a4458d05e9bcee77e8dce3c62ecf0724e",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "9a2f0550b162e559e07df44c4517eb1c8b37930af68b2768d5a6349db8db960d"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
}
],
"Route wallet/encrypt throws if wallet is encrypted": [
{
"value": {
"encrypted": false,
"version": 4,
"id": "e7b0cdf4-ebf7-4647-ac4a-970c161b8f67",
"name": "A",
"spendingKey": "c8698709f51e8b8b27366ac47773a84e103286a04f95ed5c74861fcc5b14f1e8",
"viewKey": "d660a67d58a6f26435ca6993a4013de10d56a6607b67c0e94fdfb4e7d2392e99b8058e548f657fa72d9bc41ece07dbbd07811444f334b96d824a3029b640cb3c",
"incomingViewKey": "e628ba4f58057825e21dfeefb5b652db4c8c4dead57958d46062261c8188a000",
"outgoingViewKey": "aed6efb86774d5292dd6b6851d6011fb2d76648694748baa1685b93cce2e0980",
"publicAddress": "867706d9da5632afc0cfdf7044c759d9b05579781c3c1743500aa42d6ede1c18",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "547970bb590fde11a35a47cb39cab5f7df609987ea53e566095db73763fa2903"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
},
{
"value": {
"encrypted": false,
"version": 4,
"id": "b637868f-b7a1-466c-b14e-05f3fc493ad5",
"name": "B",
"spendingKey": "8a80e1564e85b27a572b52b32334e9a26dd917e8054afe9c3df65ab502b5d268",
"viewKey": "a85b813148406d81c5efa836026f9c946e3544de3e11316c9ba352b186c8c9730c89e1faf4863c5c6c85d1bad6774c43cf3d6ed6a5442d218cda74cdad36bc16",
"incomingViewKey": "857e9d97a89b07ffb5eef5774ea6e3a808e2eba90d375be8fa023addb1d50605",
"outgoingViewKey": "5d2d37392995f438b03d7d83842b6565799991ed23438c51a615f3baecc465ac",
"publicAddress": "410c313cec38ce1ab3805104802c133c7353ff1b2584aa71c556215c2caabcee",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "5686d627e789c93c8dfd0f2546e099873a167b65834a4007b3b03728d01d260e"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
}
]
}
37 changes: 37 additions & 0 deletions ironfish/src/rpc/routes/wallet/encrypt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { useAccountFixture } from '../../../testUtilities'
import { createRouteTest } from '../../../testUtilities/routeTest'
import { RPC_ERROR_CODES } from '../../adapters/errors'

describe('Route wallet/encrypt', () => {
const routeTest = createRouteTest()

it('encrypts accounts', async () => {
await useAccountFixture(routeTest.node.wallet, 'A')
await useAccountFixture(routeTest.node.wallet, 'B')

await routeTest.client.wallet.encrypt({ passphrase: 'foobar' })

const status = await routeTest.client.wallet.getAccountsStatus()

expect(status.content.encrypted).toBe(true)
expect(status.content.locked).toBe(true)
})

it('throws if wallet is encrypted', async () => {
await useAccountFixture(routeTest.node.wallet, 'A')
await useAccountFixture(routeTest.node.wallet, 'B')

await routeTest.client.wallet.encrypt({ passphrase: 'foobar' })

await expect(routeTest.client.wallet.encrypt({ passphrase: 'foobar' })).rejects.toThrow(
expect.objectContaining({
message: expect.any(String),
status: 400,
code: RPC_ERROR_CODES.WALLET_ALREADY_ENCRYPTED,
}),
)
})
})
41 changes: 41 additions & 0 deletions ironfish/src/rpc/routes/wallet/encrypt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import * as yup from 'yup'
import { RPC_ERROR_CODES, RpcValidationError } from '../../adapters/errors'
import { ApiNamespace } from '../namespaces'
import { routes } from '../router'
import { AssertHasRpcContext } from '../rpcContext'

export type EncryptWalletRequest = { passphrase: string }
export type EncryptWalletResponse = undefined

export const EncryptWalletRequestSchema: yup.ObjectSchema<EncryptWalletRequest> = yup
.object({
passphrase: yup.string().defined(),
})
.defined()

export const EncryptWalletResponseSchema: yup.MixedSchema<EncryptWalletResponse> = yup
.mixed()
.oneOf([undefined] as const)

routes.register<typeof EncryptWalletRequestSchema, EncryptWalletResponse>(
`${ApiNamespace.wallet}/encrypt`,
EncryptWalletRequestSchema,
async (request, context): Promise<void> => {
AssertHasRpcContext(request, context, 'wallet')

const encrypted = await context.wallet.accountsEncrypted()
if (encrypted) {
throw new RpcValidationError(
'Wallet is already encrypted',
400,
RPC_ERROR_CODES.WALLET_ALREADY_ENCRYPTED,
)
}

await context.wallet.encrypt(request.data.passphrase)
request.end()
},
)
10 changes: 9 additions & 1 deletion ironfish/src/rpc/routes/wallet/getAccountsStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export type GetAccountsStatusRequest = Record<string, never> | undefined

export type GetAccountsStatusResponse = {
accounts: RpcAccountStatus[]
encrypted: boolean
locked: boolean
}

export const GetAccountsStatusRequestSchema: yup.ObjectSchema<GetAccountsStatusRequest> = yup
Expand All @@ -22,6 +24,8 @@ export const GetAccountsStatusRequestSchema: yup.ObjectSchema<GetAccountsStatusR
export const GetAccountsStatusResponseSchema: yup.ObjectSchema<GetAccountsStatusResponse> = yup
.object({
accounts: yup.array(RpcAccountStatusSchema).defined(),
encrypted: yup.boolean().defined(),
locked: yup.boolean().defined(),
})
.defined()

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

request.end({ accounts })
request.end({
accounts,
encrypted: await node.wallet.accountsEncrypted(),
locked: node.wallet.locked,
})
},
)
1 change: 1 addition & 0 deletions ironfish/src/rpc/routes/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './createAccount'
export * from './createTransaction'
export * from './deleteTransaction'
export * from './estimateFeeRates'
export * from './encrypt'
export * from './exportAccount'
export * from './getAccountNotesStream'
export * from './getAccountStatus'
Expand Down
4 changes: 4 additions & 0 deletions ironfish/src/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1834,6 +1834,10 @@ export class Wallet {
})
}

async accountsEncrypted(): Promise<boolean> {
return this.walletDb.accountsEncrypted()
}

async encrypt(passphrase: string, tx?: IDatabaseTransaction): Promise<void> {
const unlock = await this.createTransactionMutex.lock()

Expand Down

0 comments on commit 4e59dea

Please sign in to comment.