From 474a171f5e6684ab7d11bb2987fc90fe6e1b37c8 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 6 Jun 2023 15:00:10 +0200 Subject: wallet-core: deterministic p2p contract encryption --- packages/taler-util/src/taler-crypto.ts | 6 +- .../src/crypto/cryptoImplementation.ts | 2 + .../taler-wallet-core/src/crypto/cryptoTypes.ts | 3 + packages/taler-wallet-core/src/db.ts | 13 +++ .../src/operations/pay-peer-pull-credit.ts | 4 + .../src/operations/pay-peer-push-debit.ts | 116 +++++++++++++-------- 6 files changed, 98 insertions(+), 46 deletions(-) (limited to 'packages') diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts index c4eb925f7..ed3b82cd0 100644 --- a/packages/taler-util/src/taler-crypto.ts +++ b/packages/taler-util/src/taler-crypto.ts @@ -1465,6 +1465,7 @@ export function encryptContractForMerge( contractPriv: ContractPrivateKey, mergePriv: MergePrivateKey, contractTerms: any, + nonce: EncryptionNonce, ): Promise { const contractTermsCanon = canonicalJson(contractTerms) + "\0"; const contractTermsBytes = stringToBytes(contractTermsCanon); @@ -1476,13 +1477,14 @@ export function encryptContractForMerge( contractTermsCompressed, ]); const key = keyExchangeEcdhEddsa(contractPriv, pursePub); - return encryptWithDerivedKey(getRandomBytesF(24), key, data, mergeSalt); + return encryptWithDerivedKey(nonce, key, data, mergeSalt); } export function encryptContractForDeposit( pursePub: PursePublicKey, contractPriv: ContractPrivateKey, contractTerms: any, + nonce: EncryptionNonce, ): Promise { const contractTermsCanon = canonicalJson(contractTerms) + "\0"; const contractTermsBytes = stringToBytes(contractTermsCanon); @@ -1493,7 +1495,7 @@ export function encryptContractForDeposit( contractTermsCompressed, ]); const key = keyExchangeEcdhEddsa(contractPriv, pursePub); - return encryptWithDerivedKey(getRandomBytesF(24), key, data, depositSalt); + return encryptWithDerivedKey(nonce, key, data, depositSalt); } export interface DecryptForMergeResult { diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts index c0c8e0d01..15ca1ea95 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -1495,6 +1495,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { decodeCrock(req.contractPriv), decodeCrock(req.mergePriv), req.contractTerms, + decodeCrock(req.nonce), ); const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT) .put(hash(enc)) @@ -1531,6 +1532,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { decodeCrock(req.pursePub), decodeCrock(req.contractPriv), req.contractTerms, + decodeCrock(req.nonce), ); const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_ECONTRACT) .put(hash(enc)) diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts index 930db03a8..7eaa4a55a 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts @@ -181,6 +181,7 @@ export interface EncryptContractRequest { pursePub: string; pursePriv: string; mergePriv: string; + nonce: string; } export interface EncryptContractResponse { @@ -195,6 +196,8 @@ export interface EncryptContractForDepositRequest { pursePub: string; pursePriv: string; + + nonce: string; } export interface EncryptContractForDepositResponse { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index a5db49649..c4cdf769b 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1839,6 +1839,14 @@ export interface PeerPushPaymentInitiationRecord { contractPriv: string; contractPub: string; + /** + * 24 byte nonce. + */ + contractEncNonce: string; + + /** + * FIXME: Put those in a different object store! + */ contractTerms: PeerContractTerms; purseExpiration: TalerProtocolTimestamp; @@ -1911,6 +1919,11 @@ export interface PeerPullPaymentInitiationRecord { contractPub: string; contractPriv: string; + contractEncNonce: string; + + /** + * FIXME: Put in separate object store! + */ contractTerms: PeerContractTerms; mergeTimestamp: TalerPreciseTimestamp; diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index a85df66d2..fc2718ce5 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -405,6 +405,7 @@ export async function processPeerPullCredit( contractTerms: pullIni.contractTerms, pursePriv: pullIni.pursePriv, pursePub: pullIni.pursePub, + nonce: pullIni.contractEncNonce, }); const purseExpiration = pullIni.contractTerms.purse_expiration; @@ -690,6 +691,8 @@ export async function initiatePeerPullPayment( const mergeReserveRowId = mergeReserveInfo.rowId; checkDbInvariant(!!mergeReserveRowId); + const contractEncNonce = encodeCrock(getRandomBytes(24)); + const wi = await getExchangeWithdrawalInfo( ws, exchangeBaseUrl, @@ -711,6 +714,7 @@ export async function initiatePeerPullPayment( status: PeerPullPaymentInitiationStatus.PendingCreatePurse, contractTerms: contractTerms, mergeTimestamp, + contractEncNonce, mergeReserveRowId: mergeReserveRowId, contractPriv: contractKeyPair.priv, contractPub: contractKeyPair.pub, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts index ac0aa9c87..a03980d46 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -34,6 +34,10 @@ import { TransactionMinorState, TransactionState, TransactionType, + decodeCrock, + encodeCrock, + getRandomBytes, + hash, j2s, stringifyTalerUri, } from "@gnu-taler/taler-util"; @@ -57,11 +61,7 @@ import { OperationAttemptResultType, constructTaskIdentifier, } from "../util/retries.js"; -import { - runLongpollAsync, - spendCoins, - runOperationWithErrorReporting, -} from "./common.js"; +import { runLongpollAsync, spendCoins } from "./common.js"; import { constructTransactionIdentifier, notifyTransition, @@ -69,6 +69,7 @@ import { } from "./transactions.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkLogicInvariant } from "../util/invariants.js"; +import { EncryptContractRequest } from "../crypto/cryptoTypes.js"; const logger = new Logger("pay-peer-push-debit.ts"); @@ -100,6 +101,7 @@ async function processPeerPushDebitCreateReserve( ws: InternalWalletState, peerPushInitiation: PeerPushPaymentInitiationRecord, ): Promise { + logger.info("processing peer-push-debit pending(create-reserve)"); const pursePub = peerPushInitiation.pursePub; const purseExpiration = peerPushInitiation.purseExpiration; const hContractTerms = peerPushInitiation.contractTermsHash; @@ -124,32 +126,49 @@ async function processPeerPushDebitCreateReserve( coins, }); - const econtractResp = await ws.cryptoApi.encryptContractForMerge({ + const encryptContractRequest: EncryptContractRequest = { contractTerms: peerPushInitiation.contractTerms, mergePriv: peerPushInitiation.mergePriv, pursePriv: peerPushInitiation.pursePriv, pursePub: peerPushInitiation.pursePub, contractPriv: peerPushInitiation.contractPriv, contractPub: peerPushInitiation.contractPub, - }); + nonce: peerPushInitiation.contractEncNonce, + }; + + logger.info(`encrypt contract request: ${j2s(encryptContractRequest)}`); + + const econtractResp = await ws.cryptoApi.encryptContractForMerge( + encryptContractRequest, + ); + + const econtractHash = encodeCrock( + hash(decodeCrock(econtractResp.econtract.econtract)), + ); + + logger.info(`econtract hash: ${econtractHash}`); const createPurseUrl = new URL( `purses/${peerPushInitiation.pursePub}/create`, peerPushInitiation.exchangeBaseUrl, ); + const reqBody = { + amount: peerPushInitiation.amount, + merge_pub: peerPushInitiation.mergePub, + purse_sig: purseSigResp.sig, + h_contract_terms: hContractTerms, + purse_expiration: purseExpiration, + deposits: depositSigsResp.deposits, + min_age: 0, + econtract: econtractResp.econtract, + }; + + logger.info(`request body: ${j2s(reqBody)}`); + const httpResp = await ws.http.fetch(createPurseUrl.href, { method: "POST", - body: { - amount: peerPushInitiation.amount, - merge_pub: peerPushInitiation.mergePub, - purse_sig: purseSigResp.sig, - h_contract_terms: hContractTerms, - purse_expiration: purseExpiration, - deposits: depositSigsResp.deposits, - min_age: 0, - econtract: econtractResp.econtract, - }, + body: reqBody, }); const resp = await httpResp.json(); @@ -157,24 +176,16 @@ async function processPeerPushDebitCreateReserve( logger.info(`resp: ${j2s(resp)}`); if (httpResp.status !== HttpStatusCode.Ok) { + // FIXME: do proper error reporting throw Error("got error response from exchange"); } - await ws.db - .mktx((x) => [x.peerPushPaymentInitiations]) - .runReadWrite(async (tx) => { - const ppi = await tx.peerPushPaymentInitiations.get(pursePub); - if (!ppi) { - return; - } - ppi.status = PeerPushPaymentInitiationStatus.Done; - await tx.peerPushPaymentInitiations.put(ppi); - }); + await transitionPeerPushDebitTransaction(ws, pursePub, { + stFrom: PeerPushPaymentInitiationStatus.PendingCreatePurse, + stTo: PeerPushPaymentInitiationStatus.PendingReady, + }); - return { - type: OperationAttemptResultType.Finished, - result: undefined, - }; + return OperationAttemptResult.finishedEmpty(); } async function processPeerPushDebitAbortingDeletePurse( @@ -278,6 +289,7 @@ async function transitionPeerPushDebitTransaction( const oldTxState = computePeerPushDebitTransactionState(ppiRec); ppiRec.status = transitionSpec.stTo; const newTxState = computePeerPushDebitTransactionState(ppiRec); + // FIXME: We don't transition here?! return { oldTxState, newTxState, @@ -341,6 +353,7 @@ async function processPeerPushDebitReady( ws: InternalWalletState, peerPushInitiation: PeerPushPaymentInitiationRecord, ): Promise { + logger.info("processing peer-push-debit pending(ready)"); const pursePub = peerPushInitiation.pursePub; const retryTag = constructTaskIdentifier({ tag: PendingTaskType.PeerPushDebit, @@ -434,6 +447,12 @@ export async function processPeerPushDebit( return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation); case PeerPushPaymentInitiationStatus.AbortingRefresh: return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation); + default: { + const txState = computePeerPushDebitTransactionState(peerPushInitiation); + logger.warn( + `not processing peer-push-debit transaction in state ${j2s(txState)}`, + ); + } } return { @@ -482,7 +501,16 @@ export async function initiatePeerPushDebit( coinSelRes.result.coins, ); - await ws.db + const pursePub = pursePair.pub; + + const transactionId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); + + const contractEncNonce = encodeCrock(getRandomBytes(24)); + + const transitionInfo = await ws.db .mktx((x) => [ x.exchanges, x.contractTerms, @@ -509,7 +537,7 @@ export async function initiatePeerPushDebit( refreshReason: RefreshReason.PayPeerPush, }); - await tx.peerPushPaymentInitiations.add({ + const ppi: PeerPushPaymentInitiationRecord = { amount: Amounts.stringify(instructedAmount), contractPriv: contractKeyPair.priv, contractPub: contractKeyPair.pub, @@ -523,27 +551,28 @@ export async function initiatePeerPushDebit( timestampCreated: TalerPreciseTimestamp.now(), status: PeerPushPaymentInitiationStatus.PendingCreatePurse, contractTerms: contractTerms, + contractEncNonce, coinSel: { coinPubs: sel.coins.map((x) => x.coinPub), contributions: sel.coins.map((x) => x.contribution), }, totalCost: Amounts.stringify(totalAmount), - }); + }; + + await tx.peerPushPaymentInitiations.add(ppi); await tx.contractTerms.put({ h: hContractTerms, contractTermsRaw: contractTerms, }); - }); - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.PeerPushDebit, - pursePub: pursePair.pub, - }); - - await runOperationWithErrorReporting(ws, taskId, async () => { - return await processPeerPushDebit(ws, pursePair.pub); - }); + const newTxState = computePeerPushDebitTransactionState(ppi); + return { + oldTxState: { major: TransactionMajorState.None }, + newTxState, + }; + }); + notifyTransition(ws, transactionId, transitionInfo); return { contractPriv: contractKeyPair.priv, @@ -903,4 +932,3 @@ export function computePeerPushDebitTransactionState( }; } } - -- cgit v1.2.3