- GET /keys#
Get a list of all denomination keys offered by the exchange, as well as the exchange’s current online signing key.
Request:
- Query Parameters:
last_issue_date – Optional argument specifying the maximum value of any of the
stamp_startmembers of the denomination keys of a/keysresponse that is already known to the client. Allows the exchange to only return keys that have changed since that timestamp. The given value must be an unsigned 64-bit integer representing seconds after 1970. If the timestamp does not exactly match thestamp_startof one of the denomination keys, all keys are returned.
Response:
- 200 OK:
The exchange responds with a ExchangeKeysResponse object. This request should virtually always be successful. It only fails if the exchange is misconfigured or has not yet been provisioned with key signatures via
taler-exchange-offline.
Details:
interface ExchangeKeysResponse { // libtool-style representation of the Exchange protocol version, see // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning // The format is "current:revision:age". version: string; // The exchange's base URL. base_url: string; // The exchange's currency or asset unit. currency: string; // Shopping URL where users may find shops that accept // digital cash issued by this exchange. // @since protocol **v21**. shopping_url?: string; // Open banking gateway base URL where wallets can // initiate wire transfers to withdraw // digital cash from this exchange. // @since protocol **v30**. open_banking_gateway?: string; // Instructs wallets to use certain bank-specific // language (for buttons) and/or other UI/UX customization // for compliance with the rules of that bank. // The specific customizations to apply are done on a per-wallet // basis as requested by the specific bank. They only // apply when it is clear that the wallet is using digital // cash from that bank. This is an advisory option, not // all wallets must support all compliance languages. // @since protocol **v24**. bank_compliance_language?: string; // How wallets should render this currency. currency_specification: CurrencySpecification; // Small(est?) amount that can likely be transferred to // the exchange. Should be the default amount for KYC // authentication wire transfers to this exchange. // Optional, not present if not known or not configured. // @since protocol **v21**. tiny_amount?: Amount; // Absolute cost offset for the STEFAN curve used // to (over) approximate fees payable by amount. stefan_abs: Amount; // Factor to multiply the logarithm of the amount // with to (over) approximate fees payable by amount. // Note that the total to be paid is first to be // divided by the smallest denomination to obtain // the value that the logarithm is to be taken of. stefan_log: Amount; // Linear cost factor for the STEFAN curve used // to (over) approximate fees payable by amount. // // Note that this is a scalar, as it is multiplied // with the actual amount. stefan_lin: Float; // Type of the asset. "fiat", "crypto", "regional" // or "stock". Wallets should adjust their UI/UX // based on this value. asset_type: string; // Array of wire accounts operated by the exchange for // incoming wire transfers. accounts: ExchangeWireAccount[]; // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank") // to wire fees. wire_fees: { method : AggregateTransferFee[] }; // List of exchanges that this exchange is partnering // with to enable wallet-to-wallet transfers. wads: ExchangePartnerListEntry[]; // Set to true if this exchange allows the use // of reserves for rewards. // @deprecated in protocol **v18**. rewards_allowed: false; // Set to true if this exchange has KYC enabled and thus // requires KYC auth wire transfers prior to a first deposit. // @since in protocol **v24**. kyc_enabled: boolean; // Set to TRUE if wallets should disable the direct deposit feature // and deposits should only go via Taler merchant APIs. // Mainly used for regional currency and event currency deployments // where wallets are not eligible to deposit back into originating // bank accounts and, because KYC is not enabled, wallets are thus // likely to send money to nirvana instead of where users want it. // @since in protocol **v30**. disable_direct_deposit: boolean; // EdDSA master public key of the exchange, used to sign entries // in denoms and signkeys. master_public_key: EddsaPublicKey; // Relative duration until inactive reserves are closed; // not signed (!), can change without notice. reserve_closing_delay: RelativeTime; // Threshold amounts beyond which wallet should // trigger the KYC process of the issuing exchange. // Optional option, if not given there is no limit. // Currency must match currency. wallet_balance_limit_without_kyc?: Amount[]; // Array of limits that apply to all accounts. // All of the given limits will be hard limits. // Wallets and merchants are expected to obey them // and not even allow the user to cross them. // @since protocol **v21**. hard_limits: AccountLimit[]; // Array of limits with a soft threshold of zero // that apply to all accounts without KYC. // Wallets and merchants are expected to trigger // a KYC process before attempting any zero-limited // operations. // @since protocol **v21**. zero_limits: ZeroLimitedOperation[]; // Denominations offered by this exchange denominations: DenomGroup[]; // Compact EdDSA signature (binary-only) over the // contatentation of all of the master_sigs (in reverse // chronological order by group) in the arrays under // "denominations". Signature of TALER_ExchangeKeySetPS exchange_sig: EddsaSignature; // Public EdDSA key of the exchange that was used to generate the signature. // Should match one of the exchange's signing keys from signkeys. It is given // explicitly as the client might otherwise be confused by clock skew as to // which signing key was used for the exchange_sig. exchange_pub: EddsaPublicKey; // Denominations for which the exchange currently offers/requests recoup. recoup: RecoupDenoms[]; // Array of globally applicable fees by time range. global_fees: GlobalFees[]; // The date when the denomination keys were last updated. list_issue_date: Timestamp; // Auditors of the exchange. auditors: AuditorKeys[]; // The exchange's signing keys. signkeys: SignKey[]; // Optional field with a dictionary of (name, object) pairs defining the // supported and enabled extensions, such as age_restriction. extensions?: { name: ExtensionManifest }; // Signature by the exchange master key of the SHA-256 hash of the // normalized JSON-object of field extensions, if it was set. // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS. extensions_sig?: EddsaSignature; }
The specification for the account object is:
interface ExchangeWireAccount { // Full 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: EddsaSignature; // Display label wallets should use to show this // bank account. // @since protocol **v19**. bank_label?: string; // *Signed* integer with the display priority for // this bank account. Optional, 0 if missing. // @since protocol **v19**. priority?: Integer; }
type AccountRestriction = | RegexAccountRestriction | DenyAllAccountRestriction
// Account restriction that disables this type of // account for the indicated operation categorically. interface DenyAllAccountRestriction { type: "deny"; }
// Accounts interacting with this type of account // restriction must have a normalized payto://-URI matching // the given regex. 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?: { [lang_tag: string]: string }; }
interface ZeroLimitedOperation { // Operation that is limited to an amount of // zero until the client has passed some KYC check. // Must be one of "WITHDRAW", "DEPOSIT", // (p2p) "MERGE", (wallet) "BALANCE", // (reserve) "CLOSE", "AGGREGATE", // "TRANSACTION" or "REFUND". operation_type: string; }
interface AccountLimit { // Operation that is limited. // Must be one of "WITHDRAW", "DEPOSIT", // (p2p) "MERGE", (wallet) "BALANCE", // (reserve) "CLOSE", "AGGREGATE", // "TRANSACTION" or "REFUND". operation_type: string; // Timeframe during which the limit applies. // Not applicable for all operation_types // (but always present in this object anyway). timeframe: RelativeTime; // Maximum amount allowed during the given timeframe. // Zero if the operation is simply forbidden. threshold: Amount; // True if this is a soft limit that could be raised // by passing KYC checks. Clients *may* deliberately // try to cross limits and trigger measures resulting // in 451 responses to begin KYC processes. // Clients that are aware of hard limits *should* // inform users about the hard limit and prevent flows // in the UI that would cause violations of hard limits. // Made optional in **v21** with a default of 'false' if missing. soft_limit?: boolean; }
interface GlobalFees { // What date (inclusive) does these fees go into effect? start_date: Timestamp; // What date (exclusive) does this fees stop going into effect? end_date: Timestamp; // Account history fee, charged when a user wants to // obtain a reserve/account history. history_fee: Amount; // 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: Amount; // Purse fee, charged only if a purse is abandoned // and was not covered by the account limit. purse_fee: Amount; // 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: RelativeTime; // Non-negative number of concurrent purses that any // account holder is allowed to create without having // to pay the purse_fee. purse_account_limit: Integer; // 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: RelativeTime; // Signature of TALER_GlobalFeesPS. master_sig: EddsaSignature; }
type DenomGroup = | DenomGroupRsa | DenomGroupCs | DenomGroupRsaAgeRestricted | DenomGroupCsAgeRestricted;
interface DenomGroupRsa extends DenomGroupCommon { cipher: "RSA"; denoms: ({ rsa_pub: RsaPublicKey; } & DenomCommon)[]; }
interface DenomGroupCs extends DenomGroupCommon { cipher: "CS"; denoms: ({ cs_pub: Cs25519Point; } & DenomCommon)[]; }
interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { cipher: "RSA+age_restricted"; age_mask: AgeMask; denoms: ({ rsa_pub: RsaPublicKey; } & DenomCommon)[]; }
interface DenomGroupCsAgeRestricted extends DenomGroupCommon { cipher: "CS+age_restricted"; age_mask: AgeMask; denoms: ({ cs_pub: Cs25519Point; } & DenomCommon)[]; }
// Common attributes for all denomination groups interface DenomGroupCommon { // How much are coins of this denomination worth? value: Amount; // Fee charged by the exchange for withdrawing a coin of this denomination. fee_withdraw: Amount; // Fee charged by the exchange for depositing a coin of this denomination. fee_deposit: Amount; // Fee charged by the exchange for refreshing a coin of this denomination. fee_refresh: Amount; // Fee charged by the exchange for refunding a coin of this denomination. fee_refund: Amount; }
interface DenomCommon { // Signature of TALER_DenominationKeyValidityPS. master_sig: EddsaSignature; // When does the denomination key become valid? stamp_start: Timestamp; // When is it no longer possible to withdraw coins // of this denomination? Note that while this option // is given per denomination, all concurrently active // denominations (of the same cipher type) // will have exactly the same withdraw // expiration time. Thus, the wallet can be sure what // is the smallest denomination being offered at any // particular point in time, and not worry about the // exchange having merely failed to sign the key of // only the smallest denomination unit. stamp_expire_withdraw: Timestamp; // When is it no longer possible to deposit coins // of this denomination? stamp_expire_deposit: Timestamp; // 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: Timestamp; // 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; }
Fees for any of the operations can be zero, but the fields must still be present. The currency of the
fee_deposit,fee_refreshandfee_refundmust match the currency of thevalue. Theoretically, thefee_withdrawcould be in a different currency, but this is not currently supported by the implementation.interface RecoupDenoms { // Hash of the public key of the denomination that is being revoked under // emergency protocol (see /recoup). h_denom_pub: HashCode; // We do not include any signature here, as the primary use-case for // this emergency involves the exchange having lost its signing keys, // so such a signature here would be pretty worthless. However, the // exchange will not honor /recoup requests unless they are for // denomination keys listed here. }
A signing key in the
signkeyslist is a JSON object with the following fields:interface SignKey { // The actual exchange's EdDSA signing public key. key: EddsaPublicKey; // Initial validity date for the signing key. stamp_start: Timestamp; // Date when the exchange will stop using the signing key, allowed to overlap // slightly with the next signing key's validity to allow for clock skew. stamp_expire: Timestamp; // Date when all signatures made by the signing key expire and should // henceforth no longer be considered valid in legal disputes. stamp_end: Timestamp; // Signature over key and stamp_expire by the exchange master key. // Signature of TALER_ExchangeSigningKeyValidityPS. // Must have purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY. master_sig: EddsaSignature; }
An entry in the
auditorslist is a JSON object with the following fields:interface AuditorKeys { // The auditor's EdDSA signing public key. auditor_pub: EddsaPublicKey; // The auditor's URL. auditor_url: string; // The auditor's name (for humans). auditor_name: string; // An array of denomination keys the auditor affirms with its signature. // Note that the message only includes the hash of the public key, while the // signature is actually over the expanded information including expiration // times and fees. The exact format is described below. denomination_keys: AuditorDenominationKey[]; }
interface AuditorDenominationKey { // Hash of the public RSA key used to sign coins of the respective // denomination. Note that the auditor's signature covers more than just // the hash, but this other information is already provided in denoms and // thus not repeated here. denom_pub_h: HashCode; // Signature of TALER_ExchangeKeyValidityPS. auditor_sig: EddsaSignature; }
The same auditor may appear multiple times in the array for different subsets of denomination keys, and the same denomination key hash may be listed multiple times for the same or different auditors. The wallet or merchant just should check that the denomination keys they use are in the set for at least one of the auditors that they accept.
Note
Both the individual denominations and the denomination list is signed, allowing customers to prove that they received an inconsistent list.
Aggregate wire transfer fees representing the fees the exchange charges per wire transfer to a merchant must be specified as an array in all wire transfer response objects under
fees. The respective array contains objects with the following members:interface AggregateTransferFee { // Per transfer wire transfer fee. wire_fee: Amount; // Per transfer closing fee. closing_fee: Amount; // What date (inclusive) does this fee go into effect? // The different fees must cover the full time period in which // any of the denomination keys are valid without overlap. start_date: Timestamp; // What date (exclusive) does this fee stop going into effect? // The different fees must cover the full time period in which // any of the denomination keys are valid without overlap. end_date: Timestamp; // Signature of TALER_MasterWireFeePS with // purpose TALER_SIGNATURE_MASTER_WIRE_FEES. sig: EddsaSignature; }
interface ExchangePartnerListEntry { // Base URL of the partner exchange. partner_base_url: string; // Public master key of the partner exchange. partner_master_pub: EddsaPublicKey; // Per exchange-to-exchange transfer (wad) fee. wad_fee: Amount; // Exchange-to-exchange wad (wire) transfer frequency. wad_frequency: RelativeTime; // When did this partnership begin (under these conditions)? start_date: Timestamp; // How long is this partnership expected to last? end_date: Timestamp; // Signature using the exchange's offline key over // TALER_WadPartnerSignaturePS // with purpose TALER_SIGNATURE_MASTER_PARTNER_DETAILS. master_sig: EddsaSignature; }