diff options
author | Florian Dold <florian@dold.me> | 2022-08-09 15:00:45 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-08-16 17:55:12 +0200 |
commit | ac8f116780a860c8f4acfdf5553bf90d76afe236 (patch) | |
tree | 38abecb5ad3a3660161909ee9ca229d4ce08eb4a /packages/taler-wallet-core/src/operations/peer-to-peer.ts | |
parent | fb8372dfbf27b7b4e8b2fe4f81aa2ba18bfcf638 (diff) | |
download | wallet-core-ac8f116780a860c8f4acfdf5553bf90d76afe236.tar.xz |
implement peer to peer push payments
Diffstat (limited to 'packages/taler-wallet-core/src/operations/peer-to-peer.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/peer-to-peer.ts | 149 |
1 files changed, 74 insertions, 75 deletions
diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts b/packages/taler-wallet-core/src/operations/peer-to-peer.ts index 658cbe4f7..4d2f2bb5f 100644 --- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts +++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2019 GNUnet e.V. + (C) 2022 GNUnet e.V. 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 @@ -30,35 +30,35 @@ import { codecForAmountString, codecForAny, codecForExchangeGetContractResponse, + constructPayPushUri, ContractTermsUtil, decodeCrock, Duration, eddsaGetPublic, encodeCrock, ExchangePurseMergeRequest, + getRandomBytes, InitiatePeerPushPaymentRequest, InitiatePeerPushPaymentResponse, j2s, Logger, + parsePayPushUri, strcmp, TalerProtocolTimestamp, UnblindedSignature, WalletAccountMergeFlags, } from "@gnu-taler/taler-util"; -import { url } from "inspector"; import { CoinStatus, + MergeReserveInfo, OperationStatus, - ReserveRecord, ReserveRecordStatus, + WithdrawalGroupRecord, } from "../db.js"; -import { - checkSuccessResponseOrThrow, - readSuccessResponseJsonOrThrow, - throwUnexpectedRequestError, -} from "../util/http.js"; +import { readSuccessResponseJsonOrThrow } from "../util/http.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { checkDbInvariant } from "../util/invariants.js"; +import { internalCreateWithdrawalGroup } from "./withdraw.js"; const logger = new Logger("operations/peer-to-peer.ts"); @@ -265,6 +265,10 @@ export async function initiatePeerToPeerPush( mergePriv: mergePair.priv, pursePub: pursePair.pub, exchangeBaseUrl: coinSelRes.exchangeBaseUrl, + talerUri: constructPayPushUri({ + exchangeBaseUrl: coinSelRes.exchangeBaseUrl, + contractPriv: econtractResp.contractPriv, + }), }; } @@ -281,26 +285,19 @@ export async function checkPeerPushPayment( ws: InternalWalletState, req: CheckPeerPushPaymentRequest, ): Promise<CheckPeerPushPaymentResponse> { - const getPurseUrl = new URL( - `purses/${req.pursePub}/deposit`, - req.exchangeBaseUrl, - ); + // FIXME: Check if existing record exists! - const contractPub = encodeCrock( - eddsaGetPublic(decodeCrock(req.contractPriv)), - ); + const uri = parsePayPushUri(req.talerUri); - const purseHttpResp = await ws.http.get(getPurseUrl.href); + if (!uri) { + throw Error("got invalid taler://pay-push URI"); + } - const purseStatus = await readSuccessResponseJsonOrThrow( - purseHttpResp, - codecForExchangePurseStatus(), - ); + const exchangeBaseUrl = uri.exchangeBaseUrl; + const contractPriv = uri.contractPriv; + const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv))); - const getContractUrl = new URL( - `contracts/${contractPub}`, - req.exchangeBaseUrl, - ); + const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl); const contractHttpResp = await ws.http.get(getContractUrl.href); @@ -309,22 +306,36 @@ export async function checkPeerPushPayment( codecForExchangeGetContractResponse(), ); + const pursePub = contractResp.purse_pub; + const dec = await ws.cryptoApi.decryptContractForMerge({ ciphertext: contractResp.econtract, - contractPriv: req.contractPriv, - pursePub: req.pursePub, + contractPriv: contractPriv, + pursePub: pursePub, }); + const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl); + + const purseHttpResp = await ws.http.get(getPurseUrl.href); + + const purseStatus = await readSuccessResponseJsonOrThrow( + purseHttpResp, + codecForExchangePurseStatus(), + ); + + const peerPushPaymentIncomingId = encodeCrock(getRandomBytes(32)); + await ws.db .mktx((x) => ({ peerPushPaymentIncoming: x.peerPushPaymentIncoming, })) .runReadWrite(async (tx) => { await tx.peerPushPaymentIncoming.add({ - contractPriv: req.contractPriv, - exchangeBaseUrl: req.exchangeBaseUrl, + peerPushPaymentIncomingId, + contractPriv: contractPriv, + exchangeBaseUrl: exchangeBaseUrl, mergePriv: dec.mergePriv, - pursePub: req.pursePub, + pursePub: pursePub, timestampAccepted: TalerProtocolTimestamp.now(), contractTerms: dec.contractTerms, }); @@ -333,6 +344,7 @@ export async function checkPeerPushPayment( return { amount: purseStatus.balance, contractTerms: dec.contractTerms, + peerPushPaymentIncomingId, }; } @@ -343,9 +355,9 @@ export function talerPaytoFromExchangeReserve( const url = new URL(exchangeBaseUrl); let proto: string; if (url.protocol === "http:") { - proto = "taler+http"; + proto = "taler-reserve-http"; } else if (url.protocol === "https:") { - proto = "taler"; + proto = "taler-reserve"; } else { throw Error(`unsupported exchange base URL protocol (${url.protocol})`); } @@ -365,69 +377,45 @@ export async function acceptPeerPushPayment( const peerInc = await ws.db .mktx((x) => ({ peerPushPaymentIncoming: x.peerPushPaymentIncoming })) .runReadOnly(async (tx) => { - return tx.peerPushPaymentIncoming.get([ - req.exchangeBaseUrl, - req.pursePub, - ]); + return tx.peerPushPaymentIncoming.get(req.peerPushPaymentIncomingId); }); if (!peerInc) { - throw Error("can't accept unknown incoming p2p push payment"); + throw Error( + `can't accept unknown incoming p2p push payment (${req.peerPushPaymentIncomingId})`, + ); } const amount = Amounts.parseOrThrow(peerInc.contractTerms.amount); - // We have to create the key pair outside of the transaction, + // We have to eagerly create the key pair outside of the transaction, // due to the async crypto API. const newReservePair = await ws.cryptoApi.createEddsaKeypair({}); - const reserve: ReserveRecord | undefined = await ws.db + const mergeReserveInfo: MergeReserveInfo = await ws.db .mktx((x) => ({ exchanges: x.exchanges, - reserves: x.reserves, + withdrawalGroups: x.withdrawalGroups, })) .runReadWrite(async (tx) => { - const ex = await tx.exchanges.get(req.exchangeBaseUrl); + const ex = await tx.exchanges.get(peerInc.exchangeBaseUrl); checkDbInvariant(!!ex); - if (ex.currentMergeReservePub) { - return await tx.reserves.get(ex.currentMergeReservePub); + if (ex.currentMergeReserveInfo) { + return ex.currentMergeReserveInfo; } - const rec: ReserveRecord = { - exchangeBaseUrl: req.exchangeBaseUrl, - // FIXME: field will be removed in the future, folded into withdrawal/p2p record. - reserveStatus: ReserveRecordStatus.Dormant, - timestampCreated: TalerProtocolTimestamp.now(), - instructedAmount: Amounts.getZero(amount.currency), - currency: amount.currency, - reservePub: newReservePair.pub, + await tx.exchanges.put(ex); + ex.currentMergeReserveInfo = { reservePriv: newReservePair.priv, - timestampBankConfirmed: undefined, - timestampReserveInfoPosted: undefined, - // FIXME! - initialDenomSel: undefined as any, - // FIXME! - initialWithdrawalGroupId: "", - initialWithdrawalStarted: false, - lastError: undefined, - operationStatus: OperationStatus.Pending, - retryInfo: undefined, - bankInfo: undefined, - restrictAge: undefined, - senderWire: undefined, + reservePub: newReservePair.pub, }; - await tx.reserves.put(rec); - return rec; + return ex.currentMergeReserveInfo; }); - if (!reserve) { - throw Error("can't create reserve"); - } - const mergeTimestamp = TalerProtocolTimestamp.now(); const reservePayto = talerPaytoFromExchangeReserve( - reserve.exchangeBaseUrl, - reserve.reservePub, + peerInc.exchangeBaseUrl, + mergeReserveInfo.reservePub, ); const sigRes = await ws.cryptoApi.signPurseMerge({ @@ -442,12 +430,12 @@ export async function acceptPeerPushPayment( purseFee: Amounts.stringify(Amounts.getZero(amount.currency)), pursePub: peerInc.pursePub, reservePayto, - reservePriv: reserve.reservePriv, + reservePriv: mergeReserveInfo.reservePriv, }); const mergePurseUrl = new URL( - `purses/${req.pursePub}/merge`, - req.exchangeBaseUrl, + `purses/${peerInc.pursePub}/merge`, + peerInc.exchangeBaseUrl, ); const mergeReq: ExchangePurseMergeRequest = { @@ -459,6 +447,17 @@ export async function acceptPeerPushPayment( const mergeHttpReq = await ws.http.postJson(mergePurseUrl.href, mergeReq); + logger.info(`merge request: ${j2s(mergeReq)}`); const res = await readSuccessResponseJsonOrThrow(mergeHttpReq, codecForAny()); - logger.info(`merge result: ${j2s(res)}`); + logger.info(`merge response: ${j2s(res)}`); + + await internalCreateWithdrawalGroup(ws, { + amount, + exchangeBaseUrl: peerInc.exchangeBaseUrl, + reserveStatus: ReserveRecordStatus.QueryingStatus, + reserveKeyPair: { + priv: mergeReserveInfo.reservePriv, + pub: mergeReserveInfo.reservePub, + }, + }); } |