From 5c4c25516df9d65d29dc7f3f38b5a2a1a8e9e374 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sat, 27 Nov 2021 20:56:58 +0100 Subject: wallet: support both protocol versions --- packages/taler-util/src/codec.ts | 23 ++++++ packages/taler-util/src/libtool-version.ts | 7 +- packages/taler-util/src/logging.ts | 3 +- packages/taler-util/src/talerCrypto.ts | 27 ++++--- packages/taler-util/src/talerTypes.ts | 111 +++++++++++++++++++++++------ packages/taler-util/src/walletTypes.ts | 2 + 6 files changed, 139 insertions(+), 34 deletions(-) (limited to 'packages/taler-util/src') diff --git a/packages/taler-util/src/codec.ts b/packages/taler-util/src/codec.ts index 8605ff335..2ea64a249 100644 --- a/packages/taler-util/src/codec.ts +++ b/packages/taler-util/src/codec.ts @@ -417,3 +417,26 @@ export function codecOptional(innerCodec: Codec): Codec { }, }; } + +export type CodecType = T extends Codec ? X : any; + +export function codecForEither>>( + ...alts: [...T] +): Codec> { + return { + decode(x: any, c?: Context): any { + for (const alt of alts) { + try { + return alt.decode(x, c); + } catch (e) { + continue; + } + } + throw new DecodingError( + `No alternative matched at at ${renderContext(c)}`, + ); + }, + }; +} + +const x = codecForEither(codecForString(), codecForNumber()); diff --git a/packages/taler-util/src/libtool-version.ts b/packages/taler-util/src/libtool-version.ts index 17d2bbbdc..ed11a4e95 100644 --- a/packages/taler-util/src/libtool-version.ts +++ b/packages/taler-util/src/libtool-version.ts @@ -27,14 +27,15 @@ export interface VersionMatchResult { * Is the first version compatible with the second? */ compatible: boolean; + /** - * Is the first version older (-1), newser (+1) or + * Is the first version older (-1), newer (+1) or * identical (0)? */ currentCmp: number; } -interface Version { +export interface Version { current: number; revision: number; age: number; @@ -64,7 +65,7 @@ export namespace LibtoolVersion { return { compatible, currentCmp }; } - function parseVersion(v: string): Version | undefined { + export function parseVersion(v: string): Version | undefined { const [currentStr, revisionStr, ageStr, ...rest] = v.split(":"); if (rest.length !== 0) { return undefined; diff --git a/packages/taler-util/src/logging.ts b/packages/taler-util/src/logging.ts index 8b9de1ab0..117664d8c 100644 --- a/packages/taler-util/src/logging.ts +++ b/packages/taler-util/src/logging.ts @@ -55,7 +55,7 @@ export function setGlobalLogLevelFromString(logLevelStr: string) { break; default: if (isNode) { - process.stderr.write(`Invalid log level, defaulting to WARNING`); + process.stderr.write(`Invalid log level, defaulting to WARNING\n`); } else { console.warn(`Invalid log level, defaulting to WARNING`); } @@ -143,6 +143,7 @@ export class Logger { case LogLevel.Info: case LogLevel.Warn: case LogLevel.Error: + return true; case LogLevel.None: return false; } diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index c20ce72a6..d96c23236 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -349,18 +349,25 @@ export function hash(d: Uint8Array): Uint8Array { return nacl.hash(d); } +/** + * Hash a denomination public key according to the + * algorithm of exchange protocol v10. + */ export function hashDenomPub(pub: DenominationPubKey): Uint8Array { - if (pub.cipher !== DenomKeyType.Rsa) { - throw Error("unsupported cipher"); + if (pub.cipher === DenomKeyType.Rsa) { + const pubBuf = decodeCrock(pub.rsa_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); + uint8ArrayBuf.set(pubBuf, 8); + return nacl.hash(uint8ArrayBuf); + } else if (pub.cipher === DenomKeyType.LegacyRsa) { + return hash(decodeCrock(pub.rsa_public_key)); + } else { + throw Error(`unsupported cipher (${pub.cipher}), unable to hash`); } - const pubBuf = decodeCrock(pub.rsa_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); - uint8ArrayBuf.set(pubBuf, 8); - return nacl.hash(uint8ArrayBuf); } export function eddsaSign(msg: Uint8Array, eddsaPriv: Uint8Array): Uint8Array { diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts index bd9c67d7e..15dc88ca5 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -38,6 +38,7 @@ import { codecForConstNumber, buildCodecForUnion, codecForConstString, + codecForEither, } from "./codec.js"; import { Timestamp, @@ -50,7 +51,7 @@ import { codecForAmountString } from "./amounts.js"; /** * Denomination as found in the /keys response from the exchange. */ -export class Denomination { +export class ExchangeDenomination { /** * Value of one coin of the denomination. */ @@ -58,8 +59,11 @@ export class Denomination { /** * Public signing key of the denomination. + * + * The "string" alternative is for the old exchange protocol (v9) that + * only supports RSA keys. */ - denom_pub: DenominationPubKey; + denom_pub: DenominationPubKey | string; /** * Fee for withdrawing. @@ -128,7 +132,7 @@ export class AuditorDenomSig { /** * Auditor information as given by the exchange in /keys. */ -export class Auditor { +export class ExchangeAuditor { /** * Auditor's public key. */ @@ -157,8 +161,10 @@ export interface RecoupRequest { /** * Signature over the coin public key by the denomination. + * + * The string variant is for the legacy exchange protocol. */ - denom_sig: UnblindedSignature; + denom_sig: UnblindedSignature | string; /** * Coin public key of the coin we want to refund. @@ -198,11 +204,20 @@ export interface RecoupConfirmation { old_coin_pub?: string; } -export interface UnblindedSignature { +export type UnblindedSignature = + | RsaUnblindedSignature + | LegacyRsaUnblindedSignature; + +export interface RsaUnblindedSignature { cipher: DenomKeyType.Rsa; rsa_signature: string; } +export interface LegacyRsaUnblindedSignature { + cipher: DenomKeyType.LegacyRsa; + rsa_signature: string; +} + /** * Deposit permission for a single coin. */ @@ -211,18 +226,25 @@ export interface CoinDepositPermission { * Signature by the coin. */ coin_sig: string; + /** * Public key of the coin being spend. */ coin_pub: string; + /** * Signature made by the denomination public key. + * + * The string variant is for legacy protocol support. */ - ub_sig: UnblindedSignature; + + ub_sig: UnblindedSignature | string; + /** * The denomination public key associated with this coin. */ h_denom: string; + /** * The amount that is subtracted from this coin with this payment. */ @@ -358,6 +380,11 @@ export interface ContractTerms { */ h_wire: string; + /** + * Legacy wire hash, used for deposit operations with an older exchange. + */ + h_wire_legacy?: string; + /** * Hash of the merchant's wire details. */ @@ -662,7 +689,7 @@ export class ExchangeKeysJson { /** * List of offered denominations. */ - denoms: Denomination[]; + denoms: ExchangeDenomination[]; /** * The exchange's master public key. @@ -672,7 +699,7 @@ export class ExchangeKeysJson { /** * The list of auditors (partially) auditing the exchange. */ - auditors: Auditor[]; + auditors: ExchangeAuditor[]; /** * Timestamp when this response was issued. @@ -802,6 +829,7 @@ export class TipPickupGetResponse { export enum DenomKeyType { Rsa = 1, ClauseSchnorr = 2, + LegacyRsa = 3, } export interface RsaBlindedDenominationSignature { @@ -809,18 +837,25 @@ 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; + | CSBlindedDenominationSignature + | LegacyRsaBlindedDenominationSignature; export const codecForBlindedDenominationSignature = () => buildCodecForUnion() .discriminateOn("cipher") .alternative(1, codecForRsaBlindedDenominationSignature()) + .alternative(3, codecForLegacyRsaBlindedDenominationSignature()) .build("BlindedDenominationSignature"); export const codecForRsaBlindedDenominationSignature = () => @@ -829,8 +864,17 @@ export const codecForRsaBlindedDenominationSignature = () => .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 { - ev_sig: BlindedDenominationSignature; + /** + * The string variant is for legacy protocol support. + */ + ev_sig: BlindedDenominationSignature | string; } /** @@ -925,7 +969,10 @@ export interface ExchangeMeltResponse { } export interface ExchangeRevealItem { - ev_sig: BlindedDenominationSignature; + /** + * The string variant is for the legacy v9 protocol. + */ + ev_sig: BlindedDenominationSignature | string; } export interface ExchangeRevealResponse { @@ -1044,7 +1091,15 @@ export interface BankWithdrawalOperationPostResponse { transfer_done: boolean; } -export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; +export type DenominationPubKey = + | RsaDenominationPubKey + | CsDenominationPubKey + | LegacyRsaDenominationPubKey; + +export interface LegacyRsaDenominationPubKey { + cipher: DenomKeyType.LegacyRsa; + rsa_public_key: string; +} export interface RsaDenominationPubKey { cipher: DenomKeyType.Rsa; @@ -1061,6 +1116,7 @@ export const codecForDenominationPubKey = () => buildCodecForUnion() .discriminateOn("cipher") .alternative(1, codecForRsaDenominationPubKey()) + .alternative(3, codecForLegacyRsaDenominationPubKey()) .build("DenominationPubKey"); export const codecForRsaDenominationPubKey = () => @@ -1069,6 +1125,12 @@ export const codecForRsaDenominationPubKey = () => .property("rsa_public_key", codecForString()) .build("DenominationPubKey"); +export const codecForLegacyRsaDenominationPubKey = () => + buildCodecForObject() + .property("cipher", codecForConstNumber(3)) + .property("rsa_public_key", codecForString()) + .build("LegacyRsaDenominationPubKey"); + export const codecForBankWithdrawalOperationPostResponse = (): Codec => buildCodecForObject() .property("transfer_done", codecForBoolean()) @@ -1080,10 +1142,13 @@ export type EddsaSignatureString = string; export type EddsaPublicKeyString = string; export type CoinPublicKeyString = string; -export const codecForDenomination = (): Codec => - buildCodecForObject() +export const codecForDenomination = (): Codec => + buildCodecForObject() .property("value", codecForString()) - .property("denom_pub", codecForDenominationPubKey()) + .property( + "denom_pub", + codecForEither(codecForDenominationPubKey(), codecForString()), + ) .property("fee_withdraw", codecForString()) .property("fee_deposit", codecForString()) .property("fee_refresh", codecForString()) @@ -1101,8 +1166,8 @@ export const codecForAuditorDenomSig = (): Codec => .property("auditor_sig", codecForString()) .build("AuditorDenomSig"); -export const codecForAuditor = (): Codec => - buildCodecForObject() +export const codecForAuditor = (): Codec => + buildCodecForObject() .property("auditor_pub", codecForString()) .property("auditor_url", codecForString()) .property("denomination_keys", codecForList(codecForAuditorDenomSig())) @@ -1261,7 +1326,7 @@ export const codecForExchangeKeysJson = (): Codec => .property("signkeys", codecForList(codecForExchangeSigningKey())) .property("version", codecForString()) .property("reserve_closing_delay", codecForDuration) - .build("KeysJson"); + .build("ExchangeKeysJson"); export const codecForWireFeesJson = (): Codec => buildCodecForObject() @@ -1327,7 +1392,10 @@ export const codecForRecoupConfirmation = (): Codec => export const codecForWithdrawResponse = (): Codec => buildCodecForObject() - .property("ev_sig", codecForBlindedDenominationSignature()) + .property( + "ev_sig", + codecForEither(codecForBlindedDenominationSignature(), codecForString()), + ) .build("WithdrawResponse"); export const codecForMerchantPayResponse = (): Codec => @@ -1345,7 +1413,10 @@ export const codecForExchangeMeltResponse = (): Codec => export const codecForExchangeRevealItem = (): Codec => buildCodecForObject() - .property("ev_sig", codecForBlindedDenominationSignature()) + .property( + "ev_sig", + codecForEither(codecForBlindedDenominationSignature(), codecForString()), + ) .build("ExchangeRevealItem"); export const codecForExchangeRevealResponse = (): Codec => diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index f00e2907f..ced30e4db 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -49,6 +49,7 @@ import { codecForContractTerms, ContractTerms, DenominationPubKey, + DenomKeyType, UnblindedSignature, } from "./talerTypes.js"; import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes.js"; @@ -515,6 +516,7 @@ export interface DepositInfo { merchantPub: string; feeDeposit: AmountJson; wireInfoHash: string; + denomKeyType: DenomKeyType; denomPubHash: string; denomSig: UnblindedSignature; } -- cgit v1.2.3