/*
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
*/
/**
* 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 {
buildCodecForObject,
codecForString,
codecForList,
codecOptional,
codecForAny,
codecForNumber,
codecForBoolean,
codecForMap,
Codec,
codecForConstNumber,
buildCodecForUnion,
codecForConstString,
} from "./codec.js";
import {
codecForTimestamp,
codecForDuration,
TalerProtocolTimestamp,
TalerProtocolDuration,
} from "./time.js";
import { codecForAmountString } from "./amounts.js";
import { strcmp } from "./helpers.js";
import { AgeCommitmentProof, Edx25519PublicKey } from "./talerCrypto.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?: Edx25519PublicKey[];
}
/**
* Information about an exchange as stored inside a
* merchant's contract terms.
*/
export interface ExchangeHandle {
/**
* Master public signing key of the exchange.
*/
master_pub: string;
/**
* Base URL of the exchange.
*/
url: string;
}
export interface AuditorHandle {
/**
* Official name of the auditor.
*/
name: string;
/**
* Master public signing key of the auditor.
*/
auditor_pub: string;
/**
* 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 {
name: string;
jurisdiction?: Location;
address?: 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?: { [lang_tag: string]: string };
// The number of units of the product to deliver to the customer.
quantity?: number;
// 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?: string;
// 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.
*/
export interface ContractTerms {
/**
* Hash of the merchant's wire details.
*/
h_wire: string;
/**
* Hash of the merchant's wire details.
*/
auto_refund?: TalerProtocolDuration;
/**
* Wire method the merchant wants to use.
*/
wire_method: string;
/**
* Human-readable short summary of the contract.
*/
summary: string;
summary_i18n?: InternationalizedString;
/**
* Nonce used to ensure freshness.
*/
nonce: string;
/**
* Total amount payable.
*/
amount: string;
/**
* Auditors accepted by the merchant.
*/
auditors: AuditorHandle[];
/**
* Deadline to pay for the contract.
*/
pay_deadline: TalerProtocolTimestamp;
/**
* Maximum deposit fee covered by the merchant.
*/
max_fee: string;
/**
* Information about the merchant.
*/
merchant: MerchantInfo;
/**
* Public key of the merchant.
*/
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;
/**
* List of accepted exchanges.
*/
exchanges: ExchangeHandle[];
/**
* Products that are sold in this contract.
*/
products?: Product[];
/**
* Deadline for refunds.
*/
refund_deadline: TalerProtocolTimestamp;
/**
* Deadline for the wire transfer.
*/
wire_transfer_deadline: TalerProtocolTimestamp;
/**
* Time when the contract was generated by the merchant.
*/
timestamp: TalerProtocolTimestamp;
/**
* Order id to uniquely identify the purchase within
* one merchant instance.
*/
order_id: string;
/**
* Base URL of the merchant's backend.
*/
merchant_base_url: string;
/**
* Fulfillment URL to view the product or
* delivery status.
*/
fulfillment_url?: string;
/**
* URL meant to share the shopping cart.
*/
public_reorder_url?: string;
/**
* Plain text fulfillment message in the merchant's default language.
*/
fulfillment_message?: string;
/**
* Internationalized fulfillment messages.
*/
fulfillment_message_i18n?: InternationalizedString;
/**
* Share of the wire fee that must be settled with one payment.
*/
wire_fee_amortization?: number;
/**
* Maximum wire fee that the merchant agrees to pay for.
*/
max_wire_fee?: string;
minimum_age?: number;
/**
* Extra data, interpreted by the mechant only.
*/
extra?: any;
}
/**
* 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;
}
/**
* Response for a refund pickup or a /pay in abort mode.
*/
export interface MerchantRefundResponse {
/**
* Public key of the merchant
*/
merchant_pub: string;
/**
* Contract terms hash of the contract that
* is being refunded.
*/
h_contract_terms: string;
/**
* The signed refund permissions, to be sent to the exchange.
*/
refunds: MerchantAbortPayRefundDetails[];
}
/**
* 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 {
/**
* List of offered denominations.
*/
denoms: ExchangeDenomination[];
/**
* 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;
}
/**
* Wire fees as announced by the exchange.
*/
export class WireFeesJson {
/**
* Cost of a wire transfer.
*/
wire_fee: string;
wad_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;
}
export interface AccountInfo {
payto_uri: string;
master_sig: string;
}
export interface ExchangeWireJson {
accounts: AccountInfo[];
fees: { [methodName: string]: WireFeesJson[] };
}
/**
* 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 {
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 TipPickupGetResponse {
tip_amount: string;
exchange_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 codecForBlindedDenominationSignature = () =>
buildCodecForUnion()
.discriminateOn("cipher")
.alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature())
.build("BlindedDenominationSignature");
export const codecForRsaBlindedDenominationSignature = () =>
buildCodecForObject()
.property("cipher", codecForConstString(DenomKeyType.Rsa))
.property("blinded_rsa_signature", codecForString())
.build("RsaBlindedDenominationSignature");
export class WithdrawResponse {
ev_sig: BlindedDenominationSignature;
}
export class WithdrawBatchResponse {
ev_sigs: WithdrawResponse[];
}
/**
* Easy to process format for the public data of coins
* managed by the wallet.
*/
export interface CoinDumpJson {
coins: Array<{
/**
* The coin's denomination's public key.
*/
denom_pub: DenominationPubKey;
/**
* Hash of denom_pub.
*/
denom_pub_hash: string;
/**
* Value of the denomination (without any fees).
*/
denom_value: string;
/**
* Public key of the coin.
*/
coin_pub: string;
/**
* Base URL of the exchange for the coin.
*/
exchange_base_url: string;
/**
* Remaining value on the coin, to the knowledge of
* the wallet.
*/
remaining_value: string;
/**
* Public key of the parent coin.
* Only present if this coin was obtained via refreshing.
*/
refresh_parent_coin_pub: string | undefined;
/**
* Public key of the reserve for this coin.
* Only present if this coin was obtained via refreshing.
*/
withdrawal_reserve_pub: string | undefined;
/**
* Is the coin suspended?
* Suspended coins are not considered for payments.
*/
coin_suspended: boolean;
/**
* Information about the age restriction
*/
ageCommitmentProof: AgeCommitmentProof | undefined;
}>;
}
export interface MerchantPayResponse {
sig: 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;
/**
* Amount that was refunded in total.
*/
refund_amount: 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 {
transfer_done: boolean;
}
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 codecForDenominationPubKey = () =>
buildCodecForUnion()
.discriminateOn("cipher")
.alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey())
.alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
.build("DenominationPubKey");
export const codecForRsaDenominationPubKey = () =>
buildCodecForObject()
.property("cipher", codecForConstString(DenomKeyType.Rsa))
.property("rsa_public_key", codecForString())
.property("age_mask", codecForNumber())
.build("DenominationPubKey");
export const codecForCsDenominationPubKey = () =>
buildCodecForObject()
.property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr))
.property("cs_public_key", codecForString())
.property("age_mask", codecForNumber())
.build("CsDenominationPubKey");
export const codecForBankWithdrawalOperationPostResponse =
(): Codec =>
buildCodecForObject()
.property("transfer_done", codecForBoolean())
.build("BankWithdrawalOperationPostResponse");
export type AmountString = string;
export type Base32String = string;
export type EddsaSignatureString = string;
export type EddsaPublicKeyString = string;
export type CoinPublicKeyString = string;
export const codecForDenomination = (): Codec =>
buildCodecForObject()
.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 =>
buildCodecForObject()
.property("denom_pub_h", codecForString())
.property("auditor_sig", codecForString())
.build("AuditorDenomSig");
export const codecForAuditor = (): Codec =>
buildCodecForObject()
.property("auditor_pub", codecForString())
.property("auditor_url", codecForString())
.property("denomination_keys", codecForList(codecForAuditorDenomSig()))
.build("Auditor");
export const codecForExchangeHandle = (): Codec =>
buildCodecForObject()
.property("master_pub", codecForString())
.property("url", codecForString())
.build("ExchangeHandle");
export const codecForAuditorHandle = (): Codec =>
buildCodecForObject()
.property("name", codecForString())
.property("auditor_pub", codecForString())
.property("url", codecForString())
.build("AuditorHandle");
export const codecForLocation = (): Codec =>
buildCodecForObject()
.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 =>
buildCodecForObject()
.property("name", codecForString())
.property("address", codecOptional(codecForLocation()))
.property("jurisdiction", codecOptional(codecForLocation()))
.build("MerchantInfo");
export const codecForTax = (): Codec =>
buildCodecForObject()
.property("name", codecForString())
.property("tax", codecForString())
.build("Tax");
export const codecForInternationalizedString =
(): Codec => codecForMap(codecForString());
export const codecForProduct = (): Codec =>
buildCodecForObject()
.property("product_id", codecOptional(codecForString()))
.property("description", codecForString())
.property(
"description_i18n",
codecOptional(codecForInternationalizedString()),
)
.property("quantity", codecOptional(codecForNumber()))
.property("unit", codecOptional(codecForString()))
.property("price", codecOptional(codecForString()))
.build("Tax");
export const codecForContractTerms = (): Codec =>
buildCodecForObject()
.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", codecForString())
.property("auditors", codecForList(codecForAuditorHandle()))
.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", codecForString())
.property("max_wire_fee", codecOptional(codecForString()))
.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("ContractTerms");
export const codecForMerchantRefundPermission =
(): Codec =>
buildCodecForObject()
.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 codecForMerchantRefundResponse =
(): Codec =>
buildCodecForObject()
.property("merchant_pub", codecForString())
.property("h_contract_terms", codecForString())
.property("refunds", codecForList(codecForMerchantRefundPermission()))
.build("MerchantRefundResponse");
export const codecForBlindSigWrapperV2 = (): Codec =>
buildCodecForObject()
.property("blind_sig", codecForBlindedDenominationSignature())
.build("MerchantBlindSigWrapperV2");
export const codecForMerchantTipResponseV2 = (): Codec =>
buildCodecForObject()
.property("blind_sigs", codecForList(codecForBlindSigWrapperV2()))
.build("MerchantTipResponseV2");
export const codecForRecoup = (): Codec =>
buildCodecForObject()
.property("h_denom_pub", codecForString())
.build("Recoup");
export const codecForExchangeSigningKey = (): Codec =>
buildCodecForObject()
.property("key", codecForString())
.property("master_sig", codecForString())
.property("stamp_end", codecForTimestamp)
.property("stamp_start", codecForTimestamp)
.property("stamp_expire", codecForTimestamp)
.build("ExchangeSignKeyJson");
export const codecForExchangeKeysJson = (): Codec =>
buildCodecForObject()
.property("denoms", codecForList(codecForDenomination()))
.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)
.build("ExchangeKeysJson");
export const codecForWireFeesJson = (): Codec =>
buildCodecForObject()
.property("wire_fee", codecForString())
.property("closing_fee", codecForString())
.property("wad_fee", codecForString())
.property("sig", codecForString())
.property("start_date", codecForTimestamp)
.property("end_date", codecForTimestamp)
.build("WireFeesJson");
export const codecForAccountInfo = (): Codec =>
buildCodecForObject()
.property("payto_uri", codecForString())
.property("master_sig", codecForString())
.build("AccountInfo");
export const codecForExchangeWireJson = (): Codec =>
buildCodecForObject()
.property("accounts", codecForList(codecForAccountInfo()))
.property("fees", codecForMap(codecForList(codecForWireFeesJson())))
.build("ExchangeWireJson");
export const codecForProposal = (): Codec =>
buildCodecForObject()
.property("contract_terms", codecForAny())
.property("sig", codecForString())
.build("Proposal");
export const codecForCheckPaymentResponse = (): Codec =>
buildCodecForObject()
.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 =>
buildCodecForObject()
.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 codecForTipPickupGetResponse = (): Codec =>
buildCodecForObject()
.property("tip_amount", codecForString())
.property("exchange_url", codecForString())
.property("expiration", codecForTimestamp)
.build("TipPickupGetResponse");
export const codecForRecoupConfirmation = (): Codec =>
buildCodecForObject()
.property("reserve_pub", codecOptional(codecForString()))
.property("old_coin_pub", codecOptional(codecForString()))
.build("RecoupConfirmation");
export const codecForWithdrawResponse = (): Codec =>
buildCodecForObject()
.property("ev_sig", codecForBlindedDenominationSignature())
.build("WithdrawResponse");
export const codecForWithdrawBatchResponse = (): Codec =>
buildCodecForObject()
.property("ev_sigs", codecForList(codecForWithdrawResponse()))
.build("WithdrawBatchResponse");
export const codecForMerchantPayResponse = (): Codec =>
buildCodecForObject()
.property("sig", codecForString())
.build("MerchantPayResponse");
export const codecForExchangeMeltResponse = (): Codec =>
buildCodecForObject()
.property("exchange_pub", codecForString())
.property("exchange_sig", codecForString())
.property("noreveal_index", codecForNumber())
.property("refresh_base_url", codecOptional(codecForString()))
.build("ExchangeMeltResponse");
export const codecForExchangeRevealItem = (): Codec =>
buildCodecForObject()
.property("ev_sig", codecForBlindedDenominationSignature())
.build("ExchangeRevealItem");
export const codecForExchangeRevealResponse =
(): Codec =>
buildCodecForObject()
.property("ev_sigs", codecForList(codecForExchangeRevealItem()))
.build("ExchangeRevealResponse");
export const codecForMerchantCoinRefundSuccessStatus =
(): Codec =>
buildCodecForObject()
.property("type", codecForConstString("success"))
.property("coin_pub", codecForString())
.property("exchange_status", codecForConstNumber(200))
.property("exchange_sig", codecForString())
.property("rtransaction_id", codecForNumber())
.property("refund_amount", codecForString())
.property("exchange_pub", codecForString())
.property("execution_time", codecForTimestamp)
.build("MerchantCoinRefundSuccessStatus");
export const codecForMerchantCoinRefundFailureStatus =
(): Codec =>
buildCodecForObject()
.property("type", codecForConstString("failure"))
.property("coin_pub", codecForString())
.property("exchange_status", codecForNumber())
.property("rtransaction_id", codecForNumber())
.property("refund_amount", codecForString())
.property("exchange_code", codecOptional(codecForNumber()))
.property("exchange_reply", codecOptional(codecForAny()))
.property("execution_time", codecForTimestamp)
.build("MerchantCoinRefundFailureStatus");
export const codecForMerchantCoinRefundStatus =
(): Codec =>
buildCodecForUnion()
.discriminateOn("type")
.alternative("success", codecForMerchantCoinRefundSuccessStatus())
.alternative("failure", codecForMerchantCoinRefundFailureStatus())
.build("MerchantCoinRefundStatus");
export const codecForMerchantOrderStatusPaid =
(): Codec =>
buildCodecForObject()
.property("refund_amount", codecForString())
.property("refunded", codecForBoolean())
.build("MerchantOrderStatusPaid");
export const codecForMerchantOrderRefundPickupResponse =
(): Codec =>
buildCodecForObject()
.property("merchant_pub", codecForString())
.property("refund_amount", codecForString())
.property("refunds", codecForList(codecForMerchantCoinRefundStatus()))
.build("MerchantOrderRefundPickupResponse");
export const codecForMerchantOrderStatusUnpaid =
(): Codec =>
buildCodecForObject()
.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 const codecForAbortResponse = (): Codec =>
buildCodecForObject()
.property("refunds", codecForList(codecForMerchantAbortPayRefundStatus()))
.build("AbortResponse");
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 const codecForMerchantAbortPayRefundSuccessStatus =
(): Codec =>
buildCodecForObject()
.property("exchange_pub", codecForString())
.property("exchange_sig", codecForString())
.property("exchange_status", codecForConstNumber(200))
.property("type", codecForConstString("success"))
.build("MerchantAbortPayRefundSuccessStatus");
export const codecForMerchantAbortPayRefundFailureStatus =
(): Codec =>
buildCodecForObject()
.property("exchange_code", codecForNumber())
.property("exchange_reply", codecForAny())
.property("exchange_status", codecForNumber())
.property("type", codecForConstString("failure"))
.build("MerchantAbortPayRefundFailureStatus");
export const codecForMerchantAbortPayRefundStatus =
(): Codec =>
buildCodecForUnion()
.discriminateOn("type")
.alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
.alternative("failure", codecForMerchantAbortPayRefundFailureStatus())
.build("MerchantAbortPayRefundStatus");
export interface TalerConfigResponse {
name: string;
version: string;
currency?: string;
}
export const codecForTalerConfigResponse = (): Codec =>
buildCodecForObject()
.property("name", codecForString())
.property("version", codecForString())
.property("currency", codecOptional(codecForString()))
.build("TalerConfigResponse");
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 =>
buildCodecForObject()
.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 =>
buildCodecForObject()
.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 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?: Edx25519PublicKey[];
}
export interface DepositSuccess {
// 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;
// the EdDSA signature of TALER_DepositConfirmationPS using a current
// signing key of the exchange 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: 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 const codecForDepositSuccess = (): Codec =>
buildCodecForObject()
.property("exchange_pub", codecForString())
.property("exchange_sig", codecForString())
.property("exchange_timestamp", codecForTimestamp)
.property("transaction_base_url", codecOptional(codecForString()))
.build("DepositSuccess");