Skip to content

Commit

Permalink
FIX: errors when working with multisig seeds with passphrases (rel Bl…
Browse files Browse the repository at this point in the history
  • Loading branch information
Overtorment committed Apr 8, 2024
1 parent f65655b commit 20848e9
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 18 deletions.
18 changes: 9 additions & 9 deletions class/wallets/multisig-hd-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,15 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
/**
* Stored cosigner can be EITHER xpub (or Zpub or smth), OR mnemonic phrase. This method converts it to xpub
*
* @param cosigner {string} Zpub (or similar) or mnemonic seed
* @param index {number}
* @returns {string} xpub
* @private
*/
_getXpubFromCosigner(cosigner: string) {
protected _getXpubFromCosignerIndex(index: number) {
let cosigner: string = this._cosigners[index];
if (MultisigHDWallet.isXprvString(cosigner)) cosigner = MultisigHDWallet.convertXprvToXpub(cosigner);
let xpub = cosigner;
if (!MultisigHDWallet.isXpubString(cosigner)) {
const index = this._cosigners.indexOf(cosigner);
xpub = MultisigHDWallet.seedToXpub(
cosigner,
this._cosignersCustomPaths[index] || this._derivationPath,
Expand All @@ -289,12 +289,12 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {

_getAddressFromNode(nodeIndex: number, index: number) {
const pubkeys = [];
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
for (const [cosignerIndex] of this._cosigners.entries()) {
this._nodes[nodeIndex] = this._nodes[nodeIndex] || [];
let _node;

if (!this._nodes[nodeIndex][cosignerIndex]) {
const xpub = this._getXpubFromCosigner(cosigner);
const xpub = this._getXpubFromCosignerIndex(cosignerIndex);
const hdNode = bip32.fromBase58(xpub);
_node = hdNode.derive(nodeIndex);
this._nodes[nodeIndex][cosignerIndex] = _node;
Expand Down Expand Up @@ -725,7 +725,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
_addPsbtInput(psbt: Psbt, input: CoinSelectReturnInput, sequence: number, masterFingerprintBuffer?: Buffer) {
const bip32Derivation = []; // array per each pubkey thats gona be used
const pubkeys = [];
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
for (const [cosignerIndex] of this._cosigners.entries()) {
if (!input.address) {
throw new Error('Could not find address in input');
}
Expand All @@ -740,7 +740,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
throw new Error('Could not find derivation path for address ' + input.address);
}

const xpub = this._getXpubFromCosigner(cosigner);
const xpub = this._getXpubFromCosignerIndex(cosignerIndex);
const hdNode0 = bip32.fromBase58(xpub);
const splt = path.split('/');
const internal = +splt[splt.length - 2];
Expand Down Expand Up @@ -837,7 +837,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
_getOutputDataForChange(address: string): TOutputData {
const bip32Derivation: TBip32Derivation = []; // array per each pubkey thats gona be used
const pubkeys = [];
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
for (const [cosignerIndex] of this._cosigners.entries()) {
const path = this._getDerivationPathByAddressWithCustomPath(
address,
this._cosignersCustomPaths[cosignerIndex] || this._derivationPath,
Expand All @@ -849,7 +849,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
throw new Error('Could not find derivation path for address ' + address);
}

const xpub = this._getXpubFromCosigner(cosigner);
const xpub = this._getXpubFromCosignerIndex(cosignerIndex);
const hdNode0 = bip32.fromBase58(xpub);
const splt = path.split('/');
const internal = +splt[splt.length - 2];
Expand Down
10 changes: 8 additions & 2 deletions screen/wallets/ViewEditMultisigCosigners.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const ViewEditMultisigCosigners = ({ route }: Props) => {
const [exportString, setExportString] = useState('{}'); // used in exportCosigner()
const [exportStringURv2, setExportStringURv2] = useState(''); // used in QR
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: '', passphrase: '', path: '', fp: '', isLoading: false }); // string rendered in modal
const [askPassphrase, setAskPassphrase] = useState(false);
const [isAdvancedModeEnabledRender, setIsAdvancedModeEnabledRender] = useState(false);
const data = useRef<any[]>();
Expand Down Expand Up @@ -275,6 +275,9 @@ const ViewEditMultisigCosigners = ({ route }: Props) => {
entries={vaultKeyData.seed.split(' ')}
appendNumber
/>
{vaultKeyData.passphrase.length > 1 && (
<Text style={[styles.textDestination, stylesHook.textDestination]}>{vaultKeyData.passphrase}</Text>
)}
</>
)}
<BlueSpacing20 />
Expand Down Expand Up @@ -344,6 +347,7 @@ const ViewEditMultisigCosigners = ({ route }: Props) => {
setVaultKeyData({
keyIndex,
seed: '',
passphrase: '',
xpub,
fp,
path,
Expand Down Expand Up @@ -385,12 +389,14 @@ const ViewEditMultisigCosigners = ({ route }: Props) => {
onPress: () => {
const keyIndex = el.index + 1;
const seed = wallet.getCosigner(keyIndex);
const passphrase = wallet.getCosignerPassphrase(keyIndex);
setVaultKeyData({
keyIndex,
seed,
xpub: '',
fp: '',
path: '',
passphrase: passphrase ?? '',
isLoading: false,
});
setIsMnemonicsModalVisible(true);
Expand All @@ -400,7 +406,7 @@ const ViewEditMultisigCosigners = ({ route }: Props) => {
presentAlert({ message: 'Cannot find derivation path for this cosigner' });
return;
}
const xpub = wallet.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(seed, path));
const xpub = wallet.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(seed, path, passphrase));
setExportString(MultisigCosigner.exportToJson(fp, xpub, path));
setExportStringURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
setExportFilename('bw-cosigner-' + fp + '.json');
Expand Down
13 changes: 6 additions & 7 deletions screen/wallets/addMultisigStep2.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ const WalletsAddMultisigStep2 = () => {
setIsLoading(true);
setIsMnemonicsModalVisible(true);

// filling cache
setTimeout(() => {
// filling cache
setXpubCacheForMnemonics(w.getSecret());
Expand Down Expand Up @@ -229,7 +228,7 @@ const WalletsAddMultisigStep2 = () => {
} else {
const path = getPath();

const xpub = getXpubCacheForMnemonics(cosigner[0]);
const xpub = getXpubCacheForMnemonics(cosigner[0], cosigner[3]);
const fp = getFpCacheForMnemonics(cosigner[0], cosigner[3]);
setCosignerXpub(MultisigCosigner.exportToJson(fp, xpub, path));
setCosignerXpubURv2(encodeUR(MultisigCosigner.exportToJson(fp, xpub, path))[0]);
Expand All @@ -238,17 +237,17 @@ const WalletsAddMultisigStep2 = () => {
}
};

const getXpubCacheForMnemonics = seed => {
const getXpubCacheForMnemonics = (seed, passphrase) => {
const path = getPath();
return staticCache[seed + path] || setXpubCacheForMnemonics(seed);
return staticCache[seed + path + passphrase] || setXpubCacheForMnemonics(seed, passphrase);
};

const setXpubCacheForMnemonics = seed => {
const setXpubCacheForMnemonics = (seed, passphrase) => {
const path = getPath();
const w = new MultisigHDWallet();
w.setDerivationPath(path);
staticCache[seed + path] = w.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(seed, path));
return staticCache[seed + path];
staticCache[seed + path + passphrase] = w.convertXpubToMultisignatureXpub(MultisigHDWallet.seedToXpub(seed, path, passphrase));
return staticCache[seed + path + passphrase];
};

const getFpCacheForMnemonics = (seed, passphrase) => {
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/multisig-hd-wallet.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,59 @@ describe('multisig-wallet (native segwit)', () => {
assert.strictEqual(w.getCosignerPassphrase(3), w2.getCosignerPassphrase(3));
});

it('can work with passphrases when seeds are the same but passwords differ', () => {
// 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(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
undefined,
undefined,
'1',
);
w.addCosigner(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
undefined,
undefined,
'2',
);
w.setDerivationPath(path);
w.setM(2);

assert.strictEqual(w.getCosignerPassphrase(1), '1');
assert.strictEqual(w.getCosignerPassphrase(2), '2');

assert.strictEqual(w._getExternalAddressByIndex(0), 'bc1qhlxgpu8deq24p3hgyh0f9zkj3uxzg2hcs4ccfy65cr9fg8vm30tqdlssmv');

assert.strictEqual(
w.convertXpubToMultisignatureXpub(
MultisigHDWallet.seedToXpub(w.getCosigner(1), w.getCustomDerivationPathForCosigner(1), w.getCosignerPassphrase(1)),
),
'Zpub74GDyQuS45cpaH8C24Mfrk3Kvrtw78ZtX918Wj4T7dBQSW5BMcxiJAYh95Upjf9ywSbzomNf1SqVrzeZLpwxBjH488uWNaFWkXv1B93HRe7',
);

//

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.getCosignerPassphrase(1), w2.getCosignerPassphrase(1));
assert.strictEqual(w.getCosignerPassphrase(2), w2.getCosignerPassphrase(2));

const w3coordinator = new MultisigHDWallet();
w3coordinator.setSecret(w.getXpub());
assert.strictEqual(w3coordinator.getFingerprint(1), '126CF4F5');
assert.strictEqual(
w3coordinator.getCosigner(1),
'Zpub74GDyQuS45cpaH8C24Mfrk3Kvrtw78ZtX918Wj4T7dBQSW5BMcxiJAYh95Upjf9ywSbzomNf1SqVrzeZLpwxBjH488uWNaFWkXv1B93HRe7',
);

assert.strictEqual(w._getExternalAddressByIndex(0), w3coordinator._getExternalAddressByIndex(0));
assert.strictEqual(w._getInternalAddressByIndex(0), w3coordinator._getInternalAddressByIndex(0));
});

it('can import descriptor from Sparrow', () => {
const payload =
'UR:CRYPTO-OUTPUT/TAADMETAADMSOEADAOAOLSTAADDLOLAOWKAXHDCLAOCEBDFLNNTKJTIOJSFSURBNFXRPEEHKDLGYRTEMRPYTGYZOCASWENCYMKPAVWJKHYAAHDCXJEFTGSZOIMFEYNDYHYZEJTBAMSJEHLDSRDDIYLSRFYTSZTKNRNYLRNDPAMTLDPZCAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYUOHFJPKOAXAAAYCYCSYASAVDTAADDLOLAOWKAXHDCLAXMSZTWZDIGERYDKFSFWTYDPFNDKLNAYSWTTMUHYZTOXHSETPEWSFXPEAYWLJSDEMTAAHDCXSPLTSTDPNTLESANSUTTLPRPFHNVSPFCNMHESOYGASTLRPYVAATNNDKFYHLQZPKLEAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYWZFEPLETAXAAAYCYCPCKRENBTAADDLOLAOWKAXHDCLAOLSFWYKYLKTFHJLPYEMGLCEDPFNSNRDDSRFASEOZTGWIALFLUIYDNFXHGVESFEMMEAAHDCXHTZETLJNKPHHAYLSCXWPNDSWPSTPGTEOJKKGHDAELSKPNNBKBSYAWZJTFWNNBDKTAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYSKTPJPMSAXAAAYCYCEBKWLAMTDWZGRZE\n';
Expand Down

0 comments on commit 20848e9

Please sign in to comment.