summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-04-19 17:12:43 +0200
committerFlorian Dold <florian@dold.me>2022-04-27 00:50:17 +0200
commita165afa6824980c409d7c2e22e24171e536800e0 (patch)
tree0e8491f092aba2280655ee4728fef0ca02bb8387 /packages/taler-wallet-core/src/operations
parent9b85d139bf7bdc360ea0894e09f6115cd9d472d8 (diff)
downloadwallet-core-a165afa6824980c409d7c2e22e24171e536800e0.tar.gz
wallet-core-a165afa6824980c409d7c2e22e24171e536800e0.tar.bz2
wallet-core-a165afa6824980c409d7c2e22e24171e536800e0.zip
wallet-core: implement age restriction support
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts19
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts24
-rw-r--r--packages/taler-wallet-core/src/operations/reserves.ts21
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.test.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts15
6 files changed, 67 insertions, 20 deletions
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index 26bca8c14..39edd6307 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -33,6 +33,7 @@ import {
ExchangeSignKeyJson,
ExchangeWireJson,
hashDenomPub,
+ j2s,
LibtoolVersion,
Logger,
NotificationType,
@@ -445,6 +446,7 @@ async function downloadExchangeKeysInfo(
);
logger.info("received /keys response");
+ logger.info(`${j2s(exchangeKeysJsonUnchecked)}`);
if (exchangeKeysJsonUnchecked.denoms.length === 0) {
throw TalerError.fromDetail(
diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts
index fa36c724f..a1773547a 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -26,6 +26,7 @@
*/
import {
AbsoluteTime,
+ AgeRestriction,
AmountJson,
Amounts,
codecForContractTerms,
@@ -197,6 +198,14 @@ export interface CoinSelectionRequest {
maxWireFee: AmountJson;
maxDepositFee: AmountJson;
+
+ /**
+ * Minimum age requirement for the coin selection.
+ *
+ * When present, only select coins with either no age restriction
+ * or coins with an age commitment that matches the minimum age.
+ */
+ minimumAge?: number;
}
/**
@@ -651,6 +660,7 @@ export function extractContractData(
merchant: parsedContractTerms.merchant,
products: parsedContractTerms.products,
summaryI18n: parsedContractTerms.summary_i18n,
+ minimumAge: parsedContractTerms.minimum_age,
};
}
@@ -825,6 +835,8 @@ async function processDownloadProposalImpl(
proposalResp.sig,
);
+ logger.trace(`extracted contract data: ${j2s(contractData)}`);
+
await ws.db
.mktx((x) => ({ proposals: x.proposals, purchases: x.purchases }))
.runReadWrite(async (tx) => {
@@ -1379,6 +1391,11 @@ export async function generateDepositPermissions(
const { coin, denom } = coinWithDenom[i];
let wireInfoHash: string;
wireInfoHash = contractData.wireInfoHash;
+ logger.trace(
+ `signing deposit permission for coin with acp=${j2s(
+ coin.ageCommitmentProof,
+ )}`,
+ );
const dp = await ws.cryptoApi.signDepositPermission({
coinPriv: coin.coinPriv,
coinPub: coin.coinPub,
@@ -1393,6 +1410,8 @@ export async function generateDepositPermissions(
spendAmount: payCoinSel.coinContributions[i],
timestamp: contractData.timestamp,
wireInfoHash,
+ ageCommitmentProof: coin.ageCommitmentProof,
+ requiredMinimumAge: contractData.minimumAge,
});
depositPermissions.push(dp);
}
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index 10584fb94..215676118 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -15,6 +15,8 @@
*/
import {
+ AgeCommitment,
+ AgeRestriction,
CoinPublicKeyString,
DenomKeyType,
encodeCrock,
@@ -22,7 +24,9 @@ import {
ExchangeProtocolVersion,
ExchangeRefreshRevealRequest,
getRandomBytes,
+ HashCodeString,
HttpStatusCode,
+ j2s,
TalerProtocolTimestamp,
} from "@gnu-taler/taler-util";
import {
@@ -83,6 +87,7 @@ import { GetReadWriteAccess } from "../util/query.js";
import { guardOperationException } from "./common.js";
import { CryptoApiStoppedError } from "../crypto/workers/cryptoDispatcher.js";
import { TalerCryptoInterface } from "../crypto/cryptoImplementation.js";
+import { TalerError } from "../errors.js";
const logger = new Logger("refresh.ts");
@@ -380,6 +385,7 @@ async function refreshMelt(
meltCoinPriv: oldCoin.coinPriv,
meltCoinPub: oldCoin.coinPub,
feeRefresh: oldDenom.feeRefresh,
+ meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
newCoinDenoms,
sessionSecretSeed: refreshSession.sessionSecretSeed,
});
@@ -388,6 +394,14 @@ async function refreshMelt(
`coins/${oldCoin.coinPub}/melt`,
oldCoin.exchangeBaseUrl,
);
+
+ let maybeAch: HashCodeString | undefined;
+ if (oldCoin.ageCommitmentProof) {
+ maybeAch = AgeRestriction.hashCommitment(
+ oldCoin.ageCommitmentProof.commitment,
+ );
+ }
+
const meltReqBody: ExchangeMeltRequest = {
coin_pub: oldCoin.coinPub,
confirm_sig: derived.confirmSig,
@@ -395,6 +409,7 @@ async function refreshMelt(
denom_sig: oldCoin.denomSig,
rc: derived.hash,
value_with_fee: Amounts.stringify(derived.meltValueWithFee),
+ age_commitment_hash: maybeAch,
};
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
@@ -475,6 +490,7 @@ export async function assembleRefreshRevealRequest(args: {
denomPubHash: string;
count: number;
}[];
+ oldAgeCommitment?: AgeCommitment;
}): Promise<ExchangeRefreshRevealRequest> {
const {
derived,
@@ -517,6 +533,7 @@ export async function assembleRefreshRevealRequest(args: {
transfer_privs: privs,
transfer_pub: derived.transferPubs[norevealIndex],
link_sigs: linkSigs,
+ old_age_commitment: args.oldAgeCommitment?.publicKeys,
};
return req;
}
@@ -622,6 +639,7 @@ async function refreshReveal(
meltCoinPub: oldCoin.coinPub,
feeRefresh: oldDenom.feeRefresh,
newCoinDenoms,
+ meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
sessionSecretSeed: refreshSession.sessionSecretSeed,
});
@@ -637,6 +655,7 @@ async function refreshReveal(
norevealIndex: norevealIndex,
oldCoinPriv: oldCoin.coinPriv,
oldCoinPub: oldCoin.coinPub,
+ oldAgeCommitment: oldCoin.ageCommitmentProof?.commitment,
});
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
@@ -822,6 +841,11 @@ async function processRefreshGroupImpl(
logger.info(
"crypto API stopped while processing refresh group, probably the wallet is currently shutting down.",
);
+ } else if (x instanceof TalerError) {
+ logger.warn("process refresh session got exception (TalerError)");
+ logger.warn(`exc ${x}`);
+ logger.warn(`exc stack ${x.stack}`);
+ logger.warn(`error detail: ${j2s(x.errorDetail)}`);
} else {
logger.warn("process refresh session got exception");
logger.warn(`exc ${x}`);
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts
index ff09d1a50..8e606bd60 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -200,6 +200,7 @@ export async function createReserve(
lastError: undefined,
currency: req.amount.currency,
operationStatus: OperationStatus.Pending,
+ restrictAge: req.restrictAge,
};
const exchangeInfo = await updateExchangeFromUrl(ws, req.exchange);
@@ -541,12 +542,9 @@ async function updateReserve(
const reserveUrl = new URL(`reserves/${reservePub}`, reserve.exchangeBaseUrl);
reserveUrl.searchParams.set("timeout_ms", "200");
- const resp = await ws.http.get(
- reserveUrl.href,
- {
- timeout: getReserveRequestTimeout(reserve),
- },
- );
+ const resp = await ws.http.get(reserveUrl.href, {
+ timeout: getReserveRequestTimeout(reserve),
+ });
const result = await readSuccessResponseJsonOrErrorCode(
resp,
@@ -632,17 +630,12 @@ async function updateReserve(
amountReservePlus,
amountReserveMinus,
).amount;
- const denomSel = selectWithdrawalDenominations(
- remainingAmount,
- denoms,
- );
+ const denomSel = selectWithdrawalDenominations(remainingAmount, denoms);
logger.trace(
`Remaining unclaimed amount in reseve is ${Amounts.stringify(
remainingAmount,
- )} and can be withdrawn with ${
- denomSel.selectedDenoms.length
- } coins`,
+ )} and can be withdrawn with ${denomSel.selectedDenoms.length} coins`,
);
if (denomSel.selectedDenoms.length === 0) {
@@ -759,6 +752,7 @@ export async function createTalerWithdrawReserve(
selectedExchange: string,
options: {
forcedDenomSel?: ForcedDenomSel;
+ restrictAge?: number;
} = {},
): Promise<AcceptWithdrawalResponse> {
await updateExchangeFromUrl(ws, selectedExchange);
@@ -774,6 +768,7 @@ export async function createTalerWithdrawReserve(
exchange: selectedExchange,
senderWire: withdrawInfo.senderWire,
exchangePaytoUri: exchangePaytoUri,
+ restrictAge: options.restrictAge,
});
// We do this here, as the reserve should be registered before we return,
// so that we can redirect the user to the bank's status page.
diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts b/packages/taler-wallet-core/src/operations/withdraw.test.ts
index e5894a3e7..9f9146719 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.test.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts
@@ -32,6 +32,7 @@ test("withdrawal selection bug repro", (t) => {
cipher: DenomKeyType.Rsa,
rsa_public_key:
"040000XT67C8KBD6B75TTQ3SK8FWXMNQW4372T3BDDGPAMB9RFCA03638W8T3F71WFEFK9NP32VKYVNFXPYRWQ1N1HDKV5J0DFEKHBPJCYSWCBJDRNWD7G8BN8PT97FA9AMV75MYEK4X54D1HGJ207JSVJBGFCATSPNTEYNHEQF1F220W00TBZR1HNPDQFD56FG0DJQ9KGHM8EC33H6AY9YN9CNX5R3Z4TZ4Q23W47SBHB13H6W74FQJG1F50X38VRSC4SR8RWBAFB7S4K8D2H4NMRFSQT892A3T0BTBW7HM5C0H2CK6FRKG31F7W9WP1S29013K5CXYE55CT8TH6N8J9B780R42Y5S3ZB6J6E9H76XBPSGH4TGYSR2VZRB98J417KCQMZKX1BB67E7W5KVE37TC9SJ904002",
+ age_mask: 0,
},
denomPubHash:
"Q21FQSSG4FXNT96Z14CHXM8N1RZAG9GPHAV8PRWS0PZAAVWH7PBW6R97M2CH19KKP65NNSWXY7B6S53PT3CBM342E357ZXDDJ8RDVW8",
@@ -86,6 +87,7 @@ test("withdrawal selection bug repro", (t) => {
cipher: DenomKeyType.Rsa,
rsa_public_key:
"040000Y63CF78QFPKRY77BRK9P557Q1GQWX3NCZ3HSYSK0Z7TT0KGRA7N4SKBKEHSTVHX1Z9DNXMJR4EXSY1TXCKV0GJ3T3YYC6Z0JNMJFVYQAV4FX5J90NZH1N33MZTV8HS9SMNAA9S6K73G4P99GYBB01B0P6M1KXZ5JRDR7VWBR3MEJHHGJ6QBMCJR3NWJRE3WJW9PRY8QPQ2S7KFWTWRESH2DBXCXWBD2SRN6P9YX8GRAEMFEGXC9V5GVJTEMH6ZDGNXFPWZE3JVJ2Q4N9GDYKBCHZCJ7M7M2RJ9ZV4Y64NAN9BT6XDC68215GKKRHTW1BBF1MYY6AR3JCTT9HYAM923RMVQR3TAEB7SDX8J76XRZWYH3AGJCZAQGMN5C8SSH9AHQ9RNQJQ15CN45R37X4YNFJV904002",
+ age_mask: 0,
},
denomPubHash:
@@ -141,6 +143,7 @@ test("withdrawal selection bug repro", (t) => {
cipher: DenomKeyType.Rsa,
rsa_public_key:
"040000YDESWC2B962DA4WK356SC50MA3N9KV0ZSGY3RC48JCTY258W909C7EEMT5BTC5KZ5T4CERCZ141P9QF87EK2BD1XEEM5GB07MB3H19WE4CQGAS8X84JBWN83PQGQXVMWE5HFA992KMGHC566GT9ZS2QPHZB6X89C4A80Z663PYAAPXP728VHAKATGNNBQ01ZZ2XD1CH9Y38YZBSPJ4K7GB2J76GBCYAVD9ENHDVWXJAXYRPBX4KSS5TXRR3K5NEN9ZV3AJD2V65K7ABRZDF5D5V1FJZZMNJ5XZ4FEREEKEBV9TDFPGJTKDEHEC60K3DN24DAATRESDJ1ZYYSYSRCAT4BT2B62ARGVMJTT5N2R126DRW9TGRWCW0ZAF2N2WET1H4NJEW77X0QT46Z5R3MZ0XPHD04002",
+ age_mask: 0,
},
denomPubHash:
"JS61DTKAFM0BX8Q4XV3ZSKB921SM8QK745Z2AFXTKFMBHHFNBD8TQ5ETJHFNDGBGX22FFN2A2ERNYG1SGSDQWNQHQQ2B14DBVJYJG8R",
@@ -195,6 +198,7 @@ test("withdrawal selection bug repro", (t) => {
cipher: DenomKeyType.Rsa,
rsa_public_key:
"040000YG3T1ADB8DVA6BD3EPV6ZHSHTDW35DEN4VH1AE6CSB7P1PSDTNTJG866PHF6QB1CCWYCVRGA0FVBJ9Q0G7KV7AD9010GDYBQH0NNPHW744MTNXVXWBGGGRGQGYK4DTYN1DSWQ1FZNDSZZPB5BEKG2PDJ93NX2JTN06Y8QMS2G734Z9XHC10EENBG2KVB7EJ3CM8PV1T32RC7AY62F3496E8D8KRHJQQTT67DSGMNKK86QXVDTYW677FG27DP20E8XY3M6FQD53NDJ1WWES91401MV1A3VXVPGC76GZVDD62W3WTJ1YMKHTTA3MRXX3VEAAH3XTKDN1ER7X6CZPMYTF8VK735VP2B2TZGTF28TTW4FZS32SBS64APCDF6SZQ427N5538TJC7SRE71YSP5ET8GS904002",
+ age_mask: 0,
},
denomPubHash:
@@ -250,6 +254,7 @@ test("withdrawal selection bug repro", (t) => {
cipher: DenomKeyType.Rsa,
rsa_public_key:
"040000ZC0G60E9QQ5PD81TSDWD9GV5Y6P8Z05NSPA696DP07NGQQVSRQXBA76Q6PRB0YFX295RG4MTQJXAZZ860ET307HSC2X37XAVGQXRVB8Q4F1V7NP5ZEVKTX75DZK1QRAVHEZGQYKSSH6DBCJNQF6V9WNQF3GEYVA4KCBHA7JF772KHXM9642C28Z0AS4XXXV2PABAN5C8CHYD5H7JDFNK3920W5Q69X0BS84XZ4RE2PW6HM1WZ6KGZ3MKWWWCPKQ1FSFABRBWKAB09PF563BEBXKY6M38QETPH5EDWGANHD0SC3QV0WXYVB7BNHNNQ0J5BNV56K563SYHM4E5ND260YRJSYA1GN5YSW2B1J5T1A1EBNYF2DN6JNJKWXWEQ42G5YS17ZSZ5EWDRA9QKV8EGTCNAD04002",
+ age_mask: 0,
},
denomPubHash:
"A41HW0Q2H9PCNMEWW0C0N45QAYVXZ8SBVRRAHE4W6X24SV1TH38ANTWDT80JXEBW9Z8PVPGT9GFV2EYZWJ5JW5W1N34NFNKHQSZ1PFR",
@@ -304,6 +309,7 @@ test("withdrawal selection bug repro", (t) => {
cipher: DenomKeyType.Rsa,
rsa_public_key:
"040000ZSK2PMVY6E3NBQ52KXMW029M60F4BWYTDS0FZSD0PE53CNZ9H6TM3GQK1WRTEKQ5GRWJ1J9DY6Y42SP47QVT1XD1G0W05SQ5F3F7P5KSWR0FJBJ9NZBXQEVN8Q4JRC94X3JJ3XV3KBYTZ2HTDFV28C3H2SRR0XGNZB4FY85NDZF1G4AEYJJ9QB3C0V8H70YB8RV3FKTNH7XS4K4HFNZHJ5H9VMX5SM9Z2DX37HA5WFH0E2MJBVVF2BWWA5M0HPPSB365RAE2AMD42Q65A96WD80X27SB2ZNQZ8WX0K13FWF85GZ6YNYAJGE1KGN06JDEKE9QD68Z651D7XE8V6664TVVC8M68S7WD0DSXMJQKQ0BNJXNDE29Q7MRX6DA3RW0PZ44B3TKRK0294FPVZTNSTA6XF04002",
+ age_mask: 0,
},
denomPubHash:
"F5NGBX33DTV4595XZZVK0S2MA1VMXFEJQERE5EBP5DS4QQ9EFRANN7YHWC1TKSHT2K6CQWDBRES8D3DWR0KZF5RET40B4AZXZ0RW1ZG",
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index d4ca58401..94f8e20b9 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -266,8 +266,6 @@ export function selectForcedWithdrawalDenominations(
denoms: DenominationRecord[],
forcedDenomSel: ForcedDenomSel,
): DenomSelectionState {
- let remaining = Amounts.copy(amountAvailable);
-
const selectedDenoms: {
count: number;
denomPubHash: string;
@@ -454,6 +452,7 @@ async function processPlanchetGenerate(
value: denom.value,
coinIndex: coinIdx,
secretSeed: withdrawalGroup.secretSeed,
+ restrictAge: reserve.restrictAge,
});
const newPlanchet: PlanchetRecord = {
blindingKey: r.blindingKey,
@@ -467,6 +466,7 @@ async function processPlanchetGenerate(
withdrawalDone: false,
withdrawSig: r.withdrawSig,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
+ ageCommitmentProof: r.ageCommitmentProof,
lastError: undefined,
};
await ws.db
@@ -701,6 +701,7 @@ async function processPlanchetVerifyAndStoreCoin(
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
},
suspended: false,
+ ageCommitmentProof: planchet.ageCommitmentProof,
};
const planchetCoinPub = planchet.coinPub;
@@ -1101,11 +1102,6 @@ export async function getExchangeWithdrawalInfo(
}
}
- const withdrawFee = Amounts.sub(
- selectedDenoms.totalWithdrawCost,
- selectedDenoms.totalCoinValue,
- ).amount;
-
const ret: ExchangeWithdrawDetails = {
earliestDepositExpiration,
exchangeInfo: exchange,
@@ -1127,6 +1123,10 @@ export async function getExchangeWithdrawalInfo(
return ret;
}
+export interface GetWithdrawalDetailsForUriOpts {
+ restrictAge?: number;
+}
+
/**
* Get more information about a taler://withdraw URI.
*
@@ -1137,6 +1137,7 @@ export async function getExchangeWithdrawalInfo(
export async function getWithdrawalDetailsForUri(
ws: InternalWalletState,
talerWithdrawUri: string,
+ opts: GetWithdrawalDetailsForUriOpts = {},
): Promise<WithdrawUriInfoResponse> {
logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`);
const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);