diff options
-rw-r--r-- | packages/taler-util/package.json | 1 | ||||
-rw-r--r-- | packages/taler-util/src/bitcoin.ts | 63 | ||||
-rw-r--r-- | packages/taler-util/src/index.ts | 1 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx | 12 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx | 94 | ||||
-rw-r--r-- | pnpm-lock.yaml | 7 |
6 files changed, 165 insertions, 13 deletions
diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json index 6a46de897..b1edbaf83 100644 --- a/packages/taler-util/package.json +++ b/packages/taler-util/package.json @@ -40,6 +40,7 @@ "typescript": "^4.5.5" }, "dependencies": { + "bech32-buffer": "^0.2.0", "big-integer": "^1.6.51", "jed": "^1.1.1", "tslib": "^2.3.1" diff --git a/packages/taler-util/src/bitcoin.ts b/packages/taler-util/src/bitcoin.ts new file mode 100644 index 000000000..dd90f514e --- /dev/null +++ b/packages/taler-util/src/bitcoin.ts @@ -0,0 +1,63 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { AmountJson } from "." +import { Amounts, } from "./amounts" +import { getRandomBytes, decodeCrock, encodeCrock } from "./talerCrypto" +import { encode as segwitEncode } from "bech32-buffer" +/** + * + * @author sebasjm + */ + +export interface SegwitAddrs { + segwitAddr1: string, + segwitAddr2: string, +} + +function buf2hex(buffer: Uint8Array) { // buffer is an ArrayBuffer + return [...new Uint8Array(buffer)] + .map(x => x.toString(16).padStart(2, '0')) + .join(''); +} + +export function generateSegwitAddress(reservePub: string): SegwitAddrs { + const pub = decodeCrock(reservePub) + + const first_rnd = getRandomBytes(4) + const second_rnd = new Uint8Array(first_rnd.length) + second_rnd.set(first_rnd) + + first_rnd[0] = first_rnd[0] & 0b0111_1111 + second_rnd[0] = second_rnd[0] | 0b1000_0000 + + const first_part = new Uint8Array(first_rnd.length + pub.length / 2) + first_part.set(first_rnd, 0) + first_part.set(pub.subarray(0, 16), 4) + const second_part = new Uint8Array(first_rnd.length + pub.length / 2) + second_part.set(first_rnd, 0) + second_part.set(pub.subarray(16, 32), 4) + + return { + segwitAddr1: segwitEncode("bc", first_part), + segwitAddr2: segwitEncode("bc", second_part), + } +} + +// https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp +export function segwitMinAmount(): AmountJson { + return Amounts.parseOrThrow("BTC:0.00000294") +}
\ No newline at end of file diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index c42e5e66a..0141be13b 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -23,6 +23,7 @@ export { fnutil } from "./fnutils.js"; export * from "./kdf.js"; export * from "./talerCrypto.js"; export * from "./http-status-codes.js"; +export * from "./bitcoin.js"; export { randomBytes, secretbox, diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx index 8d7b65b3c..16ce6120a 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.stories.tsx @@ -51,3 +51,15 @@ export const IBAN = createExample(TestedComponent, { }, exchangeBaseUrl: "https://exchange.demo.taler.net", }); + +export const Bitcoin = createExample(TestedComponent, { + reservePub: "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", + payto: + "payto://bitcoin/bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00", + amount: { + currency: "BTC", + value: 0, + fraction: 14000000, + }, + exchangeBaseUrl: "https://exchange.demo.taler.net", +}); diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx index 2c0e2fd31..08326f828 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx @@ -1,4 +1,11 @@ -import { AmountJson, parsePaytoUri, i18n } from "@gnu-taler/taler-util"; +import { + AmountJson, + parsePaytoUri, + i18n, + generateSegwitAddress, + Amounts, + segwitMinAmount, +} from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType"; import { QR } from "../components/QR"; @@ -30,18 +37,62 @@ export function ReserveCreated({ </div> ); } - return ( - <Fragment> + function TransferDetails(): VNode { + if (!paytoURI) return <Fragment />; + if (paytoURI.targetType === "bitcoin") { + const { segwitAddr1, segwitAddr2 } = generateSegwitAddress(reservePub); + const min = segwitMinAmount(); + return ( + <section> + <p> + <i18n.Translate> + Bitcoin exchange need a transaction with 3 output, one output is + the exchange account and the other two are segwit fake address for + metadata with an minimum amount. Reserve pub : {reservePub} + </i18n.Translate> + </p> + <p> + <i18n.Translate> + In bitcoincore wallet use 'Add Recipient' button to add two + additional recipient and copy adresses and amounts + </i18n.Translate> + <ul> + <li> + {paytoURI.targetPath} {Amounts.stringifyValue(amount)} BTC + </li> + <li> + {segwitAddr1} {Amounts.stringifyValue(min)} BTC + </li> + <li> + {segwitAddr2} {Amounts.stringifyValue(min)} BTC + </li> + </ul> + <i18n.Translate> + In Electrum wallet paste the following three lines in 'Pay to' + field : + </i18n.Translate> + <ul> + <li> + {paytoURI.targetPath},{Amounts.stringifyValue(amount)} + </li> + <li> + {segwitAddr1},{Amounts.stringifyValue(min)} + </li> + <li> + {segwitAddr2},{Amounts.stringifyValue(min)} + </li> + </ul> + <i18n.Translate> + Make sure the amount show{" "} + {Amounts.stringifyValue(Amounts.sum([amount, min, min]).amount)}{" "} + BTC, else you have to change the base unit to BTC + </i18n.Translate> + </p> + </section> + ); + } + return ( <section> - <h1> - <i18n.Translate>Exchange is ready for withdrawal</i18n.Translate> - </h1> - <p> - <i18n.Translate> - To complete the process you need to wire - <b>{amountToString(amount)}</b> to the exchange bank account - </i18n.Translate> - </p> <BankDetailsByPaytoType amount={amountToString(amount)} exchangeBaseUrl={exchangeBaseUrl} @@ -57,10 +108,27 @@ export function ReserveCreated({ </WarningBox> </p> </section> + ); + } + + return ( + <Fragment> + <section> + <h1> + <i18n.Translate>Exchange is ready for withdrawal</i18n.Translate> + </h1> + <p> + <i18n.Translate> + To complete the process you need to wire{` `} + <b>{amountToString(amount)}</b> to the exchange bank account + </i18n.Translate> + </p> + </section> + <TransferDetails /> <section> <p> <i18n.Translate> - Alternative, you can also scan this QR code or open + Alternative, you can also scan this QR code or open{" "} <a href={payto}>this link</a> if you have a banking app installed that supports RFC 8905 </i18n.Translate> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fd68c66a..6cad1464a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -164,6 +164,7 @@ importers: specifiers: '@types/node': ^17.0.17 ava: ^4.0.1 + bech32-buffer: ^0.2.0 big-integer: ^1.6.51 esbuild: ^0.14.21 jed: ^1.1.1 @@ -172,6 +173,7 @@ importers: tslib: ^2.3.1 typescript: ^4.5.5 dependencies: + bech32-buffer: 0.2.0 big-integer: 1.6.51 jed: 1.1.1 tslib: 2.3.1 @@ -8248,6 +8250,11 @@ packages: tweetnacl: 0.14.5 dev: true + /bech32-buffer/0.2.0: + resolution: {integrity: sha512-Ez8s82a+Xnn/m3/ftGaQJUSFG4EwNIj9adIJBw8OrHASQsXgvwLSducbcJ9El0rsrwJYJ71yBhC/hZzz3FPSCQ==} + engines: {node: '>=8'} + dev: false + /better-opn/2.1.1: resolution: {integrity: sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA==} engines: {node: '>8.0.0'} |