Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yoroi 1.4.0 Release - Ledger + Desync fix #370

Merged
merged 68 commits into from
Mar 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
fb5be20
lang key fix
Mar 14, 2019
2fa1955
Indonesian key update
Mar 14, 2019
c597ccc
fix desync and add migration
SebastienGllmt Mar 11, 2019
ca961a7
optimistic fix
SebastienGllmt Mar 18, 2019
2f8a168
another optimistic fix
SebastienGllmt Mar 18, 2019
2d03864
fix missing tx issue
SebastienGllmt Mar 18, 2019
a84d430
Merge pull request #348 from Emurgo/fix/ledger
SebastienGllmt Mar 19, 2019
57ec337
fix of #353
Mar 19, 2019
8a4c3b8
fix js-cardano-wasm
Mar 19, 2019
d94bbea
removed .(dot) from "Buy a Ledger hardware wallet."
Mar 19, 2019
5d7973e
Merge pull request #354 from Emurgo/fix/353
SebastienGllmt Mar 19, 2019
6174f68
change Medium to EMURGO
SebastienGllmt Mar 19, 2019
b7f560b
remove height restriction
SebastienGllmt Mar 20, 2019
ae03617
descriptions no longer required
SebastienGllmt Mar 20, 2019
7ff3dcc
remove all descriptions
SebastienGllmt Mar 20, 2019
2703757
Merge pull request #322 from Emurgo/feature/desync
SebastienGllmt Mar 20, 2019
30a3ae7
Merge pull request #361 from Emurgo/fix/removeDescriptions
SebastienGllmt Mar 20, 2019
d321e49
Merge branch 'develop' into fix/blogLinks
SebastienGllmt Mar 20, 2019
ebba7ac
Merge pull request #357 from Emurgo/fix/blogLinks
SebastienGllmt Mar 20, 2019
24405dd
merge conflicts
nicarq Mar 20, 2019
1fe7c96
Merge commit 'ebba7ac6516eff3a2a05c52614f6ea7a9ab3654d' into l10n_dev…
nicarq Mar 20, 2019
f0cd6a0
Merge pull request #360 from Emurgo/fix/buttonSize
SebastienGllmt Mar 21, 2019
3749350
Merge pull request #343 from Emurgo/l10n_develop
nicarq Mar 21, 2019
4cec17e
first part
SebastienGllmt Mar 21, 2019
10991df
adds dialog, qr and address show
nicarq Mar 22, 2019
6727d94
misc cleanup
SebastienGllmt Mar 22, 2019
9a1fcd5
bump version
SebastienGllmt Mar 22, 2019
8f29fc4
verify button first attempt
SebastienGllmt Mar 22, 2019
2cf6653
add i18n
SebastienGllmt Mar 22, 2019
7cd9959
small fixes
SebastienGllmt Mar 22, 2019
d8707d4
refactor and bug fixes
nicarq Mar 22, 2019
1264f00
add error displaying and loader
nicarq Mar 23, 2019
7f7436b
refactor
nicarq Mar 23, 2019
e651f7d
remove leftover test function
SebastienGllmt Mar 23, 2019
925e3a5
uninitialize bridge on close
SebastienGllmt Mar 23, 2019
c1c8ece
remove unused code
SebastienGllmt Mar 23, 2019
a3461df
fix eslint
SebastienGllmt Mar 23, 2019
d4182e9
add back accidentally removed import
SebastienGllmt Mar 23, 2019
c481aad
Merge pull request #363 from Emurgo/feature/ledgerVerify
SebastienGllmt Mar 23, 2019
0c78929
add verify function on Trezor
SebastienGllmt Mar 23, 2019
b6ddf02
add max-height
SebastienGllmt Mar 19, 2019
8c975f8
workaround for polymorph bug
SebastienGllmt Mar 24, 2019
ef46107
Merge pull request #365 from Emurgo/feature/verifyOnTrezor
nicarq Mar 24, 2019
ec1d6ee
adds error
nicarq Mar 24, 2019
427f068
Merge pull request #366 from Emurgo/fix/ledger-feature-error-display
SebastienGllmt Mar 24, 2019
63913a5
Merge pull request #358 from Emurgo/fix/longOptionSelect
nicarq Mar 25, 2019
fbd7336
New Crowdin translations (#364)
nicarq Mar 26, 2019
0707d3b
Replaced Ledger logo
vsubhuman Mar 26, 2019
c2f37d4
Add Ledger Affiliate and documentation link [ch126]
Mar 27, 2019
1bff3a8
Merge pull request #368 from Emurgo/shin/ch126/add-ledger-affiliate-a…
SebastienGllmt Mar 27, 2019
b4edc86
New translations en-US.json (Japanese)
nicarq Mar 27, 2019
de40b90
fixed top-bar with ledger icon [ch121]
Mar 27, 2019
21f4e1c
New translations en-US.json (Russian)
nicarq Mar 27, 2019
7b004bc
for top-bar ledger lcon set stroke property to none [ch121]
Mar 27, 2019
62c5529
added new Ledger logo SVG [ch121]
Mar 27, 2019
0681dab
New translations en-US.json (Japanese)
nicarq Mar 27, 2019
00937bd
Merge pull request #367 from Emurgo/vsubhuman/ch121/replace-ledger-logo
SebastienGllmt Mar 27, 2019
976b6f7
New translations en-US.json (Chinese Simplified)
nicarq Mar 27, 2019
64a3a45
New translations en-US.json (Chinese Traditional)
nicarq Mar 27, 2019
bd389d1
New translations en-US.json (Korean)
nicarq Mar 27, 2019
73b4c54
New translations en-US.json (Korean)
nicarq Mar 27, 2019
fb71677
New translations en-US.json (Korean)
nicarq Mar 27, 2019
8a7a7a1
New translations en-US.json (Korean)
nicarq Mar 27, 2019
efb7f06
New translations en-US.json (Korean)
nicarq Mar 27, 2019
b18d918
New translations en-US.json (Korean)
nicarq Mar 27, 2019
ad35230
New translations en-US.json (Russian)
nicarq Mar 27, 2019
1e68c6c
Merge pull request #369 from Emurgo/l10n_develop
SebastienGllmt Mar 27, 2019
43abb1d
package bump
SebastienGllmt Mar 27, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"react-intl",
{
"messagesDir": "./translations/messages/",
"enforceDescriptions": true,
"enforceDescriptions": false,
"extractSourceLocation": true
}
],
Expand Down
15 changes: 15 additions & 0 deletions app/actions/ada/hw-verify-address-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @flow
import Action from '../lib/Action';

