From 19637d87b160fcfee07d10a5969c2ec17f94a59a Mon Sep 17 00:00:00 2001 From: Hugh Cunningham Date: Thu, 14 Nov 2024 10:32:08 -0800 Subject: [PATCH] updates template migration for changes to MasterKey removes util from migration data --- .../data/033-encrypted-wallet-template.ts | 122 +++++++++--------- .../033-encrypted-wallet-template/util.ts | 88 ------------- 2 files changed, 62 insertions(+), 148 deletions(-) delete mode 100644 ironfish/src/migrations/data/033-encrypted-wallet-template/util.ts diff --git a/ironfish/src/migrations/data/033-encrypted-wallet-template.ts b/ironfish/src/migrations/data/033-encrypted-wallet-template.ts index 985994f237..08319c4f3b 100644 --- a/ironfish/src/migrations/data/033-encrypted-wallet-template.ts +++ b/ironfish/src/migrations/data/033-encrypted-wallet-template.ts @@ -1,20 +1,23 @@ /* 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 { Assert } from '../../assert' import { Logger } from '../../logger' import { IDatabase, IDatabaseTransaction } from '../../storage' import { createDB } from '../../storage/utils' +import { MasterKey } from '../../wallet/masterKey' import { EncryptedWalletMigrationError } from '../errors' import { Database, Migration, MigrationContext } from '../migration' -import { DecryptedAccountValue as NewDecryptedAccountValue } from './033-encrypted-wallet-template/new/accountValue' -import { DecryptedAccountValue as OldDecryptedAccountValue } from './033-encrypted-wallet-template/old/accountValue' -import { GetStores } from './033-encrypted-wallet-template/stores' import { - decryptNewEncryptedAccountValue, - decryptOldEncryptedAccountValue, - encryptOldAccountValue, - getKey, -} from './033-encrypted-wallet-template/util' + AccountValueEncoding as NewAccountValueEncoding, + DecryptedAccountValue as NewDecryptedAccountValue, +} from './033-encrypted-wallet-template/new/accountValue' +import { + AccountValueEncoding as OldAccountValueEncoding, + DecryptedAccountValue as OldDecryptedAccountValue, + EncryptedAccountValue as OldEncryptedAccountValue, +} from './033-encrypted-wallet-template/old/accountValue' +import { GetStores } from './033-encrypted-wallet-template/stores' export class Migration033 extends Migration { path = __filename @@ -33,61 +36,59 @@ export class Migration033 extends Migration { walletPassphrase: string | undefined, ): Promise { const stores = GetStores(db) + const oldEncoding = new OldAccountValueEncoding() + const newEncoding = new NewAccountValueEncoding() - // forward migration inserts from old stores into new stores for await (const account of stores.old.accounts.getAllValuesIter(tx)) { let decryptedAccount // Check if the account is encrypted, and throw an error to allow client // code to prompt for passphrase. - // - // This assumes that serialization of encrypted accounts has NOT changed, - // so deserialization works with both old and new schema. if (account.encrypted) { if (!walletPassphrase) { throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet') } - const key = await getKey( - stores.old.masterKey, - account.salt, - account.nonce, - walletPassphrase, - ) + const masterKeyValue = await stores.old.masterKey.get('key') + Assert.isNotUndefined(masterKeyValue) - // Decrypt the old encrypted account data and apply migration - decryptedAccount = decryptOldEncryptedAccountValue(account.data, key) + const masterKey = new MasterKey(masterKeyValue) + await masterKey.unlock(walletPassphrase) - logger.info(` Migrating account ${decryptedAccount.name}`) + // Decrypt encrypted account data + const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce) + decryptedAccount = oldEncoding.deserializeDecrypted(decrypted) - const migrated = this._accountForward(decryptedAccount) + // Apply migration to decrypted account data + logger.info(` Migrating account ${decryptedAccount.name}`) + const migrated = this.accountForward(decryptedAccount) // Re-encrypt the migrated data and write it to the store. // Assumes that schema for encrypted accounts has NOT changed. - const encryptedAccount = encryptOldAccountValue( - migrated, - key, - account.salt, - account.nonce, - ) + const migratedSerialized = newEncoding.serialize(migrated) + const { ciphertext: data, salt, nonce } = masterKey.encrypt(migratedSerialized) + + const encryptedAccount: OldEncryptedAccountValue = { + encrypted: true, + salt, + nonce, + data, + } await stores.new.accounts.put(decryptedAccount.id, encryptedAccount, tx) - - key.destroy() } else { decryptedAccount = account logger.info(` Migrating account ${decryptedAccount.name}`) - - const migrated = this._accountForward(decryptedAccount) + const migrated = this.accountForward(decryptedAccount) await stores.new.accounts.put(decryptedAccount.id, migrated, tx) } } } - _accountForward(oldValue: OldDecryptedAccountValue): NewDecryptedAccountValue { - // Insert forward migration logic for a decrypted account + // Implement logic to migrate (decrypted) account data to the new schema + accountForward(oldValue: OldDecryptedAccountValue): NewDecryptedAccountValue { const newValue = oldValue return newValue } @@ -104,59 +105,60 @@ export class Migration033 extends Migration { walletPassphrase: string | undefined, ): Promise { const stores = GetStores(db) + const oldEncoding = new OldAccountValueEncoding() + const newEncoding = new NewAccountValueEncoding() for await (const account of stores.new.accounts.getAllValuesIter(tx)) { let decryptedAccount // Check if the account is encrypted, and throw an error to allow client // code to prompt for passphrase. - // - // This assumes that serialization of encrypted accounts has NOT changed, - // so deserialization works with both old and new schema. if (account.encrypted) { if (!walletPassphrase) { throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet') } - // Read master key from old store. This assumes that the schema for the - // master key has NOT changed - const key = await getKey( - stores.old.masterKey, - account.salt, - account.nonce, - walletPassphrase, - ) + // Load master key from database + const masterKeyValue = await stores.old.masterKey.get('key') + Assert.isNotUndefined(masterKeyValue) - decryptedAccount = decryptNewEncryptedAccountValue(account.data, key) + const masterKey = new MasterKey(masterKeyValue) + await masterKey.unlock(walletPassphrase) - logger.info(` Migrating account ${decryptedAccount.name}`) + // Decrypt encrypted account data + const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce) + decryptedAccount = newEncoding.deserializeDecrypted(decrypted) - const migrated = this._accountBackward(decryptedAccount) + // Apply migration to decrypted account data + logger.info(` Migrating account ${decryptedAccount.name}`) + const migrated = this.accountBackward(decryptedAccount) - const encryptedAccount = encryptOldAccountValue( - migrated, - key, - account.salt, - account.nonce, - ) + // Re-encrypt the migrated data and write it to the store. + // Assumes that schema for encrypted accounts has NOT changed. + const migratedSerialized = oldEncoding.serialize(migrated) + const { ciphertext: data, salt, nonce } = masterKey.encrypt(migratedSerialized) + + const encryptedAccount: OldEncryptedAccountValue = { + encrypted: true, + salt, + nonce, + data, + } await stores.old.accounts.put(decryptedAccount.id, encryptedAccount, tx) - - key.destroy() } else { decryptedAccount = account logger.info(` Migrating account ${decryptedAccount.name}`) - - const migrated = this._accountBackward(decryptedAccount) + const migrated = this.accountBackward(decryptedAccount) await stores.old.accounts.put(decryptedAccount.id, migrated, tx) } } } - _accountBackward(newValue: NewDecryptedAccountValue): OldDecryptedAccountValue { - // Insert backward migration logic for a decrypted account + // Implement logic to rever (decrypted) account data to the old schema + accountBackward(newValue: NewDecryptedAccountValue): OldDecryptedAccountValue { const oldValue = newValue return oldValue } diff --git a/ironfish/src/migrations/data/033-encrypted-wallet-template/util.ts b/ironfish/src/migrations/data/033-encrypted-wallet-template/util.ts deleted file mode 100644 index bdcd6b04a2..0000000000 --- a/ironfish/src/migrations/data/033-encrypted-wallet-template/util.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* 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 { xchacha20poly1305 } from '@ironfish/rust-nodejs' -import { Assert } from '../../../assert' -import { IDatabaseStore } from '../../../storage' -import { AccountDecryptionFailedError } from '../../../wallet' -import { MasterKey } from '../../../wallet/masterKey' -import { - AccountValueEncoding as NewAccountValueEncoding, - DecryptedAccountValue as NewDecryptedAccountValue, -} from './new/accountValue' -import { - AccountValueEncoding as OldAccountValueEncoding, - DecryptedAccountValue as OldDecryptedAccountValue, - EncryptedAccountValue as OldEncryptedAccountValue, -} from './old/accountValue' -import { MasterKeyValue } from './old/masterKeyValue' - -export async function getKey( - masterKeyStore: IDatabaseStore<{ key: string; value: MasterKeyValue }>, - salt: Buffer, - nonce: Buffer, - passphrase: string, -): Promise { - const masterKeyValue = await masterKeyStore.get('key') - Assert.isNotUndefined(masterKeyValue) - - const masterKey = new MasterKey(masterKeyValue) - - await masterKey.unlock(passphrase) - - return masterKey.deriveKey(salt, nonce) -} - -export function encryptOldAccountValue( - decrypted: OldDecryptedAccountValue, - key: xchacha20poly1305.XChaCha20Poly1305Key, - salt: Buffer, - nonce: Buffer, -): OldEncryptedAccountValue { - // Serialize the decrypted account data using the old schema - const encoding = new OldAccountValueEncoding() - const serialized = encoding.serialize(decrypted) - - const encryptedData = key.encrypt(serialized) - - return { - encrypted: true, - salt, - nonce, - data: encryptedData, - } -} - -// Decrypts encrypted account data and deserializes it using the OLD version of -// the DecryptedAccountValue schema -export function decryptOldEncryptedAccountValue( - encrypted: Buffer, - key: xchacha20poly1305.XChaCha20Poly1305Key, -): OldDecryptedAccountValue { - try { - const decrypted = key.decrypt(encrypted) - - // Deserialize the decrypted account data using the old schema - const encoding = new OldAccountValueEncoding() - return encoding.deserializeDecrypted(decrypted) - } catch { - throw new AccountDecryptionFailedError() - } -} - -// Decrypts encrypted account data and deserializes it using the NEW version of -// the DecryptedAccountValue schema -export function decryptNewEncryptedAccountValue( - encrypted: Buffer, - key: xchacha20poly1305.XChaCha20Poly1305Key, -): NewDecryptedAccountValue { - try { - const decrypted = key.decrypt(encrypted) - - // Deserialize the decrypted account data using the new schema - const encoding = new NewAccountValueEncoding() - return encoding.deserializeDecrypted(decrypted) - } catch { - throw new AccountDecryptionFailedError() - } -}