Skip to content

Commit

Permalink
fix: OK-18297 inconsistency between the address generated by the same…
Browse files Browse the repository at this point in the history
… mnemonic in the app and the address generated by the hardware in Monero GUI (#2767)

* fix: inconsistency between the address generated by the same mnemonic phrase in the app and the address generated by the hardware in Monero GUI

* chore: enable monero in prod
  • Loading branch information
weatherstar committed Mar 26, 2023
1 parent 395c9cf commit f10b4f2
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 114 deletions.
4 changes: 1 addition & 3 deletions packages/engine/src/index.ts
Expand Up @@ -2984,19 +2984,17 @@ class Engine {
accountId,
networkId,
password,
passwordLoadedCallback,
}: {
accountId: string;
networkId: string;
password?: string;
passwordLoadedCallback?: (isLoaded: boolean) => void;
}) {
if (!networkId || !accountId) return 0;
const vault = await this.getVault({
accountId,
networkId,
});
return vault.getFrozenBalance(password, passwordLoadedCallback);
return vault.getFrozenBalance(password);
}
}

Expand Down
1 change: 0 additions & 1 deletion packages/engine/src/vaults/VaultBase.ts
Expand Up @@ -181,7 +181,6 @@ export abstract class VaultBaseChainOnly extends VaultContext {

async getFrozenBalance(
password?: string,
passwordLoadedCallback?: (isLoaded: boolean) => void,
): Promise<number | Record<string, number>> {
return 0;
}
Expand Down
53 changes: 36 additions & 17 deletions packages/engine/src/vaults/impl/xmr/KeyringHd.ts
@@ -1,17 +1,15 @@
import { mnemonicFromEntropy } from '@onekeyfe/blockchain-libs/dist/secret';
import { mnemonicToSeedSync } from 'bip39';

import { decrypt } from '@onekeyhq/engine/src/secret/encryptors/aes256';
import { COINTYPE_XMR as COIN_TYPE } from '@onekeyhq/shared/src/engine/engineConsts';

import { OneKeyInternalError } from '../../../errors';
import { slicePathTemplate } from '../../../managers/derivation';
import { getAccountNameInfoByTemplate } from '../../../managers/impl';
import { batchGetPrivateKeys } from '../../../secret';
import { AccountType } from '../../../types/account';
import { KeyringHdBase } from '../../keyring/KeyringHdBase';

import { getMoneroApi } from './sdk';
import { MoneroNetTypeEnum } from './sdk/moneroUtil/moneroUtilTypes';
import { getRawPrivateKeyFromSeed } from './utils';

import type { ExportedSeedCredential } from '../../../dbs/base';
import type { DBVariantAccount } from '../../../types/account';
Expand All @@ -34,17 +32,25 @@ export class KeyringHd extends KeyringHdBase {
const indexes = [0];

const network = await this.getNetwork();
const { entropy } = (await this.engine.dbApi.getCredential(
const { seed } = (await this.engine.dbApi.getCredential(
this.walletId,
password,
)) as ExportedSeedCredential;

const moneroApi = await getMoneroApi();

const mnemonic = mnemonicFromEntropy(entropy, password);
const seed = mnemonicToSeedSync(mnemonic);
const { pathPrefix } = slicePathTemplate(template);
const rawPrivateKey = getRawPrivateKeyFromSeed(seed, pathPrefix);
const { pathPrefix, pathSuffix } = slicePathTemplate(template);

const [privateKeyInfo] = batchGetPrivateKeys(
'ed25519',
seed,
password,
pathPrefix,
[pathSuffix.replace('{index}', '0')],
);

const rawPrivateKey = decrypt(password, privateKeyInfo.extendedKey.key);

if (!rawPrivateKey) {
throw new OneKeyInternalError('Unable to get raw private key.');
}
Expand All @@ -63,7 +69,10 @@ export class KeyringHd extends KeyringHdBase {
throw new OneKeyInternalError('Unable to get public spend/view key.');
}

const path = `${pathPrefix}/${index}`;
const path = `${pathPrefix}/${pathSuffix.replace(
'{index}',
index.toString(),
)}`;

const address = moneroApi.pubKeysToAddress(
network.isTestnet
Expand All @@ -88,26 +97,36 @@ export class KeyringHd extends KeyringHdBase {
addresses: { [this.networkId]: address },
});
}
console.log(ret);
return ret;
}

override async signTransaction(
unsignedTx: IUnsignedTxPro,
options: ISignCredentialOptions,
): Promise<SignedTx> {
const { password = '' } = options;
const dbAccount = await this.getDbAccount();
const moneroApi = await getMoneroApi();
const scanUrl = await this.getScanUrl();
const { entropy } = (await this.engine.dbApi.getCredential(
const { seed } = (await this.engine.dbApi.getCredential(
this.walletId,
options.password || '',
password,
)) as ExportedSeedCredential;

const mnemonic = mnemonicFromEntropy(entropy, options.password || '');
const seed = mnemonicToSeedSync(mnemonic);
const { pathPrefix } = slicePathTemplate(dbAccount.template as string);
const rawPrivateKey = getRawPrivateKeyFromSeed(seed, pathPrefix);
const { pathPrefix, pathSuffix } = slicePathTemplate(
dbAccount.template as string,
);

const [privateKeyInfo] = batchGetPrivateKeys(
'ed25519',
seed,
password,
pathPrefix,
[pathSuffix.replace('{index}', '0')],
);

const rawPrivateKey = decrypt(password, privateKeyInfo.extendedKey.key);

if (!rawPrivateKey) {
throw new OneKeyInternalError('Unable to get raw private key.');
}
Expand Down
41 changes: 20 additions & 21 deletions packages/engine/src/vaults/impl/xmr/Vault.ts
@@ -1,6 +1,4 @@
import { mnemonicFromEntropy } from '@onekeyfe/blockchain-libs/dist/secret';
import BigNumber from 'bignumber.js';
import { mnemonicToSeedSync } from 'bip39';
import memoizee from 'memoizee';

import { decrypt } from '@onekeyhq/engine/src/secret/encryptors/aes256';
Expand All @@ -15,6 +13,7 @@ import {
} from '../../../errors';
import { isAccountCompatibleWithNetwork } from '../../../managers/account';
import { slicePathTemplate } from '../../../managers/derivation';
import { batchGetPrivateKeys } from '../../../secret';
import { AccountCredentialType } from '../../../types/account';
import {
IDecodedTxActionType,
Expand All @@ -33,7 +32,7 @@ import { getMoneroApi } from './sdk';
import { MoneroNetTypeEnum } from './sdk/moneroUtil/moneroUtilTypes';
import settings from './settings';
import { MoneroTxPriorityEnum } from './types';
import { getDecodedTxStatus, getRawPrivateKeyFromSeed } from './utils';
import { getDecodedTxStatus } from './utils';

import type { ExportedSeedCredential } from '../../../dbs/base';
import type { Account, DBVariantAccount } from '../../../types/account';
Expand Down Expand Up @@ -83,19 +82,23 @@ export default class Vault extends VaultBase {
rawPrivateKey = decrypt(psw, privateKey).toString('hex');
} else {
const { template } = this.settings.accountNameInfo.default;
const { entropy } = (await this.engine.dbApi.getCredential(
const { seed } = (await this.engine.dbApi.getCredential(
this.walletId,
psw,
)) as ExportedSeedCredential;
const { pathPrefix } = slicePathTemplate(template);
const mnemonic = mnemonicFromEntropy(entropy, psw);
const seed = mnemonicToSeedSync(mnemonic);
const resp = getRawPrivateKeyFromSeed(seed, pathPrefix);
const { pathPrefix, pathSuffix } = slicePathTemplate(template);

if (!resp) {
throw new OneKeyInternalError('Unable to get raw private key.');
}
rawPrivateKey = resp.toString('hex');
const [privateKeyInfo] = batchGetPrivateKeys(
'ed25519',
seed,
psw,
pathPrefix,
[pathSuffix.replace('{index}', '0')],
);

rawPrivateKey = decrypt(psw, privateKeyInfo.extendedKey.key).toString(
'hex',
);
}
passwordLoadedCallback?.(true);
return rawPrivateKey;
Expand All @@ -122,7 +125,9 @@ export default class Vault extends VaultBase {
let accountIndex = 0;
if (!isImportedAccount) {
accountIndex =
index === undefined ? Number(accountId.split('/').pop()) : index;
index === undefined
? parseInt(accountId.split('/').pop() ?? '0')
: index;
}

return moneroApi.getKeyPairFromRawPrivatekey({
Expand Down Expand Up @@ -357,7 +362,6 @@ export default class Vault extends VaultBase {
this.accountId,
privateKey,
);

switch (credentialType) {
case AccountCredentialType.PrivateSpendKey:
return Buffer.from(privateSpendKey).toString('hex');
Expand Down Expand Up @@ -548,7 +552,7 @@ export default class Vault extends VaultBase {
const [publicSpendKey, publicViewKey] = account.pub.split(',');
const isSubaddress = account.id.startsWith('imported')
? false
: Number(account.path.split('/').pop()) !== 0;
: parseInt(account.path.split('/').pop() ?? '0') !== 0;

return moneroApi.pubKeysToAddress(
isTestnet ? MoneroNetTypeEnum.TestNet : MoneroNetTypeEnum.MainNet,
Expand Down Expand Up @@ -603,17 +607,12 @@ export default class Vault extends VaultBase {
};
}

override async getFrozenBalance(
password?: string,
passwordLoadedCallback?: (isLoaded: boolean) => void,
) {
override async getFrozenBalance(password?: string) {
const client = await this.getClient({
password,
passwordLoadedCallback,
});
const { address } = await this.getOutputAccount();
const { decimals } = await this.engine.getNativeTokenInfo(this.networkId);

try {
const [totalBN] = await client.getBalances([
{
Expand Down
6 changes: 1 addition & 5 deletions packages/engine/src/vaults/impl/xmr/sdk/helper.ts
Expand Up @@ -5,7 +5,6 @@ import {
Lazy_KeyImageCacheForWalletWith,
} from '@mymonero/mymonero-keyimage-cache';
import axios from 'axios';
import sha3 from 'js-sha3';

import { OneKeyInternalError } from '../../../../errors';

Expand Down Expand Up @@ -169,10 +168,7 @@ class Helper {
if (isPrivateSpendKey) {
privateSpendKey = raw;
} else {
const rawSecretSpendKey = new Uint8Array(
sha3.keccak_256.update(raw).arrayBuffer(),
);
privateSpendKey = this.scReduce32(rawSecretSpendKey);
privateSpendKey = this.scReduce32(raw);
}

const privateViewKey = this.hashToScalar(privateSpendKey);
Expand Down
3 changes: 1 addition & 2 deletions packages/engine/src/vaults/impl/xmr/settings.ts
Expand Up @@ -24,13 +24,12 @@ const settings: IVaultSettings = Object.freeze({
addressDerivationDisabled: true,
validationRequired: true,
disabledInExtension: true,
enabledInDevModeOnly: true,

accountNameInfo: {
default: {
prefix: 'XMR',
category: `44'/${COINTYPE_XMR}'`,
template: `m/44'/${COINTYPE_XMR}'/0'/0/${INDEX_PLACEHOLDER}`,
template: `m/44'/${COINTYPE_XMR}'/${INDEX_PLACEHOLDER}'`,
coinType: COINTYPE_XMR,
},
},
Expand Down
59 changes: 0 additions & 59 deletions packages/engine/src/vaults/impl/xmr/utils.ts
@@ -1,68 +1,9 @@
import { fromSeed } from 'bip32';

import { IDecodedTxStatus } from '../../types';

import type { IOnChainHistoryTx } from './types';
import type { BIP32Interface } from 'bip32';

const TX_MIN_CONFIRMS = 10;

export function calcBip32ExtendedKey(
path: string,
bip32RootKey: BIP32Interface,
) {
// Check there's a root key to derive from
if (!bip32RootKey) {
return bip32RootKey;
}
let extendedKey = bip32RootKey;
// Derive the key from the path
const pathBits = path.split('/');
for (let i = 0; i < pathBits.length; i += 1) {
const bit = pathBits[i];
const index = parseInt(bit);
if (Number.isNaN(index)) {
// eslint-disable-next-line no-continue
continue;
}
const hardened = bit[bit.length - 1] === "'";
const isPriv = !extendedKey.isNeutered();
const invalidDerivationPath = hardened && !isPriv;
if (invalidDerivationPath) {
return null;
}
if (hardened) {
extendedKey = extendedKey.deriveHardened(index);
} else {
extendedKey = extendedKey.derive(index);
}
}
return extendedKey;
}

export function getRawPrivateKeyFromSeed(seed: Buffer, pathPrefix: string) {
const rootKey = fromSeed(seed, {
messagePrefix: 'x18XMR Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x7f,
scriptHash: 0xc4,
wif: 0x3f,
});

const extendedKey = calcBip32ExtendedKey(pathPrefix, rootKey);

if (!extendedKey) {
return null;
}
const key = extendedKey.derive(0);
const rawPrivateKey = key.privateKey;

return rawPrivateKey;
}

export function getDecodedTxStatus(
tx: IOnChainHistoryTx,
blockchainHeight: number,
Expand Down
5 changes: 0 additions & 5 deletions packages/kit/src/hooks/useTokens.ts
Expand Up @@ -12,7 +12,6 @@ import { OnekeyNetwork } from '@onekeyhq/shared/src/config/networkIds';
import debugLogger from '@onekeyhq/shared/src/logger/debugLogger';

import backgroundApiProxy from '../background/instance/backgroundApiProxy';
import { setIsPasswordLoadedInVault } from '../store/reducers/data';
import { getPreBaseValue } from '../utils/priceUtils';

import { useAppSelector } from './useAppSelector';
Expand Down Expand Up @@ -224,23 +223,19 @@ export const useFrozenBalance = ({
useEffect(() => {
(async () => {
let password;
let passwordLoadedCallback;

const vaultSettings = await backgroundApiProxy.engine.getVaultSettings(
networkId,
);
if (vaultSettings.validationRequired) {
password = await backgroundApiProxy.servicePassword.getPassword();
passwordLoadedCallback = (isLoaded: boolean) =>
backgroundApiProxy.dispatch(setIsPasswordLoadedInVault(isLoaded));
}

backgroundApiProxy.engine
.getFrozenBalance({
accountId,
networkId,
password,
passwordLoadedCallback,
})
.then(setFrozenBalance)
.catch((e) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/engine/engineConsts.ts
Expand Up @@ -194,4 +194,4 @@ export const PRICE_EXPIRED_TIME = getTimeDurationMs({ minute: 15 });
export const ACCOUNT_DERIVATION_DB_MIGRATION_VERSION = '4.0.0';
export const FIX_COSMOS_TEMPLATE_DB_MIGRATION_VERSION = '4.2.0';

export const CHAINS_DISPLAYED_IN_DEV: string[] = [IMPL_XMR];
export const CHAINS_DISPLAYED_IN_DEV: string[] = [];

0 comments on commit f10b4f2

Please sign in to comment.