summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/tip.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-09-08 19:27:08 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-09-08 19:27:08 +0530
commitb9e43e652e606706a81f39d4f888477580de79b0 (patch)
treed6840162173a9c97414c3662a7ad43bf7349f1d6 /packages/taler-wallet-core/src/operations/tip.ts
parentb063382d25d1ed8572ebe2f52bf54247379300d5 (diff)
downloadwallet-core-b9e43e652e606706a81f39d4f888477580de79b0.tar.gz
wallet-core-b9e43e652e606706a81f39d4f888477580de79b0.tar.bz2
wallet-core-b9e43e652e606706a81f39d4f888477580de79b0.zip
fix tipping and adjust DB
Diffstat (limited to 'packages/taler-wallet-core/src/operations/tip.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts131
1 files changed, 72 insertions, 59 deletions
diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts
index 6fe374bf0..6ccd262b0 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -31,6 +31,9 @@ import {
updateRetryInfoTimeout,
WithdrawalSourceType,
TipPlanchet,
+ CoinRecord,
+ CoinSourceType,
+ CoinStatus,
} from "../types/dbTypes";
import {
getExchangeWithdrawalInfo,
@@ -40,13 +43,14 @@ import {
} from "./withdraw";
import { updateExchangeFromUrl } from "./exchanges";
import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
-import { guardOperationException } from "./errors";
+import { guardOperationException, makeErrorDetails } from "./errors";
import { NotificationType } from "../types/notifications";
import { getTimestampNow } from "../util/time";
import { readSuccessResponseJsonOrThrow } from "../util/http";
import { URL } from "../util/url";
import { Logger } from "../util/logging";
import { checkDbInvariant } from "../util/invariants";
+import { TalerErrorCode } from "../TalerErrorCode";
const logger = new Logger("operations/tip.ts");
@@ -99,7 +103,7 @@ export async function prepareTip(
walletTipId: walletTipId,
acceptedTimestamp: undefined,
rejectedTimestamp: undefined,
- amount,
+ tipAmountRaw: amount,
deadline: tipPickupStatus.expiration,
exchangeUrl: tipPickupStatus.exchange_url,
merchantBaseUrl: res.merchantBaseUrl,
@@ -109,10 +113,10 @@ export async function prepareTip(
response: undefined,
createdTimestamp: getTimestampNow(),
merchantTipId: res.merchantTipId,
- totalFees: Amounts.add(
+ tipAmountEffective: Amounts.sub(amount, Amounts.add(
withdrawDetails.overhead,
withdrawDetails.withdrawFee,
- ).amount,
+ ).amount).amount,
retryInfo: initRetryInfo(),
lastError: undefined,
denomsSel: denomSelectionInfoToState(selectedDenoms),
@@ -122,10 +126,10 @@ export async function prepareTip(
const tipStatus: PrepareTipResult = {
accepted: !!tipRecord && !!tipRecord.acceptedTimestamp,
- amount: Amounts.stringify(tipPickupStatus.tip_amount),
+ tipAmountRaw: Amounts.stringify(tipPickupStatus.tip_amount),
exchangeBaseUrl: tipPickupStatus.exchange_url,
expirationTimestamp: tipPickupStatus.expiration,
- totalFees: Amounts.stringify(tipRecord.totalFees),
+ tipAmountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
walletTipId: tipRecord.walletTipId,
};
@@ -182,13 +186,13 @@ async function resetTipRetry(
async function processTipImpl(
ws: InternalWalletState,
- tipId: string,
+ walletTipId: string,
forceNow: boolean,
): Promise<void> {
if (forceNow) {
- await resetTipRetry(ws, tipId);
+ await resetTipRetry(ws, walletTipId);
}
- let tipRecord = await ws.db.get(Stores.tips, tipId);
+ let tipRecord = await ws.db.get(Stores.tips, walletTipId);
if (!tipRecord) {
return;
}
@@ -216,7 +220,7 @@ async function processTipImpl(
planchets.push(r);
}
}
- await ws.db.mutate(Stores.tips, tipId, (r) => {
+ await ws.db.mutate(Stores.tips, walletTipId, (r) => {
if (!r.planchets) {
r.planchets = planchets;
}
@@ -224,7 +228,7 @@ async function processTipImpl(
});
}
- tipRecord = await ws.db.get(Stores.tips, tipId);
+ tipRecord = await ws.db.get(Stores.tips, walletTipId);
checkDbInvariant(!!tipRecord, "tip record should be in database");
checkDbInvariant(!!tipRecord.planchets, "tip record should have planchets");
@@ -246,55 +250,68 @@ async function processTipImpl(
codecForTipResponse(),
);
- if (response.reserve_sigs.length !== tipRecord.planchets.length) {
+ if (response.blind_sigs.length !== tipRecord.planchets.length) {
throw Error("number of tip responses does not match requested planchets");
}
- const withdrawalGroupId = encodeCrock(getRandomBytes(32));
- const planchets: PlanchetRecord[] = [];
-
- for (let i = 0; i < tipRecord.planchets.length; i++) {
- const tipPlanchet = tipRecord.planchets[i];
- const coinEvHash = await ws.cryptoApi.hashEncoded(tipPlanchet.coinEv);
- const planchet: PlanchetRecord = {
- blindingKey: tipPlanchet.blindingKey,
- coinEv: tipPlanchet.coinEv,
- coinPriv: tipPlanchet.coinPriv,
- coinPub: tipPlanchet.coinPub,
- coinValue: tipPlanchet.coinValue,
- denomPub: tipPlanchet.denomPub,
- denomPubHash: tipPlanchet.denomPubHash,
- reservePub: response.reserve_pub,
- withdrawSig: response.reserve_sigs[i].reserve_sig,
- isFromTip: true,
- coinEvHash,
- coinIdx: i,
- withdrawalDone: false,
- withdrawalGroupId: withdrawalGroupId,
- lastError: undefined,
- };
- planchets.push(planchet);
- }
+ const newCoinRecords: CoinRecord[] = [];
- const withdrawalGroup: WithdrawalGroupRecord = {
- exchangeBaseUrl: tipRecord.exchangeUrl,
- source: {
- type: WithdrawalSourceType.Tip,
- tipId: tipRecord.walletTipId,
- },
- timestampStart: getTimestampNow(),
- withdrawalGroupId: withdrawalGroupId,
- rawWithdrawalAmount: tipRecord.amount,
- retryInfo: initRetryInfo(),
- timestampFinish: undefined,
- lastError: undefined,
- denomsSel: tipRecord.denomsSel,
- };
+ for (let i = 0; i < response.blind_sigs.length; i++) {
+ const blindedSig = response.blind_sigs[i].blind_sig;
+
+ const planchet = tipRecord.planchets[i];
+
+ const denomSig = await ws.cryptoApi.rsaUnblind(
+ blindedSig,
+ planchet.blindingKey,
+ planchet.denomPub,
+ );
+
+ const isValid = await ws.cryptoApi.rsaVerify(
+ planchet.coinPub,
+ denomSig,
+ planchet.denomPub,
+ );
+
+ if (!isValid) {
+ await ws.db.runWithWriteTransaction([Stores.planchets], async (tx) => {
+ const tipRecord = await tx.get(Stores.tips, walletTipId);
+ if (!tipRecord) {
+ return;
+ }
+ tipRecord.lastError = makeErrorDetails(
+ TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID,
+ "invalid signature from the exchange (via merchant tip) after unblinding",
+ {},
+ );
+ await tx.put(Stores.tips, tipRecord);
+ });
+ return;
+ }
+
+ newCoinRecords.push({
+ blindingKey: planchet.blindingKey,
+ coinPriv: planchet.coinPriv,
+ coinPub: planchet.coinPub,
+ coinSource: {
+ type: CoinSourceType.Tip,
+ coinIndex: i,
+ walletTipId: walletTipId,
+ },
+ currentAmount: planchet.coinValue,
+ denomPub: planchet.denomPub,
+ denomPubHash: planchet.denomPubHash,
+ denomSig: denomSig,
+ exchangeBaseUrl: tipRecord.exchangeUrl,
+ status: CoinStatus.Fresh,
+ suspended: false,
+ });
+ }
await ws.db.runWithWriteTransaction(
- [Stores.tips, Stores.withdrawalGroups],
+ [Stores.coins, Stores.tips, Stores.withdrawalGroups],
async (tx) => {
- const tr = await tx.get(Stores.tips, tipId);
+ const tr = await tx.get(Stores.tips, walletTipId);
if (!tr) {
return;
}
@@ -303,16 +320,12 @@ async function processTipImpl(
}
tr.pickedUp = true;
tr.retryInfo = initRetryInfo(false);
-
await tx.put(Stores.tips, tr);
- await tx.put(Stores.withdrawalGroups, withdrawalGroup);
- for (const p of planchets) {
- await tx.put(Stores.planchets, p);
+ for (const cr of newCoinRecords) {
+ await tx.put(Stores.coins, cr);
}
},
);
-
- await processWithdrawGroup(ws, withdrawalGroupId);
}
export async function acceptTip(