Skip to content

Commit

Permalink
feat(ironfish): Add encrypt method to wallet (#5248)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohanjadvani authored Aug 14, 2024
1 parent ab0847b commit 3a452da
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 2 deletions.
60 changes: 60 additions & 0 deletions ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture
Original file line number Diff line number Diff line change
Expand Up @@ -6898,5 +6898,65 @@
}
]
}
],
"Wallet encrypt saves encrypted blobs to disk and updates the wallet account fields": [
{
"value": {
"encrypted": false,
"version": 4,
"id": "f49b047c-5140-4b7b-9527-ddc262783e91",
"name": "A",
"spendingKey": "0db21b7c8a42e1690a20a0a5fc7522d5e818e219cb1fdce371525d1b4787f2fa",
"viewKey": "024dbfadd8740380a505fd6038604c72f8796bde51cc6c94631cdd626656379c449f9953ceb45323ba12fe175b4b9897715c0f183dacd6a2eb7e8f04a5e9d4c9",
"incomingViewKey": "b34d1189c00ee074ecceffee7f5b61272a625df2023dd383af36b1212f576a04",
"outgoingViewKey": "1af19d206b9ef93946f1f052ac0bb7375ac3f2e8777ca2e3abeb4104c40d44d0",
"publicAddress": "1fbddcc770972a6c3503b7a6fdd174044cf35d3ef171d68e8cf1e5702c16271b",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "c24c3fe541c7f6ea59d3c8a98199dc0b3696928a5abfd4e6314de1b933589a04"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
},
{
"value": {
"encrypted": false,
"version": 4,
"id": "666561d9-6ff7-4ae1-a5e3-e0c3775a0a4b",
"name": "B",
"spendingKey": "61837c88868454f64f096c139cd7f1dc44e2aba494fa0be78491f65a2dd81b85",
"viewKey": "caf7b5e225d20021beea59c56ba2844c07ed646434bed5546c731a1eb7914412c92e38c6695ddbaa28f5818057d3757407b71e59479f1a13f4947a6d4930fa2d",
"incomingViewKey": "1b234f2e510f10540665686dcfb850dc00eaf56363a6cfeffcf614ce5d73b301",
"outgoingViewKey": "d44acb51dd32912ec1c6d709afbdd8701ee1ea821c019c13da4e0cc5f352b1ed",
"publicAddress": "a6186075a14d9d6ead67f208d6329cc5582b0ec77e154bca7e1550e25aa19b4e",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "0f8be35575f3df948fd6f4eba41e378c87d4588b69825e65c6319bd754835e0a"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
}
]
}
28 changes: 28 additions & 0 deletions ironfish/src/wallet/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2374,4 +2374,32 @@ describe('Wallet', () => {
expect(node.wallet.shouldDecryptForAccount(block.header, account)).toBe(true)
})
})

