FIX: PSBT with HW wallets flow (closes #1822)

This commit is contained in:
Overtorment 2020-09-16 17:52:10 +01:00
parent bedf44e6f8
commit ec30394ed5
13 changed files with 101 additions and 34 deletions

View file

@ -41,7 +41,7 @@ class DeeplinkSchemaMatch {
event.url = event.url.substring(11);
}
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) {
if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(event.url)) {
RNFS.readFile(event.url)
.then(file => {
if (file) {
@ -203,13 +203,23 @@ class DeeplinkSchemaMatch {
}
static isTXNFile(filePath) {
return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('.txn');
return (
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
filePath.toLowerCase().endsWith('.txn')
);
}
static isPossiblySignedPSBTFile(filePath) {
return (
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
filePath.toLowerCase().endsWith('-signed.psbt')
);
}
static isPossiblyPSBTFile(filePath) {
return (
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
filePath.toLowerCase().endsWith('-signed.psbt')
filePath.toLowerCase().endsWith('.psbt')
);
}

View file

@ -170,7 +170,7 @@
"details_next": "Next",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "note to self",
"details_scan": "Scan",
"details_total_exceeds_balance": "The sending amount exceeds the available balance.",

View file

@ -170,7 +170,7 @@
"details_next": "Next",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "catatan pribadi",
"details_scan": "Pindai",
"details_total_exceeds_balance": "Jumlah yang dikirim melebihi saldo.",

View file

@ -170,7 +170,7 @@
"details_next": "次",
"details_no_maximum": "選択したウォレットは、最大残高の自動計算に対応していません。このウォレットを選択してもよろしいですか?",
"details_no_multiple": "選択したウォレットは、複数の受信者へのビットコインの送信をサポートしていません。このウォレットを選択してもよろしいですか?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "ラベル",
"details_scan": "読取り",
"details_total_exceeds_balance": "送金額が利用可能残額を超えています。",

View file

@ -170,7 +170,7 @@
"details_next": "Volgende",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "notitie voor mezelf",
"details_scan": "Scan",
"details_total_exceeds_balance": "Het verzendingsbedrag overschrijdt het beschikbare saldo.",

View file

@ -170,7 +170,7 @@
"details_next": "Ďalej",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "poznámka pre seba",
"details_scan": "Skenovať",
"details_total_exceeds_balance": "Čiastka, ktorú chcete poslať, presahuje dostupný zostatok.",

View file

@ -170,7 +170,7 @@
"details_next": "Nästa",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "egen notering",
"details_scan": "Skanna",
"details_total_exceeds_balance": "Beloppet överstiger plånbokens tillgängliga belopp",

View file

@ -170,7 +170,7 @@
"details_next": "ถัดไป",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "หมายเหตุถึงตัวท่านเอง",
"details_scan": "สแกน",
"details_total_exceeds_balance": "จำนวนเงินที่จะส่งเกินเงินที่มี.",

View file

@ -170,7 +170,7 @@
"details_next": "Next",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "kendime not",
"details_scan": "Tara",
"details_total_exceeds_balance": "Gönderme miktarı mevcut bakiyeyi aşıyor.",

View file

@ -170,7 +170,7 @@
"details_next": "Next",
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
"details_note_placeholder": "消息",
"details_scan": "扫描",
"details_total_exceeds_balance": "余额不足",

View file

@ -772,32 +772,51 @@ export default class SendDetails extends Component {
);
};
/**
* watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
* so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
* user whether he wants to broadcast it.
* alternatively, user can export psbt file, sign it externally and then import it
*
* @returns {Promise<void>}
*/
importTransaction = async () => {
if (this.state.fromWallet.type !== WatchOnlyWallet.type) {
alert('Error: importing transaction in non-watchonly wallet (this should never happen)');
return;
}
try {
const res = await DocumentPicker.pick({
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles],
});
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(res.uri)) {
// we assume that transaction is already signed, so all we have to do is get txhex and pass it to next screen
// so user can broadcast:
const file = await RNFS.readFile(res.uri, 'ascii');
const bufferDecoded = Buffer.from(file, 'ascii').toString('base64');
if (bufferDecoded) {
if (this.state.fromWallet.type === WatchOnlyWallet.type) {
// watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
// so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
// user whether he wants to broadcast it.
// alternatively, user can export psbt file, sign it externally and then import it
this.props.navigation.navigate('PsbtWithHardwareWallet', {
memo: this.state.memo,
fromWallet: this.state.fromWallet,
psbt: file,
});
this.setState({ isLoading: false });
return;
}
} else {
throw new Error();
}
const psbt = bitcoin.Psbt.fromBase64(file);
const txhex = psbt.extractTransaction().toHex();
this.props.navigation.navigate('PsbtWithHardwareWallet', {
memo: this.state.memo,
fromWallet: this.state.fromWallet,
txhex,
});
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
} else if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
// looks like transaction is UNsigned, so we construct PSBT object and pass to next screen
// so user can do smth with it:
const file = await RNFS.readFile(res.uri, 'ascii');
const psbt = bitcoin.Psbt.fromBase64(file);
this.props.navigation.navigate('PsbtWithHardwareWallet', {
memo: this.state.memo,
fromWallet: this.state.fromWallet,
psbt,
});
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
} else if (DeeplinkSchemaMatch.isTXNFile(res.uri)) {
// plain text file with txhex ready to broadcast
const file = await RNFS.readFile(res.uri, 'ascii');
this.props.navigation.navigate('PsbtWithHardwareWallet', {
memo: this.state.memo,
@ -805,7 +824,8 @@ export default class SendDetails extends Component {
txhex: file,
});
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
return;
} else {
alert('Unrecognized file format');
}
} catch (err) {
if (!DocumentPicker.isCancel(err)) {

View file

@ -173,7 +173,7 @@ export default class PsbtWithHardwareWallet extends Component {
}
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
// this looks like NOT base64, so maybe its transaction's hex
this.setState({ txhex: ret.data }, () => this.props.navigation.dangerouslyGetParent().pop());
this.setState({ txhex: ret.data });
return;
}
try {
@ -201,11 +201,20 @@ export default class PsbtWithHardwareWallet extends Component {
}
static getDerivedStateFromProps(nextProps, prevState) {
if (!prevState.psbt && !nextProps.route.params.txhex) {
alert('There is no transaction signing in progress');
return {
...prevState,
isLoading: true,
};
}
const deepLinkPSBT = nextProps.route.params.deepLinkPSBT;
const txhex = nextProps.route.params.txhex;
if (deepLinkPSBT) {
const psbt = bitcoin.Psbt.fromBase64(deepLinkPSBT);
try {
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, deepLinkPSBT);
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, psbt);
return {
...prevState,
txhex: Tx.toHex(),

View file

@ -220,4 +220,32 @@ describe('unit - DeepLinkSchemaMatch', function () {
});
assert.strictEqual(encoded, 'bitcoin:1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH?amount=20.3&label=Foobar');
});
it('recognizes files', () => {
// txn files:
assert.ok(DeeplinkSchemaMatch.isTXNFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'));
assert.ok(!DeeplinkSchemaMatch.isPossiblySignedPSBTFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'));
assert.ok(DeeplinkSchemaMatch.isTXNFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'));
assert.ok(
!DeeplinkSchemaMatch.isPossiblySignedPSBTFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'),
);
// psbt files (signed):
assert.ok(
DeeplinkSchemaMatch.isPossiblySignedPSBTFile(
'content://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt',
),
);
assert.ok(
DeeplinkSchemaMatch.isPossiblySignedPSBTFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt'),
);
assert.ok(!DeeplinkSchemaMatch.isTXNFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt'));
assert.ok(!DeeplinkSchemaMatch.isTXNFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt'));
// psbt files (unsigned):
assert.ok(DeeplinkSchemaMatch.isPossiblyPSBTFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex.psbt'));
assert.ok(DeeplinkSchemaMatch.isPossiblyPSBTFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex.psbt'));
});
});