-
Notifications
You must be signed in to change notification settings - Fork 572
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
stores identity in multisig account value
updates the MultisigKeys interface to store the multisig account identity in the account value adds a migration, 034, that derives the account identity from the account secret already stored in the account updates importAccount to derive identity from secret at time of import. deriving the identity instead of using the identity included in the import maintains backwards compatibility
- Loading branch information
Showing
20 changed files
with
1,054 additions
and
4 deletions.
There are no files selected for viewing
172 changes: 172 additions & 0 deletions
172
ironfish/src/migrations/data/033-multisig-keys-identity.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* 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 { multisig } from '@ironfish/rust-nodejs' | ||
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 { | ||
AccountValueEncoding as NewAccountValueEncoding, | ||
DecryptedAccountValue as NewDecryptedAccountValue, | ||
} from './033-multisig-keys-identity/new/accountValue' | ||
import { | ||
AccountValueEncoding as OldAccountValueEncoding, | ||
DecryptedAccountValue as OldDecryptedAccountValue, | ||
EncryptedAccountValue as OldEncryptedAccountValue, | ||
} from './033-multisig-keys-identity/old/accountValue' | ||
import { isSignerMultisig } from './033-multisig-keys-identity/old/multisigKeys' | ||
import { GetStores } from './033-multisig-keys-identity/stores' | ||
|
||
export class Migration033 extends Migration { | ||
path = __filename | ||
database = Database.WALLET | ||
|
||
prepare(context: MigrationContext): IDatabase { | ||
return createDB({ location: context.config.walletDatabasePath }) | ||
} | ||
|
||
async forward( | ||
context: MigrationContext, | ||
db: IDatabase, | ||
tx: IDatabaseTransaction | undefined, | ||
logger: Logger, | ||
dryRun: boolean, | ||
walletPassphrase: string | undefined, | ||
): Promise<void> { | ||
const stores = GetStores(db) | ||
const oldEncoding = new OldAccountValueEncoding() | ||
const newEncoding = new NewAccountValueEncoding() | ||
|
||
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. | ||
if (account.encrypted) { | ||
if (!walletPassphrase) { | ||
throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet') | ||
} | ||
|
||
const masterKeyValue = await stores.old.masterKey.get('key') | ||
Assert.isNotUndefined(masterKeyValue) | ||
|
||
const masterKey = new MasterKey(masterKeyValue) | ||
await masterKey.unlock(walletPassphrase) | ||
|
||
// Decrypt encrypted account data | ||
const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce) | ||
decryptedAccount = oldEncoding.deserializeDecrypted(decrypted) | ||
|
||
// 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. | ||
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) | ||
} else { | ||
decryptedAccount = account | ||
|
||
logger.info(` Migrating account ${decryptedAccount.name}`) | ||
const migrated = this.accountForward(decryptedAccount) | ||
|
||
await stores.new.accounts.put(decryptedAccount.id, migrated, tx) | ||
} | ||
} | ||
} | ||
|
||
accountForward(oldValue: OldDecryptedAccountValue): NewDecryptedAccountValue { | ||
const multisigKeys = oldValue.multisigKeys | ||
if (!multisigKeys || !isSignerMultisig(multisigKeys)) { | ||
return oldValue | ||
} | ||
|
||
const secret = new multisig.ParticipantSecret(Buffer.from(multisigKeys.secret, 'hex')) | ||
const newValue = { | ||
...oldValue, | ||
multisigKeys: { | ||
...multisigKeys, | ||
identity: secret.toIdentity().serialize().toString('hex'), | ||
}, | ||
} | ||
return newValue | ||
} | ||
|
||
async backward( | ||
context: MigrationContext, | ||
db: IDatabase, | ||
tx: IDatabaseTransaction | undefined, | ||
logger: Logger, | ||
dryRun: boolean, | ||
walletPassphrase: string | undefined, | ||
): Promise<void> { | ||
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. | ||
if (account.encrypted) { | ||
if (!walletPassphrase) { | ||
throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet') | ||
} | ||
|
||
// Load master key from database | ||
const masterKeyValue = await stores.old.masterKey.get('key') | ||
Assert.isNotUndefined(masterKeyValue) | ||
|
||
const masterKey = new MasterKey(masterKeyValue) | ||
await masterKey.unlock(walletPassphrase) | ||
|
||
// Decrypt encrypted account data | ||
const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce) | ||
decryptedAccount = newEncoding.deserializeDecrypted(decrypted) | ||
|
||
// Apply migration to decrypted account data | ||
logger.info(` Migrating account ${decryptedAccount.name}`) | ||
const migrated = this.accountBackward(decryptedAccount) | ||
|
||
// Re-encrypt the migrated data and write it to the store. | ||
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) | ||
} else { | ||
decryptedAccount = account | ||
|
||
logger.info(` Migrating account ${decryptedAccount.name}`) | ||
const migrated = this.accountBackward(decryptedAccount) | ||
|
||
await stores.old.accounts.put(decryptedAccount.id, migrated, tx) | ||
} | ||
} | ||
} | ||
|
||
accountBackward(newValue: NewDecryptedAccountValue): OldDecryptedAccountValue { | ||
const oldValue = newValue | ||
return oldValue | ||
} | ||
} |
Oops, something went wrong.