diff options
Diffstat (limited to 'packages/taler-util/src/taler-types.ts')
-rw-r--r-- | packages/taler-util/src/taler-types.ts | 2417 |
1 files changed, 2417 insertions, 0 deletions
diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts new file mode 100644 index 000000000..392e7149c --- /dev/null +++ b/packages/taler-util/src/taler-types.ts @@ -0,0 +1,2417 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Type and schema definitions and helpers for the core GNU Taler protocol. + * + * Even though the rest of the wallet uses camelCase for fields, use snake_case + * here, since that's the convention for the Taler JSON+HTTP API. + */ + +/** + * Imports. + */ + +import { Amounts, codecForAmountString } from "./amounts.js"; +import { + Codec, + buildCodecForObject, + buildCodecForUnion, + codecForAny, + codecForBoolean, + codecForConstString, + codecForList, + codecForMap, + codecForNumber, + codecForString, + codecForStringURL, + codecOptional, +} from "./codec.js"; +import { strcmp } from "./helpers.js"; +import { + CurrencySpecification, + codecForCurrencySpecificiation, + codecForEither, + codecForProduct, +} from "./index.js"; +import { Edx25519PublicKeyEnc } from "./taler-crypto.js"; +import { + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForDuration, + codecForTimestamp, +} from "./time.js"; + +/** + * Denomination as found in the /keys response from the exchange. + */ +export class ExchangeDenomination { + /** + * Value of one coin of the denomination. + */ + value: string; + + /** + * Public signing key of the denomination. + */ + denom_pub: DenominationPubKey; + + /** + * Fee for withdrawing. + */ + fee_withdraw: string; + + /** + * Fee for depositing. + */ + fee_deposit: string; + + /** + * Fee for refreshing. + */ + fee_refresh: string; + + /** + * Fee for refunding. + */ + fee_refund: string; + + /** + * Start date from which withdraw is allowed. + */ + stamp_start: TalerProtocolTimestamp; + + /** + * End date for withdrawing. + */ + stamp_expire_withdraw: TalerProtocolTimestamp; + + /** + * Expiration date after which the exchange can forget about + * the currency. + */ + stamp_expire_legal: TalerProtocolTimestamp; + + /** + * Date after which the coins of this denomination can't be + * deposited anymore. + */ + stamp_expire_deposit: TalerProtocolTimestamp; + + /** + * Signature over the denomination information by the exchange's master + * signing key. + */ + master_sig: string; +} + +/** + * Signature by the auditor that a particular denomination key is audited. + */ +export class AuditorDenomSig { + /** + * Denomination public key's hash. + */ + denom_pub_h: string; + + /** + * The signature. + */ + auditor_sig: string; +} + +/** + * Auditor information as given by the exchange in /keys. + */ +export class ExchangeAuditor { + /** + * Auditor's public key. + */ + auditor_pub: string; + + /** + * Base URL of the auditor. + */ + auditor_url: string; + + /** + * List of signatures for denominations by the auditor. + */ + denomination_keys: AuditorDenomSig[]; +} + +export type ExchangeWithdrawValue = + | ExchangeRsaWithdrawValue + | ExchangeCsWithdrawValue; + +export interface ExchangeRsaWithdrawValue { + cipher: "RSA"; +} + +export interface ExchangeCsWithdrawValue { + cipher: "CS"; + + /** + * CSR R0 value + */ + r_pub_0: string; + + /** + * CSR R1 value + */ + r_pub_1: string; +} + +export interface RecoupRequest { + /** + * Hashed denomination public key of the coin we want to get + * paid back. + */ + denom_pub_hash: string; + + /** + * Signature over the coin public key by the denomination. + * + * The string variant is for the legacy exchange protocol. + */ + denom_sig: UnblindedSignature; + + /** + * Blinding key that was used during withdraw, + * used to prove that we were actually withdrawing the coin. + */ + coin_blind_key_secret: string; + + /** + * Signature of TALER_RecoupRequestPS created with the coin's private key. + */ + coin_sig: string; + + ewv: ExchangeWithdrawValue; +} + +export interface RecoupRefreshRequest { + /** + * Hashed enomination public key of the coin we want to get + * paid back. + */ + denom_pub_hash: string; + + /** + * Signature over the coin public key by the denomination. + * + * The string variant is for the legacy exchange protocol. + */ + denom_sig: UnblindedSignature; + + /** + * Coin's blinding factor. + */ + coin_blind_key_secret: string; + + /** + * Signature of TALER_RecoupRefreshRequestPS created with + * the coin's private key. + */ + coin_sig: string; + + ewv: ExchangeWithdrawValue; +} + +/** + * Response that we get from the exchange for a payback request. + */ +export interface RecoupConfirmation { + /** + * Public key of the reserve that will receive the payback. + */ + reserve_pub?: string; + + /** + * Public key of the old coin that will receive the recoup, + * provided if refreshed was true. + */ + old_coin_pub?: string; +} + +export type UnblindedSignature = RsaUnblindedSignature; + +export interface RsaUnblindedSignature { + cipher: DenomKeyType.Rsa; + rsa_signature: string; +} + +/** + * Deposit permission for a single coin. + */ +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; + + /** + * The denomination public key associated with this coin. + */ + h_denom: string; + + /** + * The amount that is subtracted from this coin with this payment. + */ + contribution: string; + + /** + * URL of the exchange this coin was withdrawn from. + */ + exchange_url: string; + + minimum_age_sig?: EddsaSignatureString; + + age_commitment?: Edx25519PublicKeyEnc[]; + + h_age_commitment?: string; +} + +/** + * Information about an exchange as stored inside a + * merchant's contract terms. + */ +export interface ExchangeHandle { + // The exchange's base URL. + url: string; + + // Master public key of the exchange. + master_pub: EddsaPublicKeyString; +} + +export interface AuditorHandle { + /** + * Official name of the auditor. + */ + name: string; + + /** + * Master public signing key of the auditor. + */ + auditor_pub: EddsaPublicKeyString; + + /** + * Base URL of the auditor. + */ + url: string; +} + +// Delivery location, loosely modeled as a subset of +// ISO20022's PostalAddress25. +export interface Location { + // Nation with its own government. + country?: string; + + // Identifies a subdivision of a country such as state, region, county. + country_subdivision?: string; + + // Identifies a subdivision within a country sub-division. + district?: string; + + // Name of a built-up area, with defined boundaries, and a local government. + town?: string; + + // Specific location name within the town. + town_location?: string; + + // Identifier consisting of a group of letters and/or numbers that + // is added to a postal address to assist the sorting of mail. + post_code?: string; + + // Name of a street or thoroughfare. + street?: string; + + // Name of the building or house. + building_name?: string; + + // Number that identifies the position of a building on a street. + building_number?: string; + + // Free-form address lines, should not exceed 7 elements. + address_lines?: string[]; +} + +export interface MerchantInfo { + // The merchant's legal name of business. + name: string; + + // Label for a location with the business address of the merchant. + email?: string; + + // Label for a location with the business address of the merchant. + website?: string; + + // An optional base64-encoded product image. + logo?: ImageDataUrl; + + // Label for a location with the business address of the merchant. + address?: Location; + + // Label for a location that denotes the jurisdiction for disputes. + // Some of the typical fields for a location (such as a street address) may be absent. + jurisdiction?: Location; +} + +export interface Tax { + // the name of the tax + name: string; + + // amount paid in tax + tax: AmountString; +} + +export interface Product { + // merchant-internal identifier for the product. + product_id?: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions + description_i18n?: InternationalizedString; + + // The number of units of the product to deliver to the customer. + quantity?: Integer; + + // The unit in which the product is measured (liters, kilograms, packages, etc.) + unit?: string; + + // The price of the product; this is the total price for quantity times unit of this product. + price?: AmountString; + + // An optional base64-encoded product image + image?: ImageDataUrl; + + // a list of taxes paid by the merchant for this product. Can be empty. + taxes?: Tax[]; + + // time indicating when this product should be delivered + delivery_date?: TalerProtocolTimestamp; +} + +export interface InternationalizedString { + [lang_tag: string]: string; +} + +/** + * Contract terms from a merchant. + * FIXME: Add type field! + */ +export interface MerchantContractTerms { + // The hash of the merchant instance's wire details. + h_wire: string; + + // Specifies for how long the wallet should try to get an + // automatic refund for the purchase. If this field is + // present, the wallet should wait for a few seconds after + // the purchase and then automatically attempt to obtain + // a refund. The wallet should probe until "delay" + // after the payment was successful (i.e. via long polling + // or via explicit requests with exponential back-off). + // + // In particular, if the wallet is offline + // at that time, it MUST repeat the request until it gets + // one response from the merchant after the delay has expired. + // If the refund is granted, the wallet MUST automatically + // recover the payment. This is used in case a merchant + // knows that it might be unable to satisfy the contract and + // desires for the wallet to attempt to get the refund without any + // customer interaction. Note that it is NOT an error if the + // merchant does not grant a refund. + auto_refund?: TalerProtocolDuration; + + // Wire transfer method identifier for the wire method associated with h_wire. + // The wallet may only select exchanges via a matching auditor if the + // exchange also supports this wire method. + // The wire transfer fees must be added based on this wire transfer method. + wire_method: string; + + // Human-readable description of the whole purchase. + summary: string; + + // Map from IETF BCP 47 language tags to localized summaries. + summary_i18n?: InternationalizedString; + + // Unique, free-form identifier for the proposal. + // Must be unique within a merchant instance. + // For merchants that do not store proposals in their DB + // before the customer paid for them, the order_id can be used + // by the frontend to restore a proposal from the information + // encoded in it (such as a short product identifier and timestamp). + order_id: string; + + // Total price for the transaction. + // The exchange will subtract deposit fees from that amount + // before transferring it to the merchant. + amount: string; + + // Nonce generated by the wallet and echoed by the merchant + // in this field when the proposal is generated. + nonce: string; + + // After this deadline, the merchant won't accept payments for the contract. + pay_deadline: TalerProtocolTimestamp; + + // More info about the merchant, see below. + merchant: MerchantInfo; + + // Merchant's public key used to sign this proposal; this information + // is typically added by the backend. Note that this can be an ephemeral key. + merchant_pub: string; + + // Time indicating when the order should be delivered. + // May be overwritten by individual products. + delivery_date?: TalerProtocolTimestamp; + + // Delivery location for (all!) products. + delivery_location?: Location; + + // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + exchanges: ExchangeHandle[]; + + // List of products that are part of the purchase (see Product). + products?: Product[]; + + // After this deadline has passed, no refunds will be accepted. + refund_deadline: TalerProtocolTimestamp; + + // Transfer deadline for the exchange. Must be in the + // deposit permissions of coins used to pay for this order. + wire_transfer_deadline: TalerProtocolTimestamp; + + // Time when this contract was generated. + timestamp: TalerProtocolTimestamp; + + // Base URL of the (public!) merchant backend API. + // Must be an absolute URL that ends with a slash. + merchant_base_url: string; + + // URL that will show that the order was successful after + // it has been paid for. Optional, but either fulfillment_url + // or fulfillment_message must be specified in every + // contract terms. + // + // If a non-unique fulfillment URL is used, a customer can only + // buy the order once and will be redirected to a previous purchase + // when trying to buy an order with the same fulfillment URL a second + // time. This is useful for digital goods that a customer only needs + // to buy once but should be able to repeatedly download. + // + // For orders where the customer is expected to be able to make + // repeated purchases (for equivalent goods), the fulfillment URL + // should be made unique for every order. The easiest way to do + // this is to include a unique order ID in the fulfillment URL. + // + // When POSTing to the merchant, the placeholder text "${ORDER_ID}" + // is be replaced with the actual order ID (useful if the + // order ID is generated server-side and needs to be + // in the URL). Note that this placeholder can only be used once. + // Front-ends may use other means to generate a unique fulfillment URL. + fulfillment_url?: string; + + // URL where the same contract could be ordered again (if + // available). Returned also at the public order endpoint + // for people other than the actual buyer (hence public, + // in case order IDs are guessable). + public_reorder_url?: string; + + // Message shown to the customer after paying for the order. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_message?: string; + + // Map from IETF BCP 47 language tags to localized fulfillment + // messages. + fulfillment_message_i18n?: InternationalizedString; + + // Maximum total deposit fee accepted by the merchant for this contract. + // Overrides defaults of the merchant instance. + max_fee: string; + + // Extra data that is only interpreted by the merchant frontend. + // Useful when the merchant needs to store extra information on a + // contract without storing it separately in their database. + // Must really be an Object (not a string, integer, float or array). + extra?: any; + + // Minimum age the buyer must have (in years). Default is 0. + // This value is at least as large as the maximum over all + // minimum age requirements of the products in this contract. + // It might also be set independent of any product, due to + // legal requirements. + minimum_age?: Integer; +} + +/** + * Refund permission in the format that the merchant gives it to us. + */ +export interface MerchantAbortPayRefundDetails { + /** + * Amount to be refunded. + */ + refund_amount: string; + + /** + * Fee for the refund. + */ + refund_fee: string; + + /** + * Public key of the coin being refunded. + */ + coin_pub: string; + + /** + * Refund transaction ID between merchant and exchange. + */ + rtransaction_id: number; + + /** + * Exchange's key used for the signature. + */ + exchange_pub?: string; + + /** + * Exchange's signature to confirm the refund. + */ + exchange_sig?: string; + + /** + * Error replay from the exchange (if any). + */ + exchange_reply?: any; + + /** + * Error code from the exchange (if any). + */ + exchange_code?: number; + + /** + * HTTP status code of the exchange's response + * to the merchant's refund request. + */ + exchange_http_status: number; +} + +/** + * Planchet detail sent to the merchant. + */ +export interface TipPlanchetDetail { + /** + * Hashed denomination public key. + */ + denom_pub_hash: string; + + /** + * Coin's blinded public key. + */ + coin_ev: CoinEnvelope; +} + +/** + * Request sent to the merchant to pick up a tip. + */ +export interface TipPickupRequest { + /** + * Identifier of the tip. + */ + tip_id: string; + + /** + * List of planchets the wallet wants to use for the tip. + */ + planchets: TipPlanchetDetail[]; +} + +/** + * Reserve signature, defined as separate class to facilitate + * schema validation. + */ +export interface MerchantBlindSigWrapperV1 { + /** + * Reserve signature. + */ + blind_sig: string; +} + +/** + * Response of the merchant + * to the TipPickupRequest. + */ +export interface MerchantTipResponseV1 { + /** + * The order of the signatures matches the planchets list. + */ + blind_sigs: MerchantBlindSigWrapperV1[]; +} + +export interface MerchantBlindSigWrapperV2 { + blind_sig: BlindedDenominationSignature; +} + +/** + * Response of the merchant + * to the TipPickupRequest. + */ +export interface MerchantTipResponseV2 { + /** + * The order of the signatures matches the planchets list. + */ + blind_sigs: MerchantBlindSigWrapperV2[]; +} + +/** + * Element of the payback list that the + * exchange gives us in /keys. + */ +export class Recoup { + /** + * The hash of the denomination public key for which the payback is offered. + */ + h_denom_pub: string; +} + +/** + * Structure of one exchange signing key in the /keys response. + */ +export class ExchangeSignKeyJson { + stamp_start: TalerProtocolTimestamp; + stamp_expire: TalerProtocolTimestamp; + stamp_end: TalerProtocolTimestamp; + key: EddsaPublicKeyString; + master_sig: EddsaSignatureString; +} + +/** + * Structure that the exchange gives us in /keys. + */ +export class ExchangeKeysJson { + /** + * Canonical, public base URL of the exchange. + */ + base_url: string; + + currency: string; + + /** + * The exchange's master public key. + */ + master_public_key: string; + + /** + * The list of auditors (partially) auditing the exchange. + */ + auditors: ExchangeAuditor[]; + + /** + * Timestamp when this response was issued. + */ + list_issue_date: TalerProtocolTimestamp; + + /** + * List of revoked denominations. + */ + recoup?: Recoup[]; + + /** + * Short-lived signing keys used to sign online + * responses. + */ + signkeys: ExchangeSignKeyJson[]; + + /** + * Protocol version. + */ + version: string; + + reserve_closing_delay: TalerProtocolDuration; + + global_fees: GlobalFees[]; + + accounts: ExchangeWireAccount[]; + + wire_fees: { [methodName: string]: WireFeesJson[] }; + + denominations: DenomGroup[]; +} + +export type DenomGroup = + | DenomGroupRsa + | DenomGroupCs + | DenomGroupRsaAgeRestricted + | DenomGroupCsAgeRestricted; + +export interface DenomGroupCommon { + // How much are coins of this denomination worth? + value: AmountString; + + // Fee charged by the exchange for withdrawing a coin of this denomination. + fee_withdraw: AmountString; + + // Fee charged by the exchange for depositing a coin of this denomination. + fee_deposit: AmountString; + + // Fee charged by the exchange for refreshing a coin of this denomination. + fee_refresh: AmountString; + + // Fee charged by the exchange for refunding a coin of this denomination. + fee_refund: AmountString; + + // XOR of all the SHA-512 hash values of the denominations' public keys + // in this group. Note that for hashing, the binary format of the + // public keys is used, and not their base32 encoding. + hash: HashCodeString; +} + +export interface DenomCommon { + // Signature of TALER_DenominationKeyValidityPS. + master_sig: EddsaSignatureString; + + // When does the denomination key become valid? + stamp_start: TalerProtocolTimestamp; + + // When is it no longer possible to deposit coins + // of this denomination? + stamp_expire_withdraw: TalerProtocolTimestamp; + + // Timestamp indicating by when legal disputes relating to these coins must + // be settled, as the exchange will afterwards destroy its evidence relating to + // transactions involving this coin. + stamp_expire_legal: TalerProtocolTimestamp; + + stamp_expire_deposit: TalerProtocolTimestamp; + + // Set to 'true' if the exchange somehow "lost" + // the private key. The denomination was not + // necessarily revoked, but still cannot be used + // to withdraw coins at this time (theoretically, + // the private key could be recovered in the + // future; coins signed with the private key + // remain valid). + lost?: boolean; +} + +export type RsaPublicKeySring = string; +export type AgeMask = number; +export type ImageDataUrl = string; + +/** + * 32-byte value representing a point on Curve25519. + */ +export type Cs25519Point = string; + +export interface DenomGroupRsa extends DenomGroupCommon { + cipher: "RSA"; + + denoms: ({ + rsa_pub: RsaPublicKeySring; + } & DenomCommon)[]; +} + +export interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { + cipher: "RSA+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + rsa_pub: RsaPublicKeySring; + } & DenomCommon)[]; +} + +export interface DenomGroupCs extends DenomGroupCommon { + cipher: "CS"; + age_mask: AgeMask; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; +} + +export interface DenomGroupCsAgeRestricted extends DenomGroupCommon { + cipher: "CS+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; +} + +export interface GlobalFees { + // What date (inclusive) does these fees go into effect? + start_date: TalerProtocolTimestamp; + + // What date (exclusive) does this fees stop going into effect? + end_date: TalerProtocolTimestamp; + + // Account history fee, charged when a user wants to + // obtain a reserve/account history. + history_fee: AmountString; + + // Annual fee charged for having an open account at the + // exchange. Charged to the account. If the account + // balance is insufficient to cover this fee, the account + // is automatically deleted/closed. (Note that the exchange + // will keep the account history around for longer for + // regulatory reasons.) + account_fee: AmountString; + + // Purse fee, charged only if a purse is abandoned + // and was not covered by the account limit. + purse_fee: AmountString; + + // How long will the exchange preserve the account history? + // After an account was deleted/closed, the exchange will + // retain the account history for legal reasons until this time. + history_expiration: TalerProtocolDuration; + + // Non-negative number of concurrent purses that any + // account holder is allowed to create without having + // to pay the purse_fee. + purse_account_limit: number; + + // How long does an exchange keep a purse around after a purse + // has expired (or been successfully merged)? A 'GET' request + // for a purse will succeed until the purse expiration time + // plus this value. + purse_timeout: TalerProtocolDuration; + + // Signature of TALER_GlobalFeesPS. + master_sig: string; +} +/** + * Wire fees as announced by the exchange. + */ +export class WireFeesJson { + /** + * Cost of a wire transfer. + */ + wire_fee: string; + + /** + * Cost of clising a reserve. + */ + closing_fee: string; + + /** + * Signature made with the exchange's master key. + */ + sig: string; + + /** + * Date from which the fee applies. + */ + start_date: TalerProtocolTimestamp; + + /** + * Data after which the fee doesn't apply anymore. + */ + end_date: TalerProtocolTimestamp; +} + +/** + * Proposal returned from the contract URL. + */ +export class Proposal { + /** + * Contract terms for the propoal. + * Raw, un-decoded JSON object. + */ + contract_terms: any; + + /** + * Signature over contract, made by the merchant. The public key used for signing + * must be contract_terms.merchant_pub. + */ + sig: string; +} + +/** + * Response from the internal merchant API. + */ +export class CheckPaymentResponse { + order_status: string; + refunded: boolean | undefined; + refunded_amount: string | undefined; + contract_terms: any | undefined; + taler_pay_uri: string | undefined; + contract_url: string | undefined; +} + +/** + * Response from the bank. + */ +export class WithdrawOperationStatusResponse { + status: "selected" | "aborted" | "confirmed" | "pending"; + + selection_done: boolean; + + transfer_done: boolean; + + aborted: boolean; + + amount: string; + + sender_wire?: string; + + suggested_exchange?: string; + + confirm_transfer_url?: string; + + wire_types: string[]; +} + +/** + * Response from the merchant. + */ +export class RewardPickupGetResponse { + reward_amount: string; + + exchange_url: string; + + next_url?: string; + + expiration: TalerProtocolTimestamp; +} + +export enum DenomKeyType { + 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 { + cipher: DenomKeyType.Rsa; + blinded_rsa_signature: string; +} + +export interface CSBlindedDenominationSignature { + cipher: DenomKeyType.ClauseSchnorr; +} + +export type BlindedDenominationSignature = + | RsaBlindedDenominationSignature + | CSBlindedDenominationSignature; + +export const codecForRsaBlindedDenominationSignature = () => + buildCodecForObject<RsaBlindedDenominationSignature>() + .property("cipher", codecForConstString(DenomKeyType.Rsa)) + .property("blinded_rsa_signature", codecForString()) + .build("RsaBlindedDenominationSignature"); + +export const codecForBlindedDenominationSignature = () => + buildCodecForUnion<BlindedDenominationSignature>() + .discriminateOn("cipher") + .alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature()) + .build("BlindedDenominationSignature"); + +export class ExchangeWithdrawResponse { + ev_sig: BlindedDenominationSignature; +} + +export class ExchangeWithdrawBatchResponse { + ev_sigs: ExchangeWithdrawResponse[]; +} + +export interface MerchantPayResponse { + sig: string; + pos_confirmation?: string; +} + +export interface ExchangeMeltRequest { + coin_pub: CoinPublicKeyString; + confirm_sig: EddsaSignatureString; + denom_pub_hash: HashCodeString; + denom_sig: UnblindedSignature; + rc: string; + value_with_fee: AmountString; + age_commitment_hash?: HashCodeString; +} + +export interface ExchangeMeltResponse { + /** + * Which of the kappa indices does the client not have to reveal. + */ + noreveal_index: number; + + /** + * Signature of TALER_RefreshMeltConfirmationPS whereby the exchange + * affirms the successful melt and confirming the noreveal_index + */ + exchange_sig: EddsaSignatureString; + + /* + * public EdDSA key of the exchange that was used to generate the signature. + * Should match one of the exchange's signing keys from /keys. Again given + * explicitly as the client might otherwise be confused by clock skew as to + * which signing key was used. + */ + exchange_pub: EddsaPublicKeyString; + + /* + * Base URL to use for operations on the refresh context + * (so the reveal operation). If not given, + * the base URL is the same as the one used for this request. + * Can be used if the base URL for /refreshes/ differs from that + * for /coins/, i.e. for load balancing. Clients SHOULD + * respect the refresh_base_url if provided. Any HTTP server + * belonging to an exchange MUST generate a 307 or 308 redirection + * to the correct base URL should a client uses the wrong base + * URL, or if the base URL has changed since the melt. + * + * When melting the same coin twice (technically allowed + * as the response might have been lost on the network), + * the exchange may return different values for the refresh_base_url. + */ + refresh_base_url?: string; +} + +export interface ExchangeRevealItem { + ev_sig: BlindedDenominationSignature; +} + +export interface ExchangeRevealResponse { + // List of the exchange's blinded RSA signatures on the new coins. + ev_sigs: ExchangeRevealItem[]; +} + +interface MerchantOrderStatusPaid { + // Was the payment refunded (even partially, via refund or abort)? + refunded: boolean; + + // Is any amount of the refund still waiting to be picked up (even partially)? + refund_pending: boolean; + + // Amount that was refunded in total. + refund_amount: AmountString; + + // Amount that already taken by the wallet. + refund_taken: AmountString; +} + +interface MerchantOrderRefundResponse { + /** + * Amount that was refunded in total. + */ + refund_amount: AmountString; + + /** + * Successful refunds for this payment, empty array for none. + */ + refunds: MerchantCoinRefundStatus[]; + + /** + * Public key of the merchant. + */ + merchant_pub: EddsaPublicKeyString; +} + +export type MerchantCoinRefundStatus = + | MerchantCoinRefundSuccessStatus + | MerchantCoinRefundFailureStatus; + +export interface MerchantCoinRefundSuccessStatus { + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: EddsaSignatureString; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: TalerProtocolTimestamp; +} + +export interface MerchantCoinRefundFailureStatus { + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: any; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: TalerProtocolTimestamp; +} + +export interface MerchantOrderStatusUnpaid { + /** + * URI that the wallet must process to complete the payment. + */ + taler_pay_uri: string; + + /** + * Alternative order ID which was paid for already in the same session. + * + * Only given if the same product was purchased before in the same session. + */ + already_paid_order_id?: string; +} + +/** + * Response body for the following endpoint: + * + * POST {talerBankIntegrationApi}/withdrawal-operation/{wopid} + */ +export interface BankWithdrawalOperationPostResponse { + // Current status of the operation + // pending: the operation is pending parameters selection (exchange and reserve public key) + // selected: the operations has been selected and is pending confirmation + // aborted: the operation has been aborted + // confirmed: the transfer has been confirmed and registered by the bank + status: "selected" | "aborted" | "confirmed" | "pending"; + + // URL that the user needs to navigate to in order to + // complete some final confirmation (e.g. 2FA). + // + // Only applicable when status is selected or pending. + // It may contain withdrawal operation id + confirm_transfer_url?: string; + + // Deprecated field use status instead + // The transfer has been confirmed and registered by the bank. + // Does not guarantee that the funds have arrived at the exchange already. + transfer_done: boolean; +} + +export const codeForBankWithdrawalOperationPostResponse = + (): Codec<BankWithdrawalOperationPostResponse> => + buildCodecForObject<BankWithdrawalOperationPostResponse>() + .property( + "status", + codecForEither( + codecForConstString("selected"), + codecForConstString("confirmed"), + codecForConstString("aborted"), + codecForConstString("pending"), + ), + ) + .property("confirm_transfer_url", codecOptional(codecForString())) + .property("transfer_done", codecForBoolean()) + .build("BankWithdrawalOperationPostResponse"); + +export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; + +export interface RsaDenominationPubKey { + readonly cipher: DenomKeyType.Rsa; + readonly rsa_public_key: string; + readonly age_mask: number; +} + +export interface CsDenominationPubKey { + readonly cipher: DenomKeyType.ClauseSchnorr; + readonly age_mask: number; + readonly cs_public_key: string; +} + +export namespace DenominationPubKey { + export function cmp( + p1: DenominationPubKey, + p2: DenominationPubKey, + ): -1 | 0 | 1 { + if (p1.cipher < p2.cipher) { + return -1; + } else if (p1.cipher > p2.cipher) { + return +1; + } else if ( + p1.cipher === DenomKeyType.Rsa && + p2.cipher === DenomKeyType.Rsa + ) { + if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { + return -1; + } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { + return 1; + } + return strcmp(p1.rsa_public_key, p2.rsa_public_key); + } else if ( + p1.cipher === DenomKeyType.ClauseSchnorr && + p2.cipher === DenomKeyType.ClauseSchnorr + ) { + if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { + return -1; + } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { + return 1; + } + return strcmp(p1.cs_public_key, p2.cs_public_key); + } else { + throw Error("unsupported cipher"); + } + } +} + +export const codecForRsaDenominationPubKey = () => + buildCodecForObject<RsaDenominationPubKey>() + .property("cipher", codecForConstString(DenomKeyType.Rsa)) + .property("rsa_public_key", codecForString()) + .property("age_mask", codecForNumber()) + .build("DenominationPubKey"); + +export const codecForCsDenominationPubKey = () => + buildCodecForObject<CsDenominationPubKey>() + .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr)) + .property("cs_public_key", codecForString()) + .property("age_mask", codecForNumber()) + .build("CsDenominationPubKey"); + +export const codecForDenominationPubKey = () => + buildCodecForUnion<DenominationPubKey>() + .discriminateOn("cipher") + .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey()) + .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) + .build("DenominationPubKey"); + +declare const __amount_str: unique symbol; +export type AmountString = string & { [__amount_str]: true }; +// export type AmountString = string; +export type Base32String = string; +export type EddsaSignatureString = string; +export type EddsaPublicKeyString = string; +export type EddsaPrivateKeyString = string; +export type CoinPublicKeyString = string; + +export const codecForDenomination = (): Codec<ExchangeDenomination> => + buildCodecForObject<ExchangeDenomination>() + .property("value", codecForString()) + .property("denom_pub", codecForDenominationPubKey()) + .property("fee_withdraw", codecForString()) + .property("fee_deposit", codecForString()) + .property("fee_refresh", codecForString()) + .property("fee_refund", codecForString()) + .property("stamp_start", codecForTimestamp) + .property("stamp_expire_withdraw", codecForTimestamp) + .property("stamp_expire_legal", codecForTimestamp) + .property("stamp_expire_deposit", codecForTimestamp) + .property("master_sig", codecForString()) + .build("Denomination"); + +export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => + buildCodecForObject<AuditorDenomSig>() + .property("denom_pub_h", codecForString()) + .property("auditor_sig", codecForString()) + .build("AuditorDenomSig"); + +export const codecForAuditor = (): Codec<ExchangeAuditor> => + buildCodecForObject<ExchangeAuditor>() + .property("auditor_pub", codecForString()) + .property("auditor_url", codecForString()) + .property("denomination_keys", codecForList(codecForAuditorDenomSig())) + .build("Auditor"); + +export const codecForExchangeHandle = (): Codec<ExchangeHandle> => + buildCodecForObject<ExchangeHandle>() + .property("master_pub", codecForString()) + .property("url", codecForString()) + .build("ExchangeHandle"); + +export const codecForAuditorHandle = (): Codec<AuditorHandle> => + buildCodecForObject<AuditorHandle>() + .property("name", codecForString()) + .property("auditor_pub", codecForString()) + .property("url", codecForString()) + .build("AuditorHandle"); + +export const codecForLocation = (): Codec<Location> => + buildCodecForObject<Location>() + .property("country", codecOptional(codecForString())) + .property("country_subdivision", codecOptional(codecForString())) + .property("building_name", codecOptional(codecForString())) + .property("building_number", codecOptional(codecForString())) + .property("district", codecOptional(codecForString())) + .property("street", codecOptional(codecForString())) + .property("post_code", codecOptional(codecForString())) + .property("town", codecOptional(codecForString())) + .property("town_location", codecOptional(codecForString())) + .property("address_lines", codecOptional(codecForList(codecForString()))) + .build("Location"); + +export const codecForMerchantInfo = (): Codec<MerchantInfo> => + buildCodecForObject<MerchantInfo>() + .property("name", codecForString()) + .property("address", codecOptional(codecForLocation())) + .property("jurisdiction", codecOptional(codecForLocation())) + .build("MerchantInfo"); + +export const codecForInternationalizedString = + (): Codec<InternationalizedString> => codecForMap(codecForString()); + +export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> => + buildCodecForObject<MerchantContractTerms>() + .property("order_id", codecForString()) + .property("fulfillment_url", codecOptional(codecForString())) + .property("fulfillment_message", codecOptional(codecForString())) + .property( + "fulfillment_message_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("merchant_base_url", codecForString()) + .property("h_wire", codecForString()) + .property("auto_refund", codecOptional(codecForDuration)) + .property("wire_method", codecForString()) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .property("nonce", codecForString()) + .property("amount", codecForAmountString()) + .property("pay_deadline", codecForTimestamp) + .property("refund_deadline", codecForTimestamp) + .property("wire_transfer_deadline", codecForTimestamp) + .property("timestamp", codecForTimestamp) + .property("delivery_location", codecOptional(codecForLocation())) + .property("delivery_date", codecOptional(codecForTimestamp)) + .property("max_fee", codecForAmountString()) + .property("merchant", codecForMerchantInfo()) + .property("merchant_pub", codecForString()) + .property("exchanges", codecForList(codecForExchangeHandle())) + .property("products", codecOptional(codecForList(codecForProduct()))) + .property("extra", codecForAny()) + .property("minimum_age", codecOptional(codecForNumber())) + .build("MerchantContractTerms"); + +export const codecForPeerContractTerms = (): Codec<PeerContractTerms> => + buildCodecForObject<PeerContractTerms>() + .property("summary", codecForString()) + .property("amount", codecForAmountString()) + .property("purse_expiration", codecForTimestamp) + .build("PeerContractTerms"); + +export const codecForMerchantRefundPermission = + (): Codec<MerchantAbortPayRefundDetails> => + buildCodecForObject<MerchantAbortPayRefundDetails>() + .property("refund_amount", codecForAmountString()) + .property("refund_fee", codecForAmountString()) + .property("coin_pub", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("exchange_sig", codecOptional(codecForString())) + .property("exchange_pub", codecOptional(codecForString())) + .build("MerchantRefundPermission"); + +export const codecForBlindSigWrapperV2 = (): Codec<MerchantBlindSigWrapperV2> => + buildCodecForObject<MerchantBlindSigWrapperV2>() + .property("blind_sig", codecForBlindedDenominationSignature()) + .build("MerchantBlindSigWrapperV2"); + +export const codecForMerchantTipResponseV2 = (): Codec<MerchantTipResponseV2> => + buildCodecForObject<MerchantTipResponseV2>() + .property("blind_sigs", codecForList(codecForBlindSigWrapperV2())) + .build("MerchantTipResponseV2"); + +export const codecForRecoup = (): Codec<Recoup> => + buildCodecForObject<Recoup>() + .property("h_denom_pub", codecForString()) + .build("Recoup"); + +export const codecForExchangeSigningKey = (): Codec<ExchangeSignKeyJson> => + buildCodecForObject<ExchangeSignKeyJson>() + .property("key", codecForString()) + .property("master_sig", codecForString()) + .property("stamp_end", codecForTimestamp) + .property("stamp_start", codecForTimestamp) + .property("stamp_expire", codecForTimestamp) + .build("ExchangeSignKeyJson"); + +export const codecForGlobalFees = (): Codec<GlobalFees> => + buildCodecForObject<GlobalFees>() + .property("start_date", codecForTimestamp) + .property("end_date", codecForTimestamp) + .property("history_fee", codecForAmountString()) + .property("account_fee", codecForAmountString()) + .property("purse_fee", codecForAmountString()) + .property("history_expiration", codecForDuration) + .property("purse_account_limit", codecForNumber()) + .property("purse_timeout", codecForDuration) + .property("master_sig", codecForString()) + .build("GlobalFees"); + +// FIXME: Validate properly! +export const codecForNgDenominations: Codec<DenomGroup> = codecForAny(); + +export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => + buildCodecForObject<ExchangeKeysJson>() + .property("base_url", codecForString()) + .property("currency", codecForString()) + .property("master_public_key", codecForString()) + .property("auditors", codecForList(codecForAuditor())) + .property("list_issue_date", codecForTimestamp) + .property("recoup", codecOptional(codecForList(codecForRecoup()))) + .property("signkeys", codecForList(codecForExchangeSigningKey())) + .property("version", codecForString()) + .property("reserve_closing_delay", codecForDuration) + .property("global_fees", codecForList(codecForGlobalFees())) + .property("accounts", codecForList(codecForExchangeWireAccount())) + .property("wire_fees", codecForMap(codecForList(codecForWireFeesJson()))) + .property("denominations", codecForList(codecForNgDenominations)) + .build("ExchangeKeysJson"); + +export const codecForWireFeesJson = (): Codec<WireFeesJson> => + buildCodecForObject<WireFeesJson>() + .property("wire_fee", codecForString()) + .property("closing_fee", codecForString()) + .property("sig", codecForString()) + .property("start_date", codecForTimestamp) + .property("end_date", codecForTimestamp) + .build("WireFeesJson"); + +export const codecForProposal = (): Codec<Proposal> => + buildCodecForObject<Proposal>() + .property("contract_terms", codecForAny()) + .property("sig", codecForString()) + .build("Proposal"); + +export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> => + buildCodecForObject<CheckPaymentResponse>() + .property("order_status", codecForString()) + .property("refunded", codecOptional(codecForBoolean())) + .property("refunded_amount", codecOptional(codecForString())) + .property("contract_terms", codecOptional(codecForAny())) + .property("taler_pay_uri", codecOptional(codecForString())) + .property("contract_url", codecOptional(codecForString())) + .build("CheckPaymentResponse"); + +export const codecForWithdrawOperationStatusResponse = + (): Codec<WithdrawOperationStatusResponse> => + buildCodecForObject<WithdrawOperationStatusResponse>() + .property( + "status", + codecForEither( + codecForConstString("selected"), + codecForConstString("confirmed"), + codecForConstString("aborted"), + codecForConstString("pending"), + ), + ) + .property("selection_done", codecForBoolean()) + .property("transfer_done", codecForBoolean()) + .property("aborted", codecForBoolean()) + .property("amount", codecForString()) + .property("sender_wire", codecOptional(codecForString())) + .property("suggested_exchange", codecOptional(codecForString())) + .property("confirm_transfer_url", codecOptional(codecForString())) + .property("wire_types", codecForList(codecForString())) + .build("WithdrawOperationStatusResponse"); + +export const codecForRewardPickupGetResponse = + (): Codec<RewardPickupGetResponse> => + buildCodecForObject<RewardPickupGetResponse>() + .property("reward_amount", codecForString()) + .property("exchange_url", codecForString()) + .property("next_url", codecOptional(codecForString())) + .property("expiration", codecForTimestamp) + .build("TipPickupGetResponse"); + +export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> => + buildCodecForObject<RecoupConfirmation>() + .property("reserve_pub", codecOptional(codecForString())) + .property("old_coin_pub", codecOptional(codecForString())) + .build("RecoupConfirmation"); + +export const codecForWithdrawResponse = (): Codec<ExchangeWithdrawResponse> => + buildCodecForObject<ExchangeWithdrawResponse>() + .property("ev_sig", codecForBlindedDenominationSignature()) + .build("WithdrawResponse"); + +export const codecForExchangeWithdrawBatchResponse = + (): Codec<ExchangeWithdrawBatchResponse> => + buildCodecForObject<ExchangeWithdrawBatchResponse>() + .property("ev_sigs", codecForList(codecForWithdrawResponse())) + .build("WithdrawBatchResponse"); + +export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => + buildCodecForObject<MerchantPayResponse>() + .property("sig", codecForString()) + .property("pos_confirmation", codecOptional(codecForString())) + .build("MerchantPayResponse"); + +export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> => + buildCodecForObject<ExchangeMeltResponse>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("noreveal_index", codecForNumber()) + .property("refresh_base_url", codecOptional(codecForString())) + .build("ExchangeMeltResponse"); + +export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> => + buildCodecForObject<ExchangeRevealItem>() + .property("ev_sig", codecForBlindedDenominationSignature()) + .build("ExchangeRevealItem"); + +export const codecForExchangeRevealResponse = + (): Codec<ExchangeRevealResponse> => + buildCodecForObject<ExchangeRevealResponse>() + .property("ev_sigs", codecForList(codecForExchangeRevealItem())) + .build("ExchangeRevealResponse"); + +export const codecForMerchantOrderStatusPaid = + (): Codec<MerchantOrderStatusPaid> => + buildCodecForObject<MerchantOrderStatusPaid>() + .property("refund_amount", codecForAmountString()) + .property("refund_taken", codecForAmountString()) + .property("refund_pending", codecForBoolean()) + .property("refunded", codecForBoolean()) + .build("MerchantOrderStatusPaid"); + +export const codecForMerchantOrderStatusUnpaid = + (): Codec<MerchantOrderStatusUnpaid> => + buildCodecForObject<MerchantOrderStatusUnpaid>() + .property("taler_pay_uri", codecForString()) + .property("already_paid_order_id", codecOptional(codecForString())) + .build("MerchantOrderStatusUnpaid"); + +export interface AbortRequest { + // hash of the order's contract terms (this is used to authenticate the + // wallet/customer in case $ORDER_ID is guessable). + h_contract: string; + + // List of coins the wallet would like to see refunds for. + // (Should be limited to the coins for which the original + // payment succeeded, as far as the wallet knows.) + coins: AbortingCoin[]; +} + +export interface AbortingCoin { + // Public key of a coin for which the wallet is requesting an abort-related refund. + coin_pub: EddsaPublicKeyString; + + // The amount to be refunded (matches the original contribution) + contribution: AmountString; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; +} + +export interface AbortResponse { + // List of refund responses about the coins that the wallet + // requested an abort for. In the same order as the 'coins' + // from the original request. + // The rtransaction_id is implied to be 0. + refunds: MerchantAbortPayRefundStatus[]; +} + +export type MerchantAbortPayRefundStatus = + | MerchantAbortPayRefundSuccessStatus + | MerchantAbortPayRefundFailureStatus; + +// Details about why a refund failed. +export interface MerchantAbortPayRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: unknown; +} + +// Additional details needed to verify the refund confirmation signature +// (h_contract_terms and merchant_pub) are already known +// to the wallet and thus not included. +export interface MerchantAbortPayRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: string; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: string; +} + +export interface FutureKeysResponse { + future_denoms: any[]; + + future_signkeys: any[]; + + master_pub: string; + + denom_secmod_public_key: string; + + // Public key of the signkey security module. + signkey_secmod_public_key: string; +} + +export const codecForKeysManagementResponse = (): Codec<FutureKeysResponse> => + buildCodecForObject<FutureKeysResponse>() + .property("master_pub", codecForString()) + .property("future_signkeys", codecForList(codecForAny())) + .property("future_denoms", codecForList(codecForAny())) + .property("denom_secmod_public_key", codecForAny()) + .property("signkey_secmod_public_key", codecForAny()) + .build("FutureKeysResponse"); + +export interface MerchantConfigResponse { + currency: string; + name: string; + version: string; +} + +export const codecForMerchantConfigResponse = + (): Codec<MerchantConfigResponse> => + buildCodecForObject<MerchantConfigResponse>() + .property("currency", codecForString()) + .property("name", codecForString()) + .property("version", codecForString()) + .build("MerchantConfigResponse"); + +export enum ExchangeProtocolVersion { + /** + * Current version supported by the wallet. + */ + V12 = 12, +} + +export enum MerchantProtocolVersion { + /** + * 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 ExchangeBatchWithdrawRequest { + planchets: ExchangeWithdrawRequest[]; +} + +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[]; + + /** + * Iff the corresponding denomination has support for age restriction, + * the client MUST provide the original age commitment, i.e. the vector + * of public keys. + */ + old_age_commitment?: Edx25519PublicKeyEnc[]; +} + +interface DepositConfirmationSignature { + // The EdDSA signature of `TALER_DepositConfirmationPS` using a current + // `signing key of the exchange <sign-key-priv>` affirming the successful + // deposit and that the exchange will transfer the funds after the refund + // deadline, or as soon as possible if the refund deadline is zero. + exchange_sig: EddsaSignatureString; +} + +export interface BatchDepositSuccess { + // Optional base URL of the exchange for looking up wire transfers + // associated with this transaction. If not given, + // the base URL is the same as the one used for this request. + // Can be used if the base URL for ``/transactions/`` differs from that + // for ``/coins/``, i.e. for load balancing. Clients SHOULD + // respect the ``transaction_base_url`` if provided. Any HTTP server + // belonging to an exchange MUST generate a 307 or 308 redirection + // to the correct base URL should a client uses the wrong base + // URL, or if the base URL has changed since the deposit. + transaction_base_url?: string; + + // Timestamp when the deposit was received by the exchange. + exchange_timestamp: TalerProtocolTimestamp; + + // `Public EdDSA key of the exchange <sign-key-pub>` that was used to + // generate the signature. + // Should match one of the exchange's signing keys from ``/keys``. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; + + // Array of deposit confirmation signatures from the exchange + // Entries must be in the same order the coins were given + // in the batch deposit request. + exchange_sig: EddsaSignatureString; +} + +export const codecForBatchDepositSuccess = (): Codec<BatchDepositSuccess> => + buildCodecForObject<BatchDepositSuccess>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("exchange_timestamp", codecForTimestamp) + .property("transaction_base_url", codecOptional(codecForString())) + .build("BatchDepositSuccess"); + +export interface TrackTransactionWired { + // Raw wire transfer identifier of the deposit. + wtid: Base32String; + + // When was the wire transfer given to the bank. + execution_time: TalerProtocolTimestamp; + + // The contribution of this coin to the total (without fees) + coin_contribution: AmountString; + + // Binary-only Signature_ with purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE + // over a TALER_ConfirmWirePS + // whereby the exchange affirms the successful wire transfer. + exchange_sig: EddsaSignatureString; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. Again given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; +} + +export const codecForTackTransactionWired = (): Codec<TrackTransactionWired> => + buildCodecForObject<TrackTransactionWired>() + .property("wtid", codecForString()) + .property("execution_time", codecForTimestamp) + .property("coin_contribution", codecForAmountString()) + .property("exchange_sig", codecForString()) + .property("exchange_pub", codecForString()) + .build("TackTransactionWired"); + +interface TrackTransactionAccepted { + // Legitimization target that the merchant should + // use to check for its KYC status using + // the /kyc-check/$REQUIREMENT_ROW/... endpoint. + // Optional, not present if the deposit has not + // yet been aggregated to the point that a KYC + // need has been evaluated. + requirement_row?: number; + + // True if the KYC check for the merchant has been + // satisfied. False does not mean that KYC + // is strictly needed, unless also a + // legitimization_uuid is provided. + kyc_ok: boolean; + + // Time by which the exchange currently thinks the deposit will be executed. + // Actual execution may be later if the KYC check is not satisfied by then. + execution_time: TalerProtocolTimestamp; +} + +export const codecForTackTransactionAccepted = + (): Codec<TrackTransactionAccepted> => + buildCodecForObject<TrackTransactionAccepted>() + .property("requirement_row", codecOptional(codecForNumber())) + .property("kyc_ok", codecForBoolean()) + .property("execution_time", codecForTimestamp) + .build("TackTransactionAccepted"); + +export type TrackTransaction = + | ({ type: "accepted" } & TrackTransactionAccepted) + | ({ type: "wired" } & TrackTransactionWired); + +export interface PurseDeposit { + /** + * Amount to be deposited, can be a fraction of the + * coin's total value. + */ + amount: AmountString; + + /** + * Hash of denomination RSA key with which the coin is signed. + */ + denom_pub_hash: HashCodeString; + + /** + * Exchange's unblinded RSA signature of the coin. + */ + ub_sig: UnblindedSignature; + + /** + * Age commitment for the coin, if the denomination is age-restricted. + */ + age_commitment?: string[]; + + /** + * Attestation for the minimum age, if the denomination is age-restricted. + */ + attest?: string; + + /** + * Signature over TALER_PurseDepositSignaturePS + * of purpose TALER_SIGNATURE_WALLET_PURSE_DEPOSIT + * made by the customer with the + * coin's private key. + */ + coin_sig: EddsaSignatureString; + + /** + * Public key of the coin being deposited into the purse. + */ + coin_pub: EddsaPublicKeyString; +} + +export interface ExchangePurseMergeRequest { + // payto://-URI of the account the purse is to be merged into. + // Must be of the form: 'payto://taler/$EXCHANGE_URL/$RESERVE_PUB'. + payto_uri: string; + + // EdDSA signature of the account/reserve affirming the merge + // over a TALER_AccountMergeSignaturePS. + // Must be of purpose TALER_SIGNATURE_ACCOUNT_MERGE + reserve_sig: EddsaSignatureString; + + // EdDSA signature of the purse private key affirming the merge + // over a TALER_PurseMergeSignaturePS. + // Must be of purpose TALER_SIGNATURE_PURSE_MERGE. + merge_sig: EddsaSignatureString; + + // Client-side timestamp of when the merge request was made. + merge_timestamp: TalerProtocolTimestamp; +} + +export interface ExchangeGetContractResponse { + purse_pub: string; + econtract_sig: string; + econtract: string; +} + +export const codecForExchangeGetContractResponse = + (): Codec<ExchangeGetContractResponse> => + buildCodecForObject<ExchangeGetContractResponse>() + .property("purse_pub", codecForString()) + .property("econtract_sig", codecForString()) + .property("econtract", codecForString()) + .build("ExchangeGetContractResponse"); + +/** + * Contract terms between two wallets (as opposed to a merchant and wallet). + */ +export interface PeerContractTerms { + amount: AmountString; + summary: string; + purse_expiration: TalerProtocolTimestamp; +} + +export interface EncryptedContract { + // Encrypted contract. + econtract: string; + + // Signature over the (encrypted) contract. + econtract_sig: string; + + // Ephemeral public key for the DH operation to decrypt the encrypted contract. + contract_pub: string; +} + +/** + * Payload for /reserves/{reserve_pub}/purse + * endpoint of the exchange. + */ +export interface ExchangeReservePurseRequest { + /** + * Minimum amount that must be credited to the reserve, that is + * the total value of the purse minus the deposit fees. + * If the deposit fees are lower, the contribution to the + * reserve can be higher! + */ + purse_value: AmountString; + + // Minimum age required for all coins deposited into the purse. + min_age: number; + + // Purse fee the reserve owner is willing to pay + // for the purse creation. Optional, if not present + // the purse is to be created from the purse quota + // of the reserve. + purse_fee: AmountString; + + // Optional encrypted contract, in case the buyer is + // proposing the contract and thus establishing the + // purse with the payment. + econtract?: EncryptedContract; + + // EdDSA public key used to approve merges of this purse. + merge_pub: EddsaPublicKeyString; + + // EdDSA signature of the purse private key affirming the merge + // over a TALER_PurseMergeSignaturePS. + // Must be of purpose TALER_SIGNATURE_PURSE_MERGE. + merge_sig: EddsaSignatureString; + + // EdDSA signature of the account/reserve affirming the merge. + // Must be of purpose TALER_SIGNATURE_WALLET_ACCOUNT_MERGE + reserve_sig: EddsaSignatureString; + + // Purse public key. + purse_pub: EddsaPublicKeyString; + + // EdDSA signature of the purse over + // TALER_PurseRequestSignaturePS of + // purpose TALER_SIGNATURE_PURSE_REQUEST + // confirming that the + // above details hold for this purse. + purse_sig: EddsaSignatureString; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCodeString; + + // Client-side timestamp of when the merge request was made. + merge_timestamp: TalerProtocolTimestamp; + + // Indicative time by which the purse should expire + // if it has not been paid. + purse_expiration: TalerProtocolTimestamp; +} + +export interface ExchangePurseDeposits { + // Array of coins to deposit into the purse. + deposits: PurseDeposit[]; +} + +/** + * @deprecated batch deposit should be used. + */ +export interface ExchangeDepositRequest { + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: AmountString; + + // The merchant's account details. + // In case of an auction policy, it refers to the seller. + merchant_payto_uri: string; + + // The salt is used to hide the payto_uri from customers + // when computing the h_wire of the merchant. + wire_salt: string; + + // SHA-512 hash of the contract of the merchant with the customer. Further + // details are never disclosed to the exchange. + h_contract_terms: HashCodeString; + + // Hash of denomination RSA key with which the coin is signed. + denom_pub_hash: HashCodeString; + + // Exchange's unblinded RSA signature of the coin. + ub_sig: UnblindedSignature; + + // Timestamp when the contract was finalized. + timestamp: TalerProtocolTimestamp; + + // Indicative time by which the exchange undertakes to transfer the funds to + // the merchant, in case of successful payment. A wire transfer deadline of 'never' + // is not allowed. + wire_transfer_deadline: TalerProtocolTimestamp; + + // EdDSA public key of the merchant, so that the client can identify the + // merchant for refund requests. + // + // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a + // policy via extension. + merchant_pub: EddsaPublicKeyString; + + // Date until which the merchant can issue a refund to the customer via the + // exchange, to be omitted if refunds are not allowed. + // + // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a + // policy via extension. + refund_deadline?: TalerProtocolTimestamp; + + // CAVEAT: THIS IS WORK IN PROGRESS + // (Optional) policy for the deposit. + // This might be a refund, auction or escrow policy. + // + // Note that support for policies is an optional feature of the exchange. + // Optional features are so called "extensions" in Taler. The exchange + // provides the list of supported extensions, including policies, in the + // ExtensionsManifestsResponse response to the /keys endpoint. + policy?: any; + + // Signature over TALER_DepositRequestPS, made by the customer with the + // coin's private key. + coin_sig: EddsaSignatureString; + + h_age_commitment?: string; +} + +export type WireSalt = string; + +export interface ExchangeBatchDepositRequest { + // The merchant's account details. + merchant_payto_uri: string; + + // The salt is used to hide the ``payto_uri`` from customers + // when computing the ``h_wire`` of the merchant. + wire_salt: WireSalt; + + // SHA-512 hash of the contract of the merchant with the customer. Further + // details are never disclosed to the exchange. + h_contract_terms: HashCodeString; + + // The list of coins that are going to be deposited with this Request. + coins: BatchDepositRequestCoin[]; + + // Timestamp when the contract was finalized. + timestamp: TalerProtocolTimestamp; + + // Indicative time by which the exchange undertakes to transfer the funds to + // the merchant, in case of successful payment. A wire transfer deadline of 'never' + // is not allowed. + wire_transfer_deadline: TalerProtocolTimestamp; + + // EdDSA `public key of the merchant <merchant-pub>`, so that the client can identify the + // merchant for refund requests. + merchant_pub: EddsaPublicKeyString; + + // Date until which the merchant can issue a refund to the customer via the + // exchange, to be omitted if refunds are not allowed. + // + // THIS FIELD WILL BE DEPRECATED, once the refund mechanism becomes a + // policy via extension. + refund_deadline?: TalerProtocolTimestamp; + + // CAVEAT: THIS IS WORK IN PROGRESS + // (Optional) policy for the batch-deposit. + // This might be a refund, auction or escrow policy. + policy?: any; +} + +export interface BatchDepositRequestCoin { + // EdDSA public key of the coin being deposited. + coin_pub: EddsaPublicKeyString; + + // Hash of denomination RSA key with which the coin is signed. + denom_pub_hash: HashCodeString; + + // Exchange's unblinded RSA signature of the coin. + ub_sig: UnblindedSignature; + + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: Amounts; + + // Signature over `TALER_DepositRequestPS`, made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignatureString; + + h_age_commitment?: string; +} + +export interface WalletKycUuid { + // UUID that the wallet should use when initiating + // the KYC check. + requirement_row: number; + + // Hash of the payto:// account URI for the wallet. + h_payto: string; +} + +export const codecForWalletKycUuid = (): Codec<WalletKycUuid> => + buildCodecForObject<WalletKycUuid>() + .property("requirement_row", codecForNumber()) + .property("h_payto", codecForString()) + .build("WalletKycUuid"); + +export interface MerchantUsingTemplateDetails { + summary?: string; + amount?: AmountString; +} + +export interface ExchangeRefundRequest { + // Amount to be refunded, can be a fraction of the + // coin's total deposit value (including deposit fee); + // must be larger than the refund fee. + refund_amount: AmountString; + + // SHA-512 hash of the contact of the merchant with the customer. + h_contract_terms: HashCodeString; + + // 64-bit transaction id of the refund transaction between merchant and customer. + rtransaction_id: number; + + // EdDSA public key of the merchant. + merchant_pub: EddsaPublicKeyString; + + // EdDSA signature of the merchant over a + // TALER_RefundRequestPS with purpose + // TALER_SIGNATURE_MERCHANT_REFUND + // affirming the refund. + merchant_sig: EddsaPublicKeyString; +} + +export interface ExchangeRefundSuccessResponse { + // The EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND over + // a TALER_RecoupRefreshConfirmationPS + // using a current signing key of the + // exchange affirming the successful refund. + exchange_sig: EddsaSignatureString; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; +} + +export const codecForExchangeRefundSuccessResponse = + (): Codec<ExchangeRefundSuccessResponse> => + buildCodecForObject<ExchangeRefundSuccessResponse>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .build("ExchangeRefundSuccessResponse"); + +export type AccountRestriction = + | RegexAccountRestriction + | DenyAllAccountRestriction; + +export interface DenyAllAccountRestriction { + type: "deny"; +} + +// Accounts interacting with this type of account +// restriction must have a payto://-URI matching +// the given regex. +export interface RegexAccountRestriction { + type: "regex"; + + // Regular expression that the payto://-URI of the + // partner account must follow. The regular expression + // should follow posix-egrep, but without support for character + // classes, GNU extensions, back-references or intervals. See + // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html + // for a description of the posix-egrep syntax. Applications + // may support regexes with additional features, but exchanges + // must not use such regexes. + payto_regex: string; + + // Hint for a human to understand the restriction + // (that is hopefully easier to comprehend than the regex itself). + human_hint: string; + + // Map from IETF BCP 47 language tags to localized + // human hints. + human_hint_i18n?: InternationalizedString; +} + +export interface ExchangeWireAccount { + // payto:// URI identifying the account and wire method + payto_uri: string; + + // URI to convert amounts from or to the currency used by + // this wire account of the exchange. Missing if no + // conversion is applicable. + conversion_url?: string; + + // Restrictions that apply to bank accounts that would send + // funds to the exchange (crediting this exchange bank account). + // Optional, empty array for unrestricted. + credit_restrictions: AccountRestriction[]; + + // Restrictions that apply to bank accounts that would receive + // funds from the exchange (debiting this exchange bank account). + // Optional, empty array for unrestricted. + debit_restrictions: AccountRestriction[]; + + // Signature using the exchange's offline key over + // a TALER_MasterWireDetailsPS + // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. + master_sig: EddsaSignatureString; + + // Display label wallets should use to show this + // bank account. + // Since protocol **v19**. + bank_label?: string; + priority?: number; +} + +export const codecForExchangeWireAccount = (): Codec<ExchangeWireAccount> => + buildCodecForObject<ExchangeWireAccount>() + .property("conversion_url", codecOptional(codecForStringURL())) + .property("credit_restrictions", codecForList(codecForAny())) + .property("debit_restrictions", codecForList(codecForAny())) + .property("master_sig", codecForString()) + .property("payto_uri", codecForString()) + .property("bank_label", codecOptional(codecForString())) + .property("priority", codecOptional(codecForNumber())) + .build("WireAccount"); + +export type Integer = number; + +export interface BankConversionInfoConfig { + // libtool-style representation of the Bank protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the API. + name: "taler-conversion-info"; + + regional_currency: string; + + fiat_currency: string; + + // Currency used by this bank. + regional_currency_specification: CurrencySpecification; + + // External currency used during conversion. + fiat_currency_specification: CurrencySpecification; +} + +export const codecForBankConversionInfoConfig = + (): Codec<BankConversionInfoConfig> => + buildCodecForObject<BankConversionInfoConfig>() + .property("name", codecForConstString("taler-conversion-info")) + .property("version", codecForString()) + .property("fiat_currency", codecForString()) + .property("regional_currency", codecForString()) + .property("fiat_currency_specification", codecForCurrencySpecificiation()) + .property( + "regional_currency_specification", + codecForCurrencySpecificiation(), + ) + .build("BankConversionInfoConfig"); + +export interface DenominationExpiredMessage { + // Taler error code. Note that beyond + // expiration this message format is also + // used if the key is not yet valid, or + // has been revoked. + code: number; + + // Signature by the exchange over a + // TALER_DenominationExpiredAffirmationPS. + // Must have purpose TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED. + exchange_sig: EddsaSignatureString; + + // Public key of the exchange used to create + // the 'exchange_sig. + exchange_pub: EddsaPublicKeyString; + + // Hash of the denomination public key that is unknown. + h_denom_pub: HashCodeString; + + // When was the signature created. + timestamp: TalerProtocolTimestamp; + + // What kind of operation was requested that now + // failed? + oper: string; +} + +export const codecForDenominationExpiredMessage = () => + buildCodecForObject<DenominationExpiredMessage>() + .property("code", codecForNumber()) + .property("exchange_sig", codecForString()) + .property("exchange_pub", codecForString()) + .property("h_denom_pub", codecForString()) + .property("timestamp", codecForTimestamp) + .property("oper", codecForString()) + .build("DenominationExpiredMessage"); + +export interface CoinHistoryResponse { + // Current balance of the coin. + balance: AmountString; + + // Hash of the coin's denomination. + h_denom_pub: HashCodeString; + + // Transaction history for the coin. + history: any[]; +} + +export const codecForCoinHistoryResponse = () => + buildCodecForObject<CoinHistoryResponse>() + .property("balance", codecForAmountString()) + .property("h_denom_pub", codecForString()) + .property("history", codecForAny()) + .build("CoinHistoryResponse"); |