From 5c93f15157b4fc9d0fefb6bb2a9956592ebb1ec9 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 21 Feb 2022 12:40:51 +0100 Subject: [PATCH] towards implementing breaking exchange protocol changes --- packages/taler-util/src/talerCrypto.ts | 115 +++++-- packages/taler-util/src/talerTypes.ts | 174 +++++----- packages/taler-util/src/walletTypes.ts | 309 ++++++++++-------- .../taler-wallet-cli/src/harness/harness.ts | 2 +- .../test-exchange-timetravel.ts | 4 +- .../src/crypto/cryptoTypes.ts | 29 +- .../src/crypto/workers/cryptoApi.ts | 3 +- .../crypto/workers/cryptoImplementation.ts | 237 ++++++-------- packages/taler-wallet-core/src/db.ts | 47 ++- .../src/operations/backup/import.ts | 3 +- .../src/operations/backup/index.ts | 62 ++-- .../src/operations/deposits.ts | 66 ++-- .../src/operations/exchanges.ts | 10 +- .../taler-wallet-core/src/operations/pay.ts | 10 +- .../src/operations/refresh.ts | 43 +-- .../taler-wallet-core/src/operations/tip.ts | 44 +-- .../src/operations/withdraw.test.ts | 14 +- .../src/operations/withdraw.ts | 29 +- .../src/util/coinSelection.test.ts | 14 +- 19 files changed, 558 insertions(+), 657 deletions(-) diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index c2640317a..40d2e55da 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -22,10 +22,15 @@ * Imports. */ import * as nacl from "./nacl-fast.js"; -import { kdf } from "./kdf.js"; +import { kdf, kdfKw } from "./kdf.js"; import bigint from "big-integer"; -import { DenominationPubKey, DenomKeyType } from "./talerTypes.js"; -import { AssertionError, equal } from "assert"; +import { + CoinEnvelope, + DenominationPubKey, + DenomKeyType, + HashCodeString, +} from "./talerTypes.js"; +import { Logger } from "./logging.js"; export function getRandomBytes(n: number): Uint8Array { return nacl.randomBytes(n); @@ -365,7 +370,7 @@ export type CsBlindingSecrets = { beta: [Uint8Array, Uint8Array]; }; -function typedArrayConcat(chunks: Uint8Array[]): Uint8Array { +export function typedArrayConcat(chunks: Uint8Array[]): Uint8Array { let payloadLen = 0; for (const c of chunks) { payloadLen += c.byteLength; @@ -490,9 +495,7 @@ export function deriveBSeed( * @param coinPriv coin private key * @returns nonce */ -export function deriveWithdrawNonce( - coinPriv: Uint8Array, -): Uint8Array { +export function deriveWithdrawNonce(coinPriv: Uint8Array): Uint8Array { const outLen = 32; const salt = stringToBytes("n"); return kdf(outLen, coinPriv, salt); @@ -539,7 +542,7 @@ export async function csUnblind( csSig: CsBlindSignature, ): Promise { if (b != 0 && b != 1) { - throw new AssertionError(); + throw new Error(); } const secrets = deriveSecrets(bseed); const rPubDash = (await calcRBlind(csPub, secrets, rPub))[b]; @@ -595,9 +598,38 @@ export function hash(d: Uint8Array): Uint8Array { return nacl.hash(d); } +export function hashCoinEv( + coinEv: CoinEnvelope, + denomPubHash: HashCodeString, +): Uint8Array { + const hashContext = createHashContext(); + hashContext.update(decodeCrock(denomPubHash)); + hashCoinEvInner(coinEv, hashContext); + return hashContext.finish(); +} + +const logger = new Logger("talerCrypto.ts"); + +export function hashCoinEvInner( + coinEv: CoinEnvelope, + hashState: nacl.HashState, +): void { + const hashInputBuf = new ArrayBuffer(4); + const uint8ArrayBuf = new Uint8Array(hashInputBuf); + const dv = new DataView(hashInputBuf); + dv.setUint32(0, DenomKeyType.toIntTag(coinEv.cipher)); + hashState.update(uint8ArrayBuf); + switch (coinEv.cipher) { + case DenomKeyType.Rsa: + hashState.update(decodeCrock(coinEv.rsa_blinded_planchet)); + return; + default: + throw new Error(); + } +} + /** - * Hash a denomination public key according to the - * algorithm of exchange protocol v10. + * Hash a denomination public key. */ export function hashDenomPub(pub: DenominationPubKey): Uint8Array { if (pub.cipher === DenomKeyType.Rsa) { @@ -606,18 +638,16 @@ export function hashDenomPub(pub: DenominationPubKey): Uint8Array { const uint8ArrayBuf = new Uint8Array(hashInputBuf); const dv = new DataView(hashInputBuf); dv.setUint32(0, pub.age_mask ?? 0); - dv.setUint32(4, pub.cipher); + dv.setUint32(4, DenomKeyType.toIntTag(pub.cipher)); uint8ArrayBuf.set(pubBuf, 8); return nacl.hash(uint8ArrayBuf); - } else if (pub.cipher === DenomKeyType.LegacyRsa) { - return hash(decodeCrock(pub.rsa_public_key)); } else if (pub.cipher === DenomKeyType.ClauseSchnorr) { const pubBuf = decodeCrock(pub.cs_public_key); const hashInputBuf = new ArrayBuffer(pubBuf.length + 4 + 4); const uint8ArrayBuf = new Uint8Array(hashInputBuf); const dv = new DataView(hashInputBuf); dv.setUint32(0, pub.age_mask ?? 0); - dv.setUint32(4, pub.cipher); + dv.setUint32(4, DenomKeyType.toIntTag(pub.cipher)); uint8ArrayBuf.set(pubBuf, 8); return nacl.hash(uint8ArrayBuf); } else { @@ -652,18 +682,57 @@ export interface FreshCoin { bks: Uint8Array; } +// export function setupRefreshPlanchet( +// secretSeed: Uint8Array, +// coinNumber: number, +// ): FreshCoin { +// const info = stringToBytes("taler-coin-derivation"); +// const saltArrBuf = new ArrayBuffer(4); +// const salt = new Uint8Array(saltArrBuf); +// const saltDataView = new DataView(saltArrBuf); +// saltDataView.setUint32(0, coinNumber); +// const out = kdf(64, secretSeed, salt, info); +// const coinPriv = out.slice(0, 32); +// const bks = out.slice(32, 64); +// return { +// bks, +// coinPriv, +// coinPub: eddsaGetPublic(coinPriv), +// }; +// } + +function bufferForUint32(n: number): Uint8Array { + const arrBuf = new ArrayBuffer(4); + const buf = new Uint8Array(arrBuf); + const dv = new DataView(arrBuf); + dv.setUint32(0, n); + return buf; +} + export function setupRefreshPlanchet( - secretSeed: Uint8Array, + transferSecret: Uint8Array, coinNumber: number, ): FreshCoin { - const info = stringToBytes("taler-coin-derivation"); - const saltArrBuf = new ArrayBuffer(4); - const salt = new Uint8Array(saltArrBuf); - const saltDataView = new DataView(saltArrBuf); - saltDataView.setUint32(0, coinNumber); - const out = kdf(64, secretSeed, salt, info); - const coinPriv = out.slice(0, 32); - const bks = out.slice(32, 64); + // See TALER_transfer_secret_to_planchet_secret in C impl + const planchetMasterSecret = kdfKw({ + ikm: transferSecret, + outputLength: 32, + salt: bufferForUint32(coinNumber), + info: stringToBytes("taler-coin-derivation"), + }); + + const coinPriv = kdfKw({ + ikm: planchetMasterSecret, + outputLength: 32, + salt: stringToBytes("coin"), + }); + + const bks = kdfKw({ + ikm: planchetMasterSecret, + outputLength: 32, + salt: stringToBytes("bks"), + }); + return { bks, coinPriv, diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts index 7305122bd..59d37dece 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -60,11 +60,8 @@ export class ExchangeDenomination { /** * Public signing key of the denomination. - * - * The "string" alternative is for the old exchange protocol (v9) that - * only supports RSA keys. */ - denom_pub: DenominationPubKey | string; + denom_pub: DenominationPubKey; /** * Fee for withdrawing. @@ -162,7 +159,7 @@ export interface RecoupRequest { * * The string variant is for the legacy exchange protocol. */ - denom_sig: UnblindedSignature | string; + denom_sig: UnblindedSignature; /** * Blinding key that was used during withdraw, @@ -188,7 +185,7 @@ export interface RecoupRefreshRequest { * * The string variant is for the legacy exchange protocol. */ - denom_sig: UnblindedSignature | string; + denom_sig: UnblindedSignature; /** * Coin's blinding factor. @@ -218,20 +215,13 @@ export interface RecoupConfirmation { old_coin_pub?: string; } -export type UnblindedSignature = - | RsaUnblindedSignature - | LegacyRsaUnblindedSignature; +export type UnblindedSignature = RsaUnblindedSignature; export interface RsaUnblindedSignature { cipher: DenomKeyType.Rsa; rsa_signature: string; } -export interface LegacyRsaUnblindedSignature { - cipher: DenomKeyType.LegacyRsa; - rsa_signature: string; -} - /** * Deposit permission for a single coin. */ @@ -252,7 +242,7 @@ export interface CoinDepositPermission { * The string variant is for legacy protocol support. */ - ub_sig: UnblindedSignature | string; + ub_sig: UnblindedSignature; /** * The denomination public key associated with this coin. @@ -841,9 +831,19 @@ export class TipPickupGetResponse { } export enum DenomKeyType { - Rsa = 1, - ClauseSchnorr = 2, - LegacyRsa = 3, + Rsa = "RSA", + ClauseSchnorr = "CS", +} + +export namespace DenomKeyType { + export function toIntTag(t: DenomKeyType): number { + switch (t) { + case DenomKeyType.Rsa: + return 1; + case DenomKeyType.ClauseSchnorr: + return 2; + } + } } export interface RsaBlindedDenominationSignature { @@ -851,44 +851,28 @@ export interface RsaBlindedDenominationSignature { blinded_rsa_signature: string; } -export interface LegacyRsaBlindedDenominationSignature { - cipher: DenomKeyType.LegacyRsa; - blinded_rsa_signature: string; -} - export interface CSBlindedDenominationSignature { cipher: DenomKeyType.ClauseSchnorr; } export type BlindedDenominationSignature = | RsaBlindedDenominationSignature - | CSBlindedDenominationSignature - | LegacyRsaBlindedDenominationSignature; + | CSBlindedDenominationSignature; export const codecForBlindedDenominationSignature = () => buildCodecForUnion() .discriminateOn("cipher") - .alternative(1, codecForRsaBlindedDenominationSignature()) - .alternative(3, codecForLegacyRsaBlindedDenominationSignature()) + .alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature()) .build("BlindedDenominationSignature"); export const codecForRsaBlindedDenominationSignature = () => buildCodecForObject() - .property("cipher", codecForConstNumber(1)) + .property("cipher", codecForConstString(DenomKeyType.Rsa)) .property("blinded_rsa_signature", codecForString()) .build("RsaBlindedDenominationSignature"); -export const codecForLegacyRsaBlindedDenominationSignature = () => - buildCodecForObject() - .property("cipher", codecForConstNumber(1)) - .property("blinded_rsa_signature", codecForString()) - .build("LegacyRsaBlindedDenominationSignature"); - export class WithdrawResponse { - /** - * The string variant is for legacy protocol support. - */ - ev_sig: BlindedDenominationSignature | string; + ev_sig: BlindedDenominationSignature; } /** @@ -983,10 +967,7 @@ export interface ExchangeMeltResponse { } export interface ExchangeRevealItem { - /** - * The string variant is for the legacy v9 protocol. - */ - ev_sig: BlindedDenominationSignature | string; + ev_sig: BlindedDenominationSignature; } export interface ExchangeRevealResponse { @@ -1105,26 +1086,18 @@ export interface BankWithdrawalOperationPostResponse { transfer_done: boolean; } -export type DenominationPubKey = - | RsaDenominationPubKey - | CsDenominationPubKey - | LegacyRsaDenominationPubKey; - -export interface LegacyRsaDenominationPubKey { - cipher: DenomKeyType.LegacyRsa; - rsa_public_key: string; -} +export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; export interface RsaDenominationPubKey { - cipher: DenomKeyType.Rsa; - rsa_public_key: string; - age_mask?: number; + readonly cipher: DenomKeyType.Rsa; + readonly rsa_public_key: string; + readonly age_mask?: number; } export interface CsDenominationPubKey { - cipher: DenomKeyType.ClauseSchnorr; - age_mask: number; - cs_public_key: string; + readonly cipher: DenomKeyType.ClauseSchnorr; + readonly age_mask: number; + readonly cs_public_key: string; } export namespace DenominationPubKey { @@ -1136,12 +1109,6 @@ export namespace DenominationPubKey { return -1; } else if (p1.cipher > p2.cipher) { return +1; - } - if ( - p1.cipher === DenomKeyType.LegacyRsa && - p2.cipher === DenomKeyType.LegacyRsa - ) { - return strcmp(p1.rsa_public_key, p2.rsa_public_key); } else if ( p1.cipher === DenomKeyType.Rsa && p2.cipher === DenomKeyType.Rsa @@ -1166,41 +1133,24 @@ export namespace DenominationPubKey { throw Error("unsupported cipher"); } } - - export function lift(p1: DenominationPubKey | string): DenominationPubKey { - if (typeof p1 === "string") { - return { - cipher: DenomKeyType.LegacyRsa, - rsa_public_key: p1, - }; - } - return p1; - } } export const codecForDenominationPubKey = () => buildCodecForUnion() .discriminateOn("cipher") - .alternative(1, codecForRsaDenominationPubKey()) - .alternative(2, codecForCsDenominationPubKey()) - .alternative(3, codecForLegacyRsaDenominationPubKey()) + .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey()) + .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) .build("DenominationPubKey"); export const codecForRsaDenominationPubKey = () => buildCodecForObject() - .property("cipher", codecForConstNumber(1)) + .property("cipher", codecForConstString(DenomKeyType.Rsa)) .property("rsa_public_key", codecForString()) .build("DenominationPubKey"); -export const codecForLegacyRsaDenominationPubKey = () => - buildCodecForObject() - .property("cipher", codecForConstNumber(3)) - .property("rsa_public_key", codecForString()) - .build("LegacyRsaDenominationPubKey"); - export const codecForCsDenominationPubKey = () => buildCodecForObject() - .property("cipher", codecForConstNumber(2)) + .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr)) .property("cs_public_key", codecForString()) .build("CsDenominationPubKey"); @@ -1219,10 +1169,7 @@ export type CoinPublicKeyString = string; export const codecForDenomination = (): Codec => buildCodecForObject() .property("value", codecForString()) - .property( - "denom_pub", - codecForEither(codecForDenominationPubKey(), codecForString()), - ) + .property("denom_pub", codecForDenominationPubKey()) .property("fee_withdraw", codecForString()) .property("fee_deposit", codecForString()) .property("fee_refresh", codecForString()) @@ -1470,10 +1417,7 @@ export const codecForRecoupConfirmation = (): Codec => export const codecForWithdrawResponse = (): Codec => buildCodecForObject() - .property( - "ev_sig", - codecForEither(codecForBlindedDenominationSignature(), codecForString()), - ) + .property("ev_sig", codecForBlindedDenominationSignature()) .build("WithdrawResponse"); export const codecForMerchantPayResponse = (): Codec => @@ -1491,10 +1435,7 @@ export const codecForExchangeMeltResponse = (): Codec => export const codecForExchangeRevealItem = (): Codec => buildCodecForObject() - .property( - "ev_sig", - codecForEither(codecForBlindedDenominationSignature(), codecForString()), - ) + .property("ev_sig", codecForBlindedDenominationSignature()) .build("ExchangeRevealItem"); export const codecForExchangeRevealResponse = @@ -1711,17 +1652,48 @@ export const codecForMerchantConfigResponse = .build("MerchantConfigResponse"); export enum ExchangeProtocolVersion { - V9 = 9, + /** + * Current version supported by the wallet. + */ V12 = 12, } export enum MerchantProtocolVersion { - /** - * Legacy version that is still supported. - */ - V1 = 1, /** * Current version supported by the wallet. */ V3 = 3, } + +export type CoinEnvelope = CoinEnvelopeRsa | CoinEnvelopeCs; + +export interface CoinEnvelopeRsa { + cipher: DenomKeyType.Rsa; + rsa_blinded_planchet: string; +} + +export interface CoinEnvelopeCs { + cipher: DenomKeyType.ClauseSchnorr; + // FIXME: add remaining fields +} + +export type HashCodeString = string; + +export interface ExchangeWithdrawRequest { + denom_pub_hash: HashCodeString; + reserve_sig: EddsaSignatureString; + coin_ev: CoinEnvelope; +} + +export interface ExchangeRefreshRevealRequest { + new_denoms_h: HashCodeString[]; + coin_evs: CoinEnvelope[]; + /** + * kappa - 1 transfer private keys (ephemeral ECDHE keys). + */ + transfer_privs: string[]; + + transfer_pub: EddsaPublicKeyString; + + link_sigs: EddsaSignatureString[]; +} diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 4a871e743..2219316b3 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -47,6 +47,7 @@ import { import { AmountString, codecForContractTerms, + CoinEnvelope, ContractTerms, DenominationPubKey, DenomKeyType, @@ -136,11 +137,12 @@ export interface ConfirmPayResultPending { export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending; -export const codecForConfirmPayResultPending = (): Codec => - buildCodecForObject() - .property("lastError", codecForAny()) - .property("type", codecForConstString(ConfirmPayResultType.Pending)) - .build("ConfirmPayResultPending"); +export const codecForConfirmPayResultPending = + (): Codec => + buildCodecForObject() + .property("lastError", codecForAny()) + .property("type", codecForConstString(ConfirmPayResultType.Pending)) + .build("ConfirmPayResultPending"); export const codecForConfirmPayResultDone = (): Codec => buildCodecForObject() @@ -322,45 +324,48 @@ export enum PreparePayResultType { AlreadyConfirmed = "already-confirmed", } -export const codecForPreparePayResultPaymentPossible = (): Codec => - buildCodecForObject() - .property("amountEffective", codecForAmountString()) - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForContractTerms()) - .property("proposalId", codecForString()) - .property("contractTermsHash", codecForString()) - .property("noncePriv", codecForString()) - .property( - "status", - codecForConstString(PreparePayResultType.PaymentPossible), - ) - .build("PreparePayResultPaymentPossible"); +export const codecForPreparePayResultPaymentPossible = + (): Codec => + buildCodecForObject() + .property("amountEffective", codecForAmountString()) + .property("amountRaw", codecForAmountString()) + .property("contractTerms", codecForContractTerms()) + .property("proposalId", codecForString()) + .property("contractTermsHash", codecForString()) + .property("noncePriv", codecForString()) + .property( + "status", + codecForConstString(PreparePayResultType.PaymentPossible), + ) + .build("PreparePayResultPaymentPossible"); -export const codecForPreparePayResultInsufficientBalance = (): Codec => - buildCodecForObject() - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForAny()) - .property("proposalId", codecForString()) - .property("noncePriv", codecForString()) - .property( - "status", - codecForConstString(PreparePayResultType.InsufficientBalance), - ) - .build("PreparePayResultInsufficientBalance"); +export const codecForPreparePayResultInsufficientBalance = + (): Codec => + buildCodecForObject() + .property("amountRaw", codecForAmountString()) + .property("contractTerms", codecForAny()) + .property("proposalId", codecForString()) + .property("noncePriv", codecForString()) + .property( + "status", + codecForConstString(PreparePayResultType.InsufficientBalance), + ) + .build("PreparePayResultInsufficientBalance"); -export const codecForPreparePayResultAlreadyConfirmed = (): Codec => - buildCodecForObject() - .property( - "status", - codecForConstString(PreparePayResultType.AlreadyConfirmed), - ) - .property("amountEffective", codecForAmountString()) - .property("amountRaw", codecForAmountString()) - .property("paid", codecForBoolean()) - .property("contractTerms", codecForAny()) - .property("contractTermsHash", codecForString()) - .property("proposalId", codecForString()) - .build("PreparePayResultAlreadyConfirmed"); +export const codecForPreparePayResultAlreadyConfirmed = + (): Codec => + buildCodecForObject() + .property( + "status", + codecForConstString(PreparePayResultType.AlreadyConfirmed), + ) + .property("amountEffective", codecForAmountString()) + .property("amountRaw", codecForAmountString()) + .property("paid", codecForBoolean()) + .property("contractTerms", codecForAny()) + .property("contractTermsHash", codecForString()) + .property("proposalId", codecForString()) + .build("PreparePayResultAlreadyConfirmed"); export const codecForPreparePayResult = (): Codec => buildCodecForUnion() @@ -461,7 +466,7 @@ export interface PlanchetCreationResult { denomPub: DenominationPubKey; blindingKey: string; withdrawSig: string; - coinEv: string; + coinEv: CoinEnvelope; coinValue: AmountJson; coinEvHash: string; } @@ -543,12 +548,13 @@ export interface ExchangeListItem { tos: ExchangeTos; } -const codecForExchangeTos = (): Codec => buildCodecForObject() - .property("acceptedVersion", codecOptional(codecForString())) - .property("currentVersion", codecOptional(codecForString())) - .property("contentType", codecOptional(codecForString())) - .property("content", codecOptional(codecForString())) - .build("ExchangeTos") +const codecForExchangeTos = (): Codec => + buildCodecForObject() + .property("acceptedVersion", codecOptional(codecForString())) + .property("currentVersion", codecOptional(codecForString())) + .property("contentType", codecOptional(codecForString())) + .property("content", codecOptional(codecForString())) + .build("ExchangeTos"); export const codecForExchangeListItem = (): Codec => buildCodecForObject() @@ -670,10 +676,11 @@ export interface ForceExchangeUpdateRequest { exchangeBaseUrl: string; } -export const codecForForceExchangeUpdateRequest = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .build("AddExchangeRequest"); +export const codecForForceExchangeUpdateRequest = + (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .build("AddExchangeRequest"); export interface GetExchangeTosRequest { exchangeBaseUrl: string; @@ -691,11 +698,12 @@ export interface AcceptManualWithdrawalRequest { amount: string; } -export const codecForAcceptManualWithdrawalRequet = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("amount", codecForString()) - .build("AcceptManualWithdrawalRequest"); +export const codecForAcceptManualWithdrawalRequet = + (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForString()) + .build("AcceptManualWithdrawalRequest"); export interface GetWithdrawalDetailsForAmountRequest { exchangeBaseUrl: string; @@ -707,28 +715,31 @@ export interface AcceptBankIntegratedWithdrawalRequest { exchangeBaseUrl: string; } -export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("talerWithdrawUri", codecForString()) - .build("AcceptBankIntegratedWithdrawalRequest"); +export const codecForAcceptBankIntegratedWithdrawalRequest = + (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("talerWithdrawUri", codecForString()) + .build("AcceptBankIntegratedWithdrawalRequest"); -export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("amount", codecForString()) - .build("GetWithdrawalDetailsForAmountRequest"); +export const codecForGetWithdrawalDetailsForAmountRequest = + (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForString()) + .build("GetWithdrawalDetailsForAmountRequest"); export interface AcceptExchangeTosRequest { exchangeBaseUrl: string; etag: string; } -export const codecForAcceptExchangeTosRequest = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("etag", codecForString()) - .build("AcceptExchangeTosRequest"); +export const codecForAcceptExchangeTosRequest = + (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("etag", codecForString()) + .build("AcceptExchangeTosRequest"); export interface ApplyRefundRequest { talerRefundUri: string; @@ -742,18 +753,20 @@ export const codecForApplyRefundRequest = (): Codec => export interface GetWithdrawalDetailsForUriRequest { talerWithdrawUri: string; } -export const codecForGetWithdrawalDetailsForUri = (): Codec => - buildCodecForObject() - .property("talerWithdrawUri", codecForString()) - .build("GetWithdrawalDetailsForUriRequest"); +export const codecForGetWithdrawalDetailsForUri = + (): Codec => + buildCodecForObject() + .property("talerWithdrawUri", codecForString()) + .build("GetWithdrawalDetailsForUriRequest"); export interface ListKnownBankAccountsRequest { currency?: string; } -export const codecForListKnownBankAccounts = (): Codec => - buildCodecForObject() - .property("currency", codecOptional(codecForString())) - .build("ListKnownBankAccountsRequest"); +export const codecForListKnownBankAccounts = + (): Codec => + buildCodecForObject() + .property("currency", codecOptional(codecForString())) + .build("ListKnownBankAccountsRequest"); export interface GetExchangeWithdrawalInfo { exchangeBaseUrl: string; @@ -761,15 +774,16 @@ export interface GetExchangeWithdrawalInfo { tosAcceptedFormat?: string[]; } -export const codecForGetExchangeWithdrawalInfo = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("amount", codecForAmountJson()) - .property( - "tosAcceptedFormat", - codecOptional(codecForList(codecForString())), - ) - .build("GetExchangeWithdrawalInfo"); +export const codecForGetExchangeWithdrawalInfo = + (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForAmountJson()) + .property( + "tosAcceptedFormat", + codecOptional(codecForList(codecForString())), + ) + .build("GetExchangeWithdrawalInfo"); export interface AbortProposalRequest { proposalId: string; @@ -853,17 +867,17 @@ export interface RefreshPlanchetInfo { /** * Public key for the coin. */ - publicKey: string; + coinPub: string; /** * Private key for the coin. */ - privateKey: string; + coinPriv: string; /** * Blinded public key. */ - coinEv: string; + coinEv: CoinEnvelope; coinEvHash: string; @@ -896,12 +910,13 @@ export interface RecoveryLoadRequest { strategy?: RecoveryMergeStrategy; } -export const codecForWithdrawTestBalance = (): Codec => - buildCodecForObject() - .property("amount", codecForString()) - .property("bankBaseUrl", codecForString()) - .property("exchangeBaseUrl", codecForString()) - .build("WithdrawTestBalanceRequest"); +export const codecForWithdrawTestBalance = + (): Codec => + buildCodecForObject() + .property("amount", codecForString()) + .property("bankBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForString()) + .build("WithdrawTestBalanceRequest"); export interface ApplyRefundResponse { contractTermsHash: string; @@ -935,11 +950,12 @@ export interface SetCoinSuspendedRequest { suspended: boolean; } -export const codecForSetCoinSuspendedRequest = (): Codec => - buildCodecForObject() - .property("coinPub", codecForString()) - .property("suspended", codecForBoolean()) - .build("SetCoinSuspendedRequest"); +export const codecForSetCoinSuspendedRequest = + (): Codec => + buildCodecForObject() + .property("coinPub", codecForString()) + .property("suspended", codecForBoolean()) + .build("SetCoinSuspendedRequest"); export interface ForceRefreshRequest { coinPubList: string[]; @@ -972,10 +988,11 @@ export interface AbortPayWithRefundRequest { proposalId: string; } -export const codecForAbortPayWithRefundRequest = (): Codec => - buildCodecForObject() - .property("proposalId", codecForString()) - .build("AbortPayWithRefundRequest"); +export const codecForAbortPayWithRefundRequest = + (): Codec => + buildCodecForObject() + .property("proposalId", codecForString()) + .build("AbortPayWithRefundRequest"); export interface GetFeeForDepositRequest { depositPaytoUri: string; @@ -987,18 +1004,18 @@ export interface CreateDepositGroupRequest { amount: AmountString; } - export const codecForGetFeeForDeposit = (): Codec => buildCodecForObject() .property("amount", codecForAmountString()) .property("depositPaytoUri", codecForString()) .build("GetFeeForDepositRequest"); -export const codecForCreateDepositGroupRequest = (): Codec => - buildCodecForObject() - .property("amount", codecForAmountString()) - .property("depositPaytoUri", codecForString()) - .build("CreateDepositGroupRequest"); +export const codecForCreateDepositGroupRequest = + (): Codec => + buildCodecForObject() + .property("amount", codecForAmountString()) + .property("depositPaytoUri", codecForString()) + .build("CreateDepositGroupRequest"); export interface CreateDepositGroupResponse { depositGroupId: string; @@ -1015,10 +1032,11 @@ export interface TrackDepositGroupResponse { }[]; } -export const codecForTrackDepositGroupRequest = (): Codec => - buildCodecForObject() - .property("depositGroupId", codecForAmountString()) - .build("TrackDepositGroupRequest"); +export const codecForTrackDepositGroupRequest = + (): Codec => + buildCodecForObject() + .property("depositGroupId", codecForAmountString()) + .build("TrackDepositGroupRequest"); export interface WithdrawUriInfoResponse { amount: AmountString; @@ -1026,12 +1044,13 @@ export interface WithdrawUriInfoResponse { possibleExchanges: ExchangeListItem[]; } -export const codecForWithdrawUriInfoResponse = (): Codec => - buildCodecForObject() - .property("amount", codecForAmountString()) - .property("defaultExchangeBaseUrl", codecOptional(codecForString())) - .property("possibleExchanges", codecForList(codecForExchangeListItem())) - .build("WithdrawUriInfoResponse"); +export const codecForWithdrawUriInfoResponse = + (): Codec => + buildCodecForObject() + .property("amount", codecForAmountString()) + .property("defaultExchangeBaseUrl", codecOptional(codecForString())) + .property("possibleExchanges", codecForList(codecForExchangeListItem())) + .build("WithdrawUriInfoResponse"); export interface WalletCurrencyInfo { trustedAuditors: { @@ -1054,15 +1073,17 @@ export interface RetryTransactionRequest { transactionId: string; } -export const codecForDeleteTransactionRequest = (): Codec => - buildCodecForObject() - .property("transactionId", codecForString()) - .build("DeleteTransactionRequest"); +export const codecForDeleteTransactionRequest = + (): Codec => + buildCodecForObject() + .property("transactionId", codecForString()) + .build("DeleteTransactionRequest"); -export const codecForRetryTransactionRequest = (): Codec => - buildCodecForObject() - .property("transactionId", codecForString()) - .build("RetryTransactionRequest"); +export const codecForRetryTransactionRequest = + (): Codec => + buildCodecForObject() + .property("transactionId", codecForString()) + .build("RetryTransactionRequest"); export interface SetWalletDeviceIdRequest { /** @@ -1071,10 +1092,11 @@ export interface SetWalletDeviceIdRequest { walletDeviceId: string; } -export const codecForSetWalletDeviceIdRequest = (): Codec => - buildCodecForObject() - .property("walletDeviceId", codecForString()) - .build("SetWalletDeviceIdRequest"); +export const codecForSetWalletDeviceIdRequest = + (): Codec => + buildCodecForObject() + .property("walletDeviceId", codecForString()) + .build("SetWalletDeviceIdRequest"); export interface WithdrawFakebankRequest { amount: AmountString; @@ -1082,12 +1104,13 @@ export interface WithdrawFakebankRequest { bank: string; } -export const codecForWithdrawFakebankRequest = (): Codec => - buildCodecForObject() - .property("amount", codecForAmountString()) - .property("bank", codecForString()) - .property("exchange", codecForString()) - .build("WithdrawFakebankRequest"); +export const codecForWithdrawFakebankRequest = + (): Codec => + buildCodecForObject() + .property("amount", codecForAmountString()) + .property("bank", codecForString()) + .property("exchange", codecForString()) + .build("WithdrawFakebankRequest"); export interface ImportDb { dump: any; @@ -1095,4 +1118,4 @@ export interface ImportDb { export const codecForImportDbRequest = (): Codec => buildCodecForObject() .property("dump", codecForAny()) - .build("ImportDbRequest") + .build("ImportDbRequest"); diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts index fc489327f..b4d2884d1 100644 --- a/packages/taler-wallet-cli/src/harness/harness.ts +++ b/packages/taler-wallet-cli/src/harness/harness.ts @@ -1525,7 +1525,7 @@ export class ExchangeService implements ExchangeServiceInterface { this.exchangeHttpProc = this.globalState.spawnService( "taler-exchange-httpd", - ["-c", this.configFilename, ...this.timetravelArgArr], + ["-LINFO", "-c", this.configFilename, ...this.timetravelArgArr], `exchange-httpd-${this.name}`, ); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts b/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts index eceb26d79..ed07114eb 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts @@ -201,8 +201,8 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) { for (const da of denomPubs1) { let found = false; for (const db of denomPubs2) { - const d1 = DenominationPubKey.lift(da.denomPub); - const d2 = DenominationPubKey.lift(db.denomPub); + const d1 = da.denomPub; + const d2 = db.denomPub; if (DenominationPubKey.cmp(d1, d2) === 0) { found = true; break; diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts index 93a7cd1c4..94abb8f7c 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts @@ -30,8 +30,10 @@ import { AmountJson, AmountString, + CoinEnvelope, DenominationPubKey, ExchangeProtocolVersion, + RefreshPlanchetInfo, UnblindedSignature, } from "@gnu-taler/taler-util"; @@ -74,32 +76,7 @@ export interface DerivedRefreshSession { /** * Planchets for each cut-and-choose instance. */ - planchetsForGammas: { - /** - * Public key for the coin. - */ - publicKey: string; - - /** - * Private key for the coin. - */ - privateKey: string; - - /** - * Blinded public key. - */ - coinEv: string; - - /** - * Hash of the blinded public key. - */ - coinEvHash: string; - - /** - * Blinding key used. - */ - blindingKey: string; - }[][]; + planchetsForGammas: RefreshPlanchetInfo[][]; /** * The transfer keys, kappa of them. diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index 29c2553a5..16446bb9e 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -28,6 +28,7 @@ import { CryptoWorker } from "./cryptoWorkerInterface.js"; import { CoinDepositPermission, + CoinEnvelope, RecoupRefreshRequest, RecoupRequest, } from "@gnu-taler/taler-util"; @@ -452,7 +453,7 @@ export class CryptoApi { newDenomHash: string, oldCoinPub: string, transferPub: string, - coinEv: string, + coinEv: CoinEnvelope, ): Promise { return this.doRpc( "signCoinLink", diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index bff2e0eb5..9f6d82348 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -26,19 +26,49 @@ // FIXME: Crypto should not use DB Types! import { - AmountJson, Amounts, BenchmarkResult, buildSigPS, - CoinDepositPermission, createEddsaKeyPair, createHashContext, decodeCrock, - DenomKeyType, DepositInfo, eddsaGetPublic, eddsaSign, eddsaVerify, - encodeCrock, ExchangeProtocolVersion, - FreshCoin, hash, hashDenomPub, kdf, keyExchangeEcdheEddsa, - // Logger, - MakeSyncSignatureRequest, PlanchetCreationRequest, PlanchetCreationResult, - randomBytes, RecoupRefreshRequest, + AmountJson, + Amounts, + BenchmarkResult, + buildSigPS, + CoinDepositPermission, + CoinEnvelope, + createEddsaKeyPair, + createHashContext, + decodeCrock, + DenomKeyType, + DepositInfo, + eddsaGetPublic, + eddsaSign, + eddsaVerify, + encodeCrock, + ExchangeProtocolVersion, + FreshCoin, + hash, + HashCodeString, + hashCoinEv, + hashCoinEvInner, + hashDenomPub, + keyExchangeEcdheEddsa, + Logger, + MakeSyncSignatureRequest, + PlanchetCreationRequest, + PlanchetCreationResult, + randomBytes, + RecoupRefreshRequest, RecoupRequest, - RefreshPlanchetInfo, rsaBlind, rsaUnblind, rsaVerify, setupRefreshPlanchet, + RefreshPlanchetInfo, + rsaBlind, + rsaUnblind, + rsaVerify, + setupRefreshPlanchet, setupRefreshTransferPub, setupTipPlanchet, - setupWithdrawPlanchet, stringToBytes, TalerSignaturePurpose, Timestamp, timestampTruncateToSecond + setupWithdrawPlanchet, + stringToBytes, + TalerSignaturePurpose, + Timestamp, + timestampTruncateToSecond, + typedArrayConcat, } from "@gnu-taler/taler-util"; import bigint from "big-integer"; import { DenominationRecord, WireFee } from "../../db.js"; @@ -50,10 +80,10 @@ import { DerivedTipPlanchet, DeriveRefreshSessionRequest, DeriveTipRequest, - SignTrackTransactionRequest + SignTrackTransactionRequest, } from "../cryptoTypes.js"; -// const logger = new Logger("cryptoImplementation.ts"); +const logger = new Logger("cryptoImplementation.ts"); function amountToBuffer(amount: AmountJson): Uint8Array { const buffer = new ArrayBuffer(8 + 4 + 12); @@ -130,7 +160,7 @@ async function myEddsaSign( export class CryptoImplementation { static enableTracing = false; - constructor(private primitiveWorker?: PrimitiveWorker) { } + constructor(private primitiveWorker?: PrimitiveWorker) {} /** * Create a pre-coin of the given denomination to be withdrawn from then given @@ -139,26 +169,26 @@ export class CryptoImplementation { async createPlanchet( req: PlanchetCreationRequest, ): Promise { - if ( - req.denomPub.cipher === DenomKeyType.Rsa || - req.denomPub.cipher === DenomKeyType.LegacyRsa - ) { + const denomPub = req.denomPub; + if (denomPub.cipher === DenomKeyType.Rsa) { const reservePub = decodeCrock(req.reservePub); - const denomPubRsa = decodeCrock(req.denomPub.rsa_public_key); + const denomPubRsa = decodeCrock(denomPub.rsa_public_key); const derivedPlanchet = setupWithdrawPlanchet( decodeCrock(req.secretSeed), req.coinIndex, ); const coinPubHash = hash(derivedPlanchet.coinPub); const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPubRsa); + const coinEv: CoinEnvelope = { + cipher: DenomKeyType.Rsa, + rsa_blinded_planchet: encodeCrock(ev), + }; const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount; const denomPubHash = hashDenomPub(req.denomPub); - const evHash = hash(ev); - + const evHash = hashCoinEv(coinEv, encodeCrock(denomPubHash)); const withdrawRequest = buildSigPS( TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW, ) - .put(reservePub) .put(amountToBuffer(amountWithFee)) .put(denomPubHash) .put(evHash) @@ -171,14 +201,11 @@ export class CryptoImplementation { const planchet: PlanchetCreationResult = { blindingKey: encodeCrock(derivedPlanchet.bks), - coinEv: encodeCrock(ev), + coinEv, coinPriv: encodeCrock(derivedPlanchet.coinPriv), coinPub: encodeCrock(derivedPlanchet.coinPub), coinValue: req.value, - denomPub: { - cipher: req.denomPub.cipher, - rsa_public_key: encodeCrock(denomPubRsa), - }, + denomPub, denomPubHash: encodeCrock(denomPubHash), reservePub: encodeCrock(reservePub), withdrawSig: sigResult.sig, @@ -194,11 +221,8 @@ export class CryptoImplementation { * Create a planchet used for tipping, including the private keys. */ createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet { - if ( - req.denomPub.cipher !== DenomKeyType.Rsa && - req.denomPub.cipher !== DenomKeyType.LegacyRsa - ) { - throw Error("unsupported cipher"); + if (req.denomPub.cipher !== DenomKeyType.Rsa) { + throw Error(`unsupported cipher (${req.denomPub.cipher})`); } const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex); const denomPub = decodeCrock(req.denomPub.rsa_public_key); @@ -236,15 +260,7 @@ export class CryptoImplementation { const coinPriv = decodeCrock(req.coinPriv); const coinSig = eddsaSign(p, coinPriv); - if (req.denomPub.cipher === DenomKeyType.LegacyRsa) { - const paybackRequest: RecoupRequest = { - coin_blind_key_secret: req.blindingKey, - coin_sig: encodeCrock(coinSig), - denom_pub_hash: req.denomPubHash, - denom_sig: req.denomSig.rsa_signature, - }; - return paybackRequest; - } else { + if (req.denomPub.cipher === DenomKeyType.Rsa) { const paybackRequest: RecoupRequest = { coin_blind_key_secret: req.blindingKey, coin_sig: encodeCrock(coinSig), @@ -252,6 +268,8 @@ export class CryptoImplementation { denom_sig: req.denomSig, }; return paybackRequest; + } else { + throw new Error(); } } @@ -268,15 +286,7 @@ export class CryptoImplementation { const coinPriv = decodeCrock(req.coinPriv); const coinSig = eddsaSign(p, coinPriv); - if (req.denomPub.cipher === DenomKeyType.LegacyRsa) { - const recoupRequest: RecoupRefreshRequest = { - coin_blind_key_secret: req.blindingKey, - coin_sig: encodeCrock(coinSig), - denom_pub_hash: req.denomPubHash, - denom_sig: req.denomSig.rsa_signature, - }; - return recoupRequest; - } else { + if (req.denomPub.cipher === DenomKeyType.Rsa) { const recoupRequest: RecoupRefreshRequest = { coin_blind_key_secret: req.blindingKey, coin_sig: encodeCrock(coinSig), @@ -284,6 +294,8 @@ export class CryptoImplementation { denom_sig: req.denomSig, }; return recoupRequest; + } else { + throw new Error(); } } @@ -364,26 +376,11 @@ export class CryptoImplementation { sig: string, masterPub: string, ): boolean { - if (versionCurrent === ExchangeProtocolVersion.V12) { - const paytoHash = hash(stringToBytes(paytoUri + "\0")); - const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS) - .put(paytoHash) - .build(); - return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)); - } else if (versionCurrent === ExchangeProtocolVersion.V9) { - const h = kdf( - 64, - stringToBytes("exchange-wire-signature"), - stringToBytes(paytoUri + "\0"), - new Uint8Array(0), - ); - const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS) - .put(h) - .build(); - return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)); - } else { - throw Error(`unsupported version (${versionCurrent})`); - } + const paytoHash = hash(stringToBytes(paytoUri + "\0")); + const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS) + .put(paytoHash) + .build(); + return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)); } isValidContractTermsSignature( @@ -444,10 +441,12 @@ export class CryptoImplementation { ): Promise { // FIXME: put extensions here if used const hExt = new Uint8Array(64); + const hAgeCommitment = new Uint8Array(32); let d: Uint8Array; if (depositInfo.denomKeyType === DenomKeyType.Rsa) { d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT) .put(decodeCrock(depositInfo.contractTermsHash)) + .put(hAgeCommitment) .put(hExt) .put(decodeCrock(depositInfo.wireInfoHash)) .put(decodeCrock(depositInfo.denomPubHash)) @@ -457,18 +456,6 @@ export class CryptoImplementation { .put(amountToBuffer(depositInfo.feeDeposit)) .put(decodeCrock(depositInfo.merchantPub)) .build(); - } else if (depositInfo.denomKeyType === DenomKeyType.LegacyRsa) { - d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT) - .put(decodeCrock(depositInfo.contractTermsHash)) - .put(decodeCrock(depositInfo.wireInfoHash)) - .put(decodeCrock(depositInfo.denomPubHash)) - .put(timestampRoundedToBuffer(depositInfo.timestamp)) - .put(timestampRoundedToBuffer(depositInfo.refundDeadline)) - .put(amountToBuffer(depositInfo.spendAmount)) - .put(amountToBuffer(depositInfo.feeDeposit)) - .put(decodeCrock(depositInfo.merchantPub)) - .put(decodeCrock(depositInfo.coinPub)) - .build(); } else { throw Error("unsupported exchange protocol version"); } @@ -490,18 +477,10 @@ export class CryptoImplementation { }, }; return s; - } else if (depositInfo.denomKeyType === DenomKeyType.LegacyRsa) { - const s: CoinDepositPermission = { - coin_pub: depositInfo.coinPub, - coin_sig: coinSigRes.sig, - contribution: Amounts.stringify(depositInfo.spendAmount), - h_denom: depositInfo.denomPubHash, - exchange_url: depositInfo.exchangeBaseUrl, - ub_sig: depositInfo.denomSig.rsa_signature, - }; - return s; } else { - throw Error("unsupported merchant protocol version"); + throw Error( + `unsupported denomination cipher (${depositInfo.denomKeyType})`, + ); } } @@ -551,17 +530,18 @@ export class CryptoImplementation { for (const denomSel of newCoinDenoms) { for (let i = 0; i < denomSel.count; i++) { - if (denomSel.denomPub.cipher === DenomKeyType.LegacyRsa) { - const r = decodeCrock(denomSel.denomPub.rsa_public_key); - sessionHc.update(r); + if (denomSel.denomPub.cipher === DenomKeyType.Rsa) { + const denomPubHash = hashDenomPub(denomSel.denomPub); + sessionHc.update(denomPubHash); } else { - sessionHc.update(hashDenomPub(denomSel.denomPub)); + throw new Error(); } } } sessionHc.update(decodeCrock(meltCoinPub)); sessionHc.update(amountToBuffer(valueWithFee)); + for (let i = 0; i < kappa; i++) { const planchets: RefreshPlanchetInfo[] = []; for (let j = 0; j < newCoinDenoms.length; j++) { @@ -594,24 +574,29 @@ export class CryptoImplementation { coinPub = fresh.coinPub; blindingFactor = fresh.bks; } - const pubHash = hash(coinPub); - if ( - denomSel.denomPub.cipher !== DenomKeyType.Rsa && - denomSel.denomPub.cipher !== DenomKeyType.LegacyRsa - ) { + const coinPubHash = hash(coinPub); + if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) { throw Error("unsupported cipher, can't create refresh session"); } - const denomPub = decodeCrock(denomSel.denomPub.rsa_public_key); - const ev = rsaBlind(pubHash, blindingFactor, denomPub); + const rsaDenomPub = decodeCrock(denomSel.denomPub.rsa_public_key); + const ev = rsaBlind(coinPubHash, blindingFactor, rsaDenomPub); + const coinEv: CoinEnvelope = { + cipher: DenomKeyType.Rsa, + rsa_blinded_planchet: encodeCrock(ev), + }; + const coinEvHash = hashCoinEv( + coinEv, + encodeCrock(hashDenomPub(denomSel.denomPub)), + ); const planchet: RefreshPlanchetInfo = { blindingKey: encodeCrock(blindingFactor), - coinEv: encodeCrock(ev), - privateKey: encodeCrock(coinPriv), - publicKey: encodeCrock(coinPub), - coinEvHash: encodeCrock(hash(ev)), + coinEv, + coinPriv: encodeCrock(coinPriv), + coinPub: encodeCrock(coinPub), + coinEvHash: encodeCrock(coinEvHash), }; planchets.push(planchet); - sessionHc.update(ev); + hashCoinEvInner(coinEv, sessionHc); } } planchetsForGammas.push(planchets); @@ -619,26 +604,15 @@ export class CryptoImplementation { const sessionHash = sessionHc.finish(); let confirmData: Uint8Array; - if (req.exchangeProtocolVersion === ExchangeProtocolVersion.V9) { - confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT) - .put(sessionHash) - .put(decodeCrock(meltCoinDenomPubHash)) - .put(amountToBuffer(valueWithFee)) - .put(amountToBuffer(meltFee)) - .put(decodeCrock(meltCoinPub)) - .build(); - } else if (req.exchangeProtocolVersion === ExchangeProtocolVersion.V12) { - confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT) - .put(sessionHash) - .put(decodeCrock(meltCoinDenomPubHash)) - .put(amountToBuffer(valueWithFee)) - .put(amountToBuffer(meltFee)) - .build(); - } else { - throw Error( - `Exchange protocol version (${req.exchangeProtocolVersion}) not supported`, - ); - } + // FIXME: fill in age commitment + const hAgeCommitment = new Uint8Array(32); + confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT) + .put(sessionHash) + .put(decodeCrock(meltCoinDenomPubHash)) + .put(hAgeCommitment) + .put(amountToBuffer(valueWithFee)) + .put(amountToBuffer(meltFee)) + .build(); const confirmSigResp = await myEddsaSign(this.primitiveWorker, { msg: encodeCrock(confirmData), @@ -678,12 +652,15 @@ export class CryptoImplementation { newDenomHash: string, oldCoinPub: string, transferPub: string, - coinEv: string, + coinEv: CoinEnvelope, ): Promise { - const coinEvHash = hash(decodeCrock(coinEv)); + const coinEvHash = hashCoinEv(coinEv, newDenomHash); + // FIXME: fill in + const hAgeCommitment = new Uint8Array(32); const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK) .put(decodeCrock(newDenomHash)) .put(decodeCrock(transferPub)) + .put(hAgeCommitment) .put(coinEvHash) .build(); const sig = await myEddsaSign(this.primitiveWorker, { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 7f7dd10ff..410311530 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -38,6 +38,7 @@ import { TalerErrorDetails, Timestamp, UnblindedSignature, + CoinEnvelope, } from "@gnu-taler/taler-util"; import { RetryInfo } from "./util/retries.js"; import { PayCoinSelection } from "./util/coinSelection.js"; @@ -602,7 +603,7 @@ export interface PlanchetRecord { withdrawSig: string; - coinEv: string; + coinEv: CoinEnvelope; coinEvHash: string; @@ -1154,7 +1155,6 @@ export interface WalletContractData { timestamp: Timestamp; wireMethod: string; wireInfoHash: string; - wireInfoLegacyHash?: string; maxDepositFee: AmountJson; } @@ -1294,9 +1294,9 @@ export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; */ export type ConfigRecord = | { - key: typeof WALLET_BACKUP_STATE_KEY; - value: WalletBackupConfState; - } + key: typeof WALLET_BACKUP_STATE_KEY; + value: WalletBackupConfState; + } | { key: "currencyDefaultsApplied"; value: boolean }; export interface WalletBackupConfState { @@ -1392,9 +1392,9 @@ export interface WithdrawalGroupRecord { /** * UID of the denomination selection. - * + * * Used for merging backups. - * + * * FIXME: Should this not also include a timestamp for more logical merging? */ denomSelUid: string; @@ -1480,17 +1480,17 @@ export enum BackupProviderStateTag { export type BackupProviderState = | { - tag: BackupProviderStateTag.Provisional; - } + tag: BackupProviderStateTag.Provisional; + } | { - tag: BackupProviderStateTag.Ready; - nextBackupTimestamp: Timestamp; - } + tag: BackupProviderStateTag.Ready; + nextBackupTimestamp: Timestamp; + } | { - tag: BackupProviderStateTag.Retrying; - retryInfo: RetryInfo; - lastError?: TalerErrorDetails; - }; + tag: BackupProviderStateTag.Retrying; + retryInfo: RetryInfo; + lastError?: TalerErrorDetails; + }; export interface BackupProviderTerms { supportedProtocolVersion: string; @@ -1875,9 +1875,9 @@ export function exportDb(db: IDBDatabase): Promise { } export interface DatabaseDump { - name: string, - stores: { [s: string]: any }, - version: string, + name: string; + stores: { [s: string]: any }; + version: string; } export function importDb(db: IDBDatabase, dump: DatabaseDump): Promise { @@ -1891,12 +1891,11 @@ export function importDb(db: IDBDatabase, dump: DatabaseDump): Promise { const name = db.objectStoreNames[i]; const storeDump = dump.stores[name]; if (!storeDump) continue; - Object.keys(storeDump).forEach(async key => { - const value = storeDump[key] + Object.keys(storeDump).forEach(async (key) => { + const value = storeDump[key]; if (!value) return; - tx.objectStore(name).put(value) - }) - + tx.objectStore(name).put(value); + }); } }); } diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 9f63441dd..21b10a945 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -337,8 +337,7 @@ export async function importBackup( for (const backupDenomination of backupExchangeDetails.denominations) { if ( - backupDenomination.denom_pub.cipher !== DenomKeyType.Rsa && - backupDenomination.denom_pub.cipher !== DenomKeyType.LegacyRsa + backupDenomination.denom_pub.cipher !== DenomKeyType.Rsa ) { throw Error("unsupported cipher"); } diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index e3950ef90..5eb248611 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -168,10 +168,7 @@ async function computeBackupCryptoData( }; for (const backupExchangeDetails of backupContent.exchange_details) { for (const backupDenom of backupExchangeDetails.denominations) { - if ( - backupDenom.denom_pub.cipher !== DenomKeyType.Rsa && - backupDenom.denom_pub.cipher !== DenomKeyType.LegacyRsa - ) { + if (backupDenom.denom_pub.cipher !== DenomKeyType.Rsa) { throw Error("unsupported cipher"); } for (const backupCoin of backupDenom.coins) { @@ -192,18 +189,14 @@ async function computeBackupCryptoData( LibtoolVersion.compare(backupExchangeDetails.protocol_version, "9") ?.compatible ) { - cryptoData.rsaDenomPubToHash[ - backupDenom.denom_pub.rsa_public_key - ] = encodeCrock( - hash(decodeCrock(backupDenom.denom_pub.rsa_public_key)), - ); + cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] = + encodeCrock(hash(decodeCrock(backupDenom.denom_pub.rsa_public_key))); } else if ( LibtoolVersion.compare(backupExchangeDetails.protocol_version, "10") ?.compatible ) { - cryptoData.rsaDenomPubToHash[ - backupDenom.denom_pub.rsa_public_key - ] = encodeCrock(hashDenomPub(backupDenom.denom_pub)); + cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] = + encodeCrock(hashDenomPub(backupDenom.denom_pub)); } else { throw Error("unsupported exchange protocol version"); } @@ -220,9 +213,8 @@ async function computeBackupCryptoData( ); const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(prop.nonce_priv))); cryptoData.proposalNoncePrivToPub[prop.nonce_priv] = noncePub; - cryptoData.proposalIdToContractTermsHash[ - prop.proposal_id - ] = contractTermsHash; + cryptoData.proposalIdToContractTermsHash[prop.proposal_id] = + contractTermsHash; } for (const purch of backupContent.purchases) { const contractTermsHash = await cryptoApi.hashString( @@ -230,9 +222,8 @@ async function computeBackupCryptoData( ); const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv))); cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub; - cryptoData.proposalIdToContractTermsHash[ - purch.proposal_id - ] = contractTermsHash; + cryptoData.proposalIdToContractTermsHash[purch.proposal_id] = + contractTermsHash; } return cryptoData; } @@ -548,10 +539,11 @@ export interface RemoveBackupProviderRequest { provider: string; } -export const codecForRemoveBackupProvider = (): Codec => - buildCodecForObject() - .property("provider", codecForString()) - .build("RemoveBackupProviderRequest"); +export const codecForRemoveBackupProvider = + (): Codec => + buildCodecForObject() + .property("provider", codecForString()) + .build("RemoveBackupProviderRequest"); export async function removeBackupProvider( ws: InternalWalletState, @@ -619,12 +611,13 @@ interface SyncTermsOfServiceResponse { version: string; } -const codecForSyncTermsOfServiceResponse = (): Codec => - buildCodecForObject() - .property("storage_limit_in_megabytes", codecForNumber()) - .property("annual_fee", codecForAmountString()) - .property("version", codecForString()) - .build("SyncTermsOfServiceResponse"); +const codecForSyncTermsOfServiceResponse = + (): Codec => + buildCodecForObject() + .property("storage_limit_in_megabytes", codecForNumber()) + .property("annual_fee", codecForAmountString()) + .property("version", codecForString()) + .build("SyncTermsOfServiceResponse"); export interface AddBackupProviderRequest { backupProviderBaseUrl: string; @@ -637,12 +630,13 @@ export interface AddBackupProviderRequest { activate?: boolean; } -export const codecForAddBackupProviderRequest = (): Codec => - buildCodecForObject() - .property("backupProviderBaseUrl", codecForString()) - .property("name", codecForString()) - .property("activate", codecOptional(codecForBoolean())) - .build("AddBackupProviderRequest"); +export const codecForAddBackupProviderRequest = + (): Codec => + buildCodecForObject() + .property("backupProviderBaseUrl", codecForString()) + .property("name", codecForString()) + .property("activate", codecOptional(codecForBoolean())) + .build("AddBackupProviderRequest"); export async function addBackupProvider( ws: InternalWalletState, diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 8a5b35732..25b9cb92d 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -27,7 +27,11 @@ import { CreateDepositGroupRequest, CreateDepositGroupResponse, DenomKeyType, - durationFromSpec, encodeCrock, GetFeeForDepositRequest, getRandomBytes, getTimestampNow, + durationFromSpec, + encodeCrock, + GetFeeForDepositRequest, + getRandomBytes, + getTimestampNow, Logger, NotificationType, parsePaytoUri, @@ -38,7 +42,7 @@ import { timestampTruncateToSecond, TrackDepositGroupRequest, TrackDepositGroupResponse, - URL + URL, } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../common.js"; import { DepositGroupRecord, OperationStatus } from "../db.js"; @@ -54,7 +58,7 @@ import { getCandidatePayCoins, getTotalPaymentCost, hashWire, - hashWireLegacy + hashWireLegacy, } from "./pay.js"; import { getTotalRefreshCost } from "./refresh.js"; @@ -199,47 +203,21 @@ async function processDepositGroupImpl( } const perm = depositPermissions[i]; let requestBody: any; - if ( - typeof perm.ub_sig === "string" || - perm.ub_sig.cipher === DenomKeyType.LegacyRsa - ) { - // Legacy request - logger.info("creating legacy deposit request"); - const wireHash = hashWireLegacy( - depositGroup.wire.payto_uri, - depositGroup.wire.salt, - ); - requestBody = { - contribution: Amounts.stringify(perm.contribution), - wire: depositGroup.wire, - h_wire: wireHash, - h_contract_terms: depositGroup.contractTermsHash, - ub_sig: perm.ub_sig, - timestamp: depositGroup.contractTermsRaw.timestamp, - wire_transfer_deadline: - depositGroup.contractTermsRaw.wire_transfer_deadline, - refund_deadline: depositGroup.contractTermsRaw.refund_deadline, - coin_sig: perm.coin_sig, - denom_pub_hash: perm.h_denom, - merchant_pub: depositGroup.merchantPub, - }; - } else { - logger.info("creating v10 deposit request"); - requestBody = { - contribution: Amounts.stringify(perm.contribution), - merchant_payto_uri: depositGroup.wire.payto_uri, - wire_salt: depositGroup.wire.salt, - h_contract_terms: depositGroup.contractTermsHash, - ub_sig: perm.ub_sig, - timestamp: depositGroup.contractTermsRaw.timestamp, - wire_transfer_deadline: - depositGroup.contractTermsRaw.wire_transfer_deadline, - refund_deadline: depositGroup.contractTermsRaw.refund_deadline, - coin_sig: perm.coin_sig, - denom_pub_hash: perm.h_denom, - merchant_pub: depositGroup.merchantPub, - }; - } + logger.info("creating v10 deposit request"); + requestBody = { + contribution: Amounts.stringify(perm.contribution), + merchant_payto_uri: depositGroup.wire.payto_uri, + wire_salt: depositGroup.wire.salt, + h_contract_terms: depositGroup.contractTermsHash, + ub_sig: perm.ub_sig, + timestamp: depositGroup.contractTermsRaw.timestamp, + wire_transfer_deadline: + depositGroup.contractTermsRaw.wire_transfer_deadline, + refund_deadline: depositGroup.contractTermsRaw.refund_deadline, + coin_sig: perm.coin_sig, + denom_pub_hash: perm.h_denom, + merchant_pub: depositGroup.merchantPub, + }; const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url); logger.info(`depositing to ${url}`); const httpResp = await ws.http.postJson(url.href, requestBody); diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 87200c2f9..c50afc215 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -83,15 +83,7 @@ function denominationRecordFromKeys( denomIn: ExchangeDenomination, ): DenominationRecord { let denomPub: DenominationPubKey; - // We support exchange protocol v9 and v10. - if (typeof denomIn.denom_pub === "string") { - denomPub = { - cipher: DenomKeyType.LegacyRsa, - rsa_public_key: denomIn.denom_pub, - }; - } else { - denomPub = denomIn.denom_pub; - } + denomPub = denomIn.denom_pub; const denomPubHash = encodeCrock(hashDenomPub(denomPub)); const d: DenominationRecord = { denomPub, diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 8f0727c8b..4870d446a 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -606,7 +606,6 @@ export function extractContractData( timestamp: parsedContractTerms.timestamp, wireMethod: parsedContractTerms.wire_method, wireInfoHash: parsedContractTerms.h_wire, - wireInfoLegacyHash: parsedContractTerms.h_wire_legacy, maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee), merchant: parsedContractTerms.merchant, products: parsedContractTerms.products, @@ -1515,14 +1514,7 @@ export async function generateDepositPermissions( for (let i = 0; i < payCoinSel.coinPubs.length; i++) { const { coin, denom } = coinWithDenom[i]; let wireInfoHash: string; - if ( - coin.denomPub.cipher === DenomKeyType.LegacyRsa && - contractData.wireInfoLegacyHash - ) { - wireInfoHash = contractData.wireInfoLegacyHash; - } else { - wireInfoHash = contractData.wireInfoHash; - } + wireInfoHash = contractData.wireInfoHash; const dp = await ws.cryptoApi.signDepositPermission({ coinPriv: coin.coinPriv, coinPub: coin.coinPub, diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 1e5dd68a8..ba4cb697d 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -18,8 +18,10 @@ import { DenomKeyType, encodeCrock, ExchangeProtocolVersion, + ExchangeRefreshRevealRequest, getRandomBytes, HttpStatusCode, + j2s, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -369,10 +371,6 @@ async function refreshMelt( let exchangeProtocolVersion: ExchangeProtocolVersion; switch (d.oldDenom.denomPub.cipher) { - case DenomKeyType.LegacyRsa: { - exchangeProtocolVersion = ExchangeProtocolVersion.V9; - break; - } case DenomKeyType.Rsa: { exchangeProtocolVersion = ExchangeProtocolVersion.V12; break; @@ -397,16 +395,7 @@ async function refreshMelt( oldCoin.exchangeBaseUrl, ); let meltReqBody: any; - if (oldCoin.denomPub.cipher === DenomKeyType.LegacyRsa) { - meltReqBody = { - coin_pub: oldCoin.coinPub, - confirm_sig: derived.confirmSig, - denom_pub_hash: oldCoin.denomPubHash, - denom_sig: oldCoin.denomSig.rsa_signature, - rc: derived.hash, - value_with_fee: Amounts.stringify(derived.meltValueWithFee), - }; - } else { + if (oldCoin.denomPub.cipher === DenomKeyType.Rsa) { meltReqBody = { coin_pub: oldCoin.coinPub, confirm_sig: derived.confirmSig, @@ -569,10 +558,6 @@ async function refreshReveal( let exchangeProtocolVersion: ExchangeProtocolVersion; switch (d.oldDenom.denomPub.cipher) { - case DenomKeyType.LegacyRsa: { - exchangeProtocolVersion = ExchangeProtocolVersion.V9; - break; - } case DenomKeyType.Rsa: { exchangeProtocolVersion = ExchangeProtocolVersion.V12; break; @@ -600,7 +585,6 @@ async function refreshReveal( throw Error("refresh index error"); } - const evs = planchets.map((x: RefreshPlanchetInfo) => x.coinEv); const newDenomsFlat: string[] = []; const linkSigs: string[] = []; @@ -620,10 +604,9 @@ async function refreshReveal( } } - const req = { - coin_evs: evs, + const req: ExchangeRefreshRevealRequest = { + coin_evs: planchets.map((x) => x.coinEv), new_denoms_h: newDenomsFlat, - rc: derived.hash, transfer_privs: privs, transfer_pub: derived.transferPubs[norevealIndex], link_sigs: linkSigs, @@ -666,20 +649,14 @@ async function refreshReveal( continue; } const pc = derived.planchetsForGammas[norevealIndex][newCoinIndex]; - if ( - denom.denomPub.cipher !== DenomKeyType.Rsa && - denom.denomPub.cipher !== DenomKeyType.LegacyRsa - ) { + if (denom.denomPub.cipher !== DenomKeyType.Rsa) { throw Error("cipher unsupported"); } const evSig = reveal.ev_sigs[newCoinIndex].ev_sig; let rsaSig: string; if (typeof evSig === "string") { rsaSig = evSig; - } else if ( - evSig.cipher === DenomKeyType.Rsa || - evSig.cipher === DenomKeyType.LegacyRsa - ) { + } else if (evSig.cipher === DenomKeyType.Rsa) { rsaSig = evSig.blinded_rsa_signature; } else { throw Error("unsupported cipher"); @@ -691,8 +668,8 @@ async function refreshReveal( ); const coin: CoinRecord = { blindingKey: pc.blindingKey, - coinPriv: pc.privateKey, - coinPub: pc.publicKey, + coinPriv: pc.coinPriv, + coinPub: pc.coinPub, currentAmount: denom.value, denomPub: denom.denomPub, denomPubHash: denom.denomPubHash, @@ -707,7 +684,7 @@ async function refreshReveal( oldCoinPub: refreshGroup.oldCoinPubs[coinIndex], }, suspended: false, - coinEvHash: pc.coinEv, + coinEvHash: pc.coinEvHash, }; coins.push(coin); diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index f985d8aad..039fb64a1 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -306,37 +306,13 @@ async function processTipImpl( // FIXME: Maybe we want to signal to the caller that the transient error happened? return; } - - // FIXME: Do this earlier? - const merchantInfo = await ws.merchantOps.getMerchantInfo( - ws, - tipRecord.merchantBaseUrl, - ); - let blindedSigs: BlindedDenominationSignature[] = []; - if (merchantInfo.protocolVersionCurrent === MerchantProtocolVersion.V3) { - const response = await readSuccessResponseJsonOrThrow( - merchantResp, - codecForMerchantTipResponseV2(), - ); - blindedSigs = response.blind_sigs.map((x) => x.blind_sig); - } else if ( - merchantInfo.protocolVersionCurrent === MerchantProtocolVersion.V1 - ) { - const response = await readSuccessResponseJsonOrThrow( - merchantResp, - codecForMerchantTipResponseV1(), - ); - blindedSigs = response.blind_sigs.map((x) => ({ - cipher: DenomKeyType.Rsa, - blinded_rsa_signature: x.blind_sig, - })); - } else { - throw Error( - `unsupported merchant protocol version (${merchantInfo.protocolVersionCurrent})`, - ); - } + const response = await readSuccessResponseJsonOrThrow( + merchantResp, + codecForMerchantTipResponseV2(), + ); + blindedSigs = response.blind_sigs.map((x) => x.blind_sig); if (blindedSigs.length !== planchets.length) { throw Error("number of tip responses does not match requested planchets"); @@ -352,17 +328,11 @@ async function processTipImpl( const planchet = planchets[i]; checkLogicInvariant(!!planchet); - if ( - denom.denomPub.cipher !== DenomKeyType.Rsa && - denom.denomPub.cipher !== DenomKeyType.LegacyRsa - ) { + if (denom.denomPub.cipher !== DenomKeyType.Rsa) { throw Error("unsupported cipher"); } - if ( - blindedSig.cipher !== DenomKeyType.Rsa && - blindedSig.cipher !== DenomKeyType.LegacyRsa - ) { + if (blindedSig.cipher !== DenomKeyType.Rsa) { throw Error("unsupported cipher"); } diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts b/packages/taler-wallet-core/src/operations/withdraw.test.ts index 2c890a121..02540848a 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.test.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { Amounts } from "@gnu-taler/taler-util"; +import { Amounts, DenomKeyType } from "@gnu-taler/taler-util"; import test from "ava"; import { DenominationRecord, DenominationVerificationStatus } from "../db.js"; import { selectWithdrawalDenominations } from "./withdraw.js"; @@ -29,7 +29,7 @@ test("withdrawal selection bug repro", (t) => { const denoms: DenominationRecord[] = [ { denomPub: { - cipher: 1, + cipher: DenomKeyType.Rsa, rsa_public_key: "040000XT67C8KBD6B75TTQ3SK8FWXMNQW4372T3BDDGPAMB9RFCA03638W8T3F71WFEFK9NP32VKYVNFXPYRWQ1N1HDKV5J0DFEKHBPJCYSWCBJDRNWD7G8BN8PT97FA9AMV75MYEK4X54D1HGJ207JSVJBGFCATSPNTEYNHEQF1F220W00TBZR1HNPDQFD56FG0DJQ9KGHM8EC33H6AY9YN9CNX5R3Z4TZ4Q23W47SBHB13H6W74FQJG1F50X38VRSC4SR8RWBAFB7S4K8D2H4NMRFSQT892A3T0BTBW7HM5C0H2CK6FRKG31F7W9WP1S29013K5CXYE55CT8TH6N8J9B780R42Y5S3ZB6J6E9H76XBPSGH4TGYSR2VZRB98J417KCQMZKX1BB67E7W5KVE37TC9SJ904002", }, @@ -83,7 +83,7 @@ test("withdrawal selection bug repro", (t) => { }, { denomPub: { - cipher: 1, + cipher: DenomKeyType.Rsa, rsa_public_key: "040000Y63CF78QFPKRY77BRK9P557Q1GQWX3NCZ3HSYSK0Z7TT0KGRA7N4SKBKEHSTVHX1Z9DNXMJR4EXSY1TXCKV0GJ3T3YYC6Z0JNMJFVYQAV4FX5J90NZH1N33MZTV8HS9SMNAA9S6K73G4P99GYBB01B0P6M1KXZ5JRDR7VWBR3MEJHHGJ6QBMCJR3NWJRE3WJW9PRY8QPQ2S7KFWTWRESH2DBXCXWBD2SRN6P9YX8GRAEMFEGXC9V5GVJTEMH6ZDGNXFPWZE3JVJ2Q4N9GDYKBCHZCJ7M7M2RJ9ZV4Y64NAN9BT6XDC68215GKKRHTW1BBF1MYY6AR3JCTT9HYAM923RMVQR3TAEB7SDX8J76XRZWYH3AGJCZAQGMN5C8SSH9AHQ9RNQJQ15CN45R37X4YNFJV904002", }, @@ -138,7 +138,7 @@ test("withdrawal selection bug repro", (t) => { }, { denomPub: { - cipher: 1, + cipher: DenomKeyType.Rsa, rsa_public_key: "040000YDESWC2B962DA4WK356SC50MA3N9KV0ZSGY3RC48JCTY258W909C7EEMT5BTC5KZ5T4CERCZ141P9QF87EK2BD1XEEM5GB07MB3H19WE4CQGAS8X84JBWN83PQGQXVMWE5HFA992KMGHC566GT9ZS2QPHZB6X89C4A80Z663PYAAPXP728VHAKATGNNBQ01ZZ2XD1CH9Y38YZBSPJ4K7GB2J76GBCYAVD9ENHDVWXJAXYRPBX4KSS5TXRR3K5NEN9ZV3AJD2V65K7ABRZDF5D5V1FJZZMNJ5XZ4FEREEKEBV9TDFPGJTKDEHEC60K3DN24DAATRESDJ1ZYYSYSRCAT4BT2B62ARGVMJTT5N2R126DRW9TGRWCW0ZAF2N2WET1H4NJEW77X0QT46Z5R3MZ0XPHD04002", }, @@ -192,7 +192,7 @@ test("withdrawal selection bug repro", (t) => { }, { denomPub: { - cipher: 1, + cipher: DenomKeyType.Rsa, rsa_public_key: "040000YG3T1ADB8DVA6BD3EPV6ZHSHTDW35DEN4VH1AE6CSB7P1PSDTNTJG866PHF6QB1CCWYCVRGA0FVBJ9Q0G7KV7AD9010GDYBQH0NNPHW744MTNXVXWBGGGRGQGYK4DTYN1DSWQ1FZNDSZZPB5BEKG2PDJ93NX2JTN06Y8QMS2G734Z9XHC10EENBG2KVB7EJ3CM8PV1T32RC7AY62F3496E8D8KRHJQQTT67DSGMNKK86QXVDTYW677FG27DP20E8XY3M6FQD53NDJ1WWES91401MV1A3VXVPGC76GZVDD62W3WTJ1YMKHTTA3MRXX3VEAAH3XTKDN1ER7X6CZPMYTF8VK735VP2B2TZGTF28TTW4FZS32SBS64APCDF6SZQ427N5538TJC7SRE71YSP5ET8GS904002", }, @@ -247,7 +247,7 @@ test("withdrawal selection bug repro", (t) => { }, { denomPub: { - cipher: 1, + cipher: DenomKeyType.Rsa, rsa_public_key: "040000ZC0G60E9QQ5PD81TSDWD9GV5Y6P8Z05NSPA696DP07NGQQVSRQXBA76Q6PRB0YFX295RG4MTQJXAZZ860ET307HSC2X37XAVGQXRVB8Q4F1V7NP5ZEVKTX75DZK1QRAVHEZGQYKSSH6DBCJNQF6V9WNQF3GEYVA4KCBHA7JF772KHXM9642C28Z0AS4XXXV2PABAN5C8CHYD5H7JDFNK3920W5Q69X0BS84XZ4RE2PW6HM1WZ6KGZ3MKWWWCPKQ1FSFABRBWKAB09PF563BEBXKY6M38QETPH5EDWGANHD0SC3QV0WXYVB7BNHNNQ0J5BNV56K563SYHM4E5ND260YRJSYA1GN5YSW2B1J5T1A1EBNYF2DN6JNJKWXWEQ42G5YS17ZSZ5EWDRA9QKV8EGTCNAD04002", }, @@ -301,7 +301,7 @@ test("withdrawal selection bug repro", (t) => { }, { denomPub: { - cipher: 1, + cipher: DenomKeyType.Rsa, rsa_public_key: "040000ZSK2PMVY6E3NBQ52KXMW029M60F4BWYTDS0FZSD0PE53CNZ9H6TM3GQK1WRTEKQ5GRWJ1J9DY6Y42SP47QVT1XD1G0W05SQ5F3F7P5KSWR0FJBJ9NZBXQEVN8Q4JRC94X3JJ3XV3KBYTZ2HTDFV28C3H2SRR0XGNZB4FY85NDZF1G4AEYJJ9QB3C0V8H70YB8RV3FKTNH7XS4K4HFNZHJ5H9VMX5SM9Z2DX37HA5WFH0E2MJBVVF2BWWA5M0HPPSB365RAE2AMD42Q65A96WD80X27SB2ZNQZ8WX0K13FWF85GZ6YNYAJGE1KGN06JDEKE9QD68Z651D7XE8V6664TVVC8M68S7WD0DSXMJQKQ0BNJXNDE29Q7MRX6DA3RW0PZ44B3TKRK0294FPVZTNSTA6XF04002", }, diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 79220089b..731e9b3aa 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -43,6 +43,7 @@ import { DenomKeyType, LibtoolVersion, UnblindedSignature, + ExchangeWithdrawRequest, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -497,9 +498,8 @@ async function processPlanchetExchangeRequest( `processing planchet #${coinIdx} in withdrawal ${withdrawalGroup.withdrawalGroupId}`, ); - const reqBody: any = { + const reqBody: ExchangeWithdrawRequest = { denom_pub_hash: planchet.denomPubHash, - reserve_pub: planchet.reservePub, reserve_sig: planchet.withdrawSig, coin_ev: planchet.coinEv, }; @@ -580,28 +580,12 @@ async function processPlanchetVerifyAndStoreCoin( const { planchet, exchangeBaseUrl } = d; const planchetDenomPub = planchet.denomPub; - if ( - planchetDenomPub.cipher !== DenomKeyType.Rsa && - planchetDenomPub.cipher !== DenomKeyType.LegacyRsa - ) { + if (planchetDenomPub.cipher !== DenomKeyType.Rsa) { throw Error(`cipher (${planchetDenomPub.cipher}) not supported`); } let evSig = resp.ev_sig; - if (typeof resp.ev_sig === "string") { - evSig = { - cipher: DenomKeyType.LegacyRsa, - blinded_rsa_signature: resp.ev_sig, - }; - } else { - evSig = resp.ev_sig; - } - if ( - !( - evSig.cipher === DenomKeyType.Rsa || - evSig.cipher === DenomKeyType.LegacyRsa - ) - ) { + if (!(evSig.cipher === DenomKeyType.Rsa)) { throw Error("unsupported cipher"); } @@ -639,10 +623,7 @@ async function processPlanchetVerifyAndStoreCoin( } let denomSig: UnblindedSignature; - if ( - planchet.denomPub.cipher === DenomKeyType.LegacyRsa || - planchet.denomPub.cipher === DenomKeyType.Rsa - ) { + if (planchet.denomPub.cipher === DenomKeyType.Rsa) { denomSig = { cipher: planchet.denomPub.cipher, rsa_signature: denomSigRsa, diff --git a/packages/taler-wallet-core/src/util/coinSelection.test.ts b/packages/taler-wallet-core/src/util/coinSelection.test.ts index 49f8d1635..1675a9a35 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.test.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.test.ts @@ -18,7 +18,7 @@ * Imports. */ import test from "ava"; -import { AmountJson, Amounts } from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, DenomKeyType } from "@gnu-taler/taler-util"; import { AvailableCoinInfo, selectPayCoins } from "./coinSelection.js"; function a(x: string): AmountJson { @@ -34,7 +34,7 @@ function fakeAci(current: string, feeDeposit: string): AvailableCoinInfo { availableAmount: a(current), coinPub: "foobar", denomPub: { - cipher: 1, + cipher: DenomKeyType.Rsa, rsa_public_key: "foobar", }, feeDeposit: a(feeDeposit), @@ -47,7 +47,7 @@ test("it should be able to pay if merchant takes the fees", (t) => { fakeAci("EUR:1.0", "EUR:0.1"), fakeAci("EUR:1.0", "EUR:0.0"), ]; - acis.forEach((x, i) => x.coinPub = String(i)); + acis.forEach((x, i) => (x.coinPub = String(i))); const res = selectPayCoins({ candidates: { @@ -75,7 +75,7 @@ test("it should take the last two coins if it pays less fees", (t) => { // Merchant covers the fee, this one shouldn't be used fakeAci("EUR:1.0", "EUR:0.0"), ]; - acis.forEach((x, i) => x.coinPub = String(i)); + acis.forEach((x, i) => (x.coinPub = String(i))); const res = selectPayCoins({ candidates: { @@ -102,8 +102,8 @@ test("it should take the last coins if the merchant doest not take all the fee", fakeAci("EUR:1.0", "EUR:0.5"), // this coin should be selected instead of previous one with fee fakeAci("EUR:1.0", "EUR:0.0"), - ] - acis.forEach((x, i) => x.coinPub = String(i)); + ]; + acis.forEach((x, i) => (x.coinPub = String(i))); const res = selectPayCoins({ candidates: { @@ -221,7 +221,7 @@ test("it should use the coins that spent less relative fee", (t) => { fakeAci("EUR:0.05", "EUR:0.05"), fakeAci("EUR:0.05", "EUR:0.05"), ]; - acis.forEach((x, i) => x.coinPub = String(i)); + acis.forEach((x, i) => (x.coinPub = String(i))); const res = selectPayCoins({ candidates: {