Skip to content

Commit 42099f8

Browse files
authored
stores identity in multisig account value (#5630)
* 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 * updates from pr feedback updates initialization of multisigKeys from accountValue to occur as soon as accountValue.multisigKeys is checked updates multisigKeys using its own value instead of accountValue.multisigKeys. preserves any updates to multisigKeys after initialization * updates mulitisigKeys discriminator functions to check all fields accommodates future additions of other multisigKeys types
1 parent 5e5b210 commit 42099f8

File tree

20 files changed

+1076
-6
lines changed

20 files changed

+1076
-6
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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 { multisig } from '@ironfish/rust-nodejs'
5+
import { Assert } from '../../assert'
6+
import { Logger } from '../../logger'
7+
import { IDatabase, IDatabaseTransaction } from '../../storage'
8+
import { createDB } from '../../storage/utils'
9+
import { MasterKey } from '../../wallet/masterKey'
10+
import { EncryptedWalletMigrationError } from '../errors'
11+
import { Database, Migration, MigrationContext } from '../migration'
12+
import {
13+
AccountValueEncoding as NewAccountValueEncoding,
14+
DecryptedAccountValue as NewDecryptedAccountValue,
15+
} from './033-multisig-keys-identity/new/accountValue'
16+
import {
17+
AccountValueEncoding as OldAccountValueEncoding,
18+
DecryptedAccountValue as OldDecryptedAccountValue,
19+
EncryptedAccountValue as OldEncryptedAccountValue,
20+
} from './033-multisig-keys-identity/old/accountValue'
21+
import { isSignerMultisig } from './033-multisig-keys-identity/old/multisigKeys'
22+
import { GetStores } from './033-multisig-keys-identity/stores'
23+
24+
export class Migration033 extends Migration {
25+
path = __filename
26+
database = Database.WALLET
27+
28+
prepare(context: MigrationContext): IDatabase {
29+
return createDB({ location: context.config.walletDatabasePath })
30+
}
31+
32+
async forward(
33+
context: MigrationContext,
34+
db: IDatabase,
35+
tx: IDatabaseTransaction | undefined,
36+
logger: Logger,
37+
dryRun: boolean,
38+
walletPassphrase: string | undefined,
39+
): Promise<void> {
40+
const stores = GetStores(db)
41+
const oldEncoding = new OldAccountValueEncoding()
42+
const newEncoding = new NewAccountValueEncoding()
43+
44+
for await (const account of stores.old.accounts.getAllValuesIter(tx)) {
45+
let decryptedAccount
46+
47+
// Check if the account is encrypted, and throw an error to allow client
48+
// code to prompt for passphrase.
49+
if (account.encrypted) {
50+
if (!walletPassphrase) {
51+
throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet')
52+
}
53+
54+
const masterKeyValue = await stores.old.masterKey.get('key')
55+
Assert.isNotUndefined(masterKeyValue)
56+
57+
const masterKey = new MasterKey(masterKeyValue)
58+
await masterKey.unlock(walletPassphrase)
59+
60+
// Decrypt encrypted account data
61+
const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce)
62+
decryptedAccount = oldEncoding.deserializeDecrypted(decrypted)
63+
64+
// Apply migration to decrypted account data
65+
logger.info(` Migrating account ${decryptedAccount.name}`)
66+
const migrated = this.accountForward(decryptedAccount)
67+
68+
// Re-encrypt the migrated data and write it to the store.
69+
const migratedSerialized = newEncoding.serialize(migrated)
70+
const { ciphertext: data, salt, nonce } = masterKey.encrypt(migratedSerialized)
71+
72+
const encryptedAccount: OldEncryptedAccountValue = {
73+
encrypted: true,
74+
salt,
75+
nonce,
76+
data,
77+
}
78+
79+
await stores.new.accounts.put(decryptedAccount.id, encryptedAccount, tx)
80+
} else {
81+
decryptedAccount = account
82+
83+
logger.info(` Migrating account ${decryptedAccount.name}`)
84+
const migrated = this.accountForward(decryptedAccount)
85+
86+
await stores.new.accounts.put(decryptedAccount.id, migrated, tx)
87+
}
88+
}
89+
}
90+
91+
accountForward(oldValue: OldDecryptedAccountValue): NewDecryptedAccountValue {
92+
const multisigKeys = oldValue.multisigKeys
93+
if (!multisigKeys || !isSignerMultisig(multisigKeys)) {
94+
return oldValue
95+
}
96+
97+
const secret = new multisig.ParticipantSecret(Buffer.from(multisigKeys.secret, 'hex'))
98+
const newValue = {
99+
...oldValue,
100+
multisigKeys: {
101+
...multisigKeys,
102+
identity: secret.toIdentity().serialize().toString('hex'),
103+
},
104+
}
105+
return newValue
106+
}
107+
108+
async backward(
109+
context: MigrationContext,
110+
db: IDatabase,
111+
tx: IDatabaseTransaction | undefined,
112+
logger: Logger,
113+
dryRun: boolean,
114+
walletPassphrase: string | undefined,
115+
): Promise<void> {
116+
const stores = GetStores(db)
117+
const oldEncoding = new OldAccountValueEncoding()
118+
const newEncoding = new NewAccountValueEncoding()
119+
120+
for await (const account of stores.new.accounts.getAllValuesIter(tx)) {
121+
let decryptedAccount
122+
123+
// Check if the account is encrypted, and throw an error to allow client
124+
// code to prompt for passphrase.
125+
if (account.encrypted) {
126+
if (!walletPassphrase) {
127+
throw new EncryptedWalletMigrationError('Cannot run migration on encrypted wallet')
128+
}
129+
130+
// Load master key from database
131+
const masterKeyValue = await stores.old.masterKey.get('key')
132+
Assert.isNotUndefined(masterKeyValue)
133+
134+
const masterKey = new MasterKey(masterKeyValue)
135+
await masterKey.unlock(walletPassphrase)
136+
137+
// Decrypt encrypted account data
138+
const decrypted = masterKey.decrypt(account.data, account.salt, account.nonce)
139+
decryptedAccount = newEncoding.deserializeDecrypted(decrypted)
140+
141+
// Apply migration to decrypted account data
142+
logger.info(` Migrating account ${decryptedAccount.name}`)
143+
const migrated = this.accountBackward(decryptedAccount)
144+
145+
// Re-encrypt the migrated data and write it to the store.
146+
const migratedSerialized = oldEncoding.serialize(migrated)
147+
const { ciphertext: data, salt, nonce } = masterKey.encrypt(migratedSerialized)
148+
149+
const encryptedAccount: OldEncryptedAccountValue = {
150+
encrypted: true,
151+
salt,
152+
nonce,
153+
data,
154+
}
155+
156+
await stores.old.accounts.put(decryptedAccount.id, encryptedAccount, tx)
157+
} else {
158+
decryptedAccount = account
159+
160+
logger.info(` Migrating account ${decryptedAccount.name}`)
161+
const migrated = this.accountBackward(decryptedAccount)
162+
163+
await stores.old.accounts.put(decryptedAccount.id, migrated, tx)
164+
}
165+
}
166+
}
167+
168+
accountBackward(newValue: NewDecryptedAccountValue): OldDecryptedAccountValue {
169+
const oldValue = newValue
170+
return oldValue
171+
}
172+
}

0 commit comments

Comments
 (0)