import type {
BIP32Path
} from '@cardano-foundation/ledgerjs-hw-app-cardano';
import Wallet from '../../domain/Wallet';

// ======= ADDRESSES ACTIONS =======

export default class HWVerifyAddressActions {
closeAddressDetailDialog: Action<void> = new Action();
selectAddress: Action<{ address: string, path: BIP32Path }> = new Action();
verifyAddress: Action<{ wallet: Wallet }> = new Action();
}
3 changes: 3 additions & 0 deletions app/actions/ada/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import HWConnectActions from './hw-connect-actions';
import TrezorSendActions from './trezor-send-actions';
import AdaRedemptionActions from './ada-redemption-actions';
import LedgerSendActions from './ledger-send-actions';
import HWVerifyAddressActions from './hw-verify-address-actions';

export type AdaActionsMap = {
adaRedemption: AdaRedemptionActions,
Expand All @@ -20,6 +21,7 @@ export type AdaActionsMap = {
trezorSend: TrezorSendActions,
ledgerConnect: HWConnectActions,
ledgerSend: LedgerSendActions,
hwVerifyAddress: HWVerifyAddressActions,
};

const adaActionsMap: AdaActionsMap = {
Expand All @@ -33,6 +35,7 @@ const adaActionsMap: AdaActionsMap = {
trezorSend: new TrezorSendActions(),
ledgerConnect: new HWConnectActions(),
ledgerSend: new LedgerSendActions(),
hwVerifyAddress: new HWVerifyAddressActions(),
};

export default adaActionsMap;
94 changes: 43 additions & 51 deletions app/api/ada/adaAddress.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import { Wallet } from 'rust-cardano-crypto';
import _ from 'lodash';
import config from '../../config';
import {
toAdaAddress
} from './lib/cardanoCrypto/cryptoToModel';
Expand All @@ -15,16 +14,20 @@ import {
getAddresses,
getAddressesList,
getAddressesListByType,
deleteAddress
} from './lib/lovefieldDatabase';
import type {
AddressesTableRow
} from './lib/lovefieldDatabase';
import {
getLastReceiveAddressIndex,
saveLastReceiveAddressIndex
} from './adaLocalStorage';
import {
UnusedAddressesError,
} from '../common';
import {
getAddressInHex
getAddressInHex,
getLatestUsedIndex
} from './lib/utils';
import type {
AdaAddresses,
Expand All @@ -35,8 +38,6 @@ import {
stringifyError
} from '../../utils/logging';

const { MAX_ALLOWED_UNUSED_ADDRESSES } = config.wallets;

export function isValidAdaAddress(address: string): Promise<boolean> {
try {
const result: boolean = getResultOrFail(Wallet.checkAddress(getAddressInHex(address)));
Expand Down Expand Up @@ -75,72 +76,63 @@ export function getAdaAddressesByType(addressType: AddressType): Promise<AdaAddr
return getAddressesListByType(addressType);
}

export async function newExternalAdaAddress(
cryptoAccount: CryptoAccount
): Promise<AdaAddress> {
return await newAdaAddress(cryptoAccount, 'External', addresses => {
// We use the isUsed status to now find the next unused address
const lastUsedAddressIndex = _.findLastIndex(addresses, address => address.cadIsUsed) + 1;
// TODO Move this to a config file
const unusedSpan = addresses.length - lastUsedAddressIndex;
if (unusedSpan >= MAX_ALLOWED_UNUSED_ADDRESSES) {
throw new UnusedAddressesError();
}
});
/**
* With bip44, we keep a buffer of unused addresses
* This means when we need a new address, we just use one of the unused ones in our buffer
*
* Once this address appears in a transaction, it will be marked as "used" and a new address
* will be generated by a different process to maintain bip44 compliance
*/
export async function popBip44Address(type: AddressType): Promise<AdaAddress> {
return type === 'Internal'
? popBip44InternalAddress()
: popBip44ExternalAddress();
}

/** Create and save the next address for the given account */
export async function newAdaAddress(
cryptoAccount: CryptoAccount,
addressType: AddressType,
addrValidation?: (AdaAddresses => void)
): Promise<AdaAddress> {
const address: AdaAddress = await createAdaAddress(cryptoAccount, addressType, addrValidation);
await saveAdaAddress(address, addressType);
return address;
async function popBip44InternalAddress(): Promise<AdaAddress> {
const existingAddresses = await getAdaAddressesByType('Internal');
const nextAddressIndex = getLatestUsedIndex(existingAddresses) + 1;
if (nextAddressIndex === existingAddresses.length) {
throw new UnusedAddressesError();
}
const poppedAddress = existingAddresses[nextAddressIndex];

return poppedAddress;
}

/** Create new wallet address based off bip44 and then convert it to an AdaAddress */
export async function createAdaAddress(
cryptoAccount: CryptoAccount,
addressType: AddressType,
addrValidation?: (AdaAddresses => void)
): Promise<AdaAddress> {
// Note this function doesn't just get the addresses but also calculates their isUsed status
const filteredAddresses = await getAdaAddressesByType(addressType);
if (addrValidation) {
addrValidation(filteredAddresses);
async function popBip44ExternalAddress(): Promise<AdaAddress> {
const existingAddresses = await getAdaAddressesByType('External');
const nextAddressIndex = getLastReceiveAddressIndex() + 1;
if (nextAddressIndex === existingAddresses.length) {
throw new UnusedAddressesError();
}
const addressIndex = filteredAddresses.length;
const [address]: Array<string> = getResultOrFail(
Wallet.generateAddresses(cryptoAccount, addressType, [addressIndex])
);
return toAdaAddress(cryptoAccount.account, addressType, addressIndex, address);
const poppedAddress = existingAddresses[nextAddressIndex];
saveLastReceiveAddressIndex(nextAddressIndex);

return poppedAddress;
}

/** Wrapper function to save addresses to LovefieldDB */
/** Wrapper function to save addresses to LovefieldDB
* Note: does NOT update lastReceiveAddressIndex
*/
export function saveAdaAddress(
address: AdaAddress,
addressType: AddressType
): Promise<Array<AddressesTableRow>> {
return saveAddresses([address], addressType);
}

/** Wrapper function to remove an addresse from LovefieldDB */
export function removeAdaAddress(
address: AdaAddress
): Promise<Array<void>> {
return deleteAddress(address.cadId);
}

/** Save list of addresses from lovefieldDB */
/** Save list of addresses to lovefieldDB
* Note: does NOT update lastReceiveAddressIndex
*/
export async function saveAsAdaAddresses(
cryptoAccount: CryptoAccount,
addresses: Array<string>,
offset: number,
addressType: AddressType
): Promise<Array<AddressesTableRow>> {
const mappedAddresses: Array<AdaAddress> = addresses.map((hash, index) => (
toAdaAddress(cryptoAccount.account, addressType, index, hash)
toAdaAddress(cryptoAccount.account, addressType, index + offset, hash)
));
return saveAddresses(mappedAddresses, addressType);
}
Expand Down
46 changes: 40 additions & 6 deletions app/api/ada/adaLocalStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@

import type { AdaWallet } from './adaTypes';

export type LocalStorageWallet = {
adaWallet: AdaWallet,
masterKey?: string, // unused in hardware wallets
// this is a per-account setting but we only have 1 account per wallet in Yoroi
lastReceiveAddressIndex: number
}

// Use constant keys to store/load localstorage
const storageKeys = {
ACCOUNT_KEY: 'ACCOUNT', // Note: only a single account
WALLET_KEY: 'WALLET',
LAST_BLOCK_NUMBER_KEY: 'LAST_BLOCK_NUMBER'
LAST_BLOCK_NUMBER_KEY: 'LAST_BLOCK_NUMBER',
};

/* Account storage */
Expand All @@ -25,26 +32,40 @@ export function getSingleCryptoAccount(): CryptoAccount {

/* Wallet storage */

/** @param AdaWallet cache wallet information
* @param masterKey decrypt to send funds */
export function saveAdaWallet(
export function createStoredWallet(
adaWallet: AdaWallet,
masterKey?: string
): void {
_saveInStorage(storageKeys.WALLET_KEY, { adaWallet, masterKey });
_saveInStorage(storageKeys.WALLET_KEY, ({
adaWallet,
masterKey,
lastReceiveAddressIndex: 0 // always start by showing one address
}: LocalStorageWallet));
}

export function getAdaWallet(): ?AdaWallet {
const stored = _getFromStorage(storageKeys.WALLET_KEY);
return stored ? stored.adaWallet : null;
}

export function saveAdaWallet(adaWallet: AdaWallet): void {
const stored: LocalStorageWallet = _getFromStorage(storageKeys.WALLET_KEY);
stored.adaWallet = adaWallet;
_saveInStorage(storageKeys.WALLET_KEY, stored);
}

export function getWalletMasterKey(): string {
const stored = _getFromStorage(storageKeys.WALLET_KEY);
return stored.masterKey;
}

/* Last block Nunmber storage */
export function saveWalletMasterKey(masterKey: string): void {
const stored: LocalStorageWallet = _getFromStorage(storageKeys.WALLET_KEY);
stored.masterKey = masterKey;
_saveInStorage(storageKeys.WALLET_KEY, stored);
}

/* Last block number storage */

export function saveLastBlockNumber(blockNumber: number): void {
_saveInStorage(storageKeys.LAST_BLOCK_NUMBER_KEY, blockNumber);
Expand All @@ -56,6 +77,19 @@ export function getLastBlockNumber(): number {
return Number(lastBlockNum);
}

/* Last block number storage */

export function saveLastReceiveAddressIndex(index: number): void {
const stored: LocalStorageWallet = _getFromStorage(storageKeys.WALLET_KEY);
stored.lastReceiveAddressIndex = index;
_saveInStorage(storageKeys.WALLET_KEY, stored);
}

export function getLastReceiveAddressIndex(): number {
const stored = _getFromStorage(storageKeys.WALLET_KEY);
return stored.lastReceiveAddressIndex;
}

/* Util functions */

function _saveInStorage(key: string, toSave: any): void {
Expand Down
94 changes: 94 additions & 0 deletions app/api/ada/adaMigration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// @flow

// Handle migration to newer versions of Yoroi

import {
reset,
getAddressesList
} from './lib/lovefieldDatabase';
import {
saveLastReceiveAddressIndex,
} from './adaLocalStorage';
import LocalStorageApi from '../localStorage/index';
import {
Logger,
} from '../../utils/logging';

const semver = require('semver');

export async function migrateToLatest(localStorageApi: LocalStorageApi) {
const lastLaunchVersion = await localStorageApi.getLastLaunchVersion();
Logger.info(`Starting migration for ${lastLaunchVersion}`);
/**
* Note: Although we don't start migration if the user is running a fresh installation
* We still cannot be certain any key exists in localstorage
*
* For example, somebody may have downloaded Yoroi a long time ago
* but only completed the language select before closing the application
*
* Therefore, you need to always check that data exists before migrating it
*/

/**
* Note: Be careful about the kinds of migrations you do here.
* You are essentially swapping the app state under the hood
* Therefore mobx may not notice the change as expected
*/

const migrationMap: { [ver: string]: (() => Promise<void>) } = {
'=0.0.0': async () => await testMigration(localStorageApi),
'<1.4.0': bip44Migration
};

for (const key of Object.keys(migrationMap)) {
if (semver.satisfies(lastLaunchVersion, key)) {
Logger.info(`Migration started for ${key}`);
await migrationMap[key]();
}
}
}

/**
* We use this as a dummy migration so that our tests can verify migration is working correctly
*/
async function testMigration(localStorageApi: LocalStorageApi): Promise<void> {
// changing the locale is something we can easily detect from our tests
Logger.info(`Starting testMigration`);
// Note: mobx will not notice this change until you refresh
await localStorageApi.setUserLocale('ja-JP');
}

/**
* Previous version of Yoroi were not BIP44 compliant
* Notably, it didnt scan 20 addresses ahead of the last used address.
* This causes desyncs when you use Yoroi either on multiple computers with the same wallet
* or you use the same wallet on Chrome + mobile.
*/
async function bip44Migration(): Promise<void> {
Logger.info(`Starting bip44Migration`);
const addresses = await getAddressesList();
if (!addresses || addresses.length === 0) {
return;
}
/**
* We used to consider all addresses in the DB as explicitly generated by the user
* However, BIP44 requires us to also store 20 addresses after the last used address
* Therefore the highest index in the old format is the heightest generated for new format
*/
const maxIndex = Math.max(
...addresses
.filter(address => address.change === 0)
.map(address => address.index),
0
);

// if we had more than one address, then the WALLET key must exist in localstorage
saveLastReceiveAddressIndex(maxIndex);

/**
* Once we've saved the receive address, we dump the DB entirely
* We need to do this since old wallets may have incorrect history
* Due to desync issue caused by the incorrect bip44 implementation
*/
reset();
}
Loading