describe('encrypt', () => {
it('saves encrypted blobs to disk and updates the wallet account fields', async () => {
const { node } = nodeTest
const passphrase = 'foo'

const accountA = await useAccountFixture(node.wallet, 'A')
const accountB = await useAccountFixture(node.wallet, 'B')

expect(node.wallet.accounts).toHaveLength(2)
expect(node.wallet.encryptedAccounts).toHaveLength(0)

await node.wallet.encrypt(passphrase)

expect(node.wallet.accounts).toHaveLength(0)
expect(node.wallet.encryptedAccounts).toHaveLength(2)

const encryptedAccountA = node.wallet.encryptedAccountById.get(accountA.id)
Assert.isNotUndefined(encryptedAccountA)
const decryptedAccountA = encryptedAccountA.decrypt(passphrase)
expect(accountA.serialize()).toMatchObject(decryptedAccountA.serialize())

const encryptedAccountB = node.wallet.encryptedAccountById.get(accountB.id)
Assert.isNotUndefined(encryptedAccountB)
const decryptedAccountB = encryptedAccountB.decrypt(passphrase)
expect(accountB.serialize()).toMatchObject(decryptedAccountB.serialize())
})
})
})
22 changes: 20 additions & 2 deletions ironfish/src/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class Wallet {
readonly onAccountRemoved = new Event<[account: Account]>()

protected readonly accountById = new Map<string, Account>()
protected readonly encryptedAccounts = new Map<string, EncryptedAccount>()
readonly encryptedAccountById = new Map<string, EncryptedAccount>()
readonly walletDb: WalletDB
private readonly logger: Logger
readonly workerPool: WorkerPool
Expand Down Expand Up @@ -210,13 +210,16 @@ export class Wallet {
}

private async load(): Promise<void> {
this.encryptedAccountById.clear()
this.accountById.clear()

for await (const [id, accountValue] of this.walletDb.loadAccounts()) {
if (accountValue.encrypted) {
const encryptedAccount = new EncryptedAccount({
data: accountValue.data,
walletDb: this.walletDb,
})
this.encryptedAccounts.set(id, encryptedAccount)
this.encryptedAccountById.set(id, encryptedAccount)
} else {
const account = new Account({ accountValue, walletDb: this.walletDb })
this.accountById.set(account.id, account)
Expand Down Expand Up @@ -1435,6 +1438,10 @@ export class Wallet {
return Array.from(this.accountById.values())
}

get encryptedAccounts(): EncryptedAccount[] {
return Array.from(this.encryptedAccountById.values())
}

accountExists(name: string): boolean {
return this.getAccountByName(name) !== null
}
Expand Down Expand Up @@ -1767,4 +1774,15 @@ export class Wallet {
return identity.serialize()
})
}

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

try {
await this.walletDb.encryptAccounts(this.accounts, passphrase, tx)
await this.load()
} finally {
unlock()
}
}
}
60 changes: 60 additions & 0 deletions ironfish/src/wallet/walletdb/__fixtures__/walletdb.test.ts.fixture
Original file line number Diff line number Diff line change
Expand Up @@ -790,5 +790,65 @@
"sequence": 1
}
}
],
"WalletDB encryptAccounts stores encrypted accounts": [
{
"value": {
"encrypted": false,
"version": 4,
"id": "91edb394-fe0a-4ff9-a94b-cbcffc199b76",
"name": "A",
"spendingKey": "aef3a7e8ea8329f61ae3c54b77f02f4fc94f4dd790f3bfd3d6e745e7d68021df",
"viewKey": "8e1605538c7f0d39a292d825e699c04e93d4e883172bea8ce5cf57e6be35062e745fcba8e06b0370e963ebccb2356eed25e886209cdb4b6c6c9bc1a66652ea57",
"incomingViewKey": "3e7270177fa64cd30835284663ceccb12f800e87e3d465aee824f20a93297306",
"outgoingViewKey": "3b07da84fde489af3af658b20573426a6cd5331eb1d827a5edbf52f5aaeb9d22",
"publicAddress": "13aca6bed0937635aebecc987377729edf63d3ed9576c863d9aa7318a714d7ea",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "e1b8fa4e84363090d49bee08224f8d516410650dfc6aca1e9f3fef4d1358b708"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
},
{
"value": {
"encrypted": false,
"version": 4,
"id": "57c75b9c-bdb4-4705-8dc7-40847528b5bb",
"name": "B",
"spendingKey": "4377f6743472240a7adba679e5872d58b1e17695662bb384e15984b9ed7eb3ae",
"viewKey": "364d9514508d06da950874cde5cfc1fe2e4d90cd064901e4a73c0f98f82c112fb463fe0a898e3d1976a9c3c7a4e0966d41f75e9a2cbb8767ec05f36b480b80d7",
"incomingViewKey": "4d31ebeefb51dda40f1e9a844321d065794fff7788c675a76699bd6d0a63a402",
"outgoingViewKey": "8b8d94bae8d743938e7940eec2cb3489772a422f3032a3742e8b107dc7bb34ac",
"publicAddress": "61b5033c291a7221a2a35adf7ef2885f5ee3b71ad43e32a6e67be17b3e8d376e",
"createdAt": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
},
"scanningEnabled": true,
"proofAuthorizingKey": "308703a8a801c918d4fe6168fba0ca7a4a53aadb2c6d7988031f8f9eb33cec08"
},
"head": {
"hash": {
"type": "Buffer",
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY="
},
"sequence": 1
}
}
]
}
36 changes: 36 additions & 0 deletions ironfish/src/wallet/walletdb/walletdb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useTxFixture,
} from '../../testUtilities'
import { AsyncUtils } from '../../utils'
import { EncryptedAccount } from '../account/encryptedAccount'
import { DecryptedNoteValue } from './decryptedNoteValue'

describe('WalletDB', () => {
Expand Down Expand Up @@ -456,4 +457,39 @@ describe('WalletDB', () => {
expect(storedSecret.secret).toEqualBuffer(serializedSecret)
})
})

describe('encryptAccounts', () => {
it('stores encrypted accounts', async () => {
const node = (await nodeTest.createSetup()).node
const walletDb = node.wallet.walletDb
const passphrase = 'test'

const accountA = await useAccountFixture(node.wallet, 'A')
const accountB = await useAccountFixture(node.wallet, 'B')

await walletDb.encryptAccounts([accountA, accountB], passphrase)

const encryptedAccountById = new Map<string, EncryptedAccount>()
for await (const [id, accountValue] of walletDb.loadAccounts()) {
if (!accountValue.encrypted) {
throw new Error('Unexpected behavior')
}

encryptedAccountById.set(
id,
new EncryptedAccount({ data: accountValue.data, walletDb }),
)
}

const encryptedAccountA = encryptedAccountById.get(accountA.id)
Assert.isNotUndefined(encryptedAccountA)
const decryptedAccountA = encryptedAccountA.decrypt(passphrase)
expect(accountA.serialize()).toMatchObject(decryptedAccountA.serialize())

const encryptedAccountB = encryptedAccountById.get(accountB.id)
Assert.isNotUndefined(encryptedAccountB)
const decryptedAccountB = encryptedAccountB.decrypt(passphrase)
expect(accountB.serialize()).toMatchObject(decryptedAccountB.serialize())
})
})
})
13 changes: 13 additions & 0 deletions ironfish/src/wallet/walletdb/walletdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,19 @@ export class WalletDB {
}
}

async encryptAccounts(
accounts: Account[],
passphrase: string,
tx?: IDatabaseTransaction,
): Promise<void> {
await this.db.withTransaction(tx, async (tx) => {
for (const account of accounts) {
const encryptedAccount = account.encrypt(passphrase)
await this.accounts.put(account.id, encryptedAccount.serialize(), tx)
}
})
}

async *loadTransactionsByTime(
account: Account,
tx?: IDatabaseTransaction,
Expand Down

0 comments on commit 3a452da

Please sign in to comment.