mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-19 05:45:15 +01:00
ADD: show fingerprint and derivation path for HD wallets
This commit is contained in:
parent
49e2b6d211
commit
9c9af0a3b0
@ -1112,4 +1112,23 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
|
||||
return { tx };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mnemonic {string} Mnemonic seed phrase
|
||||
* @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes
|
||||
*/
|
||||
static seedToFingerprint(mnemonic) {
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bitcoin.bip32.fromSeed(seed);
|
||||
let hex = root.fingerprint.toString('hex');
|
||||
while (hex.length < 8) hex = '0' + hex; // leading zeroes
|
||||
return hex.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} Hex string of fingerprint derived from wallet mnemonics. Always has lenght of 8 chars and correct leading zeroes
|
||||
*/
|
||||
getMasterFingerprintHex() {
|
||||
return AbstractHDElectrumWallet.seedToFingerprint(this.secret);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export class AbstractWallet {
|
||||
this.type = this.constructor.type;
|
||||
this.typeReadable = this.constructor.typeReadable;
|
||||
this.segwitType = this.constructor.segwitType;
|
||||
this._derivationPath = this.constructor.derivationPath;
|
||||
this.label = '';
|
||||
this.secret = ''; // private key or recovery phrase
|
||||
this.balance = 0;
|
||||
@ -128,6 +129,10 @@ export class AbstractWallet {
|
||||
return false;
|
||||
}
|
||||
|
||||
allowMasterFingerprint() {
|
||||
return false;
|
||||
}
|
||||
|
||||
weOwnAddress(address) {
|
||||
throw Error('not implemented');
|
||||
}
|
||||
@ -329,4 +334,11 @@ export class AbstractWallet {
|
||||
if ('frozen' in opts) meta.frozen = opts.frozen;
|
||||
this._utxoMetadata[`${txid}:${vout}`] = meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string|null} Root derivation path for wallet if any
|
||||
*/
|
||||
getDerivationPath() {
|
||||
return this._derivationPath || null;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDAezeedWallet';
|
||||
static typeReadable = 'HD Aezeed';
|
||||
static segwitType = 'p2wpkh';
|
||||
static derivationPath = "m/84'/0'/0'";
|
||||
|
||||
setSecret(newSecret) {
|
||||
this.secret = newSecret.trim();
|
||||
|
@ -12,6 +12,7 @@ const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet {
|
||||
static type = 'HDLegacyBreadwallet';
|
||||
static typeReadable = 'HD Legacy Breadwallet (P2PKH)';
|
||||
static derivationPath = "m/0'";
|
||||
|
||||
// track address index at which wallet switched to segwit
|
||||
_external_segwit_index = null; // eslint-disable-line camelcase
|
||||
|
@ -18,6 +18,7 @@ const MNEMONIC_TO_SEED_OPTS = {
|
||||
export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
|
||||
static type = 'HDlegacyElectrumSeedP2PKH';
|
||||
static typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)';
|
||||
static derivationPath = 'm';
|
||||
|
||||
validateMnemonic() {
|
||||
return mn.validateMnemonic(this.secret, PREFIX);
|
||||
|
@ -12,6 +12,7 @@ const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDlegacyP2PKH';
|
||||
static typeReadable = 'HD Legacy (BIP44 P2PKH)';
|
||||
static derivationPath = "m/44'/0'/0'";
|
||||
|
||||
allowSend() {
|
||||
return true;
|
||||
@ -29,6 +30,10 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowMasterFingerprint() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getXpub() {
|
||||
if (this._xpub) {
|
||||
return this._xpub; // cache hit
|
||||
|
@ -9,6 +9,7 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDsegwitBech32';
|
||||
static typeReadable = 'HD SegWit (BIP84 Bech32 Native)';
|
||||
static segwitType = 'p2wpkh';
|
||||
static derivationPath = "m/84'/0'/0'";
|
||||
|
||||
allowSend() {
|
||||
return true;
|
||||
@ -41,4 +42,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
|
||||
allowSignVerifyMessage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowMasterFingerprint() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ const MNEMONIC_TO_SEED_OPTS = {
|
||||
export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
|
||||
static type = 'HDSegwitElectrumSeedP2WPKHWallet';
|
||||
static typeReadable = 'HD Electrum (BIP32 P2WPKH)';
|
||||
static derivationPath = "m/0'";
|
||||
|
||||
validateMnemonic() {
|
||||
return mn.validateMnemonic(this.secret, PREFIX);
|
||||
|
@ -13,6 +13,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDsegwitP2SH';
|
||||
static typeReadable = 'HD SegWit (BIP49 P2SH)';
|
||||
static segwitType = 'p2sh(p2wpkh)';
|
||||
static derivationPath = "m/49'/0'/0'";
|
||||
|
||||
allowSend() {
|
||||
return true;
|
||||
@ -30,6 +31,14 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowHodlHodlTrading() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowMasterFingerprint() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get internal/external WIF by wallet index
|
||||
* @param {Boolean} internal
|
||||
@ -142,8 +151,4 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
||||
});
|
||||
return address;
|
||||
}
|
||||
|
||||
allowHodlHodlTrading() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -105,10 +105,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
}
|
||||
}
|
||||
|
||||
getDerivationPath() {
|
||||
return this._derivationPath;
|
||||
}
|
||||
|
||||
getCustomDerivationPathForCosigner(index) {
|
||||
if (index === 0) throw new Error('cosigners indexation starts from 1');
|
||||
if (index > this.getN()) return false;
|
||||
@ -189,7 +185,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
} else {
|
||||
// mnemonics. lets derive fingerprint (if it wasnt provided)
|
||||
if (!bip39.validateMnemonic(key)) throw new Error('Not a valid mnemonic phrase');
|
||||
fingerprint = fingerprint || MultisigHDWallet.seedToFingerprint(key);
|
||||
fingerprint = fingerprint || AbstractHDElectrumWallet.seedToFingerprint(key);
|
||||
}
|
||||
|
||||
if (fingerprint && this._cosignersFingerprints.indexOf(fingerprint.toUpperCase()) !== -1 && fingerprint !== '00000000') {
|
||||
@ -310,18 +306,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
return child.toBase58();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mnemonic {string} Mnemonic seed phrase
|
||||
* @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes
|
||||
*/
|
||||
static seedToFingerprint(mnemonic) {
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bitcoin.bip32.fromSeed(seed);
|
||||
let hex = root.fingerprint.toString('hex');
|
||||
while (hex.length < 8) hex = '0' + hex; // leading zeroes
|
||||
return hex.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns xpub with correct prefix accodting to this objects set derivation path, for example 'Zpub' (with
|
||||
* capital Z) for bech32 multisig
|
||||
@ -448,7 +432,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
const xpub = this.convertXpubToMultisignatureXpub(
|
||||
MultisigHDWallet.seedToXpub(this._cosigners[index], this._cosignersCustomPaths[index] || this._derivationPath),
|
||||
);
|
||||
const fingerprint = MultisigHDWallet.seedToFingerprint(this._cosigners[index]);
|
||||
const fingerprint = AbstractHDElectrumWallet.seedToFingerprint(this._cosigners[index]);
|
||||
ret += fingerprint + ': ' + xpub + '\n';
|
||||
} else {
|
||||
ret += 'seed: ' + this._cosigners[index] + '\n';
|
||||
@ -1053,7 +1037,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
if (index === -1) return;
|
||||
if (!MultisigHDWallet.isXpubValid(newCosigner)) {
|
||||
// its not an xpub, so lets derive fingerprint ourselves
|
||||
newFp = MultisigHDWallet.seedToFingerprint(newCosigner);
|
||||
newFp = AbstractHDElectrumWallet.seedToFingerprint(newCosigner);
|
||||
if (oldFp !== newFp) {
|
||||
throw new Error('Fingerprint of new seed doesnt match');
|
||||
}
|
||||
|
@ -221,6 +221,10 @@ export class WatchOnlyWallet extends LegacyWallet {
|
||||
return this.isHd();
|
||||
}
|
||||
|
||||
allowMasterFingerprint() {
|
||||
return this.getSecret().startsWith('zpub');
|
||||
}
|
||||
|
||||
useWithHardwareWalletEnabled() {
|
||||
return !!this.use_with_hardware_wallet;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
StatusBar,
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueText, BlueLoading } from '../../BlueComponents';
|
||||
import { BlueCard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText, SafeBlueArea, SecondButton } from '../../BlueComponents';
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
|
||||
import { HDLegacyBreadwalletWallet } from '../../class/wallets/hd-legacy-breadwallet-wallet';
|
||||
@ -457,7 +457,7 @@ const WalletDetails = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && !!wallet.getDerivationPath() && (
|
||||
{wallet.getDerivationPath() && (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_derivation_path}</Text>
|
||||
<BlueText>{wallet.getDerivationPath()}</BlueText>
|
||||
@ -492,29 +492,32 @@ const WalletDetails = () => {
|
||||
</Text>
|
||||
<BlueText>{wallet.getTransactions().length}</BlueText>
|
||||
</>
|
||||
|
||||
<View>
|
||||
{wallet.type === WatchOnlyWallet.type && wallet.getSecret().startsWith('zpub') && (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_advanced.toLowerCase()}</Text>
|
||||
<View style={styles.hardware}>
|
||||
<BlueText>{loc.wallets.details_use_with_hardware_wallet}</BlueText>
|
||||
<Switch value={useWithHardwareWallet} onValueChange={setUseWithHardwareWallet} />
|
||||
</View>
|
||||
<>
|
||||
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_master_fingerprint.toLowerCase()}</Text>
|
||||
<Text style={[styles.textValue, stylesHook.textValue]}>{wallet.getMasterFingerprintHex()}</Text>
|
||||
</>
|
||||
<BlueSpacing20 />
|
||||
</>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
|
||||
{wallet.allowMasterFingerprint() && (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_master_fingerprint.toLowerCase()}</Text>
|
||||
<BlueText>{wallet.getMasterFingerprintHex()}</BlueText>
|
||||
</>
|
||||
)}
|
||||
|
||||
<BlueSpacing20 />
|
||||
<SecondButton onPress={navigateToWalletExport} testID="WalletExport" title={loc.wallets.details_export_backup} />
|
||||
|
||||
<BlueSpacing20 />
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
onPress={navigateToMultisigCoordinationSetup}
|
||||
testID="MultisigCoordinationSetup"
|
||||
@ -536,8 +539,8 @@ const WalletDetails = () => {
|
||||
wallet.type === HDAezeedWallet.type ||
|
||||
wallet.type === HDSegwitP2SHWallet.type) && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton onPress={navigateToXPub} testID="XPub" title={loc.wallets.details_show_xpub} />
|
||||
|
||||
<BlueSpacing20 />
|
||||
{renderMarketplaceButton()}
|
||||
</>
|
||||
|
@ -149,4 +149,11 @@ describe('Legacy HD (BIP44)', () => {
|
||||
assert.strictEqual(signature, 'H98hmvtyPFUbR6E5Tcsqmc+eSjlYhP2vy41Y6IyHS9DVKEI5n8VEMpIEDtvlMARVce96nOqbRHXo9nD05WXH/Eo=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getInternalAddressByIndex(0), signature), true);
|
||||
});
|
||||
|
||||
it('can show fingerprint', async () => {
|
||||
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const hd = new HDLegacyP2PKHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
assert.strictEqual(hd.getMasterFingerprintHex(), '73C5DA0A');
|
||||
});
|
||||
});
|
||||
|
@ -43,6 +43,8 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
||||
assert.strictEqual(hd._getDerivationPathByAddress(hd._getExternalAddressByIndex(1)), "m/84'/0'/0'/0/1");
|
||||
assert.strictEqual(hd._getDerivationPathByAddress(hd._getInternalAddressByIndex(0)), "m/84'/0'/0'/1/0");
|
||||
assert.strictEqual(hd._getDerivationPathByAddress(hd._getInternalAddressByIndex(1)), "m/84'/0'/0'/1/1");
|
||||
|
||||
assert.strictEqual(hd.getMasterFingerprintHex(), '73C5DA0A');
|
||||
});
|
||||
|
||||
it('can generate addresses only via zpub', function () {
|
||||
|
@ -182,4 +182,11 @@ describe('P2SH Segwit HD (BIP49)', () => {
|
||||
assert.strictEqual(signature, 'I5WkniWTnJhTW74t3kTAkHq3HdiupTNgOZLpMp0hvUfAJw2HMuyRiNLl2pbNWobNCCrmvffSWM7IgkOBz/J9fYA=');
|
||||
assert.strictEqual(hd.verifyMessage('vires is numeris', hd._getInternalAddressByIndex(0), signature), true);
|
||||
});
|
||||
|
||||
it('can show fingerprint', async () => {
|
||||
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const hd = new HDSegwitP2SHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
assert.strictEqual(hd.getMasterFingerprintHex(), '73C5DA0A');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user