mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-23 23:27:26 +01:00
Merge pull request #4350 from BlueWallet/limpbrains-multisig-passphrase
ADD: Multisig seed with passphrase
This commit is contained in:
commit
309c5b10e1
6 changed files with 504 additions and 145 deletions
|
@ -1136,8 +1136,8 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||||
* @param mnemonic {string} Mnemonic phrase (12 or 24 words)
|
* @param mnemonic {string} Mnemonic phrase (12 or 24 words)
|
||||||
* @returns {string} Hex fingerprint
|
* @returns {string} Hex fingerprint
|
||||||
*/
|
*/
|
||||||
static mnemonicToFingerprint(mnemonic) {
|
static mnemonicToFingerprint(mnemonic, passphrase) {
|
||||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
const seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
||||||
return AbstractHDElectrumWallet.seedToFingerprint(seed);
|
return AbstractHDElectrumWallet.seedToFingerprint(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,15 @@ const createHash = require('create-hash');
|
||||||
const reverse = require('buffer-reverse');
|
const reverse = require('buffer-reverse');
|
||||||
const mn = require('electrum-mnemonic');
|
const mn = require('electrum-mnemonic');
|
||||||
|
|
||||||
const MNEMONIC_TO_SEED_OPTS_SEGWIT = {
|
const electrumSegwit = passphrase => ({
|
||||||
prefix: mn.PREFIXES.segwit,
|
prefix: mn.PREFIXES.segwit,
|
||||||
};
|
...(passphrase ? { passphrase } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
const MNEMONIC_TO_SEED_OPTS_STANDARD = {
|
const electrumStandart = passphrase => ({
|
||||||
prefix: mn.PREFIXES.standard,
|
prefix: mn.PREFIXES.standard,
|
||||||
};
|
...(passphrase ? { passphrase } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
const ELECTRUM_SEED_PREFIX = 'electrumseed:';
|
const ELECTRUM_SEED_PREFIX = 'electrumseed:';
|
||||||
|
|
||||||
|
@ -42,6 +44,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
this._cosigners = []; // array of xpubs or mnemonic seeds
|
this._cosigners = []; // array of xpubs or mnemonic seeds
|
||||||
this._cosignersFingerprints = []; // array of according fingerprints (if any provided)
|
this._cosignersFingerprints = []; // array of according fingerprints (if any provided)
|
||||||
this._cosignersCustomPaths = []; // array of according paths (if any provided)
|
this._cosignersCustomPaths = []; // array of according paths (if any provided)
|
||||||
|
this._cosignersPassphrases = []; // array of according passphrases (if any provided)
|
||||||
this._derivationPath = '';
|
this._derivationPath = '';
|
||||||
this._isNativeSegwit = false;
|
this._isNativeSegwit = false;
|
||||||
this._isWrappedSegwit = false;
|
this._isWrappedSegwit = false;
|
||||||
|
@ -130,6 +133,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
return this._cosigners[index];
|
return this._cosigners[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPassphrase(index) {
|
||||||
|
if (index === 0) throw new Error('cosigners indexation starts from 1');
|
||||||
|
return this._cosignersPassphrases[index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
static isXpubValid(key) {
|
static isXpubValid(key) {
|
||||||
let xpub;
|
let xpub;
|
||||||
|
|
||||||
|
@ -157,8 +165,9 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
* @param key {string} Either xpub or mnemonic phrase
|
* @param key {string} Either xpub or mnemonic phrase
|
||||||
* @param fingerprint {string} Fingerprint for cosigner that is added as xpub
|
* @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
|
* @param path {string} Custom path (if any) for cosigner that is added as mnemonics
|
||||||
|
* @param passphrase {string} BIP38 Passphrase (if any)
|
||||||
*/
|
*/
|
||||||
addCosigner(key, fingerprint, path) {
|
addCosigner(key, fingerprint, path, passphrase) {
|
||||||
if (MultisigHDWallet.isXpubString(key) && !fingerprint) {
|
if (MultisigHDWallet.isXpubString(key) && !fingerprint) {
|
||||||
throw new Error('fingerprint is required when adding cosigner as xpub (watch-only)');
|
throw new Error('fingerprint is required when adding cosigner as xpub (watch-only)');
|
||||||
}
|
}
|
||||||
|
@ -176,11 +185,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
// its an electrum seed
|
// its an electrum seed
|
||||||
const mnemonic = key.replace(ELECTRUM_SEED_PREFIX, '');
|
const mnemonic = key.replace(ELECTRUM_SEED_PREFIX, '');
|
||||||
try {
|
try {
|
||||||
mn.mnemonicToSeedSync(mnemonic, MNEMONIC_TO_SEED_OPTS_STANDARD);
|
mn.mnemonicToSeedSync(mnemonic, electrumStandart(passphrase));
|
||||||
this.setLegacy();
|
this.setLegacy();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
try {
|
try {
|
||||||
mn.mnemonicToSeedSync(mnemonic, MNEMONIC_TO_SEED_OPTS_SEGWIT);
|
mn.mnemonicToSeedSync(mnemonic, electrumSegwit(passphrase));
|
||||||
this.setNativeSegwit();
|
this.setNativeSegwit();
|
||||||
} catch (__) {
|
} catch (__) {
|
||||||
throw new Error('Not a valid electrum seed');
|
throw new Error('Not a valid electrum seed');
|
||||||
|
@ -189,7 +198,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
} else {
|
} else {
|
||||||
// mnemonics. lets derive fingerprint (if it wasnt provided)
|
// mnemonics. lets derive fingerprint (if it wasnt provided)
|
||||||
if (!bip39.validateMnemonic(key)) throw new Error('Not a valid mnemonic phrase');
|
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') {
|
if (fingerprint && this._cosignersFingerprints.indexOf(fingerprint.toUpperCase()) !== -1 && fingerprint !== '00000000') {
|
||||||
|
@ -201,6 +210,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
this._cosigners[index] = key;
|
this._cosigners[index] = key;
|
||||||
if (fingerprint) this._cosignersFingerprints[index] = fingerprint.toUpperCase();
|
if (fingerprint) this._cosignersFingerprints[index] = fingerprint.toUpperCase();
|
||||||
if (path) this._cosignersCustomPaths[index] = path;
|
if (path) this._cosignersCustomPaths[index] = path;
|
||||||
|
if (passphrase) this._cosignersPassphrases[index] = passphrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
static convertMultisigXprvToRegularXprv(Zprv) {
|
static convertMultisigXprvToRegularXprv(Zprv) {
|
||||||
|
@ -226,7 +236,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
let xpub = cosigner;
|
let xpub = cosigner;
|
||||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
||||||
const index = this._cosigners.indexOf(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);
|
return this.constructor._zpubToXpub(xpub);
|
||||||
}
|
}
|
||||||
|
@ -243,8 +257,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
|
|
||||||
_getAddressFromNode(nodeIndex, index) {
|
_getAddressFromNode(nodeIndex, index) {
|
||||||
const pubkeys = [];
|
const pubkeys = [];
|
||||||
let cosignerIndex = 0;
|
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||||
for (const cosigner of this._cosigners) {
|
|
||||||
this._nodes = this._nodes || [];
|
this._nodes = this._nodes || [];
|
||||||
this._nodes[nodeIndex] = this._nodes[nodeIndex] || [];
|
this._nodes[nodeIndex] = this._nodes[nodeIndex] || [];
|
||||||
let _node;
|
let _node;
|
||||||
|
@ -259,7 +272,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
pubkeys.push(_node.derive(index).publicKey);
|
pubkeys.push(_node.derive(index).publicKey);
|
||||||
cosignerIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isWrappedSegwit()) {
|
if (this.isWrappedSegwit()) {
|
||||||
|
@ -297,12 +309,12 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
static seedToXpub(mnemonic, path) {
|
static seedToXpub(mnemonic, path, passphrase) {
|
||||||
let seed;
|
let seed;
|
||||||
if (mnemonic.startsWith(ELECTRUM_SEED_PREFIX)) {
|
if (mnemonic.startsWith(ELECTRUM_SEED_PREFIX)) {
|
||||||
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic);
|
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic, passphrase);
|
||||||
} else {
|
} else {
|
||||||
seed = bip39.mnemonicToSeedSync(mnemonic);
|
seed = bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = bip32.fromSeed(seed);
|
const root = bip32.fromSeed(seed);
|
||||||
|
@ -434,13 +446,18 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
} else {
|
} else {
|
||||||
if (coordinationSetup) {
|
if (coordinationSetup) {
|
||||||
const xpub = this.convertXpubToMultisignatureXpub(
|
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]);
|
const fingerprint = MultisigHDWallet.mnemonicToFingerprint(this._cosigners[index], this._cosignersPassphrases[index]);
|
||||||
ret += fingerprint + ': ' + xpub + '\n';
|
ret += fingerprint + ': ' + xpub + '\n';
|
||||||
} else {
|
} else {
|
||||||
ret += 'seed: ' + this._cosigners[index] + '\n';
|
ret += 'seed: ' + this._cosigners[index];
|
||||||
ret += '# warning! sensitive information, do not disclose ^^^ \n';
|
if (this._cosignersPassphrases[index]) ret += ' - ' + this._cosignersPassphrases[index];
|
||||||
|
ret += '\n# warning! sensitive information, do not disclose ^^^ \n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,7 +497,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
? MultisigHDWallet.ckccXfp2fingerprint(cosignerData.ckcc_xfp)
|
? MultisigHDWallet.ckccXfp2fingerprint(cosignerData.ckcc_xfp)
|
||||||
: cosignerData.root_fingerprint?.toUpperCase()) || '00000000';
|
: cosignerData.root_fingerprint?.toUpperCase()) || '00000000';
|
||||||
if (cosignerData.seed) {
|
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)) {
|
} else if (cosignerData.xprv && MultisigHDWallet.isXprvValid(cosignerData.xprv)) {
|
||||||
this.addCosigner(cosignerData.xprv, fingerprint, cosignerData.derivation);
|
this.addCosigner(cosignerData.xprv, fingerprint, cosignerData.derivation);
|
||||||
} else {
|
} else {
|
||||||
|
@ -533,7 +550,8 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
} else if (key.replace('#', '').trim() === 'derivation') {
|
} else if (key.replace('#', '').trim() === 'derivation') {
|
||||||
customPathForCurrentCosigner = value.trim();
|
customPathForCurrentCosigner = value.trim();
|
||||||
} else if (key === 'seed') {
|
} else if (key === 'seed') {
|
||||||
this.addCosigner(value.trim(), false, customPathForCurrentCosigner);
|
const [seed, passphrase] = value.split(' - ');
|
||||||
|
this.addCosigner(seed.trim(), false, customPathForCurrentCosigner, passphrase);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -640,11 +658,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
_addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) {
|
_addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) {
|
||||||
const bip32Derivation = []; // array per each pubkey thats gona be used
|
const bip32Derivation = []; // array per each pubkey thats gona be used
|
||||||
const pubkeys = [];
|
const pubkeys = [];
|
||||||
for (let c = 0; c < this._cosigners.length; c++) {
|
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||||
const cosigner = this._cosigners[c];
|
const path = this._getDerivationPathByAddressWithCustomPath(
|
||||||
const path = this._getDerivationPathByAddressWithCustomPath(input.address, this._cosignersCustomPaths[c] || this._derivationPath);
|
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
|
// ^^ 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 xpub = this._getXpubFromCosigner(cosigner);
|
||||||
const hdNode0 = bip32.fromBase58(xpub);
|
const hdNode0 = bip32.fromBase58(xpub);
|
||||||
|
@ -731,14 +751,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
_getOutputDataForChange(outputData) {
|
_getOutputDataForChange(outputData) {
|
||||||
const bip32Derivation = []; // array per each pubkey thats gona be used
|
const bip32Derivation = []; // array per each pubkey thats gona be used
|
||||||
const pubkeys = [];
|
const pubkeys = [];
|
||||||
for (let c = 0; c < this._cosigners.length; c++) {
|
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||||
const cosigner = this._cosigners[c];
|
|
||||||
const path = this._getDerivationPathByAddressWithCustomPath(
|
const path = this._getDerivationPathByAddressWithCustomPath(
|
||||||
outputData.address,
|
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
|
// ^^ 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 xpub = this._getXpubFromCosigner(cosigner);
|
||||||
const hdNode0 = bip32.fromBase58(xpub);
|
const hdNode0 = bip32.fromBase58(xpub);
|
||||||
|
@ -844,22 +863,22 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
if (!skipSigning) {
|
if (!skipSigning) {
|
||||||
for (let cc = 0; cc < c; cc++) {
|
for (let cc = 0; cc < c; cc++) {
|
||||||
let signaturesMade = 0;
|
let signaturesMade = 0;
|
||||||
for (const cosigner of this._cosigners) {
|
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
if (MultisigHDWallet.isXpubString(cosigner)) continue;
|
||||||
// ok this is a mnemonic, lets try to sign
|
// ok this is a mnemonic, lets try to sign
|
||||||
if (signaturesMade >= this.getM()) {
|
if (signaturesMade >= this.getM()) {
|
||||||
// dont sign more than we need, otherwise there will be "Too many signatures" error
|
// dont sign more than we need, otherwise there will be "Too many signatures" error
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
let seed = bip39.mnemonicToSeedSync(cosigner);
|
|
||||||
if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) {
|
|
||||||
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner);
|
|
||||||
}
|
|
||||||
|
|
||||||
const hdRoot = bip32.fromSeed(seed);
|
|
||||||
psbt.signInputHD(cc, hdRoot);
|
|
||||||
signaturesMade++;
|
|
||||||
}
|
}
|
||||||
|
const passphrase = this._cosignersPassphrases[cosignerIndex];
|
||||||
|
let seed = bip39.mnemonicToSeedSync(cosigner, passphrase);
|
||||||
|
if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) {
|
||||||
|
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hdRoot = bip32.fromSeed(seed);
|
||||||
|
psbt.signInputHD(cc, hdRoot);
|
||||||
|
signaturesMade++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -871,13 +890,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
return { tx, inputs, outputs, fee, psbt };
|
return { tx, inputs, outputs, fee, psbt };
|
||||||
}
|
}
|
||||||
|
|
||||||
static convertElectrumMnemonicToSeed(cosigner) {
|
static convertElectrumMnemonicToSeed(cosigner, passphrase) {
|
||||||
let seed;
|
let seed;
|
||||||
try {
|
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 (_) {
|
} catch (_) {
|
||||||
try {
|
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 (__) {
|
} catch (__) {
|
||||||
throw new Error('Not a valid electrum mnemonic');
|
throw new Error('Not a valid electrum mnemonic');
|
||||||
}
|
}
|
||||||
|
@ -997,39 +1016,50 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
cosignPsbt(psbt) {
|
cosignPsbt(psbt) {
|
||||||
for (let cc = 0; cc < psbt.inputCount; cc++) {
|
for (let cc = 0; cc < psbt.inputCount; cc++) {
|
||||||
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
|
||||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
if (MultisigHDWallet.isXpubString(cosigner)) continue;
|
||||||
// ok this is a mnemonic, lets try to sign
|
|
||||||
const seed = bip39.mnemonicToSeedSync(cosigner);
|
|
||||||
const hdRoot = bip32.fromSeed(seed);
|
|
||||||
try {
|
|
||||||
psbt.signInputHD(cc, hdRoot);
|
|
||||||
} catch (_) {} // protects agains duplicate cosignings
|
|
||||||
|
|
||||||
if (!psbt.inputHasHDKey(cc, hdRoot)) {
|
let hdRoot;
|
||||||
// failed signing as HD. probably bitcoinjs-lib could not match provided hdRoot's
|
if (MultisigHDWallet.isXprvString(cosigner)) {
|
||||||
// fingerprint (or path?) to the ones in psbt, which is the case of stupid Electrum desktop which can
|
const xprv = MultisigHDWallet.convertMultisigXprvToRegularXprv(cosigner);
|
||||||
// put bullshit paths and fingerprints in created psbt.
|
hdRoot = bip32.fromBase58(xprv);
|
||||||
// lets try to find correct priv key and sign manually.
|
} else {
|
||||||
for (const derivation of psbt.data.inputs[cc].bip32Derivation || []) {
|
const passphrase = this._cosignersPassphrases[cosignerIndex];
|
||||||
// okay, here we assume that fingerprint is irrelevant, but ending of the path is somewhat correct and
|
const seed = cosigner.startsWith(ELECTRUM_SEED_PREFIX)
|
||||||
// correctly points to `/internal/index`, so we extract pubkey from our stored mnemonics+path and
|
? MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner, passphrase)
|
||||||
// match it to the one provided in PSBT's input, and if we have a match - we are in luck! we can sign
|
: bip39.mnemonicToSeedSync(cosigner, passphrase);
|
||||||
// with this private key.
|
hdRoot = bip32.fromSeed(seed);
|
||||||
const seed = bip39.mnemonicToSeedSync(cosigner);
|
}
|
||||||
const root = bip32.fromSeed(seed);
|
|
||||||
const splt = derivation.path.split('/');
|
|
||||||
const internal = +splt[splt.length - 2];
|
|
||||||
const index = +splt[splt.length - 1];
|
|
||||||
|
|
||||||
const path = this.getCustomDerivationPathForCosigner(cosignerIndex + 1) + `/${internal ? 1 : 0}/${index}`;
|
try {
|
||||||
// ^^^ we assume that counterparty has Zpub for specified derivation path
|
psbt.signInputHD(cc, hdRoot);
|
||||||
const child = root.derivePath(path);
|
} catch (_) {} // protects agains duplicate cosignings
|
||||||
if (psbt.inputHasPubkey(cc, child.publicKey)) {
|
|
||||||
const keyPair = ECPair.fromPrivateKey(child.privateKey);
|
if (!psbt.inputHasHDKey(cc, hdRoot)) {
|
||||||
try {
|
// failed signing as HD. probably bitcoinjs-lib could not match provided hdRoot's
|
||||||
psbt.signInput(cc, keyPair);
|
// fingerprint (or path?) to the ones in psbt, which is the case of stupid Electrum desktop which can
|
||||||
} catch (_) {}
|
// 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 (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1045,30 +1075,38 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks up cosigner by Fingerprint, and repalces all its data with new data
|
* Looks up xpub cosigner by index, and repalces it with seed + passphrase
|
||||||
*
|
*
|
||||||
* @param oldFp {string} Looks up cosigner by this fp
|
* @param externalIndex {number}
|
||||||
* @param newCosigner {string}
|
* @param mnemonic {string}
|
||||||
* @param newFp {string}
|
* @param passphrase {string}
|
||||||
* @param newPath {string}
|
|
||||||
*/
|
*/
|
||||||
replaceCosigner(oldFp, newCosigner, newFp, newPath) {
|
replaceCosignerXpubWithSeed(externalIndex, mnemonic, passphrase) {
|
||||||
const index = this._cosignersFingerprints.indexOf(oldFp);
|
const index = externalIndex - 1;
|
||||||
if (index === -1) return;
|
const fingerprint = this._cosignersFingerprints[index];
|
||||||
if (!MultisigHDWallet.isXpubValid(newCosigner)) {
|
if (!MultisigHDWallet.isXpubValid(this._cosigners[index])) throw new Error('This cosigner doesnt contain valid xpub');
|
||||||
// its not an xpub, so lets derive fingerprint ourselves
|
if (!bip39.validateMnemonic(mnemonic)) throw new Error('Not a valid mnemonic phrase');
|
||||||
newFp = MultisigHDWallet.mnemonicToFingerprint(newCosigner);
|
if (fingerprint !== MultisigHDWallet.mnemonicToFingerprint(mnemonic, passphrase)) {
|
||||||
if (oldFp !== newFp) {
|
throw new Error('Fingerprint of new seed doesnt match');
|
||||||
throw new Error('Fingerprint of new seed doesnt match');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this._cosigners[index] = mnemonic.trim();
|
||||||
|
this._cosignersPassphrases[index] = passphrase || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
this._cosignersFingerprints[index] = newFp;
|
/**
|
||||||
this._cosigners[index] = newCosigner;
|
* Looks up cosigner with seed by index, and repalces it with xpub
|
||||||
|
*
|
||||||
if (newPath && this.getDerivationPath() !== newPath) {
|
* @param externalIndex {number}
|
||||||
this._cosignersCustomPaths[index] = newPath;
|
*/
|
||||||
}
|
replaceCosignerSeedWithXpub(externalIndex) {
|
||||||
|
const index = externalIndex - 1;
|
||||||
|
const mnemonics = this._cosigners[index];
|
||||||
|
if (!bip39.validateMnemonic(mnemonics)) throw new Error('This cosigner doesnt contain valid xpub mnemonic phrase');
|
||||||
|
const passphrase = this._cosignersPassphrases[index];
|
||||||
|
const path = this._cosignersCustomPaths[index] || this._derivationPath;
|
||||||
|
const xpub = this.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(mnemonics, path, passphrase));
|
||||||
|
this._cosigners[index] = xpub;
|
||||||
|
this._cosignersPassphrases[index] = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCosigner(fp) {
|
deleteCosigner(fp) {
|
||||||
|
@ -1087,6 +1125,10 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||||
return index !== foundIndex;
|
return index !== foundIndex;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._cosignersPassphrases = this._cosignersPassphrases.filter((el, index) => {
|
||||||
|
return index !== foundIndex;
|
||||||
|
});
|
||||||
|
|
||||||
/* const newCosigners = [];
|
/* const newCosigners = [];
|
||||||
for (let c = 0; c < this._cosignersFingerprints.length; c++) {
|
for (let c = 0; c < this._cosignersFingerprints.length; c++) {
|
||||||
if (c !== index) newCosigners.push(this._cosignersFingerprints[c]);
|
if (c !== index) newCosigners.push(this._cosignersFingerprints[c]);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext, useRef, useState } from 'react';
|
import React, { useContext, useRef, useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
FlatList,
|
FlatList,
|
||||||
|
@ -8,6 +8,7 @@ import {
|
||||||
LayoutAnimation,
|
LayoutAnimation,
|
||||||
Platform,
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
|
@ -24,7 +25,7 @@ import {
|
||||||
BlueFormMultiInput,
|
BlueFormMultiInput,
|
||||||
BlueSpacing10,
|
BlueSpacing10,
|
||||||
BlueSpacing20,
|
BlueSpacing20,
|
||||||
BlueSpacing40,
|
BlueText,
|
||||||
BlueTextCentered,
|
BlueTextCentered,
|
||||||
} from '../../BlueComponents';
|
} from '../../BlueComponents';
|
||||||
import navigationStyle from '../../components/navigationStyle';
|
import navigationStyle from '../../components/navigationStyle';
|
||||||
|
@ -48,7 +49,7 @@ const isDesktop = getSystemName() === 'Mac OS X';
|
||||||
const staticCache = {};
|
const staticCache = {};
|
||||||
|
|
||||||
const WalletsAddMultisigStep2 = () => {
|
const WalletsAddMultisigStep2 = () => {
|
||||||
const { addWallet, saveToDisk, isElectrumDisabled } = useContext(BlueStorageContext);
|
const { addWallet, saveToDisk, isElectrumDisabled, isAdancedModeEnabled, sleep } = useContext(BlueStorageContext);
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
@ -64,9 +65,16 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
const [cosignerXpubFilename, setCosignerXpubFilename] = useState('bw-cosigner.json');
|
const [cosignerXpubFilename, setCosignerXpubFilename] = useState('bw-cosigner.json');
|
||||||
const [vaultKeyData, setVaultKeyData] = useState({ keyIndex: 1, xpub: '', seed: '', isLoading: false }); // string rendered in modal
|
const [vaultKeyData, setVaultKeyData] = useState({ keyIndex: 1, xpub: '', seed: '', isLoading: false }); // string rendered in modal
|
||||||
const [importText, setImportText] = useState('');
|
const [importText, setImportText] = useState('');
|
||||||
|
const [askPassphrase, setAskPassphrase] = useState(false);
|
||||||
|
const [isAdvancedModeEnabledRender, setIsAdvancedModeEnabledRender] = useState(false);
|
||||||
const openScannerButton = useRef();
|
const openScannerButton = useRef();
|
||||||
const data = useRef(new Array(n));
|
const data = useRef(new Array(n));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isAdancedModeEnabled().then(setIsAdvancedModeEnabledRender);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleOnHelpPress = () => {
|
const handleOnHelpPress = () => {
|
||||||
navigation.navigate('WalletsAddMultisigHelp');
|
navigation.navigate('WalletsAddMultisigHelp');
|
||||||
};
|
};
|
||||||
|
@ -104,9 +112,16 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onCreate = () => {
|
const onCreate = async () => {
|
||||||
setIsLoading(true);
|
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 () => {
|
const _onCreate = async () => {
|
||||||
|
@ -130,8 +145,8 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
throw new Error('This should never happen');
|
throw new Error('This should never happen');
|
||||||
}
|
}
|
||||||
for (const cc of cosigners) {
|
for (const cc of cosigners) {
|
||||||
const fp = cc[1] || getFpCacheForMnemonics(cc[0]);
|
const fp = cc[1] || getFpCacheForMnemonics(cc[0], cc[3]);
|
||||||
w.addCosigner(cc[0], fp, cc[2]);
|
w.addCosigner(cc[0], fp, cc[2], cc[3]);
|
||||||
}
|
}
|
||||||
w.setLabel(walletLabel);
|
w.setLabel(walletLabel);
|
||||||
if (!isElectrumDisabled) {
|
if (!isElectrumDisabled) {
|
||||||
|
@ -195,7 +210,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
const path = getPath();
|
const path = getPath();
|
||||||
|
|
||||||
const xpub = getXpubCacheForMnemonics(cosigner[0]);
|
const xpub = getXpubCacheForMnemonics(cosigner[0]);
|
||||||
const fp = getFpCacheForMnemonics(cosigner[0]);
|
const fp = getFpCacheForMnemonics(cosigner[0], cosigner[3]);
|
||||||
setCosignerXpub(MultisigCosigner.exportToJson(fp, xpub, path));
|
setCosignerXpub(MultisigCosigner.exportToJson(fp, xpub, path));
|
||||||
setCosignerXpubURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
|
setCosignerXpubURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
|
||||||
setCosignerXpubFilename('bw-cosigner-' + fp + '.json');
|
setCosignerXpubFilename('bw-cosigner-' + fp + '.json');
|
||||||
|
@ -216,13 +231,13 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
return staticCache[seed + path];
|
return staticCache[seed + path];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFpCacheForMnemonics = seed => {
|
const getFpCacheForMnemonics = (seed, passphrase) => {
|
||||||
return staticCache[seed] || setFpCacheForMnemonics(seed);
|
return staticCache[seed + (passphrase ?? '')] || setFpCacheForMnemonics(seed, passphrase);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setFpCacheForMnemonics = seed => {
|
const setFpCacheForMnemonics = (seed, passphrase) => {
|
||||||
staticCache[seed] = MultisigHDWallet.mnemonicToFingerprint(seed);
|
staticCache[seed + (passphrase ?? '')] = MultisigHDWallet.mnemonicToFingerprint(seed, passphrase);
|
||||||
return staticCache[seed];
|
return staticCache[seed + (passphrase ?? '')];
|
||||||
};
|
};
|
||||||
|
|
||||||
const iHaveMnemonics = () => {
|
const iHaveMnemonics = () => {
|
||||||
|
@ -234,6 +249,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
setIsProvideMnemonicsModalVisible(false);
|
setIsProvideMnemonicsModalVisible(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setImportText('');
|
setImportText('');
|
||||||
|
setAskPassphrase(false);
|
||||||
alert(loc.multisig.not_a_multisignature_xpub);
|
alert(loc.multisig.not_a_multisignature_xpub);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -261,6 +277,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
setIsProvideMnemonicsModalVisible(false);
|
setIsProvideMnemonicsModalVisible(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setImportText('');
|
setImportText('');
|
||||||
|
setAskPassphrase(false);
|
||||||
|
|
||||||
const cosignersCopy = [...cosigners];
|
const cosignersCopy = [...cosigners];
|
||||||
cosignersCopy.push([xpub, fp, path]);
|
cosignersCopy.push([xpub, fp, path]);
|
||||||
|
@ -268,7 +285,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
setCosigners(cosignersCopy);
|
setCosigners(cosignersCopy);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useMnemonicPhrase = () => {
|
const useMnemonicPhrase = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
if (MultisigHDWallet.isXpubValid(importText)) {
|
if (MultisigHDWallet.isXpubValid(importText)) {
|
||||||
|
@ -281,14 +298,28 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
return alert(loc.multisig.invalid_mnemonics);
|
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];
|
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);
|
if (Platform.OS !== 'android') LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
setCosigners(cosignersCopy);
|
setCosigners(cosignersCopy);
|
||||||
|
|
||||||
setIsProvideMnemonicsModalVisible(false);
|
setIsProvideMnemonicsModalVisible(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setImportText('');
|
setImportText('');
|
||||||
|
setAskPassphrase(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidMnemonicSeed = mnemonicSeed => {
|
const isValidMnemonicSeed = mnemonicSeed => {
|
||||||
|
@ -392,7 +423,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
navigation.navigate('ScanQRCodeRoot', {
|
navigation.navigate('ScanQRCodeRoot', {
|
||||||
screen: 'ScanQRCode',
|
screen: 'ScanQRCode',
|
||||||
params: {
|
params: {
|
||||||
onBarScanned: onBarScanned,
|
onBarScanned,
|
||||||
showFileImportButton: true,
|
showFileImportButton: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -529,6 +560,7 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
setIsProvideMnemonicsModalVisible(false);
|
setIsProvideMnemonicsModalVisible(false);
|
||||||
setImportText('');
|
setImportText('');
|
||||||
|
setAskPassphrase(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderProvideMnemonicsModal = () => {
|
const renderProvideMnemonicsModal = () => {
|
||||||
|
@ -539,7 +571,16 @@ const WalletsAddMultisigStep2 = () => {
|
||||||
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
||||||
<BlueSpacing20 />
|
<BlueSpacing20 />
|
||||||
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
|
<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 ? (
|
{isLoading ? (
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
) : (
|
) : (
|
||||||
|
@ -704,6 +745,12 @@ const styles = StyleSheet.create({
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
marginLeft: 8,
|
marginLeft: 8,
|
||||||
},
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginHorizontal: 16,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
WalletsAddMultisigStep2.navigationOptions = navigationStyle({
|
WalletsAddMultisigStep2.navigationOptions = navigationStyle({
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import React, { useContext, useRef, useState, useCallback } from 'react';
|
import React, { useContext, useRef, useState, useCallback, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Alert,
|
Alert,
|
||||||
findNodeHandle,
|
|
||||||
FlatList,
|
FlatList,
|
||||||
InteractionManager,
|
InteractionManager,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
|
@ -11,8 +10,10 @@ import {
|
||||||
Platform,
|
Platform,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
View,
|
View,
|
||||||
|
findNodeHandle,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Icon, Badge } from 'react-native-elements';
|
import { Icon, Badge } from 'react-native-elements';
|
||||||
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
||||||
|
@ -24,6 +25,7 @@ import {
|
||||||
BlueSpacing10,
|
BlueSpacing10,
|
||||||
BlueSpacing20,
|
BlueSpacing20,
|
||||||
BlueSpacing40,
|
BlueSpacing40,
|
||||||
|
BlueText,
|
||||||
BlueTextCentered,
|
BlueTextCentered,
|
||||||
} from '../../BlueComponents';
|
} from '../../BlueComponents';
|
||||||
import navigationStyle from '../../components/navigationStyle';
|
import navigationStyle from '../../components/navigationStyle';
|
||||||
|
@ -44,11 +46,12 @@ import { encodeUR } from '../../blue_modules/ur';
|
||||||
import QRCodeComponent from '../../components/QRCodeComponent';
|
import QRCodeComponent from '../../components/QRCodeComponent';
|
||||||
import alert from '../../components/Alert';
|
import alert from '../../components/Alert';
|
||||||
const fs = require('../../blue_modules/fs');
|
const fs = require('../../blue_modules/fs');
|
||||||
|
const prompt = require('../../blue_modules/prompt');
|
||||||
|
|
||||||
const ViewEditMultisigCosigners = () => {
|
const ViewEditMultisigCosigners = () => {
|
||||||
const hasLoaded = useRef(false);
|
const hasLoaded = useRef(false);
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const { wallets, setWalletsWithNewOrder, isElectrumDisabled } = useContext(BlueStorageContext);
|
const { wallets, setWalletsWithNewOrder, isElectrumDisabled, isAdancedModeEnabled } = useContext(BlueStorageContext);
|
||||||
const { navigate, goBack } = useNavigation();
|
const { navigate, goBack } = useNavigation();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const openScannerButtonRef = useRef();
|
const openScannerButtonRef = useRef();
|
||||||
|
@ -67,6 +70,8 @@ const ViewEditMultisigCosigners = () => {
|
||||||
const [exportStringURv2, setExportStringURv2] = useState(''); // used in QR
|
const [exportStringURv2, setExportStringURv2] = useState(''); // used in QR
|
||||||
const [exportFilename, setExportFilename] = useState('bw-cosigner.json');
|
const [exportFilename, setExportFilename] = useState('bw-cosigner.json');
|
||||||
const [vaultKeyData, setVaultKeyData] = useState({ keyIndex: 1, xpub: '', seed: '', path: '', fp: '', isLoading: false }); // string rendered in modal
|
const [vaultKeyData, setVaultKeyData] = useState({ keyIndex: 1, xpub: '', seed: '', path: '', fp: '', isLoading: false }); // string rendered in modal
|
||||||
|
const [askPassphrase, setAskPassphrase] = useState(false);
|
||||||
|
const [isAdvancedModeEnabledRender, setIsAdvancedModeEnabledRender] = useState(false);
|
||||||
const data = useRef();
|
const data = useRef();
|
||||||
const stylesHook = StyleSheet.create({
|
const stylesHook = StyleSheet.create({
|
||||||
root: {
|
root: {
|
||||||
|
@ -99,6 +104,11 @@ const ViewEditMultisigCosigners = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isAdancedModeEnabled().then(setIsAdvancedModeEnabledRender);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
const exportCosigner = () => {
|
const exportCosigner = () => {
|
||||||
setIsShareModalVisible(false);
|
setIsShareModalVisible(false);
|
||||||
setTimeout(() => fs.writeFileAndExport(exportFilename, exportString), 1000);
|
setTimeout(() => fs.writeFileAndExport(exportFilename, exportString), 1000);
|
||||||
|
@ -238,6 +248,7 @@ const ViewEditMultisigCosigners = () => {
|
||||||
const secret = wallet.getCosigner(el.index + 1).split(' ');
|
const secret = wallet.getCosigner(el.index + 1).split(' ');
|
||||||
leftText = `${secret[0]}...${secret[secret.length - 1]}`;
|
leftText = `${secret[0]}...${secret[secret.length - 1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<MultipleStepsListItem
|
<MultipleStepsListItem
|
||||||
|
@ -373,45 +384,54 @@ const ViewEditMultisigCosigners = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUseMnemonicPhrase = () => {
|
const handleUseMnemonicPhrase = async () => {
|
||||||
return _handleUseMnemonicPhrase(importText);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _handleUseMnemonicPhrase(importText, passphrase);
|
||||||
};
|
};
|
||||||
|
|
||||||
const _handleUseMnemonicPhrase = mnemonic => {
|
const _handleUseMnemonicPhrase = (mnemonic, passphrase) => {
|
||||||
const hd = new HDSegwitBech32Wallet();
|
const hd = new HDSegwitBech32Wallet();
|
||||||
hd.setSecret(mnemonic);
|
hd.setSecret(mnemonic);
|
||||||
if (!hd.validateMnemonic()) return alert(loc.multisig.invalid_mnemonics);
|
if (!hd.validateMnemonic()) return alert(loc.multisig.invalid_mnemonics);
|
||||||
|
try {
|
||||||
|
wallet.replaceCosignerXpubWithSeed(currentlyEditingCosignerNum, hd.getSecret(), passphrase);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return alert(e.message);
|
||||||
|
}
|
||||||
|
|
||||||
const newFp = MultisigHDWallet.mnemonicToFingerprint(hd.getSecret());
|
|
||||||
if (newFp !== wallet.getFingerprint(currentlyEditingCosignerNum)) return alert(loc.multisig.invalid_fingerprint);
|
|
||||||
|
|
||||||
wallet.deleteCosigner(newFp);
|
|
||||||
wallet.addCosigner(hd.getSecret());
|
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
setWallet(wallet);
|
setWallet(wallet);
|
||||||
setIsProvideMnemonicsModalVisible(false);
|
setIsProvideMnemonicsModalVisible(false);
|
||||||
setIsSaveButtonDisabled(false);
|
setIsSaveButtonDisabled(false);
|
||||||
|
setImportText('');
|
||||||
|
setAskPassphrase(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const xpubInsteadOfSeed = index => {
|
const xpubInsteadOfSeed = index => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(() => {
|
||||||
try {
|
try {
|
||||||
const mnemonics = wallet.getCosigner(index);
|
wallet.replaceCosignerSeedWithXpub(index);
|
||||||
const newFp = MultisigHDWallet.mnemonicToFingerprint(mnemonics);
|
|
||||||
const path = wallet.getCustomDerivationPathForCosigner(index);
|
|
||||||
const xpub = wallet.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(mnemonics, path));
|
|
||||||
wallet.deleteCosigner(newFp);
|
|
||||||
wallet.addCosigner(xpub, newFp, path);
|
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
||||||
setWallet(wallet);
|
|
||||||
setIsSaveButtonDisabled(false);
|
|
||||||
resolve();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.message);
|
|
||||||
console.log(e);
|
|
||||||
reject(e);
|
reject(e);
|
||||||
|
return alert(e.message);
|
||||||
}
|
}
|
||||||
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
|
setWallet(wallet);
|
||||||
|
setIsSaveButtonDisabled(false);
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -450,6 +470,7 @@ const ViewEditMultisigCosigners = () => {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
setIsProvideMnemonicsModalVisible(false);
|
setIsProvideMnemonicsModalVisible(false);
|
||||||
setImportText('');
|
setImportText('');
|
||||||
|
setAskPassphrase(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hideShareModal = () => {
|
const hideShareModal = () => {
|
||||||
|
@ -464,7 +485,16 @@ const ViewEditMultisigCosigners = () => {
|
||||||
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
|
||||||
<BlueSpacing20 />
|
<BlueSpacing20 />
|
||||||
<BlueFormMultiInput value={importText} onChangeText={setImportText} />
|
<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 ? (
|
{isLoading ? (
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
) : (
|
) : (
|
||||||
|
@ -625,6 +655,12 @@ const styles = StyleSheet.create({
|
||||||
tipLabelText: {
|
tipLabelText: {
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginHorizontal: 16,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ViewEditMultisigCosigners.navigationOptions = navigationStyle(
|
ViewEditMultisigCosigners.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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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', () => {
|
it('cant import garbage', () => {
|
||||||
const w = new MultisigHDWallet();
|
const w = new MultisigHDWallet();
|
||||||
w.setSecret('garbage');
|
w.setSecret('garbage');
|
||||||
|
@ -1668,14 +1721,12 @@ describe('multisig-wallet (native segwit)', () => {
|
||||||
assert.strictEqual(w.getCosignerForFingerprint(fp2coldcard), process.env.MNEMONICS_COLDCARD);
|
assert.strictEqual(w.getCosignerForFingerprint(fp2coldcard), process.env.MNEMONICS_COLDCARD);
|
||||||
assert.strictEqual(w.howManySignaturesCanWeMake(), 1);
|
assert.strictEqual(w.howManySignaturesCanWeMake(), 1);
|
||||||
|
|
||||||
w.replaceCosigner(fp2coldcard, Zpub2, fp2coldcard, path); // <-------------------
|
w.replaceCosignerSeedWithXpub(2);
|
||||||
|
|
||||||
assert.strictEqual(w.getCosigner(2), Zpub2);
|
assert.strictEqual(w.getCosigner(2), Zpub2);
|
||||||
assert.strictEqual(w.getFingerprint(2), fp2coldcard);
|
assert.strictEqual(w.getFingerprint(2), fp2coldcard);
|
||||||
assert.strictEqual(w.getCustomDerivationPathForCosigner(2), path);
|
assert.strictEqual(w.getCustomDerivationPathForCosigner(2), path);
|
||||||
|
|
||||||
w.replaceCosigner(fp2coldcard, process.env.MNEMONICS_COLDCARD); // <---------------------------
|
w.replaceCosignerXpubWithSeed(2, process.env.MNEMONICS_COLDCARD);
|
||||||
|
|
||||||
assert.strictEqual(w.getCosigner(2), process.env.MNEMONICS_COLDCARD);
|
assert.strictEqual(w.getCosigner(2), process.env.MNEMONICS_COLDCARD);
|
||||||
assert.strictEqual(w.getFingerprint(2), fp2coldcard);
|
assert.strictEqual(w.getFingerprint(2), fp2coldcard);
|
||||||
assert.strictEqual(w.getCustomDerivationPathForCosigner(2), path);
|
assert.strictEqual(w.getCustomDerivationPathForCosigner(2), path);
|
||||||
|
@ -1704,6 +1755,49 @@ describe('multisig-wallet (native segwit)', () => {
|
||||||
assert.ok(!w.getCustomDerivationPathForCosigner(2));
|
assert.ok(!w.getCustomDerivationPathForCosigner(2));
|
||||||
assert.strictEqual(w.getN(), 1);
|
assert.strictEqual(w.getN(), 1);
|
||||||
assert.strictEqual(w.getM(), 2);
|
assert.strictEqual(w.getM(), 2);
|
||||||
|
|
||||||
|
w.addCosigner(
|
||||||
|
'salon smoke bubble dolphin powder govern rival sport better arrest certain manual',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
'9WDdFSZX4d6mPxkr',
|
||||||
|
);
|
||||||
|
assert.strictEqual(w.getN(), 2);
|
||||||
|
|
||||||
|
w.replaceCosignerSeedWithXpub(2);
|
||||||
|
assert.strictEqual(
|
||||||
|
w.getCosigner(2),
|
||||||
|
'Zpub752NRx3S4ax3S5oLHLB2DAQx9X3Ek4EGvtsyYTpzQ2VRdXB6DjL5ZKiHhcUqfZM6M2KCVB5vSXEQ4jMosHWuF4dD5pwowfzL4fmJz5FaJHh',
|
||||||
|
);
|
||||||
|
assert.strictEqual(w.getFingerprint(2), '2C0908B6');
|
||||||
|
assert.strictEqual(w.getCustomDerivationPathForCosigner(2), path);
|
||||||
|
assert.ok(!w.getPassphrase(2));
|
||||||
|
|
||||||
|
w.replaceCosignerXpubWithSeed(
|
||||||
|
2,
|
||||||
|
'salon smoke bubble dolphin powder govern rival sport better arrest certain manual',
|
||||||
|
'9WDdFSZX4d6mPxkr',
|
||||||
|
);
|
||||||
|
assert.strictEqual(w.getCosigner(2), 'salon smoke bubble dolphin powder govern rival sport better arrest certain manual');
|
||||||
|
assert.strictEqual(w.getFingerprint(2), '2C0908B6');
|
||||||
|
assert.strictEqual(w.getCustomDerivationPathForCosigner(2), path);
|
||||||
|
assert.strictEqual(w.getPassphrase(2), '9WDdFSZX4d6mPxkr');
|
||||||
|
|
||||||
|
// test that after deleting cosinger with passphrase, it has been cleaned out properly
|
||||||
|
w.deleteCosigner('2C0908B6');
|
||||||
|
assert.ok(!w.getCosigner(2));
|
||||||
|
assert.ok(!w.getFingerprint(2));
|
||||||
|
assert.ok(!w.getCustomDerivationPathForCosigner(2));
|
||||||
|
assert.ok(!w.getPassphrase(2));
|
||||||
|
assert.strictEqual(w.getN(), 1);
|
||||||
|
assert.strictEqual(w.getM(), 2);
|
||||||
|
|
||||||
|
// after chaning first cosigner, make sure that he changed, not the second one
|
||||||
|
w.replaceCosignerXpubWithSeed(1, process.env.MNEMONICS_COBO);
|
||||||
|
assert.strictEqual(w.getCosigner(1), process.env.MNEMONICS_COBO);
|
||||||
|
assert.strictEqual(w.getFingerprint(1), fp1cobo);
|
||||||
|
assert.strictEqual(w.getCustomDerivationPathForCosigner(1), path);
|
||||||
|
assert.strictEqual(w.getPassphrase(1), undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can sign valid tx if we have more keys than quorum ("Too many signatures" error)', async () => {
|
it('can sign valid tx if we have more keys than quorum ("Too many signatures" error)', async () => {
|
||||||
|
@ -1829,6 +1923,42 @@ describe('multisig-wallet (native segwit)', () => {
|
||||||
assert.strictEqual(psbt.data.inputs.length, 2);
|
assert.strictEqual(psbt.data.inputs.length, 2);
|
||||||
assert.strictEqual(psbt.data.outputs.length, 1);
|
assert.strictEqual(psbt.data.outputs.length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can generate proper addresses for wallets with passphrases. Export and import such wallet', () => {
|
||||||
|
// 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.getPassphrase(1), '9WDdFSZX4d6mPxkr');
|
||||||
|
assert.strictEqual(w.getPassphrase(2), 'E5jMAzsf464Hgwns');
|
||||||
|
assert.strictEqual(w.getPassphrase(3), 'RyBFfLr7weK3nDUG');
|
||||||
|
assert.strictEqual(w._getExternalAddressByIndex(0), 'bc1q8rks34ypj5edxx82f7z7yzy4qy6dynfhcftjs9axzr2ml37p4pfs7j4uvm');
|
||||||
|
assert.strictEqual(w._getInternalAddressByIndex(0), 'bc1qjpjgumzs2afrr3mk85anwdnzd9qg5hc5p6f62un4umpyf4ccde5q4cywgy');
|
||||||
|
|
||||||
|
const w2 = new MultisigHDWallet();
|
||||||
|
w2.setSecret(w.getSecret());
|
||||||
|
|
||||||
|
assert.strictEqual(w._getExternalAddressByIndex(0), w2._getExternalAddressByIndex(0));
|
||||||
|
assert.strictEqual(w._getExternalAddressByIndex(1), w2._getExternalAddressByIndex(1));
|
||||||
|
assert.strictEqual(w.getPassphrase(1), w2.getPassphrase(1));
|
||||||
|
assert.strictEqual(w.getPassphrase(2), w2.getPassphrase(2));
|
||||||
|
assert.strictEqual(w.getPassphrase(3), w2.getPassphrase(3));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('multisig-cosigner', () => {
|
describe('multisig-cosigner', () => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue