mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-26 08:55:56 +01:00
ADD: Multisig seed with passphrase
This commit is contained in:
parent
eb1fc16eb9
commit
966c1a7505
5 changed files with 345 additions and 95 deletions
|
@ -1135,8 +1135,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
* @param mnemonic {string} Mnemonic phrase (12 or 24 words)
|
||||
* @returns {string} Hex fingerprint
|
||||
*/
|
||||
static mnemonicToFingerprint(mnemonic) {
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
static mnemonicToFingerprint(mnemonic, passphrase) {
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
||||
return AbstractHDElectrumWallet.seedToFingerprint(seed);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,15 +12,17 @@ const createHash = require('create-hash');
|
|||
const reverse = require('buffer-reverse');
|
||||
const mn = require('electrum-mnemonic');
|
||||
|
||||
const MNEMONIC_TO_SEED_OPTS_SEGWIT = {
|
||||
const electrumSegwit = passphrase => ({
|
||||
prefix: mn.PREFIXES.segwit,
|
||||
};
|
||||
...(passphrase ? { passphrase } : {}),
|
||||
});
|
||||
|
||||
const MNEMONIC_TO_SEED_OPTS_STANDARD = {
|
||||
const electrumStandart = passphrase => ({
|
||||
prefix: mn.PREFIXES.standard,
|
||||
};
|
||||
...(passphrase ? { passphrase } : {}),
|
||||
});
|
||||
|
||||
const ELECTRUM_SEED_PREFIX = 'electrumseed:';
|
||||
export const ELECTRUM_SEED_PREFIX = 'electrumseed:';
|
||||
|
||||
export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDmultisig';
|
||||
|
@ -41,6 +43,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
this._cosigners = []; // array of xpubs or mnemonic seeds
|
||||
this._cosignersFingerprints = []; // array of according fingerprints (if any provided)
|
||||
this._cosignersCustomPaths = []; // array of according paths (if any provided)
|
||||
this._cosignersPassphrases = []; // array of according passphrases (if any provided)
|
||||
this._derivationPath = '';
|
||||
this._isNativeSegwit = false;
|
||||
this._isWrappedSegwit = false;
|
||||
|
@ -157,7 +160,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
* @param fingerprint {string} Fingerprint for cosigner that is added as xpub
|
||||
* @param path {string} Custom path (if any) for cosigner that is added as mnemonics
|
||||
*/
|
||||
addCosigner(key, fingerprint, path) {
|
||||
addCosigner(key, fingerprint, path, passphrase) {
|
||||
if (MultisigHDWallet.isXpubString(key) && !fingerprint) {
|
||||
throw new Error('fingerprint is required when adding cosigner as xpub (watch-only)');
|
||||
}
|
||||
|
@ -175,11 +178,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
// its an electrum seed
|
||||
const mnemonic = key.replace(ELECTRUM_SEED_PREFIX, '');
|
||||
try {
|
||||
mn.mnemonicToSeedSync(mnemonic, MNEMONIC_TO_SEED_OPTS_STANDARD);
|
||||
mn.mnemonicToSeedSync(mnemonic, electrumStandart(passphrase));
|
||||
this.setLegacy();
|
||||
} catch (_) {
|
||||
try {
|
||||
mn.mnemonicToSeedSync(mnemonic, MNEMONIC_TO_SEED_OPTS_SEGWIT);
|
||||
mn.mnemonicToSeedSync(mnemonic, electrumSegwit(passphrase));
|
||||
this.setNativeSegwit();
|
||||
} catch (__) {
|
||||
throw new Error('Not a valid electrum seed');
|
||||
|
@ -188,7 +191,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.mnemonicToFingerprint(key);
|
||||
fingerprint = fingerprint || MultisigHDWallet.mnemonicToFingerprint(key, passphrase);
|
||||
}
|
||||
|
||||
if (fingerprint && this._cosignersFingerprints.indexOf(fingerprint.toUpperCase()) !== -1 && fingerprint !== '00000000') {
|
||||
|
@ -200,6 +203,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
this._cosigners[index] = key;
|
||||
if (fingerprint) this._cosignersFingerprints[index] = fingerprint.toUpperCase();
|
||||
if (path) this._cosignersCustomPaths[index] = path;
|
||||
if (passphrase) this._cosignersPassphrases[index] = passphrase;
|
||||
}
|
||||
|
||||
static convertMultisigXprvToRegularXprv(Zprv) {
|
||||
|
@ -225,7 +229,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
let xpub = cosigner;
|
||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
||||
const index = this._cosigners.indexOf(cosigner);
|
||||
xpub = MultisigHDWallet.seedToXpub(cosigner, this._cosignersCustomPaths[index] || this._derivationPath);
|
||||
xpub = MultisigHDWallet.seedToXpub(
|
||||
cosigner,
|
||||
this._cosignersCustomPaths[index] || this._derivationPath,
|
||||
this._cosignersPassphrases[index],
|
||||
);
|
||||
}
|
||||
return this.constructor._zpubToXpub(xpub);
|
||||
}
|
||||
|
@ -242,8 +250,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
|
||||
_getAddressFromNode(nodeIndex, index) {
|
||||
const pubkeys = [];
|
||||
let cosignerIndex = 0;
|
||||
for (const cosigner of this._cosigners) {
|
||||
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||
this._nodes = this._nodes || [];
|
||||
this._nodes[nodeIndex] = this._nodes[nodeIndex] || [];
|
||||
let _node;
|
||||
|
@ -258,7 +265,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
}
|
||||
|
||||
pubkeys.push(_node.derive(index).publicKey);
|
||||
cosignerIndex++;
|
||||
}
|
||||
|
||||
if (this.isWrappedSegwit()) {
|
||||
|
@ -296,12 +302,12 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
return address;
|
||||
}
|
||||
|
||||
static seedToXpub(mnemonic, path) {
|
||||
static seedToXpub(mnemonic, path, passphrase) {
|
||||
let seed;
|
||||
if (mnemonic.startsWith(ELECTRUM_SEED_PREFIX)) {
|
||||
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic);
|
||||
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic, passphrase);
|
||||
} else {
|
||||
seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
||||
}
|
||||
|
||||
const root = HDNode.fromSeed(seed);
|
||||
|
@ -433,7 +439,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
} else {
|
||||
if (coordinationSetup) {
|
||||
const xpub = this.convertXpubToMultisignatureXpub(
|
||||
MultisigHDWallet.seedToXpub(this._cosigners[index], this._cosignersCustomPaths[index] || this._derivationPath),
|
||||
MultisigHDWallet.seedToXpub(
|
||||
this._cosigners[index],
|
||||
this._cosignersCustomPaths[index] || this._derivationPath,
|
||||
this._cosignersPassphrases[index],
|
||||
),
|
||||
);
|
||||
const fingerprint = MultisigHDWallet.mnemonicToFingerprint(this._cosigners[index]);
|
||||
ret += fingerprint + ': ' + xpub + '\n';
|
||||
|
@ -479,7 +489,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
? MultisigHDWallet.ckccXfp2fingerprint(cosignerData.ckcc_xfp)
|
||||
: cosignerData.root_fingerprint?.toUpperCase()) || '00000000';
|
||||
if (cosignerData.seed) {
|
||||
this.addCosigner(ELECTRUM_SEED_PREFIX + cosignerData.seed, fingerprint, cosignerData.derivation);
|
||||
this.addCosigner(ELECTRUM_SEED_PREFIX + cosignerData.seed, fingerprint, cosignerData.derivation, cosignerData.passphrase);
|
||||
} else if (cosignerData.xprv && MultisigHDWallet.isXprvValid(cosignerData.xprv)) {
|
||||
this.addCosigner(cosignerData.xprv, fingerprint, cosignerData.derivation);
|
||||
} else {
|
||||
|
@ -639,11 +649,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
_addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) {
|
||||
const bip32Derivation = []; // array per each pubkey thats gona be used
|
||||
const pubkeys = [];
|
||||
for (let c = 0; c < this._cosigners.length; c++) {
|
||||
const cosigner = this._cosigners[c];
|
||||
const path = this._getDerivationPathByAddressWithCustomPath(input.address, this._cosignersCustomPaths[c] || this._derivationPath);
|
||||
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||
const path = this._getDerivationPathByAddressWithCustomPath(
|
||||
input.address,
|
||||
this._cosignersCustomPaths[cosignerIndex] || this._derivationPath,
|
||||
);
|
||||
// ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used
|
||||
const masterFingerprint = Buffer.from(this._cosignersFingerprints[c], 'hex');
|
||||
const masterFingerprint = Buffer.from(this._cosignersFingerprints[cosignerIndex], 'hex');
|
||||
|
||||
const xpub = this._getXpubFromCosigner(cosigner);
|
||||
const hdNode0 = HDNode.fromBase58(xpub);
|
||||
|
@ -730,14 +742,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
_getOutputDataForChange(outputData) {
|
||||
const bip32Derivation = []; // array per each pubkey thats gona be used
|
||||
const pubkeys = [];
|
||||
for (let c = 0; c < this._cosigners.length; c++) {
|
||||
const cosigner = this._cosigners[c];
|
||||
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||
const path = this._getDerivationPathByAddressWithCustomPath(
|
||||
outputData.address,
|
||||
this._cosignersCustomPaths[c] || this._derivationPath,
|
||||
this._cosignersCustomPaths[cosignerIndex] || this._derivationPath,
|
||||
);
|
||||
// ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used
|
||||
const masterFingerprint = Buffer.from(this._cosignersFingerprints[c], 'hex');
|
||||
const masterFingerprint = Buffer.from(this._cosignersFingerprints[cosignerIndex], 'hex');
|
||||
|
||||
const xpub = this._getXpubFromCosigner(cosigner);
|
||||
const hdNode0 = HDNode.fromBase58(xpub);
|
||||
|
@ -843,22 +854,22 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
if (!skipSigning) {
|
||||
for (let cc = 0; cc < c; cc++) {
|
||||
let signaturesMade = 0;
|
||||
for (const cosigner of this._cosigners) {
|
||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
||||
// ok this is a mnemonic, lets try to sign
|
||||
if (signaturesMade >= this.getM()) {
|
||||
// dont sign more than we need, otherwise there will be "Too many signatures" error
|
||||
continue;
|
||||
}
|
||||
let seed = bip39.mnemonicToSeedSync(cosigner);
|
||||
if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) {
|
||||
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner);
|
||||
}
|
||||
|
||||
const hdRoot = HDNode.fromSeed(seed);
|
||||
psbt.signInputHD(cc, hdRoot);
|
||||
signaturesMade++;
|
||||
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||
if (MultisigHDWallet.isXpubString(cosigner)) continue;
|
||||
// ok this is a mnemonic, lets try to sign
|
||||
if (signaturesMade >= this.getM()) {
|
||||
// dont sign more than we need, otherwise there will be "Too many signatures" error
|
||||
continue;
|
||||
}
|
||||
const passphrase = this._cosignersPassphrases[cosignerIndex];
|
||||
let seed = bip39.mnemonicToSeedSync(cosigner, passphrase);
|
||||
if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) {
|
||||
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase);
|
||||
}
|
||||
|
||||
const hdRoot = HDNode.fromSeed(seed);
|
||||
psbt.signInputHD(cc, hdRoot);
|
||||
signaturesMade++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -870,13 +881,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
return { tx, inputs, outputs, fee, psbt };
|
||||
}
|
||||
|
||||
static convertElectrumMnemonicToSeed(cosigner) {
|
||||
static convertElectrumMnemonicToSeed(cosigner, passphrase) {
|
||||
let seed;
|
||||
try {
|
||||
seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), MNEMONIC_TO_SEED_OPTS_SEGWIT);
|
||||
seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), electrumSegwit(passphrase));
|
||||
} catch (_) {
|
||||
try {
|
||||
seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), MNEMONIC_TO_SEED_OPTS_STANDARD);
|
||||
seed = mn.mnemonicToSeedSync(cosigner.replace(ELECTRUM_SEED_PREFIX, ''), electrumStandart(passphrase));
|
||||
} catch (__) {
|
||||
throw new Error('Not a valid electrum mnemonic');
|
||||
}
|
||||
|
@ -996,39 +1007,50 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
|||
cosignPsbt(psbt) {
|
||||
for (let cc = 0; cc < psbt.inputCount; cc++) {
|
||||
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
||||
// ok this is a mnemonic, lets try to sign
|
||||
const seed = bip39.mnemonicToSeedSync(cosigner);
|
||||
const hdRoot = HDNode.fromSeed(seed);
|
||||
try {
|
||||
psbt.signInputHD(cc, hdRoot);
|
||||
} catch (_) {} // protects agains duplicate cosignings
|
||||
if (MultisigHDWallet.isXpubString(cosigner)) continue;
|
||||
|
||||
if (!psbt.inputHasHDKey(cc, hdRoot)) {
|
||||
// failed signing as HD. probably bitcoinjs-lib could not match provided hdRoot's
|
||||
// fingerprint (or path?) to the ones in psbt, which is the case of stupid Electrum desktop which can
|
||||
// put bullshit paths and fingerprints in created psbt.
|
||||
// lets try to find correct priv key and sign manually.
|
||||
for (const derivation of psbt.data.inputs[cc].bip32Derivation || []) {
|
||||
// okay, here we assume that fingerprint is irrelevant, but ending of the path is somewhat correct and
|
||||
// correctly points to `/internal/index`, so we extract pubkey from our stored mnemonics+path and
|
||||
// match it to the one provided in PSBT's input, and if we have a match - we are in luck! we can sign
|
||||
// with this private key.
|
||||
const seed = bip39.mnemonicToSeedSync(cosigner);
|
||||
const root = HDNode.fromSeed(seed);
|
||||
const splt = derivation.path.split('/');
|
||||
const internal = +splt[splt.length - 2];
|
||||
const index = +splt[splt.length - 1];
|
||||
let hdRoot;
|
||||
if (MultisigHDWallet.isXprvString(cosigner)) {
|
||||
const xprv = MultisigHDWallet.convertMultisigXprvToRegularXprv(cosigner);
|
||||
hdRoot = HDNode.fromBase58(xprv);
|
||||
} else {
|
||||
const passphrase = this._cosignersPassphrases[cosignerIndex];
|
||||
const seed = cosigner.startsWith(ELECTRUM_SEED_PREFIX)
|
||||
? MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase)
|
||||
: bip39.mnemonicToSeedSync(cosigner, passphrase);
|
||||
hdRoot = HDNode.fromSeed(seed);
|
||||
}
|
||||
|
||||
const path = this.getCustomDerivationPathForCosigner(cosignerIndex + 1) + `/${internal ? 1 : 0}/${index}`;
|
||||
// ^^^ we assume that counterparty has Zpub for specified derivation path
|
||||
const child = root.derivePath(path);
|
||||
if (psbt.inputHasPubkey(cc, child.publicKey)) {
|
||||
const keyPair = ECPair.fromPrivateKey(child.privateKey);
|
||||
try {
|
||||
psbt.signInput(cc, keyPair);
|
||||
} catch (_) {}
|
||||
}
|
||||
try {
|
||||
psbt.signInputHD(cc, hdRoot);
|
||||
} catch (_) {} // protects agains duplicate cosignings
|
||||
|
||||
if (!psbt.inputHasHDKey(cc, hdRoot)) {
|
||||
// failed signing as HD. probably bitcoinjs-lib could not match provided hdRoot's
|
||||
// fingerprint (or path?) to the ones in psbt, which is the case of stupid Electrum desktop which can
|
||||
// put bullshit paths and fingerprints in created psbt.
|
||||
// lets try to find correct priv key and sign manually.
|
||||
for (const derivation of psbt.data.inputs[cc].bip32Derivation || []) {
|
||||
// okay, here we assume that fingerprint is irrelevant, but ending of the path is somewhat correct and
|
||||
// correctly points to `/internal/index`, so we extract pubkey from our stored mnemonics+path and
|
||||
// match it to the one provided in PSBT's input, and if we have a match - we are in luck! we can sign
|
||||
// with this private key.
|
||||
const splt = derivation.path.split('/');
|
||||
const internal = +splt[splt.length - 2];
|
||||
const index = +splt[splt.length - 1];
|
||||
|
||||
const path =
|
||||
hdRoot.depth === 0
|
||||
? this.getCustomDerivationPathForCosigner(cosignerIndex + 1) + `/${internal ? 1 : 0}/${index}`
|
||||
: `${internal ? 1 : 0}/${index}`;
|
||||
// ^^^ we assume that counterparty has Zpub for specified derivation path
|
||||
// if hdRoot.depth !== 0 than this hdnode was recovered from xprv and it already has been set to root path
|
||||
const child = hdRoot.derivePath(path);
|
||||
if (psbt.inputHasPubkey(cc, child.publicKey)) {
|
||||
const keyPair = ECPair.fromPrivateKey(child.privateKey);
|
||||
try {
|
||||
psbt.signInput(cc, keyPair);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useRef, useState } from 'react';
|
||||
import React, { useContext, useRef, useState, useEffect } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
|
@ -8,6 +8,7 @@ import {
|
|||
LayoutAnimation,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Switch,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
|
@ -24,7 +25,7 @@ import {
|
|||
BlueFormMultiInput,
|
||||
BlueSpacing10,
|
||||
BlueSpacing20,
|
||||
BlueSpacing40,
|
||||
BlueText,
|
||||
BlueTextCentered,
|
||||
} from '../../BlueComponents';
|
||||
import navigationStyle from '../../components/navigationStyle';
|
||||
|
@ -48,7 +49,7 @@ const isDesktop = getSystemName() === 'Mac OS X';
|
|||
const staticCache = {};
|
||||
|
||||
const WalletsAddMultisigStep2 = () => {
|
||||
const { addWallet, saveToDisk, isElectrumDisabled } = useContext(BlueStorageContext);
|
||||
const { addWallet, saveToDisk, isElectrumDisabled, isAdancedModeEnabled, sleep } = useContext(BlueStorageContext);
|
||||
const { colors } = useTheme();
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
@ -64,9 +65,16 @@ const WalletsAddMultisigStep2 = () => {
|
|||
const [cosignerXpubFilename, setCosignerXpubFilename] = useState('bw-cosigner.json');
|
||||
const [vaultKeyData, setVaultKeyData] = useState({ keyIndex: 1, xpub: '', seed: '', isLoading: false }); // string rendered in modal
|
||||
const [importText, setImportText] = useState('');
|
||||
const [askPassphrase, setAskPassphrase] = useState(false);
|
||||
const [isAdvancedModeEnabledRender, setIsAdvancedModeEnabledRender] = useState(false);
|
||||
const openScannerButton = useRef();
|
||||
const data = useRef(new Array(n));
|
||||
|
||||
useEffect(() => {
|
||||
isAdancedModeEnabled().then(setIsAdvancedModeEnabledRender);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleOnHelpPress = () => {
|
||||
navigation.navigate('WalletsAddMultisigHelp');
|
||||
};
|
||||
|
@ -137,9 +145,16 @@ const WalletsAddMultisigStep2 = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const onCreate = () => {
|
||||
const onCreate = async () => {
|
||||
setIsLoading(true);
|
||||
setTimeout(_onCreate, 100);
|
||||
await sleep(100);
|
||||
try {
|
||||
await _onCreate(); // this can fail with "Duplicate fingerprint" error or other
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
alert(e.message);
|
||||
console.log('create MS wallet error', e);
|
||||
}
|
||||
};
|
||||
|
||||
const _onCreate = async () => {
|
||||
|
@ -163,8 +178,8 @@ const WalletsAddMultisigStep2 = () => {
|
|||
throw new Error('This should never happen');
|
||||
}
|
||||
for (const cc of cosigners) {
|
||||
const fp = cc[1] || getFpCacheForMnemonics(cc[0]);
|
||||
w.addCosigner(cc[0], fp, cc[2]);
|
||||
const fp = cc[1] || getFpCacheForMnemonics(cc[0], cc[3]);
|
||||
w.addCosigner(cc[0], fp, cc[2], cc[3]);
|
||||
}
|
||||
w.setLabel(walletLabel);
|
||||
if (!isElectrumDisabled) {
|
||||
|
@ -228,7 +243,7 @@ const WalletsAddMultisigStep2 = () => {
|
|||
const path = getPath();
|
||||
|
||||
const xpub = getXpubCacheForMnemonics(cosigner[0]);
|
||||
const fp = getFpCacheForMnemonics(cosigner[0]);
|
||||
const fp = getFpCacheForMnemonics(cosigner[0], cosigner[3]);
|
||||
setCosignerXpub(MultisigCosigner.exportToJson(fp, xpub, path));
|
||||
setCosignerXpubURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
|
||||
setCosignerXpubFilename('bw-cosigner-' + fp + '.json');
|
||||
|
@ -249,13 +264,13 @@ const WalletsAddMultisigStep2 = () => {
|
|||
return staticCache[seed + path];
|
||||
};
|
||||
|
||||
const getFpCacheForMnemonics = seed => {
|
||||
return staticCache[seed] || setFpCacheForMnemonics(seed);
|
||||
const getFpCacheForMnemonics = (seed, passphrase) => {
|
||||
return staticCache[seed + (passphrase ?? '')] || setFpCacheForMnemonics(seed, passphrase);
|
||||
};
|
||||
|
||||
const setFpCacheForMnemonics = seed => {
|
||||
staticCache[seed] = MultisigHDWallet.mnemonicToFingerprint(seed);
|
||||
return staticCache[seed];
|
||||
const setFpCacheForMnemonics = (seed, passphrase) => {
|
||||
staticCache[seed + (passphrase ?? '')] = MultisigHDWallet.mnemonicToFingerprint(seed, passphrase);
|
||||
return staticCache[seed + (passphrase ?? '')];
|
||||
};
|
||||
|
||||
const iHaveMnemonics = () => {
|
||||
|
@ -267,6 +282,7 @@ const WalletsAddMultisigStep2 = () => {
|
|||
setIsProvideMnemonicsModalVisible(false);
|
||||
setIsLoading(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
alert(loc.multisig.not_a_multisignature_xpub);
|
||||
return;
|
||||
}
|
||||
|
@ -285,6 +301,7 @@ const WalletsAddMultisigStep2 = () => {
|
|||
setIsProvideMnemonicsModalVisible(false);
|
||||
setIsLoading(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
|
||||
const cosignersCopy = [...cosigners];
|
||||
cosignersCopy.push([xpub, fp, path]);
|
||||
|
@ -292,7 +309,7 @@ const WalletsAddMultisigStep2 = () => {
|
|||
setCosigners(cosignersCopy);
|
||||
};
|
||||
|
||||
const useMnemonicPhrase = () => {
|
||||
const useMnemonicPhrase = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
if (MultisigHDWallet.isXpubValid(importText)) {
|
||||
|
@ -305,14 +322,28 @@ const WalletsAddMultisigStep2 = () => {
|
|||
return alert(loc.multisig.invalid_mnemonics);
|
||||
}
|
||||
|
||||
let passphrase;
|
||||
if (askPassphrase) {
|
||||
try {
|
||||
passphrase = await prompt(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message);
|
||||
} catch (e) {
|
||||
if (e.message === 'Cancel Pressed') {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const cosignersCopy = [...cosigners];
|
||||
cosignersCopy.push([hd.getSecret(), false, false]);
|
||||
cosignersCopy.push([hd.getSecret(), false, false, passphrase]);
|
||||
if (Platform.OS !== 'android') LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setCosigners(cosignersCopy);
|
||||
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
setIsLoading(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
};
|
||||
|
||||
const isValidMnemonicSeed = mnemonicSeed => {
|
||||
|
@ -416,7 +447,7 @@ const WalletsAddMultisigStep2 = () => {
|
|||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
onBarScanned: onBarScanned,
|
||||
onBarScanned,
|
||||
showFileImportButton: true,
|
||||
},
|
||||
}),
|
||||
|
@ -553,6 +584,7 @@ const WalletsAddMultisigStep2 = () => {
|
|||
Keyboard.dismiss();
|
||||
setIsProvideMnemonicsModalVisible(false);
|
||||
setImportText('');
|
||||
setAskPassphrase(false);
|
||||
};
|
||||
|
||||
const renderProvideMnemonicsModal = () => {
|
||||
|
@ -563,7 +595,16 @@ const WalletsAddMultisigStep2 = () => {
|
|||
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
||||
<BlueSpacing20 />
|
||||
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
|
||||
<BlueSpacing40 />
|
||||
{isAdvancedModeEnabledRender && (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<View style={styles.row}>
|
||||
<BlueText>{loc.wallets.import_passphrase}</BlueText>
|
||||
<Switch testID="AskPassphrase" value={askPassphrase} onValueChange={setAskPassphrase} />
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
|
@ -771,6 +812,12 @@ const styles = StyleSheet.create({
|
|||
fontWeight: 'bold',
|
||||
marginLeft: 8,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginHorizontal: 16,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
});
|
||||
|
||||
WalletsAddMultisigStep2.navigationOptions = navigationStyle({
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"addr_history": {
|
||||
"bc1q24rc4v9r6fjtkrwfp4j57ufef56ez46rrpyjtdkhjpr687f5de0sa7ryv5": [],
|
||||
"bc1q2ajjhxhddfywm7l3uac8flehy8tjkzkd404w8k7w9zgf3fpjtr4qh4g0q3": [],
|
||||
"bc1q2ltyvkrs0uay39acfk4y0gmw7flghd3403p94x26tc8579t9adwsjp83yz": [],
|
||||
"bc1q6hqwvrfrcw3eme3gv37hk5yqe8zykmedp6jaq3g0svlgm8gajeksfjfr7t": [],
|
||||
"bc1q7n8twph2zlfw6w0p5ms9vkvj9klxqhpjy5mv5tnqpcf2pl3d3qrst2pjz7": [],
|
||||
"bc1qdd4jqq0cex38k9l3rnqrp5ltt5a4uyf2hu5ejdcs4unmuy8rxeaqt74lac": [],
|
||||
"bc1qdk2g0hxq76d806r8emp8jxpw4wtnpdcf3v60cx3tawfk6epdgghqgvl7qx": [],
|
||||
"bc1qdm743j76hm3wdzvw4xa3dx4l77vvhwg5hmwr0n94wlr6yjshny7qjecgfy": [],
|
||||
"bc1qdng92jr5er6fmxjjqd4ffqsjjhxs7s9kq2e9mwxcyf6dtt5m5tsszmwk0y": [],
|
||||
"bc1qhwrqzyqs8w5zkefkh38d4qf8xygnq4t6lrdzpncjzpghfj37wcmqvzr34t": [],
|
||||
"bc1qje83yzfand5zqt2gjawcxcygdkzrx56686p2r2650xsqrutpseyqcl2j98": [],
|
||||
"bc1qk6qe9tnlt9wz0xkllw64jdl5tqd8ha6rct8fed5khhf485p4tr2q04kfq2": [],
|
||||
"bc1qlgt26dj4g5wh20kkryvx44mlw58myu8q05wz95l99n965ccw2pxq4kpp24": [],
|
||||
"bc1qmnjpytz3kgqkpl8n6t28k88semprxw4v85lhzcun3xvf5jxjgjtslyywzn": [],
|
||||
"bc1qmpyrvv6fmkv494r9qk9nllyuyngtqj62fywcl2xzessgwf9qgrssxff69u": [],
|
||||
"bc1qnm86j32s0nlnrs7mk4c84z8rru52954cv26jlhlulkrp927xfqsqp2n6sr": [],
|
||||
"bc1qnn0n9zzfnnrtvrp0yug4gpg42vpqty0utftvs77s46un9kegzn0syksa3c": [],
|
||||
"bc1qnpmtxpfnj9grfldlc3y5gyxghzyjgw5682sdn46uum63vdh56tssw6mzdt": [],
|
||||
"bc1qq75kuw877vzywwvlstgjvyy6ep40htg4jsreavnz6wmj0qzweufsuhdapm": [],
|
||||
"bc1qqgh3vparlgnfljkk6kfk2tpakz6rvzulczx5tuw63tnw4aw52crs44hcqw": [],
|
||||
"bc1qqjurkk6q78qzm7pnvn0vmmcm8lxwj8ux6yrhzzy2u5984uhl62dqf87csz": [],
|
||||
"bc1qrhrfw75nhg367gvkpw7mn5w0pj3k9tg3mam3j4x0sslp9p6ejzxsmauvds": [],
|
||||
"bc1qs5q9kzr4v79455urwep5sq37nxtq5kaltk522t6tu8epdkgvjjcsvx60dj": [],
|
||||
"bc1qsydvt0dttn0wd6t8ua6p4xje5yfku0f42nr8hlepasamsqffan9s6cvpps": [],
|
||||
"bc1qvfk305khj7j38zpm9x4yt5cr6dtqr6cepdjuwnvk2qqnc3d87eqqadegaj": [],
|
||||
"bc1qvjgk3rd9vhs9fel8602ae5jjp4zjnkdeh85jhfgfykr4xkpmnmjqwvp5pn": [],
|
||||
"bc1qwqrvajjn6uer8jg4rm2mfu0v6ewa2vg27ucwdn7az39rma2wqe5q0pv0j3": [],
|
||||
"bc1qwvrrrm9kcgc42y8saghlxhydgdx7aaedgv3r5x2uxc8ns2htr0gqumzhzw": [],
|
||||
"bc1qxgws6t6gxtmltzlwqjcqpy2d468arpkq6calp2d2ln7a9udflfjssqhy2x": [],
|
||||
"bc1qynttcstsknxguha7pcqxe0grfkh7xceqauc0gc0yqn4aathjzw8s4nktx4": []
|
||||
},
|
||||
"addresses": {
|
||||
"change": [
|
||||
"bc1q2ltyvkrs0uay39acfk4y0gmw7flghd3403p94x26tc8579t9adwsjp83yz",
|
||||
"bc1q24rc4v9r6fjtkrwfp4j57ufef56ez46rrpyjtdkhjpr687f5de0sa7ryv5",
|
||||
"bc1qqjurkk6q78qzm7pnvn0vmmcm8lxwj8ux6yrhzzy2u5984uhl62dqf87csz",
|
||||
"bc1q2ajjhxhddfywm7l3uac8flehy8tjkzkd404w8k7w9zgf3fpjtr4qh4g0q3",
|
||||
"bc1qwvrrrm9kcgc42y8saghlxhydgdx7aaedgv3r5x2uxc8ns2htr0gqumzhzw",
|
||||
"bc1qlgt26dj4g5wh20kkryvx44mlw58myu8q05wz95l99n965ccw2pxq4kpp24",
|
||||
"bc1qs5q9kzr4v79455urwep5sq37nxtq5kaltk522t6tu8epdkgvjjcsvx60dj",
|
||||
"bc1qvfk305khj7j38zpm9x4yt5cr6dtqr6cepdjuwnvk2qqnc3d87eqqadegaj",
|
||||
"bc1qq75kuw877vzywwvlstgjvyy6ep40htg4jsreavnz6wmj0qzweufsuhdapm",
|
||||
"bc1qrhrfw75nhg367gvkpw7mn5w0pj3k9tg3mam3j4x0sslp9p6ejzxsmauvds"
|
||||
],
|
||||
"receiving": [
|
||||
"bc1qmpyrvv6fmkv494r9qk9nllyuyngtqj62fywcl2xzessgwf9qgrssxff69u",
|
||||
"bc1q7n8twph2zlfw6w0p5ms9vkvj9klxqhpjy5mv5tnqpcf2pl3d3qrst2pjz7",
|
||||
"bc1qsydvt0dttn0wd6t8ua6p4xje5yfku0f42nr8hlepasamsqffan9s6cvpps",
|
||||
"bc1qvjgk3rd9vhs9fel8602ae5jjp4zjnkdeh85jhfgfykr4xkpmnmjqwvp5pn",
|
||||
"bc1qnpmtxpfnj9grfldlc3y5gyxghzyjgw5682sdn46uum63vdh56tssw6mzdt",
|
||||
"bc1qnm86j32s0nlnrs7mk4c84z8rru52954cv26jlhlulkrp927xfqsqp2n6sr",
|
||||
"bc1qmnjpytz3kgqkpl8n6t28k88semprxw4v85lhzcun3xvf5jxjgjtslyywzn",
|
||||
"bc1qdk2g0hxq76d806r8emp8jxpw4wtnpdcf3v60cx3tawfk6epdgghqgvl7qx",
|
||||
"bc1qje83yzfand5zqt2gjawcxcygdkzrx56686p2r2650xsqrutpseyqcl2j98",
|
||||
"bc1q6hqwvrfrcw3eme3gv37hk5yqe8zykmedp6jaq3g0svlgm8gajeksfjfr7t",
|
||||
"bc1qqgh3vparlgnfljkk6kfk2tpakz6rvzulczx5tuw63tnw4aw52crs44hcqw",
|
||||
"bc1qynttcstsknxguha7pcqxe0grfkh7xceqauc0gc0yqn4aathjzw8s4nktx4",
|
||||
"bc1qhwrqzyqs8w5zkefkh38d4qf8xygnq4t6lrdzpncjzpghfj37wcmqvzr34t",
|
||||
"bc1qdm743j76hm3wdzvw4xa3dx4l77vvhwg5hmwr0n94wlr6yjshny7qjecgfy",
|
||||
"bc1qnn0n9zzfnnrtvrp0yug4gpg42vpqty0utftvs77s46un9kegzn0syksa3c",
|
||||
"bc1qdng92jr5er6fmxjjqd4ffqsjjhxs7s9kq2e9mwxcyf6dtt5m5tsszmwk0y",
|
||||
"bc1qdd4jqq0cex38k9l3rnqrp5ltt5a4uyf2hu5ejdcs4unmuy8rxeaqt74lac",
|
||||
"bc1qwqrvajjn6uer8jg4rm2mfu0v6ewa2vg27ucwdn7az39rma2wqe5q0pv0j3",
|
||||
"bc1qxgws6t6gxtmltzlwqjcqpy2d468arpkq6calp2d2ln7a9udflfjssqhy2x",
|
||||
"bc1qk6qe9tnlt9wz0xkllw64jdl5tqd8ha6rct8fed5khhf485p4tr2q04kfq2"
|
||||
]
|
||||
},
|
||||
"fiat_value": {},
|
||||
"frozen_coins": {},
|
||||
"invoices": {},
|
||||
"labels": {},
|
||||
"payment_requests": {},
|
||||
"prevouts_by_scripthash": {},
|
||||
"seed_version": 40,
|
||||
"spent_outpoints": {},
|
||||
"transactions": {},
|
||||
"tx_fees": {},
|
||||
"txi": {},
|
||||
"txo": {},
|
||||
"use_encryption": false,
|
||||
"verified_tx3": {},
|
||||
"wallet_type": "2of2",
|
||||
"x1/": {
|
||||
"derivation": "m/1'",
|
||||
"passphrase": "BlueWallet",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "8de7b2c3",
|
||||
"seed": "diagram grape account sustain bright member ethics strategy burger senior capital enforce",
|
||||
"seed_type": "segwit",
|
||||
"type": "bip32",
|
||||
"xprv": "ZprvAkWFuUtVQZqsWhwomGuqCAWpcfpd5tNXuQoBMLqqqmTVqmiRiMKXbig3PH3BcVPh2Xmb7TH7w3tDfa1Z7ik3ZgFQB3G9rAxXvp6Cy1onbYc",
|
||||
"xpub": "Zpub6yVcJzRPEwQAjC2GsJSqZJTZAhf7VM6PGdin9jFTQ6zUia3aFtdn9WzXEZBk1AcgSgQ7kLcysk5CWTuJppjFKdYJDMYVT9hZF71PyPijNUF"
|
||||
},
|
||||
"x2/": {
|
||||
"derivation": "m/48'/0'/0'/2'",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "84431270",
|
||||
"type": "bip32",
|
||||
"xprv": "ZprvAqPkyb5ridHr1gGiqSuAWcsrZ6jqv31Vyuaj4fzVcgt8v6PXH9tSigE6F8iw8pL16HWnhzEsXvJ5ur9HKvkAW16oHZuFeEYA1CBdsGGDFFB",
|
||||
"xpub": "Zpub74P7P6ckYzr9EAMBwUSAskpb78aLKVjMM8WKs4Q7B2R7ntifphChGUYa6T5viWMe5Rjy6kMwRArcg9sQsMey3s9k4JLcKiCzYrDRQMdMo8L"
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import assert from 'assert';
|
||||
import { MultisigHDWallet } from '../../class/';
|
||||
import { MultisigHDWallet, ELECTRUM_SEED_PREFIX } from '../../class/';
|
||||
import { decodeUR, encodeUR } from '../../blue_modules/ur';
|
||||
import { MultisigCosigner } from '../../class/multisig-cosigner';
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
|
@ -1417,6 +1417,59 @@ describe('multisig-wallet (native segwit)', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('can import electrum json file format with seeds and passphrase', () => {
|
||||
const json = require('./fixtures/electrum-multisig-wallet-with-seed-and-passphrase.json');
|
||||
const w = new MultisigHDWallet();
|
||||
w.setSecret(JSON.stringify(json));
|
||||
|
||||
assert.strictEqual(w.getM(), 2);
|
||||
assert.strictEqual(w.getN(), 2);
|
||||
|
||||
assert.strictEqual(w._getExternalAddressByIndex(0), 'bc1qmpyrvv6fmkv494r9qk9nllyuyngtqj62fywcl2xzessgwf9qgrssxff69u');
|
||||
assert.strictEqual(w._getExternalAddressByIndex(1), 'bc1q7n8twph2zlfw6w0p5ms9vkvj9klxqhpjy5mv5tnqpcf2pl3d3qrst2pjz7');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), 'bc1q2ltyvkrs0uay39acfk4y0gmw7flghd3403p94x26tc8579t9adwsjp83yz');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(1), 'bc1q24rc4v9r6fjtkrwfp4j57ufef56ez46rrpyjtdkhjpr687f5de0sa7ryv5');
|
||||
|
||||
assert.ok(w.isNativeSegwit());
|
||||
assert.ok(!w.isWrappedSegwit());
|
||||
assert.ok(!w.isLegacy());
|
||||
|
||||
assert.strictEqual(w.getCustomDerivationPathForCosigner(1), "m/1'");
|
||||
assert.strictEqual(w.getCustomDerivationPathForCosigner(2), "m/48'/0'/0'/2'");
|
||||
|
||||
assert.strictEqual(w.getFingerprint(1), '8de7b2c3'.toUpperCase());
|
||||
assert.strictEqual(w.getFingerprint(2), '84431270'.toUpperCase());
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
address: 'bc1qmpyrvv6fmkv494r9qk9nllyuyngtqj62fywcl2xzessgwf9qgrssxff69u',
|
||||
amount: 68419,
|
||||
height: 0,
|
||||
txId: '2d40b967bb3a4ecd8517843d01042b0dd4227192acbe0e1ad1f1cf144a1ec0c9',
|
||||
txhex:
|
||||
'02000000000101d7bf498a92b19bab8a58260efedd7e6cd3b7713ff1e9d2603ff9f06a64f66291000000001716001440512e04b685a0cd66a03bea0896c27000c828dcffffffff01430b010000000000220020d848363349dd9952d465058b3ffc9c24d0b04b4a491d8fa8c2cc208724a040e10247304402201ad742ffee74e5ae4b3867d9818b8ad6505ca5239280138f9da3f93e4c27ee0802202918fa6034485077596bf64501ae6954371e91d250ee98f5a3c5889d4dee923e012103a681da832358050bd9b197aaa55d921f1447025b999eadb018aa67c5b8f64a0900000000',
|
||||
txid: '2d40b967bb3a4ecd8517843d01042b0dd4227192acbe0e1ad1f1cf144a1ec0c9',
|
||||
value: 68419,
|
||||
vout: 0,
|
||||
wif: false,
|
||||
},
|
||||
];
|
||||
const { psbt } = w.createTransaction(
|
||||
utxos,
|
||||
[{ address: '39RXMPjwKwoEGJeABJvdG1N4nQAzfEgcos' }],
|
||||
1,
|
||||
w._getInternalAddressByIndex(3),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
assert.ok(psbt);
|
||||
// we are using .cosignPsbt for now, because .createTransaction throws
|
||||
// Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint
|
||||
// https://github.com/BlueWallet/BlueWallet/pull/2466
|
||||
const { tx } = w.cosignPsbt(psbt);
|
||||
assert.ok(tx);
|
||||
});
|
||||
|
||||
it('cant import garbage', () => {
|
||||
const w = new MultisigHDWallet();
|
||||
w.setSecret('garbage');
|
||||
|
@ -1829,6 +1882,30 @@ describe('multisig-wallet (native segwit)', () => {
|
|||
assert.strictEqual(psbt.data.inputs.length, 2);
|
||||
assert.strictEqual(psbt.data.outputs.length, 1);
|
||||
});
|
||||
|
||||
it('can generate proper addresses for wallets with passphrases', () => {
|
||||
// test case from https://github.com/BlueWallet/BlueWallet/issues/3665#issuecomment-907377442
|
||||
const path = "m/48'/0'/0'/2'";
|
||||
const w = new MultisigHDWallet();
|
||||
w.addCosigner(
|
||||
'salon smoke bubble dolphin powder govern rival sport better arrest certain manual',
|
||||
undefined,
|
||||
undefined,
|
||||
'9WDdFSZX4d6mPxkr',
|
||||
);
|
||||
w.addCosigner('chaos word void picture gas update shop wave task blossom close inner', undefined, undefined, 'E5jMAzsf464Hgwns');
|
||||
w.addCosigner(
|
||||
'plate inform scissors pill asset scatter people emotion dose primary together expose',
|
||||
undefined,
|
||||
undefined,
|
||||
'RyBFfLr7weK3nDUG',
|
||||
);
|
||||
w.setDerivationPath(path);
|
||||
w.setM(2);
|
||||
|
||||
assert.strictEqual(w._getExternalAddressByIndex(0), 'bc1q8rks34ypj5edxx82f7z7yzy4qy6dynfhcftjs9axzr2ml37p4pfs7j4uvm');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), 'bc1qjpjgumzs2afrr3mk85anwdnzd9qg5hc5p6f62un4umpyf4ccde5q4cywgy');
|
||||
});
|
||||
});
|
||||
|
||||
describe('multisig-cosigner', () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue