From 4eecf36b02f3dee502d97ac1d4b72d19d61da4ce Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 18 Jan 2016 01:06:26 +0100 Subject: restructuring and linking extension --- Makefile | 2 +- api-common.rst | 394 +++++++++++++++++++++++++++++++++ api-merchant.rst | 9 +- api-mint.rst | 564 +++++++++++++++-------------------------------- conf.py | 5 +- dev-merchant.rst | 74 ------- dev-wallet-wx.rst | 14 +- exts/tsref.py | 202 +++++++++++++++++ impl-merchant.rst | 74 +++++++ index.rst | 3 +- integration-merchant.rst | 16 +- 11 files changed, 885 insertions(+), 472 deletions(-) create mode 100644 api-common.rst delete mode 100644 dev-merchant.rst create mode 100644 exts/tsref.py create mode 100644 impl-merchant.rst diff --git a/Makefile b/Makefile index 80614508..8b06d5d3 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ clean: rm -rf $(BUILDDIR)/* html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXBUILD) -b html-linked $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/api-common.rst b/api-common.rst new file mode 100644 index 00000000..3ab82f09 --- /dev/null +++ b/api-common.rst @@ -0,0 +1,394 @@ +================================= +Common Taler HTTP API Conventions +================================= + +.. _encodings-ref: + +---------------- +Common encodings +---------------- + +This section describes how certain types of values are represented throughout the API. + + + + .. _Base32: + + * **Binary data**: + Binary data is generally encoded using Crockford's variant of Base32 (http://www.crockford.com/wrmg/base32.html), except that "U" is not excluded but also decodes to "V" to make OCR easy. We will still simply use the JSON type "base32" and the term "Crockford Base32" in the text to refer to the resulting encoding. + + * **Large numbers**: Large numbers such as RSA blinding factors and 256 bit keys, are transmitted as other binary data in Crockford Base32 encoding. + + .. _Timestamp: + + * **Timestamps**: + Timestamps are represented in JSON as a string literal `"\\/Date(x)\\/"`, where `x` is the decimal representation of the number of seconds past the Unix Epoch (January 1, 1970). The escaped slash (`\\/`) is interpreted in JSON simply as a normal slash, but distinguishes the timestamp from a normal string literal. We use the type "date" in the documentation below. Additionally, the special strings "\\/never\\/" and "\\/forever\\/" are recognized to represent the end of time. + + .. _public\ key: + + * **Public key**: EdDSA and ECDHE public keys are always points on Curve25519 and represented using the standard 256 bits Ed25519 compact format, converted to Crockford Base32_. + + .. _Signature: + + * **Signatures**: The specific signature scheme in use, like RSA blind signatures or EdDSA, depends on the context. RSA blind signatures are only used for coins and always simply base32_ encoded. + +EdDSA signatures are transmitted as 64-byte base32_ binary-encoded objects with just the R and S values (base32_ binary-only). +These signed objects always contain a purpose number unique to the context in which the signature is used, but frequently the actual binary-object must be reconstructed locally from information available only in context, such as recent messages or account detals. +These objects are described in detail in :ref:`Signatures`. + + .. _Amount: + + * **Amounts**: Amounts of currency are expressed as a JSON object with the following fields: + + .. _`tsref-type-Amount`: + + .. code-block: tsref + + interface Amount { + // name of the currency using either a three-character ISO 4217 currency + // code, or a regional currency identifier starting with a "*" followed by + // at most 10 characters. ISO 4217 exponents in the name are not supported, + // although the "fraction" is corresponds to an ISO 4217 exponent of 6. + currency: string; + + // unsigned 32 bit value in the currency, note that "1" here would + // correspond to 1 EUR or 1 USD, depending on `currency`, not 1 cent. + value: number; + + // unsigned 32 bit fractional value to be added to `value` representing + // an additional currency fraction, in units of one millionth (10e-6) + // of the base currency value. For example, a fraction + // of 500,000 would correspond to 50 cents. + fraction: number; + } + + * `currency`: name of the currency using either a three-character ISO 4217 currency code, or a regional currency identifier starting with a "*" followed by at most 10 characters. ISO 4217 exponents in the name are not supported, although the "fraction" is corresponds to an ISO 4217 exponent of 6. + * `value`: unsigned 32 bit value in the currency, note that "1" here would correspond to 1 EUR or 1 USD, depending on `currency`, not 1 cent. + * `fraction`: unsigned 32 bit fractional value to be added to `value` representing an additional currency fraction, in units of one millionth (10\ :superscript:`-6`) of the base currency value. For example, a fraction of 500,000 would correspond to 50 cents. + + +-------------- +General errors +-------------- + +Certain response formats are common for all requests. They are documented here instead of with each individual request. Furthermore, we note that clients may theoretically fail to receive any response. In this case, the client should verify that the Internet connection is working properly, and then proceed to handle the error as if an internal error (500) had been returned. + +.. http:any:: /* + + **Error Response: Internal error** + + When encountering an internal error, the mint may respond to any request with an internal server error. + + :status 500 Internal server error: This always indicates some serious internal operational error of the mint, such as a program bug, database problems, etc., and must not be used for client-side problems. When facing an internal server error, clients should retry their request after some delay. We recommended initially trying after 1s, twice more at randomized times within 1 minute, then the user should be informed and another three retries should be scheduled within the next 24h. If the error persists, a report should ultimately be made to the auditor, although the auditor API for this is not yet specified. However, as internal server errors are always reported to the mint operator, a good operator should naturally be able to address them in a timely fashion, especially within 24h. When generating an internal server error, the mint responds with a JSON object containing the following fields: + + :resheader Content-Type: application/json + :>json error: a string with the value "internal error" + :>json hint: a string with problem-specific human-readable diagnostic text and typically useful for the mint operator + + + **Error Response: Bad Request** + + When the client issues a malformed request with missing parameters or where the parameters fail to comply with the specification, the mint generates this type of response. The error should be shown to the user, while the other details are mostly intended as optional diagnostics for developers. + + :status 400 Bad Request: One of the arguments to the request is missing or malformed. + :resheader Content-Type: application/json + + .. code-block:: ts + :caption: Response Object Description + + // Description of the error, i.e. "missing parameter", "commitment violation", ... + // The other arguments are specific to the error value reported here. + error: string; + + // Name of the parameter that was bogus (if applicable) + parameter?: string; + + // Path to the argument that was bogus (if applicable) + path?: string; + + // Offset of the argument that was bogus (if applicable) + offset?: string; + + // Index of the argument that was bogus (if applicable) + index?: string; + + // Name of the object that was bogus (if applicable) + object?: string; + + // Name of the currency thant was problematic (if applicable) + currency?: string; + + // Expected type (if applicable). + type_expected?: string; + + // Type that was provided instead (if applicable). + type_actual?: string; + + + +-------------- +Binary Formats +-------------- + + .. note:: + + This section largely corresponds to the definitions in taler_signatures.h. You may also want to refer to this code, as it offers additional details on each of the members of the structs. + + .. note:: + + Due to the way of handling `big` numbers by some platforms (such as `JavaScript`, for example), wherever the following specification mentions a 64-bit value, the actual implementations + are strongly advised to rely on arithmetic up to 53 bits. + +This section specifies the binary representation of messages used in Taler's protocols. The message formats are given in a C-style pseudocode notation. Padding is always specified explicitly, and numeric values are in network byte order (big endian). + +Amounts +^^^^^^^ + +Amounts of currency are always expressed in terms of a base value, a fractional value and the denomination of the currency: + +.. sourcecode:: c + + struct TALER_AmountNBO { + uint64_t value; + uint32_t fraction; + uint8_t currency_code[12]; + }; + + +Time +^^^^ + +In signed messages, time is represented using 64-bit big-endian values, denoting microseconds since the UNIX Epoch. `UINT64_MAX` represents "never" (distant future, eternity). + +.. sourcecode:: c + + struct GNUNET_TIME_AbsoluteNBO { + uint64_t timestamp_us; + }; + +Cryptographic primitives +^^^^^^^^^^^^^^^^^^^^^^^^ + +All elliptic curve operations are on Curve25519. Public and private keys are thus 32 bytes, and signatures 64 bytes. For hashing, including HKDFs, Taler uses 512-bit hash codes (64 bytes). + +.. sourcecode:: c + + struct GNUNET_HashCode { + uint8_t hash[64]; + }; + + struct TALER_ReservePublicKeyP { + uint8_t eddsa_pub[32]; + }; + + struct TALER_ReservePrivateKeyP { + uint8_t eddsa_priv[32]; + }; + + struct TALER_ReserveSignatureP { + uint8_t eddsa_signature[64]; + }; + + struct TALER_MerchantPublicKeyP { + uint8_t eddsa_pub[32]; + }; + + struct TALER_MerchantPrivateKeyP { + uint8_t eddsa_priv[32]; + }; + + struct TALER_TransferPublicKeyP { + uint8_t ecdhe_pub[32]; + }; + + struct TALER_TransferPrivateKeyP { + uint8_t ecdhe_priv[32]; + }; + + struct TALER_MintPublicKeyP { + uint8_t eddsa_pub[32]; + }; + + struct TALER_MintPrivateKeyP { + uint8_t eddsa_priv[32]; + }; + + struct TALER_MintSignatureP { + uint8_t eddsa_signature[64]; + }; + + struct TALER_MasterPublicKeyP { + uint8_t eddsa_pub[32]; + }; + + struct TALER_MasterPrivateKeyP { + uint8_t eddsa_priv[32]; + }; + + struct TALER_MasterSignatureP { + uint8_t eddsa_signature[64]; + }; + + union TALER_CoinSpendPublicKeyP { + uint8_t eddsa_pub[32]; + uint8_t ecdhe_pub[32]; + }; + + union TALER_CoinSpendPrivateKeyP { + uint8_t eddsa_priv[32]; + uint8_t ecdhe_priv[32]; + }; + + struct TALER_CoinSpendSignatureP { + uint8_t eddsa_signature[64]; + }; + + struct TALER_TransferSecretP { + uint8_t key[sizeof (struct GNUNET_HashCode)]; + }; + + struct TALER_LinkSecretP { + uint8_t key[sizeof (struct GNUNET_HashCode)]; + }; + + struct TALER_EncryptedLinkSecretP { + uint8_t enc[sizeof (struct TALER_LinkSecretP)]; + }; + +.. _Signatures: + +Signatures +^^^^^^^^^^ + +Please note that any RSA signature is processed by a function called `GNUNET_CRYPTO_rsa_signature_encode (..)` **before** being sent over the network, so the receiving party must run `GNUNET_CRYPTO_rsa_signature_decode (..)` before verifying it. See their implementation in `src/util/crypto_rsa.c`, in GNUNET's code base. Finally, they are defined in `gnunet/gnunet_crypto_lib.h`. + +EdDSA signatures are always made on the hash of a block of the same generic format, the `struct SignedData` given below. In our notation, the type of a field can depend on the value of another field. For the following message, the length of the `payload` array must match the value of the `size` field: + +.. sourcecode:: c + + struct SignedData { + uint32_t size; + uint32_t purpose; + uint8_t payload[size - sizeof (struct SignedData)]; + }; + +The `purpose` field in `struct SignedData` is used to express the context in which the signature is made, ensuring that a signature cannot be lifted from one part of the protocol to another. The various `purpose` constants are defined in `taler_signatures.h`. The `size` field prevents padding attacks. + +In the subsequent messages, we use the following notation for signed data described in `FIELDS` with the given purpose. + +.. sourcecode:: c + + signed (purpose = SOME_CONSTANT) { + FIELDS + } msg; + +The `size` field of the corresponding `struct SignedData` is determined by the size of `FIELDS`. + +.. sourcecode:: c + + struct TALER_WithdrawRequestPS { + signed (purpose = TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW) { + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_AmountNBO amount_with_fee; + struct TALER_AmountNBO withdraw_fee; + struct GNUNET_HashCode h_denomination_pub; + struct GNUNET_HashCode h_coin_envelope; + } + }; + + struct TALER_DepositRequestPS { + signed (purpose = TALER_SIGNATURE_WALLET_COIN_DEPOSIT) { + struct GNUNET_HashCode h_contract; + struct GNUNET_HashCode h_wire; + struct GNUNET_TIME_AbsoluteNBO timestamp; + struct GNUNET_TIME_AbsoluteNBO refund_deadline; + uint64_t transaction_id; + struct TALER_AmountNBO amount_with_fee; + struct TALER_AmountNBO deposit_fee; + struct TALER_MerchantPublicKeyP merchant; + union TALER_CoinSpendPublicKeyP coin_pub; + } + }; + + struct TALER_DepositConfirmationPS { + signed (purpose = TALER_SIGNATURE_MINT_CONFIRM_DEPOSIT) { + struct GNUNET_HashCode h_contract; + struct GNUNET_HashCode h_wire; + uint64_t transaction_id GNUNET_PACKED; + struct GNUNET_TIME_AbsoluteNBO timestamp; + struct GNUNET_TIME_AbsoluteNBO refund_deadline; + struct TALER_AmountNBO amount_without_fee; + union TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_MerchantPublicKeyP merchant; + } + }; + + struct TALER_RefreshMeltCoinAffirmationPS { + signed (purpose = TALER_SIGNATURE_WALLET_COIN_MELT) { + struct GNUNET_HashCode session_hash; + struct TALER_AmountNBO amount_with_fee; + struct TALER_AmountNBO melt_fee; + union TALER_CoinSpendPublicKeyP coin_pub; + } + }; + + struct TALER_RefreshMeltConfirmationPS { + signed (purpose = TALER_SIGNATURE_MINT_CONFIRM_MELT) { + struct GNUNET_HashCode session_hash; + uint16_t noreveal_index; + } + }; + + struct TALER_MintSigningKeyValidityPS { + signed (purpose = TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY) { + struct TALER_MasterPublicKeyP master_public_key; + struct GNUNET_TIME_AbsoluteNBO start; + struct GNUNET_TIME_AbsoluteNBO expire; + struct GNUNET_TIME_AbsoluteNBO end; + struct TALER_MintPublicKeyP signkey_pub; + } + }; + + struct TALER_MintKeySetPS { + signed (purpose=TALER_SIGNATURE_MINT_KEY_SET) { + struct GNUNET_TIME_AbsoluteNBO list_issue_date; + struct GNUNET_HashCode hc; + } + }; + + struct TALER_DenominationKeyValidityPS { + signed (purpose = TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY) { + struct TALER_MasterPublicKeyP master; + struct GNUNET_TIME_AbsoluteNBO start; + struct GNUNET_TIME_AbsoluteNBO expire_withdraw; + struct GNUNET_TIME_AbsoluteNBO expire_spend; + struct GNUNET_TIME_AbsoluteNBO expire_legal; + struct TALER_AmountNBO value; + struct TALER_AmountNBO fee_withdraw; + struct TALER_AmountNBO fee_deposit; + struct TALER_AmountNBO fee_refresh; + struct GNUNET_HashCode denom_hash; + } + }; + + struct TALER_MasterWireSepaDetailsPS { + signed (purpose = TALER_SIGNATURE_MASTER_SEPA_DETAILS) { + struct GNUNET_HashCode h_sepa_details; + } + }; + + struct TALER_MintWireSupportMethodsPS { + signed (purpose = TALER_SIGNATURE_MINT_WIRE_TYPES) { + struct GNUNET_HashCode h_wire_types; + } + }; + + struct TALER_DepositTrackPS { + signed (purpose = TALER_SIGNATURE_MERCHANT_DEPOSIT_WTID) { + struct GNUNET_HashCode h_contract; + struct GNUNET_HashCode h_wire; + uint64_t transaction_id; + struct TALER_MerchantPublicKeyP merchant; + struct TALER_CoinSpendPublicKeyP coin_pub; + } + }; diff --git a/api-merchant.rst b/api-merchant.rst index e2f408f2..6f3d822c 100644 --- a/api-merchant.rst +++ b/api-merchant.rst @@ -61,15 +61,20 @@ successful response to the following two calls: Issued by the wallet when the customer wants to see the contract for a certain purchase + .. http:post:: /contract Issued by the frontend to the backend when it wants to augment its `proposition` with all the cryptographic information. For the sake of precision, the frontend encloses the following JSON inside a `contract` field to the actual JSON sent to the backend. - .. code-block:: ts + + .. _`hello-link`: + + .. code-block:: tsref interface Contract { + // `hello-link`_ // Total price for the transaction. // The mint will subtract deposit fees from that amount // before transfering it to the merchant. @@ -81,7 +86,7 @@ successful response to the following two calls: // 53-bit number chosen by the merchant to uniquely identify the contract. transaction_id: number; - // List of products that are part of the purchase (see below) + // List of products that are part of the purchase (see `below) products: Product[]; // Time when this contract was generated diff --git a/api-mint.rst b/api-mint.rst index b9a9beeb..4d63e0d5 100644 --- a/api-mint.rst +++ b/api-mint.rst @@ -2,148 +2,157 @@ The Mint RESTful JSON API ========================= -------- -General -------- - -.. _encodings-ref: - -++++++++++++++++ -Common encodings -++++++++++++++++ - -This section describes how certain types of values are represented throughout the API. - - .. _Base32: - - * **Binary data**: - Binary data is generally encoded using Crockford's variant of Base32 (http://www.crockford.com/wrmg/base32.html), except that "U" is not excluded but also decodes to "V" to make OCR easy. We will still simply use the JSON type "base32" and the term "Crockford Base32" in the text to refer to the resulting encoding. - - * **Large numbers**: Large numbers such as RSA blinding factors and 256 bit keys, are transmitted as other binary data in Crockford Base32 encoding. - - .. _Timestamp: - - * **Timestamps**: - Timestamps are represented in JSON as a string literal `"\\/Date(x)\\/"`, where `x` is the decimal representation of the number of seconds past the Unix Epoch (January 1, 1970). The escaped slash (`\\/`) is interpreted in JSON simply as a normal slash, but distinguishes the timestamp from a normal string literal. We use the type "date" in the documentation below. Additionally, the special strings "\\/never\\/" and "\\/forever\\/" are recognized to represent the end of time. - - .. _public\ key: - - * **Public key**: EdDSA and ECDHE public keys are always points on Curve25519 and represented using the standard 256 bits Ed25519 compact format, converted to Crockford Base32_. - - .. _Signature: - - * **Signatures**: The specific signature scheme in use, like RSA blind signatures or EdDSA, depends on the context. RSA blind signatures are only used for coins and always simply base32_ encoded. +------------------- +Obtaining Mint Keys +------------------- -EdDSA signatures are transmitted as 64-byte base32_ binary-encoded objects with just the R and S values (base32_ binary-only). -These signed objects always contain a purpose number unique to the context in which the signature is used, but frequently the actual binary-object must be reconstructed locally from information available only in context, such as recent messages or account detals. -These objects are described in detail in :ref:`Signatures`. - .. _Amount: +This API is used by wallets and merchants to obtain global information about the mint, such as online signing keys, available denominations and the fee structure. +This is typically the first call any mint client makes, as it returns information required to process all of the other interactions with the mint. The returned +information is secured by (1) signature(s) from the mint, especially the long-term offline signing key of the mint, which clients should cache; (2) signature(s) +from auditors, and the auditor keys should be hard-coded into the wallet as they are the trust anchors for Taler; (3) possibly by using HTTPS. - * **Amounts**: Amounts of currency are expressed as a JSON object with the following fields: - * `currency`: name of the currency using either a three-character ISO 4217 currency code, or a regional currency identifier starting with a "*" followed by at most 10 characters. ISO 4217 exponents in the name are not supported, although the "fraction" is corresponds to an ISO 4217 exponent of 6. - * `value`: unsigned 32 bit value in the currency, note that "1" here would correspond to 1 EUR or 1 USD, depending on `currency`, not 1 cent. - * `fraction`: unsigned 32 bit fractional value to be added to `value` representing an additional currency fraction, in units of one millionth (10\ :superscript:`-6`) of the base currency value. For example, a fraction of 500,000 would correspond to 50 cents. +.. http:get:: /keys + Get a list of all denomination keys offered by the bank, + as well as the bank's current online signing key. -++++++++++++++ -General errors -++++++++++++++ + **Success Response: OK** -Certain response formats are common for all requests. They are documented here instead of with each individual request. Furthermore, we note that clients may theoretically fail to receive any response. In this case, the client should verify that the Internet connection is working properly, and then proceed to handle the error as if an internal error (500) had been returned. + :status 200 OK: This request should virtually always be successful. + :resheader Content-Type: application/json + + .. code-block:: ts + + interface MintKeysResponse { + // EdDSA master public key of the mint, used to sign entries in `denoms` and `signkeys` + master_public_key: EddsaPublicKey; + + // Denomination offered by this mint. + denoms: Denom[]; + + // The date when the denomination keys were last updated. + list_issue_date: string; + + // Auditors of the mint. + auditors: Auditor[]; + + // The mint's signing keys. + signkeys: SignKey[]; + + // compact EdDSA signature_ (binary-only) over the SHA-512 hash of the + // concatenation of all SHA-512 hashes of the RSA denomination public keys + // in `denoms` in the same order as they were in `denoms`. Note that for + // hashing, the binary format of the RSA public keys is used, and not their + // base32_ encoding. Wallets cannot do much with this signature by itself; + // it is only useful when multiple clients need to establish that the mint + // is sabotaging end-user anonymity by giving disjoint denomination keys to + // different users. If a mint were to do this, this signature allows the + // clients to demonstrate to the public that the mint is dishonest. + eddsa_sig: string; + + // Public EdDSA key of the mint that was used to generate the signature. + // Should match one of the mint'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. + eddsa_pub: string; + } -.. http:any:: /* + interface Denomination { + // How much are coins of this denomination worth? + value: Amount; - **Error Response: Internal error** + // When does the denomination key become valid? + stamp_start: Timestamp; - When encountering an internal error, the mint may respond to any request with an internal server error. + // When is it no longer possible to withdraw fresh coins + // of this denomination? + stamp_expire_withdraw: Timestamp; - :status 500 Internal server error: This always indicates some serious internal operational error of the mint, such as a program bug, database problems, etc., and must not be used for client-side problems. When facing an internal server error, clients should retry their request after some delay. We recommended initially trying after 1s, twice more at randomized times within 1 minute, then the user should be informed and another three retries should be scheduled within the next 24h. If the error persists, a report should ultimately be made to the auditor, although the auditor API for this is not yet specified. However, as internal server errors are always reported to the mint operator, a good operator should naturally be able to address them in a timely fashion, especially within 24h. When generating an internal server error, the mint responds with a JSON object containing the following fields: + // When is it no longer possible to deposit coins + // of this denomination? + stamp_expire_withdraw: Timestamp; - :resheader Content-Type: application/json - :>json error: a string with the value "internal error" - :>json hint: a string with problem-specific human-readable diagnostic text and typically useful for the mint operator + // Timestamp indicating by when legal disputes relating to these coins must + // be settled, as the mint will afterwards destroy its evidence relating to + // transactions involving this coin. + stamp_expire_legal: Timestamp; + // Public (RSA) key for the denomination in base32 encoding. + denom_pub: string; - **Error Response: Bad Request** + // Fee charged by the mint for withdrawing a coin of this denomination + fee_withdraw: Amount; - When the client issues a malformed request with missing parameters or where the parameters fail to comply with the specification, the mint generates this type of response. The error should be shown to the user, while the other details are mostly intended as optional diagnostics for developers. + // Fee charged by the mint for depositing a coin of this denomination + fee_deposit: Amount; - :status 400 Bad Request: One of the arguments to the request is missing or malformed. - :resheader Content-Type: application/json - :>json string error: description of the error, i.e. missing parameter, malformed parameter, commitment violation, etc. The other arguments are specific to the error value reported here. - :>json string parameter: name of the parameter that was bogus (if applicable) - :>json string path: path to the argument that was bogus (if applicable) - :>json string offset: offset of the argument that was bogus (if applicable) - :>json string index: index of the argument that was bogus (if applicable) - :>json string object: name of the component of the object that was bogus (if applicable) - :>json string currency: currency that was problematic (if applicable) - :>json string type_expected: expected type (if applicable) - :>json string type_actual: type that was provided instead (if applicable) + // Fee charged by the mint for refreshing a coin of this denomination + fee_refresh: Amount; + // Signature with purpose + // `TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY` over the expiration + // dates, value and the key, created with the mint's master key. + master_sig: EddsaSignature; + } + Fees for any of the operations can be zero, but the fields must still be present. The currency of the `fee_deposit` and `fee_refresh` must match the currency of the `value`. Theoretically, the `fee_withdraw` could be in a different currency, but this is not currently supported by the implementation. -------------------- -Obtaining Mint Keys -------------------- + A signing key in the `signkeys` list is a JSON object with the following fields: -This API is used by wallets and merchants to obtain global information about the mint, such as online signing keys, available denominations and the fee structure. -This is typically the first call any mint client makes, as it returns information required to process all of the other interactions with the mint. The returned -information is secured by (1) signature(s) from the mint, especially the long-term offline signing key of the mint, which clients should cache; (2) signature(s) -from auditors, and the auditor keys should be hard-coded into the wallet as they are the trust anchors for Taler; (3) possibly by using HTTPS. + .. code-block:: ts + interface SignKey { + // The actual mint's EdDSA signing public key. + key: EddsaPublicKey; -.. http:get:: /keys + // Initial validity date for the signing key. + stamp_start: Timestamp; - Get a list of all denomination keys offered by the bank, - as well as the bank's current online signing key. + // Date when the mint 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; - **Success Response: OK** + // Date when all signatures made by the signing key expire and should + // henceforth no longer be considered valid in legal disputes. + stamp_end: Timestamp; - :status 200 OK: This request should virtually always be successful. - :resheader Content-Type: application/json - :>json base32 master_public_key: EdDSA master public key of the mint, used to sign entries in `denoms` and `signkeys` - :>json list denoms: A JSON list of denomination descriptions. Described below in detail. - :>json date list_issue_date: The date when the denomination keys were last updated. - :>json list auditors: A JSON list of the auditors of the mint. Described below in detail. - :>json list signkeys: A JSON list of the mint's signing keys. Described below in detail. - :>json base32 eddsa_sig: compact EdDSA signature_ (binary-only) over the SHA-512 hash of the concatenation of all SHA-512 hashes of the RSA denomination public keys in `denoms` in the same order as they were in `denoms`. Note that for hashing, the binary format of the RSA public keys is used, and not their base32_ encoding. Wallets cannot do much with this signature by itself; it is only useful when multiple clients need to establish that the mint is sabotaging end-user anonymity by giving disjoint denomination keys to different users. If a mint were to do this, this signature allows the clients to demonstrate to the public that the mint is dishonest. - :>json base32 eddsa_pub: public EdDSA key of the mint that was used to generate the signature. Should match one of the mint'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. - - A denomination description in the `denoms` list is a JSON object with the following fields: - - :>jsonarr object value: Amount_ of the denomination. A JSON object specifying an amount_. - :>jsonarr date stamp_start: timestamp_ indicating when the denomination key becomes valid. - :>jsonarr date stamp_expire_withdraw: timestamp_ indicating when the denomination key can no longer be used to withdraw fresh coins. - :>jsonarr date stamp_expire_deposit: timestamp_ indicating when coins of this denomination become invalid for depositing. - :>jsonarr date stamp_expire_legal: timestamp_ indicating by when legal disputes relating to these coins must be settled, as the mint will afterwards destroy its evidence relating to transactions involving this coin. - :>jsonarr base32 denom_pub: Public (RSA) key for the denomination in base32_ encoding. - :>jsonarr object fee_withdraw: Fee charged by the mint for withdrawing a coin of this type, encoded as a JSON object specifying an amount_. - :>jsonarr object fee_deposit: Fee charged by the mint for depositing a coin of this type, encoded as a JSON object specifying an amount_. - :>jsonarr object fee_refresh: Fee charged by the mint for melting a coin of this type during a refresh operation, encoded as a JSON object specifying an amount_. Note that the total refreshing charges will be the sum of the refresh fees for all of the melted coins and the sum of the withdraw fees for all "new" coins. - :>jsonarr base32 master_sig: Signature_ (binary-only) with purpose `TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY` over the expiration dates, value and the key, created with the mint's master key. + // A signature_ (binary-only) with purpose + // `TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY` over the `key` and + // `stamp_expire` by the mint master key. + master_sig: EddsaSignature; + } - Fees for any of the operations can be zero, but the fields must still be present. The currency of the `fee_deposit` and `fee_refresh` must match the currency of the `value`. Theoretically, the `fee_withdraw` could be in a different currency, but this is not currently supported by the implementation. + An entry in the `auditors` list is a JSON object with the following fields: - A signing key in the `signkeys` list is a JSON object with the following fields: + .. code-block:: ts - :>jsonarr base32 key: The actual mint's EdDSA signing public key. - :>jsonarr date stamp_start: Initial validity date for the signing key. - :>jsonarr date stamp_expire: Date when the mint will stop using the signing key, allowed to overlap slightly with the next signing key's validity to allow for clock skew. - :>jsonarr date stamp_end: Date when all signatures made by the signing key expire and should henceforth no longer be considered valid in legal disputes. - :>jsonarr date stamp_expire: Expiration date for the signing key. - :>jsonarr base32 master_sig: A signature_ (binary-only) with purpose `TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY` over the `key` and `stamp_expire` by the mint master key. + interface Auditor { + // The auditor's EdDSA signing public key. + auditor_pub: EddsaPublicKey; - An entry in the `auditors` list is a JSON object with the following fields: + // 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: DenominationKey[]; + } - :>jsonarr base32 auditor_pub: The auditor's EdDSA signing public key. - :>jsonarr array denomination_keys: 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. + interface DenominationKey { + // 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; - An entry in the `denomination_keys` list is a JSON object with the following field: + // A signature_ (binary-only) with purpose + // `TALER_SIGNATURE_AUDITOR_MINT_KEYS` over the mint's public key and the + // denomination key information. To verify the signature, the `denom_pub_h` + // must be resolved with the information from `denoms` + auditor_sig: EddsaSignature; - :>jsonarr base32 denom_pub_h: 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. - :>jsonarr base32 auditor_sig: A signature_ (binary-only) with purpose `TALER_SIGNATURE_AUDITOR_MINT_KEYS` over the mint's public key and the denomination key information. To verify the signature, the `denom_pub_h` must be resolved with the information from `denoms`. + } 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. @@ -164,9 +173,27 @@ Obtaining wire-transfer information :status 200: This request should virtually always be successful. :resheader Content-Type: application/json - :>json array methods: a JSON array of strings with supported payment methods, i.e. "sepa". Further information about the respective payment method is then available under /wire/METHOD, i.e. /wire/sepa if the payment method was "sepa". - :>json base32 sig: the EdDSA signature_ (binary-only) with purpose `TALER_SIGNATURE_MINT_PAYMENT_METHODS` signing over the hash over the 0-terminated strings representing the payment methods in the same order as given in methods. - :>json base32 pub: public EdDSA key of the mint that was used to generate the signature. Should match one of the mint'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. + + .. code-block:: ts + + interface WireResponse { + // Names of supported methods (i.e. "sepa" or "test"). + // Payment method METHOD is available under /wire/METHOD. + methods: string[]; + + // the EdDSA signature_ (binary-only) with purpose + // `TALER_SIGNATURE_MINT_PAYMENT_METHODS` signing over the hash over the + // 0-terminated strings representing the payment methods in the same order + // as given in methods. + sig: EddsaSignature; + + // public EdDSA key of the mint that was used to generate the signature. + // Should match one of the mint'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. + pub: EddsaPublicKey; + } + .. http:get:: /wire/test @@ -227,16 +254,43 @@ When transfering money to the mint such as via SEPA transfers, the mint creates :status 200 OK: The reserve was known to the mint, details about it follow in the body. :resheader Content-Type: application/json - :>json object balance: Total amount_ left in this reserve, an amount_ expressed as a JSON object. - :>json object history: JSON list with the history of transactions involving the reserve. + + .. code-block:: ts + + interface ReserveStatus { + // Balance left in the reserve. + balance: Amount; + + // Transaction history for this reserve + history: TransactionHistoryItem[]; + } Objects in the transaction history have the following format: - :>jsonarr string type: either the string "WITHDRAW" or the string "DEPOSIT" - :>jsonarr object amount: the amount_ that was withdrawn or deposited - :>jsonarr object wire: a JSON object with the wiring details needed by the banking system in use, present in case the `type` was "DEPOSIT" - :>jsonarr string details: base32_ binary encoding of the transaction data as a `TALER_WithdrawRequestPS` struct described in :ref:`Signatures`, only present if the `type` was "WITHDRAW". Its `purpose` should match our `type`, `amount_with_fee`, should match our `amount`, and its `size` should be consistent. - :>jsonarr object signature: the EdDSA signature_ (binary-only) made with purpose `TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW` over the transaction's details, again only present if the `type` was "WITHDRAW". + .. code-block:: ts + + interface TransactionHistoryItem { + // Either "WITHDRAW" or "DEPOSIT" + type: string; + + // The amount that was withdrawn or deposited. + amount: Amount; + + // Wiring details, only present if type is "DEPOSIT". + wire?: any; + + // binary encoding of the transaction data as a `TALER_WithdrawRequestPS` + // struct described in :ref:`Signatures`, only present if the `type` was + // "WITHDRAW". Its `purpose` should match our `type`, `amount_with_fee`, + // should match our `amount`, and its `size` should be consistent. + string?: details; + + // Signature over the transaction details. + // Purpose: TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW + signature?: EddsaSignature; + + + } **Error Response: Unknown reserve** @@ -814,273 +868,3 @@ binary-compatible with the implementation of the mint. :>json base32 secret: Decrypted transfer secret -=========================== -Binary Blob Specification -=========================== - - .. note:: - - This section largely corresponds to the definitions in taler_signatures.h. You may also want to refer to this code, as it offers additional details on each of the members of the structs. - - .. note:: - - Due to the way of handling `big` numbers by some platforms (such as `JavaScript`, for example), wherever the following specification mentions a 64-bit value, the actual implementations - are strongly advised to rely on arithmetic up to 53 bits. - -This section specifies the binary representation of messages used in Taler's protocols. The message formats are given in a C-style pseudocode notation. Padding is always specified explicitly, and numeric values are in network byte order (big endian). - ------------------------- -Amounts ------------------------- - -Amounts of currency are always expressed in terms of a base value, a fractional value and the denomination of the currency: - -.. sourcecode:: c - - struct TALER_AmountNBO { - uint64_t value; - uint32_t fraction; - uint8_t currency_code[12]; - }; - - ------------------------- -Time ------------------------- - -In signed messages, time is represented using 64-bit big-endian values, denoting microseconds since the UNIX Epoch. `UINT64_MAX` represents "never" (distant future, eternity). - -.. sourcecode:: c - - struct GNUNET_TIME_AbsoluteNBO { - uint64_t timestamp_us; - }; - ------------------------- -Cryptographic primitives ------------------------- - -All elliptic curve operations are on Curve25519. Public and private keys are thus 32 bytes, and signatures 64 bytes. For hashing, including HKDFs, Taler uses 512-bit hash codes (64 bytes). - -.. sourcecode:: c - - struct GNUNET_HashCode { - uint8_t hash[64]; - }; - - struct TALER_ReservePublicKeyP { - uint8_t eddsa_pub[32]; - }; - - struct TALER_ReservePrivateKeyP { - uint8_t eddsa_priv[32]; - }; - - struct TALER_ReserveSignatureP { - uint8_t eddsa_signature[64]; - }; - - struct TALER_MerchantPublicKeyP { - uint8_t eddsa_pub[32]; - }; - - struct TALER_MerchantPrivateKeyP { - uint8_t eddsa_priv[32]; - }; - - struct TALER_TransferPublicKeyP { - uint8_t ecdhe_pub[32]; - }; - - struct TALER_TransferPrivateKeyP { - uint8_t ecdhe_priv[32]; - }; - - struct TALER_MintPublicKeyP { - uint8_t eddsa_pub[32]; - }; - - struct TALER_MintPrivateKeyP { - uint8_t eddsa_priv[32]; - }; - - struct TALER_MintSignatureP { - uint8_t eddsa_signature[64]; - }; - - struct TALER_MasterPublicKeyP { - uint8_t eddsa_pub[32]; - }; - - struct TALER_MasterPrivateKeyP { - uint8_t eddsa_priv[32]; - }; - - struct TALER_MasterSignatureP { - uint8_t eddsa_signature[64]; - }; - - union TALER_CoinSpendPublicKeyP { - uint8_t eddsa_pub[32]; - uint8_t ecdhe_pub[32]; - }; - - union TALER_CoinSpendPrivateKeyP { - uint8_t eddsa_priv[32]; - uint8_t ecdhe_priv[32]; - }; - - struct TALER_CoinSpendSignatureP { - uint8_t eddsa_signature[64]; - }; - - struct TALER_TransferSecretP { - uint8_t key[sizeof (struct GNUNET_HashCode)]; - }; - - struct TALER_LinkSecretP { - uint8_t key[sizeof (struct GNUNET_HashCode)]; - }; - - struct TALER_EncryptedLinkSecretP { - uint8_t enc[sizeof (struct TALER_LinkSecretP)]; - }; - -.. _Signatures: - ------------------------- -Signatures ------------------------- - -Please note that any RSA signature is processed by a function called `GNUNET_CRYPTO_rsa_signature_encode (..)` **before** being sent over the network, so the receiving party must run `GNUNET_CRYPTO_rsa_signature_decode (..)` before verifying it. See their implementation in `src/util/crypto_rsa.c`, in GNUNET's code base. Finally, they are defined in `gnunet/gnunet_crypto_lib.h`. - -EdDSA signatures are always made on the hash of a block of the same generic format, the `struct SignedData` given below. In our notation, the type of a field can depend on the value of another field. For the following message, the length of the `payload` array must match the value of the `size` field: - -.. sourcecode:: c - - struct SignedData { - uint32_t size; - uint32_t purpose; - uint8_t payload[size - sizeof (struct SignedData)]; - }; - -The `purpose` field in `struct SignedData` is used to express the context in which the signature is made, ensuring that a signature cannot be lifted from one part of the protocol to another. The various `purpose` constants are defined in `taler_signatures.h`. The `size` field prevents padding attacks. - -In the subsequent messages, we use the following notation for signed data described in `FIELDS` with the given purpose. - -.. sourcecode:: c - - signed (purpose = SOME_CONSTANT) { - FIELDS - } msg; - -The `size` field of the corresponding `struct SignedData` is determined by the size of `FIELDS`. - -.. sourcecode:: c - - struct TALER_WithdrawRequestPS { - signed (purpose = TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW) { - struct TALER_ReservePublicKeyP reserve_pub; - struct TALER_AmountNBO amount_with_fee; - struct TALER_AmountNBO withdraw_fee; - struct GNUNET_HashCode h_denomination_pub; - struct GNUNET_HashCode h_coin_envelope; - } - }; - - struct TALER_DepositRequestPS { - signed (purpose = TALER_SIGNATURE_WALLET_COIN_DEPOSIT) { - struct GNUNET_HashCode h_contract; - struct GNUNET_HashCode h_wire; - struct GNUNET_TIME_AbsoluteNBO timestamp; - struct GNUNET_TIME_AbsoluteNBO refund_deadline; - uint64_t transaction_id; - struct TALER_AmountNBO amount_with_fee; - struct TALER_AmountNBO deposit_fee; - struct TALER_MerchantPublicKeyP merchant; - union TALER_CoinSpendPublicKeyP coin_pub; - } - }; - - struct TALER_DepositConfirmationPS { - signed (purpose = TALER_SIGNATURE_MINT_CONFIRM_DEPOSIT) { - struct GNUNET_HashCode h_contract; - struct GNUNET_HashCode h_wire; - uint64_t transaction_id GNUNET_PACKED; - struct GNUNET_TIME_AbsoluteNBO timestamp; - struct GNUNET_TIME_AbsoluteNBO refund_deadline; - struct TALER_AmountNBO amount_without_fee; - union TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_MerchantPublicKeyP merchant; - } - }; - - struct TALER_RefreshMeltCoinAffirmationPS { - signed (purpose = TALER_SIGNATURE_WALLET_COIN_MELT) { - struct GNUNET_HashCode session_hash; - struct TALER_AmountNBO amount_with_fee; - struct TALER_AmountNBO melt_fee; - union TALER_CoinSpendPublicKeyP coin_pub; - } - }; - - struct TALER_RefreshMeltConfirmationPS { - signed (purpose = TALER_SIGNATURE_MINT_CONFIRM_MELT) { - struct GNUNET_HashCode session_hash; - uint16_t noreveal_index; - } - }; - - struct TALER_MintSigningKeyValidityPS { - signed (purpose = TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY) { - struct TALER_MasterPublicKeyP master_public_key; - struct GNUNET_TIME_AbsoluteNBO start; - struct GNUNET_TIME_AbsoluteNBO expire; - struct GNUNET_TIME_AbsoluteNBO end; - struct TALER_MintPublicKeyP signkey_pub; - } - }; - - struct TALER_MintKeySetPS { - signed (purpose=TALER_SIGNATURE_MINT_KEY_SET) { - struct GNUNET_TIME_AbsoluteNBO list_issue_date; - struct GNUNET_HashCode hc; - } - }; - - struct TALER_DenominationKeyValidityPS { - signed (purpose = TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY) { - struct TALER_MasterPublicKeyP master; - struct GNUNET_TIME_AbsoluteNBO start; - struct GNUNET_TIME_AbsoluteNBO expire_withdraw; - struct GNUNET_TIME_AbsoluteNBO expire_spend; - struct GNUNET_TIME_AbsoluteNBO expire_legal; - struct TALER_AmountNBO value; - struct TALER_AmountNBO fee_withdraw; - struct TALER_AmountNBO fee_deposit; - struct TALER_AmountNBO fee_refresh; - struct GNUNET_HashCode denom_hash; - } - }; - - struct TALER_MasterWireSepaDetailsPS { - signed (purpose = TALER_SIGNATURE_MASTER_SEPA_DETAILS) { - struct GNUNET_HashCode h_sepa_details; - } - }; - - struct TALER_MintWireSupportMethodsPS { - signed (purpose = TALER_SIGNATURE_MINT_WIRE_TYPES) { - struct GNUNET_HashCode h_wire_types; - } - }; - - struct TALER_DepositTrackPS { - signed (purpose = TALER_SIGNATURE_MERCHANT_DEPOSIT_WTID) { - struct GNUNET_HashCode h_contract; - struct GNUNET_HashCode h_wire; - uint64_t transaction_id; - struct TALER_MerchantPublicKeyP merchant; - struct TALER_CoinSpendPublicKeyP coin_pub; - } - }; diff --git a/conf.py b/conf.py index 010cf651..f3aea86c 100644 --- a/conf.py +++ b/conf.py @@ -23,12 +23,15 @@ import os # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +needs_sphinx = '1.3' + +sys.path.append(os.path.abspath('exts')) # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'tsref', 'sphinx.ext.todo', 'sphinx.ext.pngmath', 'sphinxcontrib.httpdomain' diff --git a/dev-merchant.rst b/dev-merchant.rst deleted file mode 100644 index 773d05c1..00000000 --- a/dev-merchant.rst +++ /dev/null @@ -1,74 +0,0 @@ -================================= -Merchant Reference Implementation -================================= - ------------------------ -Architectural Overview ------------------------ - -The merchant reference implementationis divided into two independent -compontents, the `frontend` and the `backend`. - -The `frontend` is the existing shopping portal of the merchant. -The architecture tries to minimize the amount of modifications necessary -to the `frontend` as well as the trust that needs to be placed into the -`frontend` logic. Taler requires the frontend to facilitate two -JSON-based interactions between the wallet and the `backend`, and -one of those is trivial. - -The `backend` is a standalone C application intended to implement all -the cryptographic routines required to interact with the Taler wallet -and a Taler mint. - - ------------------------------- -The Merchant Backend HTTP API ------------------------------- - -The following API are made available by the merchant's `backend` to the merchant's `frontend`. - -.. http:post:: /contract - - Ask the backend to add some missing (mostly related to cryptography) information to the contract. - - :reqheader Content-Type: application/json - - The `proposition` that is to be sent from the frontend is a `contract` object without the fields - - * `merchant_pub` - * `mints` - * `H_wire` - - The `backend` then completes this information based on its configuration. - - **Success Response** - - :status 200 OK: The backend has successfully created the contract. - :resheader Content-Type: application/json - - The `frontend` should pass this response verbatim to the wallet. - - **Failure Responses: Bad contract** - - :status 400 Bad Request: Request not understood. The JSON was invalid. Possibly due to some error in formatting the JSON by the `frontend`. - -.. http:post:: /pay - - Asks the `backend` to execute the transaction with the mint and deposit the coins. - - :reqheader Content-Type: application/json - - The `frontend` passes the :ref:`deposit permission ` received from the wallet, by adding the fields `max_fee`, `amount` (see :ref:`contract`) and optionally adding a field named `edate`, indicating a deadline by which he would expect to receive the bank transfer for this deal - - **Success Response: OK** - - :status 200 OK: The mint accepted all of the coins. The `frontend` should now fullfill the contract. This response has no meaningful body, the frontend needs to generate the fullfillment page. - - **Failure Responses: Bad mint** - - :status 400 Precondition failed: The given mint is not acceptable for this merchant, as it is not in the list of accepted mints and not audited by an approved auditor. - - - **Failure Responses: Mint trouble** - - The `backend` will return verbatim the error codes received from the mint's :ref:`deposit ` API. If the wallet made a mistake, like by double-spending for example, the `frontend` should pass the reply verbatim to the browser/wallet. This should be the expected case, as the `frontend` cannot really make mistakes; the only reasonable exception is if the `backend` is unavailable, in which case the customer might appreciate some reassurance that the merchant is working on getting his systems back online. diff --git a/dev-wallet-wx.rst b/dev-wallet-wx.rst index 5790f801..d5b7c21a 100644 --- a/dev-wallet-wx.rst +++ b/dev-wallet-wx.rst @@ -91,4 +91,16 @@ IndexedDB Query Abstractions The *wxwallet* uses a fluent-style API for queries on IndexedDB. -TODO: say more +TODO: say more about this + + +------- +Testing +------- + +Test cases for the wallet are written in TypeScript and +run with `mochajs `_ and the `better-assert `_ assertion +library. + +Run the default test suite with ``XXX``. + diff --git a/exts/tsref.py b/exts/tsref.py new file mode 100644 index 00000000..abd2c90e --- /dev/null +++ b/exts/tsref.py @@ -0,0 +1,202 @@ +from pygments.util import get_bool_opt +from pygments.token import Name, Comment, Token, _TokenType +from pygments.filter import Filter +from sphinx.highlighting import PygmentsBridge +from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.pygments_styles import SphinxStyle +from pygments.formatters import HtmlFormatter +from docutils import nodes +from docutils.nodes import make_id +import re + + +_escape_html_table = { + ord('&'): u'&', + ord('<'): u'<', + ord('>'): u'>', + ord('"'): u'"', + ord("'"): u''', +} + + +class LinkingHtmlFormatter(HtmlFormatter): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._builder = kwargs['_builder'] + + def _fmt(self, value, tok): + cls = self._get_css_class(tok) + href = tok_getprop(tok, "href") + if href: + value = '%s' % (href, value) + print("got target!!!!!!!!!!!") + if cls is None or cls == "": + return value + return '%s' % (cls, value) + + + + def _format_lines(self, tokensource): + """ + Just format the tokens, without any wrapping tags. + Yield individual lines. + """ + lsep = self.lineseparator + escape_table = _escape_html_table + + line = '' + for ttype, value in tokensource: + link = get_annotation(ttype, "link") + if link: + print("got link", link) + + parts = value.translate(escape_table).split('\n') + + if len(parts) == 0: + # empty token, usually should not happen + pass + elif len(parts) == 1: + # no newline before or after token + line += self._fmt(parts[0], ttype) + else: + line += self._fmt(parts[0], ttype) + yield 1, line + lsep + for part in parts[1:-1]: + yield 1, self._fmt(part, ttype) + lsep + line = self._fmt(parts[-1], ttype) + + if line: + yield 1, line + lsep + + + +class MyPygmentsBridge(PygmentsBridge): + def __init__(self, builder, trim_doctest_flags): + self.dest = "html" + self.trim_doctest_flags = trim_doctest_flags + self.formatter_args = {'style': SphinxStyle, '_builder': builder} + self.formatter = LinkingHtmlFormatter + + +class MyHtmlBuilder(StandaloneHTMLBuilder): + name = "html-linked" + def init_highlighter(self): + if self.config.pygments_style is not None: + style = self.config.pygments_style + elif self.theme: + style = self.theme.get_confstr('theme', 'pygments_style', 'none') + else: + style = 'sphinx' + self.highlighter = MyPygmentsBridge(self, self.config.trim_doctest_flags) + + def write_doc(self, docname, doctree): + print("writing doc", docname) + self._current_docname = docname + super().write_doc(docname, doctree) + + +def get_annotation(tok, key): + if not hasattr(tok, "kv"): + return None + return tok.kv.get(key) + +def copy_token(tok): + new_tok = _TokenType(tok) + # This part is very fragile against API changes ... + new_tok.subtypes = set(tok.subtypes) + new_tok.parent = tok.parent + return new_tok + +def tok_setprop(tok, key, value): + tokid = id(tok) + e = token_props.get(tokid) + if e is None: + e = token_props[tokid] = (tok, {}) + _, kv = e + kv[key] = value + +def tok_getprop(tok, key): + tokid = id(tok) + e = token_props.get(tokid) + if e is None: + return None + _, kv = e + return kv.get(key) + + +link_reg = re.compile(r"`([^`]+)`_") + +# map from token id to props +token_props = {} + +id_to_doc = {} + + +class LinkFilter(Filter): + def __init__(self, app, **options): + self.app = app + Filter.__init__(self, **options) + + def filter(self, lexer, stream): + for ttype, value in stream: + if ttype in Token.Keyword.Type: + defname = make_id('tsref-type-' + value); + print("looking for", defname) + t = copy_token(ttype) + if defname in id_to_doc: + docname = id_to_doc[defname] + print("found", defname, "in", docname) + href = self.app.builder.get_target_uri(docname) + "#" + defname + tok_setprop(t, "href", href) + + yield t, value + elif ttype in Token.Comment: + last = 0 + for m in re.finditer(link_reg, value): + print("got link comment", value) + pre = value[last:m.start()] + if pre: + yield ttype, pre + t = copy_token(ttype) + id = make_id(m.group(1)) + if id in id_to_doc: + docname = id_to_doc[id] + href = self.app.builder.get_target_uri(docname) + "#" + id + tok_setprop(t, "href", href) + else: + print("target id not found:", id) + print("old", ttype) + print("new", t) + yield t, m.group(1) + last = m.end() + post = value[last:] + if post: + yield ttype, post + else: + yield ttype, value + + + +def remember_targets(app, doctree, docname): + for node in doctree.traverse(): + if not isinstance(node, nodes.Element): + continue + ids = node.get("ids") + if ids: + for id in ids: + id_to_doc[id] = docname + + +def setup(app): + from sphinx.highlighting import lexers + from pygments.lexers import TypeScriptLexer + from pygments.token import Name + from pygments.filters import NameHighlightFilter + lexer = TypeScriptLexer() + lexer.add_filter(LinkFilter(app)) + app.add_lexer('tsref', lexer) + app.add_builder(MyHtmlBuilder) + app.connect("doctree-resolved", remember_targets) + + + diff --git a/impl-merchant.rst b/impl-merchant.rst new file mode 100644 index 00000000..773d05c1 --- /dev/null +++ b/impl-merchant.rst @@ -0,0 +1,74 @@ +================================= +Merchant Reference Implementation +================================= + +----------------------- +Architectural Overview +----------------------- + +The merchant reference implementationis divided into two independent +compontents, the `frontend` and the `backend`. + +The `frontend` is the existing shopping portal of the merchant. +The architecture tries to minimize the amount of modifications necessary +to the `frontend` as well as the trust that needs to be placed into the +`frontend` logic. Taler requires the frontend to facilitate two +JSON-based interactions between the wallet and the `backend`, and +one of those is trivial. + +The `backend` is a standalone C application intended to implement all +the cryptographic routines required to interact with the Taler wallet +and a Taler mint. + + +------------------------------ +The Merchant Backend HTTP API +------------------------------ + +The following API are made available by the merchant's `backend` to the merchant's `frontend`. + +.. http:post:: /contract + + Ask the backend to add some missing (mostly related to cryptography) information to the contract. + + :reqheader Content-Type: application/json + + The `proposition` that is to be sent from the frontend is a `contract` object without the fields + + * `merchant_pub` + * `mints` + * `H_wire` + + The `backend` then completes this information based on its configuration. + + **Success Response** + + :status 200 OK: The backend has successfully created the contract. + :resheader Content-Type: application/json + + The `frontend` should pass this response verbatim to the wallet. + + **Failure Responses: Bad contract** + + :status 400 Bad Request: Request not understood. The JSON was invalid. Possibly due to some error in formatting the JSON by the `frontend`. + +.. http:post:: /pay + + Asks the `backend` to execute the transaction with the mint and deposit the coins. + + :reqheader Content-Type: application/json + + The `frontend` passes the :ref:`deposit permission ` received from the wallet, by adding the fields `max_fee`, `amount` (see :ref:`contract`) and optionally adding a field named `edate`, indicating a deadline by which he would expect to receive the bank transfer for this deal + + **Success Response: OK** + + :status 200 OK: The mint accepted all of the coins. The `frontend` should now fullfill the contract. This response has no meaningful body, the frontend needs to generate the fullfillment page. + + **Failure Responses: Bad mint** + + :status 400 Precondition failed: The given mint is not acceptable for this merchant, as it is not in the list of accepted mints and not audited by an approved auditor. + + + **Failure Responses: Mint trouble** + + The `backend` will return verbatim the error codes received from the mint's :ref:`deposit ` API. If the wallet made a mistake, like by double-spending for example, the `frontend` should pass the reply verbatim to the browser/wallet. This should be the expected case, as the `frontend` cannot really make mistakes; the only reasonable exception is if the `backend` is unavailable, in which case the customer might appreciate some reassurance that the merchant is working on getting his systems back online. diff --git a/index.rst b/index.rst index 17cfdfa2..ad566628 100644 --- a/index.rst +++ b/index.rst @@ -32,6 +32,7 @@ It focuses on how to install, configure and run the required software. :maxdepth: 2 impl-mint + impl-merchant ------------------------ @@ -61,6 +62,7 @@ interfaces between the core components of Taler. .. toctree:: :maxdepth: 2 + api-common api-mint api-merchant @@ -78,7 +80,6 @@ core components of the Taler reference implementation. :maxdepth: 2 dev-wallet-wx - dev-merchant ------------------ diff --git a/integration-merchant.rst b/integration-merchant.rst index 9357b809..37aac501 100644 --- a/integration-merchant.rst +++ b/integration-merchant.rst @@ -2,7 +2,19 @@ Interaction with merchant websites ================================== -This section defines the protocol between the wallet -and the merchant page. +------------- +Purchase Flow +------------- + +The purchase flow consists of the following steps: + +1. UA visits merchant's checkout page +2. The merchant's checkout page notifies the wallet + of the contract (``taler-deliver-contract``) +3. The user reviews the contract inside the wallet +4. The wallet directs the UA to the payment execution page (``taler-execute-contract``) + +The *execution page* allows the wallet to make a request +to the merchant from the store's domain. -- cgit v1.2.3