FIX: scan Cobo vault signed transaction QR

This commit is contained in:
Overtorment 2020-08-24 12:53:07 +01:00
parent bd77ad1dbd
commit a6d45a25af
4 changed files with 54 additions and 21 deletions

View file

@ -942,15 +942,24 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
* Combines 2 PSBTs into final transaction from which you can
* get HEX and broadcast
*
* @param base64one {string}
* @param base64two {string}
* @param base64one {string|Psbt}
* @param base64two {string|Psbt}
* @returns {Transaction}
*/
combinePsbt(base64one, base64two) {
const final1 = bitcoin.Psbt.fromBase64(base64one);
const final2 = bitcoin.Psbt.fromBase64(base64two);
const final1 = typeof base64one === 'string' ? bitcoin.Psbt.fromBase64(base64one) : base64one;
const final2 = typeof base64two === 'string' ? bitcoin.Psbt.fromBase64(base64two) : base64two;
final1.combine(final2);
return final1.finalizeAllInputs().extractTransaction();
let extractedTransaction;
try {
extractedTransaction = final1.finalizeAllInputs().extractTransaction();
} catch (_) {
// probably already finalized
extractedTransaction = final1.extractTransaction();
}
return extractedTransaction;
}
/**

View file

@ -698,7 +698,6 @@ export default class SendDetails extends Component {
memo: this.state.memo,
fromWallet: this.state.fromWallet,
psbt: file,
isFirstPSBTAlreadyBase64: true,
});
this.setState({ isLoading: false });
return;

View file

@ -34,7 +34,6 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import RNFS from 'react-native-fs';
import DocumentPicker from 'react-native-document-picker';
import { decodeUR, extractSingleWorkload } from 'bc-ur/dist';
import { Psbt } from 'bitcoinjs-lib';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
const EV = require('../../blue_modules/events');
@ -150,8 +149,8 @@ export default class PsbtWithHardwareWallet extends Component {
if (this.state.animatedQRCodeData.length === total) {
const payload = decodeUR(this.state.animatedQRCodeData.map(i => i.data));
const psbtB64 = Buffer.from(payload, 'hex').toString('base64');
const psbt = Psbt.fromBase64(psbtB64);
this.setState({ txhex: psbt.extractTransaction().toHex() });
const Tx = this._combinePSBT(psbtB64);
this.setState({ txhex: Tx.toHex() });
}
},
);
@ -162,14 +161,13 @@ export default class PsbtWithHardwareWallet extends Component {
};
_combinePSBT = receivedPSBT => {
return this.state.fromWallet.combinePsbt(
this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64(),
receivedPSBT,
);
return this.state.fromWallet.combinePsbt(this.state.psbt, receivedPSBT);
};
onBarScanned = ret => {
if (ret && !ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
this.props.navigation.dangerouslyGetParent().pop();
return this._onReadUniformResource(ret.data);
}
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
@ -193,7 +191,6 @@ export default class PsbtWithHardwareWallet extends Component {
memo: props.route.params.memo,
psbt: props.route.params.psbt,
fromWallet: props.route.params.fromWallet,
isFirstPSBTAlreadyBase64: props.route.params.isFirstPSBTAlreadyBase64,
isSecondPSBTAlreadyBase64: false,
deepLinkPSBT: undefined,
txhex: props.route.params.txhex || undefined,
@ -207,10 +204,7 @@ export default class PsbtWithHardwareWallet extends Component {
const txhex = nextProps.route.params.txhex;
if (deepLinkPSBT) {
try {
const Tx = prevState.fromWallet.combinePsbt(
prevState.isFirstPSBTAlreadyBase64 ? prevState.psbt : prevState.psbt.toBase64(),
deepLinkPSBT,
);
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, deepLinkPSBT);
return {
...prevState,
txhex: Tx.toHex(),
@ -293,7 +287,7 @@ export default class PsbtWithHardwareWallet extends Component {
exportPSBT = async () => {
if (Platform.OS === 'ios') {
const filePath = RNFS.TemporaryDirectoryPath + `/${this.fileName}`;
await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64());
await RNFS.writeFile(filePath, typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64());
Share.open({
url: 'file://' + filePath,
})
@ -313,7 +307,7 @@ export default class PsbtWithHardwareWallet extends Component {
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('Storage Permission: Granted');
const filePath = RNFS.DownloadDirectoryPath + `/${this.fileName}`;
await RNFS.writeFile(filePath, this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64());
await RNFS.writeFile(filePath, typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64());
alert(loc.formatString(loc.send.txSaved, { filePath: this.fileName }));
} else {
console.log('Storage Permission: Denied');
@ -424,7 +418,7 @@ export default class PsbtWithHardwareWallet extends Component {
<BlueSpacing20 />
<View style={styles.copyToClipboard}>
<BlueCopyToClipboardButton
stringToCopy={this.state.isFirstPSBTAlreadyBase64 ? this.state.psbt : this.state.psbt.toBase64()}
stringToCopy={typeof this.state.psbt === 'string' ? this.state.psbt : this.state.psbt.toBase64()}
displayText={loc.send.psbt_clipboard}
/>
</View>

View file

@ -1,5 +1,7 @@
/* global it, describe */
import { WatchOnlyWallet } from '../../class';
import { decodeUR } from 'bc-ur/dist';
import { Psbt } from 'bitcoinjs-lib';
const assert = require('assert');
describe('Watch only wallet', () => {
@ -170,6 +172,11 @@ describe('Watch only wallet', () => {
Tx.toHex(),
'02000000000101805b8c2457c38afae8bcb5688fd20af8acf820d0b027507a3d41842a039e3b7f000000000000000080028813000000000000160014c0cebcd6c3d3ca8c75dc5ec62ebe55330ef910e2b739000000000000160014e73a921eeb94a4ad470c0cbdb69ebbea05bc1e0c0247304402203d1f736733539df3ea64989fc94c1d3367165bc3d9a829d20ac7c278f959d9aa022056e1affe4ffdb4ba786419b57dfeeaafa750ee8a69229881a699ad4644345e8101210358e1db384dc3e0a8aea11ee41ed993b4567b297401fbccdbe754002cb2714f1f00000000',
);
// checking that combine can work with both base64 and pure Psbt objects
const Tx2 = w.combinePsbt(Psbt.fromBase64(unsignedPsbt), Psbt.fromBase64(signedPsbt));
assert.strictEqual(Tx2.toHex(), Tx.toHex());
});
it('ypub watch-only can generate addresses', async () => {
@ -188,3 +195,27 @@ describe('Watch only wallet', () => {
assert.ok(w.getAllExternalAddresses().includes(await w._getExternalAddressByIndex(0)));
});
});
describe('BC-UR', () => {
it('can decodeUR() and then combine unfinalized signed PSBT', () => {
const unsignedPayload = decodeUR([
'UR:BYTES/TYQ4XURNVF607QGQWYPQQQQQQ9U63JU4AD5C93Y057WNRNTV24AE8QK4DDHVT04GHTKNQZCXYHNW5QGQQQQQPLHLLLLS9LRRQQQQQQQQQQTQQ9P9YMAAVV5GVUNKD49W4GDNJ4C9GJP7383QFCQQQQQQQQQPVQQ5CXKNG9PNTGMDRV0GNWNJZS23KGG3V0KXQQQQQQQQQYQ375XRQQQQQQQQQQTQQ98UXJHTKAHE83Q8W5VGHH2G93698VZLP6PZQCPXW47RAFD36W04SNHNTZK8CLCWHXDJJRRZ2EP998STFNRYWFQPC0CC3N8X87Z5QQQGQQQQQZQQQQQQSQQQQQQQQQQQQQQQYGPQY5M4J23F3Z9TK6HZTRDD6M89QX955DEH3HXGXAC6NJQMT3CHYTJHRZXVUCLC2SQQPQQQQQQGQQQQQZQQZQQQQQQQQQQQQQ3QYQK6E2MCA75ZCRMMWZYWXNQKGKNNJC7JUXPNWR5QPYQC3EYRM4NDQ5VGENNRLP2QQQYQQQQQPQQQQQQGQQQQQQQQZQQQQQQQ6GYX3G',
]);
const uPsbtB64 = Buffer.from(unsignedPayload, 'hex').toString('base64');
const payloadSignedButNotFinalized = decodeUR([
'UR:BYTES/TR58QUMZWNLSZQR3QGQQQQQP0X5VH90TDXPVFRA8N5CU6MZ40WFC94TTDMZMA296A5CQKP39UM4QZQQQQQQ0ALLLLUP0CCCQQQQQQQQQZCQPGFFXL0TR9ZR8YANDFT42RVU4WP2YS05FUGZWQQQQQQQQQQTQQ9XP456PGV66XMGMR6YM5US5Z5DJZYTRA3SQQQQQQQPZQGPXW47RAFD36W04SNHNTZK8CLCWHXDJJRRZ2EP998STFNRYWFQPC068XPZQYGRH45ESDZ623KSNPTY2VJ37LWA2HTCCLGSDWPDDEPK48JAKNSVZTQPZQD0W5ND2M7D62YYQ74A85DRKM8ESQS2WSTZ5F4V2YNNGY9S7F0NSQYQQQQQ7VRJ5Z',
]);
const sPsbtB64 = Buffer.from(payloadSignedButNotFinalized, 'hex').toString('base64');
const w = new WatchOnlyWallet();
w.setSecret('zpub6s2RJ9qAEBW8Abhojs6LyDzF7gttcDr6EsR3Umu2aptZBb45e734rGtt4KqsCMmNyR1EEzUU2ugdVYez2VywQvAbBjUSKn8ho4Zk2c5otkk');
w.init();
const tx = w.combinePsbt(uPsbtB64, sPsbtB64);
assert.strictEqual(
tx.toHex(),
'0200000000010179a8cb95eb6982c48fa79d31cd6c557b9382d56b6ec5bea8baed300b0625e6ea0100000000feffffff02fc630000000000001600142526fbd63288672766d4aeaa1b3957054483e89e204e000000000000160014c1ad3414335a36d1b1e89ba7214151b211163ec602473044022077ad33068b4a8da130ac8a64a3efbbaabaf18fa20d705adc86d53cbb69c18258022035eea4daadf9ba51080f57a7a3476d9f300414e82c544d58a24e682161e4be700121026757c3ea5b1d39f584ef358ac7c7f0eb99b290c625642529e0b4cc6472401c3f00000000',
);
});
});