taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit e2fe2d6db16b422ee6d69ef03f1393e1f0f42749
parent 2c3456608e8e87a86a5b2f62301b4ea78a2cb00d
Author: Florian Dold <florian@dold.me>
Date:   Thu,  7 Oct 2021 12:01:40 +0200

add anastasis skeleton, put crypto in taler-util

Diffstat:
Apackages/anastasis-core/package.json | 30++++++++++++++++++++++++++++++
Apackages/anastasis-core/src/anastasis-data.ts | 742+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/anastasis-core/src/crypto.test.ts | 16++++++++++++++++
Apackages/anastasis-core/src/crypto.ts | 21+++++++++++++++++++++
Apackages/anastasis-core/src/index.ts | 15+++++++++++++++
Apackages/anastasis-core/tsconfig.json | 30++++++++++++++++++++++++++++++
Mpackages/idb-bridge/src/bridge-idb.ts | 6+++---
Mpackages/idb-bridge/src/idb-wpt-ported/wptsupport.ts | 2+-
Mpackages/taler-util/package.json | 17++++++++---------
Mpackages/taler-util/src/index.browser.ts | 40+++++++++++++++++++---------------------
Apackages/taler-util/src/index.node.ts | 23+++++++++++++++++++++++
Mpackages/taler-util/src/index.ts | 28+++++++++++++++++++++++++---
Apackages/taler-util/src/kdf.d.ts | 5+++++
Apackages/taler-util/src/kdf.js | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/taler-util/src/kdf.js.map | 2++
Rpackages/taler-wallet-core/src/crypto/primitives/kdf.ts -> packages/taler-util/src/kdf.ts | 0
Rpackages/taler-wallet-core/src/crypto/primitives/nacl-fast.ts -> packages/taler-util/src/nacl-fast.ts | 0
Apackages/taler-util/src/prng-browser.ts | 19+++++++++++++++++++
Apackages/taler-util/src/prng-node.ts | 30++++++++++++++++++++++++++++++
Rpackages/taler-wallet-core/src/crypto/primitives/sha256.ts -> packages/taler-util/src/sha256.ts | 0
Apackages/taler-util/src/talerCrypto.test.ts | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/taler-util/src/talerCrypto.ts | 452+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-cli/src/index.ts | 8++++----
Mpackages/taler-wallet-cli/src/integrationtests/harness.ts | 26+++++++++++++-------------
Mpackages/taler-wallet-cli/src/integrationtests/test-bank-api.ts | 2+-
Mpackages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts | 15+++++----------
Mpackages/taler-wallet-cli/src/lint.ts | 2+-
Dpackages/taler-wallet-core/src/crypto/talerCrypto-test.ts | 189-------------------------------------------------------------------------------
Dpackages/taler-wallet-core/src/crypto/talerCrypto.ts | 449-------------------------------------------------------------------------------
Mpackages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts | 6+++---
Mpackages/taler-wallet-core/src/headless/NodeHttpLib.ts | 5++---
Mpackages/taler-wallet-core/src/headless/helpers.ts | 2+-
Mpackages/taler-wallet-core/src/index.browser.ts | 56--------------------------------------------------------
Mpackages/taler-wallet-core/src/index.node.ts | 18------------------
Mpackages/taler-wallet-core/src/index.ts | 1-
Mpackages/taler-wallet-core/src/operations/backup/export.ts | 8+++-----
Mpackages/taler-wallet-core/src/operations/backup/index.ts | 6+++---
Mpackages/taler-wallet-core/src/operations/backup/state.ts | 2+-
Mpackages/taler-wallet-core/src/operations/deposits.ts | 4++--
Mpackages/taler-wallet-core/src/operations/exchanges.ts | 2+-
Mpackages/taler-wallet-core/src/operations/pay.ts | 2+-
Mpackages/taler-wallet-core/src/operations/recoup.ts | 2+-
Mpackages/taler-wallet-core/src/operations/refresh.ts | 2+-
Mpackages/taler-wallet-core/src/operations/reserves.ts | 4++--
Mpackages/taler-wallet-core/src/operations/tip.ts | 2+-
Mpackages/taler-wallet-core/src/util/contractTerms.ts | 4++--
Mpackages/taler-wallet-core/src/util/http.ts | 2+-
Mpackages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx | 280++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mpackages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx | 280++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mpnpm-lock.yaml | 25+++++++++++++++++++++++++
50 files changed, 2136 insertions(+), 1009 deletions(-)

diff --git a/packages/anastasis-core/package.json b/packages/anastasis-core/package.json @@ -0,0 +1,30 @@ +{ + "name": "anastasis-core", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "prepare": "tsc", + "compile": "tsc", + "pretty": "prettier --write src", + "test": "tsc && ava", + "coverage": "tsc && nyc ava", + "clean": "rimraf dist lib tsconfig.tsbuildinfo" + }, + "author": "Florian Dold <dold@taler.net>", + "license": "AGPL-3-or-later", + "type": "module", + "devDependencies": { + "ava": "^3.15.0", + "typescript": "^4.4.3" + }, + "dependencies": { + "@gnu-taler/taler-util": "workspace:^0.8.3", + "hash-wasm": "^4.9.0" + }, + "ava": { + "files": [ + "lib/**/*test.*" + ] + } +} diff --git a/packages/anastasis-core/src/anastasis-data.ts b/packages/anastasis-core/src/anastasis-data.ts @@ -0,0 +1,742 @@ +// This file is auto-generated, do not modify. +// Generated from v0.2.0-4-g61ea83c on Tue, 05 Oct 2021 10:40:32 +0200 +// To re-generate, run contrib/gen-ts.sh from the main anastasis code base. + +export const anastasisData = { + providersList: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + anastasis_provider: [ + { + url: "https://anastasis.demo.taler.net/", + currency: "KUDOS", + }, + { + url: "https://kudos.demo.anastasis.lu/", + currency: "KUDOS", + }, + { + url: "http://localhost:8086/", + currency: "TESTKUDOS", + }, + { + url: "http://localhost:8087/", + currency: "TESTKUDOS", + }, + { + url: "http://localhost:8088/", + currency: "TESTKUDOS", + }, + { + url: "http://localhost:8089/", + currency: "TESTKUDOS", + }, + ], + }, + countriesList: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + countries: [ + { + code: "al", + name: "Albania", + continent: "Europe", + name_i18n: { + de_DE: "Albanien", + en_UK: "Albania", + }, + currency: "ALL", + call_code: "+355", + }, + { + code: "be", + name: "Belgium", + continent: "Europe", + name_i18n: { + de_DE: "Belgien", + en_UK: "Belgium", + }, + currency: "EUR", + call_code: "+32", + }, + { + code: "ch", + name: "Switzerland", + continent: "Europe", + name_i18n: { + de_DE: "Schweiz", + de_CH: "Schwiiz", + fr_FR: "Suisse", + en_UK: "Swiss", + }, + currency: "CHF", + call_code: "+41", + }, + { + code: "cz", + name: "Czech Republic", + continent: "Europe", + name_i18n: { + en_UK: "Czech Republic", + }, + currency: "CZK", + call_code: "+420", + }, + { + code: "de", + name: "Germany", + continent: "Europe", + continent_i18n: { de_DE: "Europa" }, + name_i18n: { + de_DE: "Deutschland", + de_CH: "Deutschland", + fr_FR: "Allemagne", + en_UK: "Germany", + }, + currency: "EUR", + call_code: "+49", + }, + { + code: "dk", + name: "Denmark", + continent: "Europe", + continent_i18n: { de_DE: "Europa" }, + name_i18n: { + en_UK: "Denmark", + }, + currency: "DKK", + call_code: "+45", + }, + { + code: "es", + name: "Spain", + continent: "Europe", + continent_i18n: { es_ES: "Europa" }, + name_i18n: { + es_ES: "España", + }, + currency: "EUR", + call_code: "+44", + }, + { + code: "in", + name: "India", + continent: "India", + continent_i18n: { en_EN: "India" }, + name_i18n: { + de_DE: "Indien", + de_CH: "Indien", + fr_FR: "l'Inde", + en_UK: "India", + }, + currency: "INR", + call_code: "+91", + }, + { + code: "it", + name: "Italy", + continent: "Europe", + name_i18n: { + de_DE: "Italien", + en_UK: "Italy", + }, + currency: "EUR", + call_code: "+39", + }, + { + code: "jp", + name: "Japan", + continent: "Asia", + continent_i18n: { en_EN: "Japan" }, + name_i18n: { + de_DE: "Japan", + de_CH: "Japan", + en_UK: "Japan", + }, + currency: "JPY", + call_code: "+81", + }, + { + code: "sl", + name: "Slovakia", + continent: "Europe", + name_i18n: { + en_UK: "Slovakia", + }, + currency: "EUR", + call_code: "+421", + }, + { + code: "us", + name: "United States of America (USA)", + continent: "North America", + continent_i18n: { de_DE: "Nordamerika" }, + name_i18n: { + de_DE: "Vereinigte Staaten von Amerika (USA)", + de_CH: "Vereinigte Staaten von Amerika (USA)", + fr_FR: "États-Unis d'Amérique (USA)", + en_UK: "United States of America (USA)", + }, + currency: "USD", + call_code: "+1", + }, + { + code: "xx", + name: "Testland", + continent: "Testcontinent", + continent_i18n: { de_DE: "Testkontinent" }, + name_i18n: { + de_DE: "Testlandt", + de_CH: "Testlandi", + fr_FR: "Testpais", + en_UK: "Testland", + }, + currency: "TESTKUDOS", + call_code: "+00", + }, + { + code: "xy", + name: "Demoland", + continent: "Testcontinent", + continent_i18n: { de_DE: "Testkontinent" }, + name_i18n: { + de_DE: "Demolandt", + de_CH: "Demolandi", + fr_FR: "Demopais", + en_UK: "Demoland", + }, + currency: "KUDOS", + call_code: "+01", + }, + ], + }, + countryDetails: { + al: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "nid_number", + label: "Numri i Identitetit", + label_i18n: { + en: "Identity Number", + al: "Numri i Identitetit", + }, + widget: "anastasis_gtk_ia_nid_al", + uuid: "256e5d30-d65e-481b-9ac4-55f5ac03b24a", + "validation-regex": + "^[0-9A-T][0-9](((0|5)[0-9])|10|11|51|52)[0-9]{3}[A-W]$", + "validation-logic": "AL_NID_check", + }, + ], + }, + be: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "nrn_number", + label: "National Register Number", + label_i18n: { + en: "National Register Number", + }, + widget: "anastasis_gtk_ia_nid_be", + uuid: "0452f99a-06f7-48bd-8ac0-2e4ed9a24560", + "validation-regex": "^[0-9]{11}$", + "validation-logic": "BE_NRN_check", + }, + ], + }, + ch: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "ahv_number", + label: "AHV number", + label_i18n: { + de_DE: "AHV-Nummer", + de_CH: "AHV-Nummer", + }, + widget: "anastasis_gtk_ia_ahv", + uuid: "1da87570-ba16-4f62-8a7e-cbda92f51591", + "validation-regex": + "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$", + "validation-logic": "CH_AHV_check", + }, + ], + }, + cz: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "birth_number", + label: "Birth Number", + label_i18n: { + en: "Birth Number", + cz: "rodné číslo", + }, + widget: "anastasis_gtk_ia_birthnumber_cz", + uuid: "03e3a05b-1192-44f1-ac36-7425512eee1a", + "validation-regex": + "^[0-9]{2}(((0|2|5|7)[0-9])|10|11|31|32|51|52|81|82)/[0-9]{3}[0-9]?$", + "validation-logic": "CZ_BN_check", + }, + ], + }, + de: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "tax_number", + label: "Taxpayer identification number", + label_i18n: { + de_DE: "Steuerliche Identifikationsnummer", + en: "German taxpayer identification number", + }, + widget: "anastasis_gtk_ia_tax_de", + uuid: "dae48f85-e3ff-47a4-a4a3-ed981ed8c3c6", + "validation-regex": "^[0-9]{11}$", + "validation-logic": "DE_TIN_check", + }, + { + type: "string", + name: "social_security_number", + label: "Social security number", + label_i18n: { + de_DE: "Deutsche Sozialversicherungsnummer", + en: "German Social security number", + }, + widget: "anastasis_gtk_ia_ssn_de", + uuid: "d5e2aa79-1c88-4cf4-a4d2-252508b38e05", + "validation-regex": "^[0-9]{8}[[:upper:]][0-9]{3}$", + "validation-logic": "DE_SVN_check", + optional: true, + }, + ], + }, + dk: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "cpr_number", + label: "CPR-nummer", + label_i18n: { + en: "CPR Number", + dk: "CPR-nummer", + }, + widget: "anastasis_gtk_ia_cpr_dk", + uuid: "38f13a4d-4302-4ada-ada1-c3ff4a8ff689", + "validation-regex": + "^(0[1-9]|[1-2][0-9]|30|31)((0[1-9]|10|11|12))[0-9]{2}-[0-9A-Z]{4}$", + }, + ], + }, + es: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "tax_number", + label: "Tax number", + label_i18n: { + es_ES: "Número de Identificación Fiscal (DNI, NIE)", + }, + widget: "anastasis_gtk_ia_es_dni", + uuid: "ac8bd865-6be8-445c-b650-6a18eef16a49", + "validation-regex": "^[0-9MXYZ][0-9]{7}[TRWAGMYFPDXBNJZSQVHLCKE]$", + "validation-logic": "ES_DNI_check", + }, + { + type: "string", + name: "ssn_number", + label: "Social security number", + label_i18n: { + es_ES: "Número de Seguridad Social", + }, + widget: "anastasis_gtk_ia_es_ssn", + uuid: "22396a19-f3bb-497e-b63a-961fd639140e", + "validation-regex": "^[0-9]{11}$", + }, + ], + }, + in: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "aadhar_number", + label: "Aadhar number", + label_i18n: { + en: "Aadhar number", + }, + widget: "anastasis_gtk_ia_aadhar_in", + uuid: "55afe97a-98bc-48d1-bb37-a9658be3fdc9", + "validation-regex": "^[2-9]{1}[0-9]{3}\\s[0-9]{4}\\s[0-9]{4}$", + "validation-logic": "IN_AADHAR_check", + }, + ], + }, + it: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "fiscal_code", + label: "Codice fiscale", + label_i18n: { + it: "Codice fiscale", + en: "Fiscal code", + }, + widget: "anastasis_gtk_ia_cf_it", + uuid: "88f53c51-52ad-4d63-a163-ec042589f925", + "validation-regex": + "^[[:upper:]]{6}[0-9]{2}[A-EHLMPRT](([0-24-6][0-9])|(30|31|70|71))[A-MZ][0-9]{3}[A-Z]$", + "validation-logic": "IT_CF_check", + }, + ], + }, + jp: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "my_number", + label: "My number", + label_i18n: { + en: "My number", + jp: "マイナンバー", + }, + widget: "anastasis_gtk_ia_my_jp", + uuid: "90848f42-a83e-4226-8186-329696c14152", + "validation-regex": "^[0-9]{12}$", + }, + ], + }, + sk: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "birth_number", + label: "Birth Number", + label_i18n: { + en: "Birth Number", + sk: "rodné číslo", + }, + widget: "anastasis_gtk_ia_birthnumber_sk", + uuid: "1cd372fe-2cea-4928-9f29-66f2bdd8555c", + "validation-regex": + "^[0-9]{2}(((0|2|5|7)[0-9])|10|11|31|32|51|52|81|82)/[0-9]{3}[0-9]?$", + "validation-logic": "CZ_BN_check", + }, + ], + }, + us: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "birthplace", + label: "Birthplace", + widget: "anastasis_gtk_ia_birthplace", + uuid: "4c822e8e-89c6-11eb-95c4-8b077ad8489f", + }, + { + type: "string", + name: "social_security_number", + label: "Social security number", + label_i18n: { + en: "US Social security number", + }, + widget: "anastasis_gtk_ia_ssn_us", + uuid: "310a138c-b0b7-4985-b8b8-d00e765e9f9b", + "validation-regex": "^d{3}-d{2}-d{4}$", + }, + ], + }, + xx: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "sq_number", + label: "Square number", + widget: "anastasis_gtk_xx_square", + uuid: "ed790bca-89bf-11eb-96f2-233996cf644e", + "validation-regex": "^[0-9]+$", + "validation-logic": "XX_SQUARE_check", + }, + ], + }, + xy: { + license: "GPLv3+", + "SPDX-License-Identifier": "GPL3.0-or-later", + required_attributes: [ + { + type: "string", + name: "full_name", + label: "Full name", + widget: "anastasis_gtk_ia_full_name", + uuid: "9e8f463f-575f-42cb-85f3-759559997331", + }, + { + type: "date", + name: "birthdate", + label: "Birthdate", + widget: "anastasis_gtk_ia_birthdate", + uuid: "83d655c7-bdb6-484d-904e-80c1058c8854", + }, + { + type: "string", + name: "prime_number", + label: "Prime number", + widget: "anastasis_gtk_xx_prime", + uuid: "39190a95-cacb-4412-8bae-1f7da3f980b4", + "validation-regex": "^[0-9]+$", + "validation-logic": "XY_PRIME_check", + }, + ], + }, + }, +}; diff --git a/packages/anastasis-core/src/crypto.test.ts b/packages/anastasis-core/src/crypto.test.ts @@ -0,0 +1,16 @@ +import test from "ava"; + +// Vector generated with taler-anastasis-tvg +const userIdVector = { + input_id_data: { + name: "Fleabag", + ssn: "AB123", + }, + input_server_salt: "FZ48EFS7WS3R2ZR4V53A3GFFY4", + output_id: + "YS45R6CGJV84K1NN7T14ZBCPVTZ6H15XJSM1FV0R748MHPV82SM0126EBZKBAAGCR34Q9AFKPEW1HRT2Q9GQ5JRA3642AB571DKZS18", +}; + +test("user ID derivation", async (t) => { + t.fail(); +}); diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts @@ -0,0 +1,21 @@ +import { argon2id } from "hash-wasm"; + +async function userIdentifierDerive( + idData: any, + serverSalt: string, +): Promise<string> { + throw Error("not implemented"); +} + +// interface Keypair { +// pub: string; +// priv: string; +// } + +// async function accountKeypairDerive(): Promise<Keypair> {} + +// async function secureAnswerHash( +// answer: string, +// truthUuid: string, +// questionSalt: string, +// ): Promise<string> {} diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts @@ -0,0 +1,14 @@ +import { md5, sha1, sha512, sha3 } from 'hash-wasm'; + +async function run() { + console.log('MD5:', await md5('demo')); + + const int8Buffer = new Uint8Array([0, 1, 2, 3]); + console.log('SHA1:', await sha1(int8Buffer)); + console.log('SHA512:', await sha512(int8Buffer)); + + const int32Buffer = new Uint32Array([1056, 641]); + console.log('SHA3-256:', await sha3(int32Buffer, 256)); +} + +run(); +\ No newline at end of file diff --git a/packages/anastasis-core/tsconfig.json b/packages/anastasis-core/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "composite": true, + "target": "ES2018", + "module": "ESNext", + "moduleResolution": "node", + "sourceMap": true, + "lib": ["es6"], + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "strict": true, + "strictPropertyInitialization": false, + "outDir": "lib", + "noImplicitAny": true, + "noImplicitThis": true, + "incremental": true, + "esModuleInterop": true, + "importHelpers": true, + "rootDir": "src", + "baseUrl": "./src", + "typeRoots": ["./node_modules/@types"] + }, + "include": ["src/**/*"], + "references": [ + { + "path": "../taler-util/" + } + ] +} diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts @@ -805,7 +805,7 @@ export class BridgeIDBFactory { oldVersion, }); request.dispatchEvent(event2); - } catch (err) { + } catch (err: any) { request.error = new Error(); request.error.name = err.name; request.readyState = "done"; @@ -846,7 +846,7 @@ export class BridgeIDBFactory { if (BridgeIDBFactory.enableTracing) { console.log("TRACE: connected!"); } - } catch (err) { + } catch (err: any) { if (BridgeIDBFactory.enableTracing) { console.log( "TRACE: caught exception while trying to connect with backend", @@ -2704,7 +2704,7 @@ export class BridgeIDBTransaction this._active = false; throw err; } - } catch (err) { + } catch (err: any) { if (BridgeIDBFactory.enableTracing) { console.log("TRACING: error during operation: ", err); } diff --git a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts @@ -542,7 +542,7 @@ export function is_transaction_active( e.stopPropagation(); }; return true; - } catch (ex) { + } catch (ex: any) { console.log(ex.stack); t.deepEqual( ex.name, diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json @@ -3,19 +3,17 @@ "version": "0.8.3", "description": "Generic helper functionality for GNU Taler", "exports": { - ".": "./lib/index.js" - }, - "module": "./lib/index.js", - "main": "./lib/index.js", - "browser": { - "./lib/index.js": "./lib/index.browser.js" + ".": "./lib/index.node.js" }, + "module": "./lib/index.node.js", + "main": "./lib/index.node.js", + "browser": "./lib/index.browser.js", "type": "module", - "types": "./lib/index.d.ts", + "types": "./lib/index.node.d.ts", "typesVersions": { "*": { - "lib/index.d.ts": [ - "lib/index.d.ts" + "lib/index.node.d.ts": [ + "lib/index.node.d.ts" ], "src/*": [], "*": [] @@ -40,6 +38,7 @@ "typescript": "^4.2.3" }, "dependencies": { + "big-integer": "^1.6.48", "jed": "^1.1.1", "tslib": "^2.1.0" }, diff --git a/packages/taler-util/src/index.browser.ts b/packages/taler-util/src/index.browser.ts @@ -1,22 +1,21 @@ -import { TalerErrorCode } from "./taler-error-codes.js"; +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. -export { TalerErrorCode }; + 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. -export * from "./amounts.js"; -export * from "./backupTypes.js"; -export * from "./codec.js"; -export * from "./helpers.js"; -export * from "./libtool-version.js"; -export * from "./notifications.js"; -export * from "./payto.js"; -export * from "./ReserveStatus.js"; -export * from "./ReserveTransaction.js"; -export * from "./talerTypes.js"; -export * from "./taleruri.js"; -export * from "./time.js"; -export * from "./transactionsTypes.js"; -export * from "./walletTypes.js"; -export * from "./i18n.js"; -export * from "./logging.js"; -export * from "./url.js"; -export { fnutil } from "./fnutils.js"; -\ No newline at end of file + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +// Entry point for the browser. + +import { loadBrowserPrng } from "./prng-browser.js"; +loadBrowserPrng(); +export * from "./index.js"; diff --git a/packages/taler-util/src/index.node.ts b/packages/taler-util/src/index.node.ts @@ -0,0 +1,23 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +// Entry point for nodejs. + +import { initNodePrng } from "./prng-node.js"; +initNodePrng(); +export * from "./index.js"; +export * from "./talerconfig.js"; +export * from "./globbing/minimatch.js"; diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts @@ -1,3 +1,25 @@ -export * from "./index.browser.js"; -export * from "./talerconfig.js"; -export * from "./globbing/minimatch.js"; +import { TalerErrorCode } from "./taler-error-codes.js"; + +export { TalerErrorCode }; + +export * from "./amounts.js"; +export * from "./backupTypes.js"; +export * from "./codec.js"; +export * from "./helpers.js"; +export * from "./libtool-version.js"; +export * from "./notifications.js"; +export * from "./payto.js"; +export * from "./ReserveStatus.js"; +export * from "./ReserveTransaction.js"; +export * from "./talerTypes.js"; +export * from "./taleruri.js"; +export * from "./time.js"; +export * from "./transactionsTypes.js"; +export * from "./walletTypes.js"; +export * from "./i18n.js"; +export * from "./logging.js"; +export * from "./url.js"; +export { fnutil } from "./fnutils.js"; +export * from "./kdf.js"; +export * from "./talerCrypto.js"; +export { randomBytes, secretbox, secretbox_open } from "./nacl-fast.js"; diff --git a/packages/taler-util/src/kdf.d.ts b/packages/taler-util/src/kdf.d.ts @@ -0,0 +1,5 @@ +export declare function sha512(data: Uint8Array): Uint8Array; +export declare function hmac(digest: (d: Uint8Array) => Uint8Array, blockSize: number, key: Uint8Array, message: Uint8Array): Uint8Array; +export declare function hmacSha512(key: Uint8Array, message: Uint8Array): Uint8Array; +export declare function hmacSha256(key: Uint8Array, message: Uint8Array): Uint8Array; +export declare function kdf(outputLength: number, ikm: Uint8Array, salt: Uint8Array, info: Uint8Array): Uint8Array; diff --git a/packages/taler-util/src/kdf.js b/packages/taler-util/src/kdf.js @@ -0,0 +1,76 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +import * as nacl from "./nacl-fast.js"; +import { sha256 } from "./sha256.js"; +export function sha512(data) { + return nacl.hash(data); +} +export function hmac(digest, blockSize, key, message) { + if (key.byteLength > blockSize) { + key = digest(key); + } + if (key.byteLength < blockSize) { + const k = key; + key = new Uint8Array(blockSize); + key.set(k, 0); + } + const okp = new Uint8Array(blockSize); + const ikp = new Uint8Array(blockSize); + for (let i = 0; i < blockSize; i++) { + ikp[i] = key[i] ^ 0x36; + okp[i] = key[i] ^ 0x5c; + } + const b1 = new Uint8Array(blockSize + message.byteLength); + b1.set(ikp, 0); + b1.set(message, blockSize); + const h0 = digest(b1); + const b2 = new Uint8Array(blockSize + h0.length); + b2.set(okp, 0); + b2.set(h0, blockSize); + return digest(b2); +} +export function hmacSha512(key, message) { + return hmac(sha512, 128, key, message); +} +export function hmacSha256(key, message) { + return hmac(sha256, 64, key, message); +} +export function kdf(outputLength, ikm, salt, info) { + // extract + const prk = hmacSha512(salt, ikm); + // expand + const N = Math.ceil(outputLength / 32); + const output = new Uint8Array(N * 32); + for (let i = 0; i < N; i++) { + let buf; + if (i == 0) { + buf = new Uint8Array(info.byteLength + 1); + buf.set(info, 0); + } + else { + buf = new Uint8Array(info.byteLength + 1 + 32); + for (let j = 0; j < 32; j++) { + buf[j] = output[(i - 1) * 32 + j]; + } + buf.set(info, 32); + } + buf[buf.length - 1] = i + 1; + const chunk = hmacSha256(prk, buf); + output.set(chunk, i * 32); + } + return output.slice(0, outputLength); +} +//# sourceMappingURL=kdf.js.map +\ No newline at end of file diff --git a/packages/taler-util/src/kdf.js.map b/packages/taler-util/src/kdf.js.map @@ -0,0 +1 @@ +{"version":3,"file":"kdf.js","sourceRoot":"","sources":["kdf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,IAAI,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,UAAU,MAAM,CAAC,IAAgB;IACrC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,IAAI,CAClB,MAAqC,EACrC,SAAiB,EACjB,GAAe,EACf,OAAmB;IAEnB,IAAI,GAAG,CAAC,UAAU,GAAG,SAAS,EAAE;QAC9B,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;KACnB;IACD,IAAI,GAAG,CAAC,UAAU,GAAG,SAAS,EAAE;QAC9B,MAAM,CAAC,GAAG,GAAG,CAAC;QACd,GAAG,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;QAChC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;KACf;IACD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;QAClC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACvB,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;KACxB;IACD,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1D,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACf,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IACtB,MAAM,EAAE,GAAG,IAAI,UAAU,CAAC,SAAS,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IACjD,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACf,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IACtB,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAe,EAAE,OAAmB;IAC7D,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAe,EAAE,OAAmB;IAC7D,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,GAAG,CACjB,YAAoB,EACpB,GAAe,EACf,IAAgB,EAChB,IAAgB;IAEhB,UAAU;IACV,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAElC,SAAS;IACT,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,GAAG,CAAC;QACR,IAAI,CAAC,IAAI,CAAC,EAAE;YACV,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;YAC1C,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;SAClB;aAAM;YACL,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;gBAC3B,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;aACnC;YACD,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;SACnB;QACD,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;KAC3B;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;AACvC,CAAC"} +\ No newline at end of file diff --git a/packages/taler-wallet-core/src/crypto/primitives/kdf.ts b/packages/taler-util/src/kdf.ts diff --git a/packages/taler-wallet-core/src/crypto/primitives/nacl-fast.ts b/packages/taler-util/src/nacl-fast.ts diff --git a/packages/taler-util/src/prng-browser.ts b/packages/taler-util/src/prng-browser.ts @@ -0,0 +1,19 @@ +import { setPRNG } from "./nacl-fast.js"; + +export function loadBrowserPrng() { + // Initialize PRNG if environment provides CSPRNG. + // If not, methods calling randombytes will throw. + // @ts-ignore-error + const cr = typeof self !== "undefined" ? self.crypto || self.msCrypto : null; + + const QUOTA = 65536; + setPRNG(function (x: Uint8Array, n: number) { + let i; + const v = new Uint8Array(n); + for (i = 0; i < n; i += QUOTA) { + cr.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA))); + } + for (i = 0; i < n; i++) x[i] = v[i]; + for (i = 0; i < v.length; i++) v[i] = 0; + }); +} diff --git a/packages/taler-util/src/prng-node.ts b/packages/taler-util/src/prng-node.ts @@ -0,0 +1,30 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { setPRNG } from "./nacl-fast.js"; +import cr from "crypto"; + +export function initNodePrng() { + // Initialize PRNG if environment provides CSPRNG. + // If not, methods calling randombytes will throw. + if (cr && cr.randomBytes) { + setPRNG(function (x: Uint8Array, n: number) { + const v = cr.randomBytes(n); + for (let i = 0; i < n; i++) x[i] = v[i]; + for (let i = 0; i < v.length; i++) v[i] = 0; + }); + } +} diff --git a/packages/taler-wallet-core/src/crypto/primitives/sha256.ts b/packages/taler-util/src/sha256.ts diff --git a/packages/taler-util/src/talerCrypto.test.ts b/packages/taler-util/src/talerCrypto.test.ts @@ -0,0 +1,186 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports + */ +import test from "ava"; +import { + encodeCrock, + decodeCrock, + ecdheGetPublic, + eddsaGetPublic, + keyExchangeEddsaEcdhe, + keyExchangeEcdheEddsa, + stringToBytes, + bytesToString, +} from "./talerCrypto.js"; +import { sha512, kdf } from "./kdf.js"; +import * as nacl from "./nacl-fast.js"; + +test("encoding", (t) => { + const s = "Hello, World"; + const encStr = encodeCrock(stringToBytes(s)); + const outBuf = decodeCrock(encStr); + const sOut = bytesToString(outBuf); + t.deepEqual(s, sOut); +}); + +test("taler-exchange-tvg hash code", (t) => { + const input = "91JPRV3F5GG4EKJN41A62V35E8"; + const output = + "CW96WR74JS8T53EC8GKSGD49QKH4ZNFTZXDAWMMV5GJ1E4BM6B8GPN5NVHDJ8ZVXNCW7Q4WBYCV61HCA3PZC2YJD850DT29RHHN7ESR"; + + const myOutput = encodeCrock(sha512(decodeCrock(input))); + + t.deepEqual(myOutput, output); +}); + +test("taler-exchange-tvg ecdhe key", (t) => { + const priv1 = "X4T4N0M8PVQXQEBW2BA7049KFSM7J437NSDFC6GDNM3N5J9367A0"; + const pub1 = "M997P494MS6A95G1P0QYWW2VNPSHSX5Q6JBY5B9YMNYWP0B50X3G"; + const priv2 = "14A0MMQ64DCV8HE0CS3WBC9DHFJAHXRGV7NEARFJPC5R5E1697E0"; + const skm = + "NXRY2YCY7H9B6KM928ZD55WG964G59YR0CPX041DYXKBZZ85SAWNPQ8B30QRM5FMHYCXJAN0EAADJYWEF1X3PAC2AJN28626TR5A6AR"; + + const myPub1 = nacl.scalarMult_base(decodeCrock(priv1)); + t.deepEqual(encodeCrock(myPub1), pub1); + + const mySkm = nacl.hash( + nacl.scalarMult(decodeCrock(priv2), decodeCrock(pub1)), + ); + t.deepEqual(encodeCrock(mySkm), skm); +}); + +test("taler-exchange-tvg eddsa key", (t) => { + const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40"; + const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0"; + + const pair = nacl.sign_keyPair_fromSeed(decodeCrock(priv)); + t.deepEqual(encodeCrock(pair.publicKey), pub); +}); + +test("taler-exchange-tvg kdf", (t) => { + const salt = "94KPT83PCNS7J83KC5P78Y8"; + const ikm = "94KPT83MD1JJ0WV5CDS6AX10D5Q70XBM41NPAY90DNGQ8SBJD5GPR"; + const ctx = + "94KPT83141HPYVKMCNW78833D1TPWTSC41GPRWVF41NPWVVQDRG62WS04XMPWSKF4WG6JVH0EHM6A82J8S1G"; + const outLen = 64; + const out = + "GTMR4QT05Z9WF5HKVG0WK9RPXGHSMHJNW377G9GJXCA8B0FEKPF4D27RJMSJZYWSQNTBJ5EYVV7ZW18B48Z0JVJJ80RHB706Y96Q358"; + + const myOut = kdf( + outLen, + decodeCrock(ikm), + decodeCrock(salt), + decodeCrock(ctx), + ); + + t.deepEqual(encodeCrock(myOut), out); +}); + +test("taler-exchange-tvg eddsa_ecdh", (t) => { + const priv_ecdhe = "4AFZWMSGTVCHZPQ0R81NWXDCK4N58G7SDBBE5KXE080Y50370JJG"; + const pub_ecdhe = "FXFN5GPAFTKVPWJDPVXQ87167S8T82T5ZV8CDYC0NH2AE14X0M30"; + const priv_eddsa = "1KG54M8T3X8BSFSZXCR3SQBSR7Y9P53NX61M864S7TEVMJ2XVPF0"; + const pub_eddsa = "7BXWKG6N224C57RTDV8XEAHR108HG78NMA995BE8QAT5GC1S7E80"; + const key_material = + "PKZ42Z56SVK2796HG1QYBRJ6ZQM2T9QGA3JA4AAZ8G7CWK9FPX175Q9JE5P0ZAX3HWWPHAQV4DPCK10R9X3SAXHRV0WF06BHEC2ZTKR"; + + const myEcdhePub = ecdheGetPublic(decodeCrock(priv_ecdhe)); + t.deepEqual(encodeCrock(myEcdhePub), pub_ecdhe); + + const myEddsaPub = eddsaGetPublic(decodeCrock(priv_eddsa)); + t.deepEqual(encodeCrock(myEddsaPub), pub_eddsa); + + const myKm1 = keyExchangeEddsaEcdhe( + decodeCrock(priv_eddsa), + decodeCrock(pub_ecdhe), + ); + t.deepEqual(encodeCrock(myKm1), key_material); + + const myKm2 = keyExchangeEcdheEddsa( + decodeCrock(priv_ecdhe), + decodeCrock(pub_eddsa), + ); + t.deepEqual(encodeCrock(myKm2), key_material); +}); + +test("incremental hashing #1", (t) => { + const n = 1024; + const d = nacl.randomBytes(n); + + const h1 = nacl.hash(d); + const h2 = new nacl.HashState().update(d).finish(); + + const s = new nacl.HashState(); + for (let i = 0; i < n; i++) { + const b = new Uint8Array(1); + b[0] = d[i]; + s.update(b); + } + + const h3 = s.finish(); + + t.deepEqual(encodeCrock(h1), encodeCrock(h2)); + t.deepEqual(encodeCrock(h1), encodeCrock(h3)); +}); + +test("incremental hashing #2", (t) => { + const n = 10; + const d = nacl.randomBytes(n); + + const h1 = nacl.hash(d); + const h2 = new nacl.HashState().update(d).finish(); + const s = new nacl.HashState(); + for (let i = 0; i < n; i++) { + const b = new Uint8Array(1); + b[0] = d[i]; + s.update(b); + } + + const h3 = s.finish(); + + t.deepEqual(encodeCrock(h1), encodeCrock(h3)); + t.deepEqual(encodeCrock(h1), encodeCrock(h2)); +}); + +test("taler-exchange-tvg eddsa_ecdh #2", (t) => { + const priv_ecdhe = "W5FH9CFS3YPGSCV200GE8TH6MAACPKKGEG2A5JTFSD1HZ5RYT7Q0"; + const pub_ecdhe = "FER9CRS2T8783TAANPZ134R704773XT0ZT1XPFXZJ9D4QX67ZN00"; + const priv_eddsa = "MSZ1TBKC6YQ19ZFP3NTJVKWNVGFP35BBRW8FTAQJ9Z2B96VC9P4G"; + const pub_eddsa = "Y7MKG85PBT8ZEGHF08JBVZXEV70TS0PY5Y2CMEN1WXEDN63KP1A0"; + const key_material = + "G6RA58N61K7MT3WA13Q7VRTE1FQS6H43RX9HK8Z5TGAB61601GEGX51JRHHQMNKNM2R9AVC1STSGQDRHGKWVYP584YGBCTVMMJYQF30"; + + const myEcdhePub = ecdheGetPublic(decodeCrock(priv_ecdhe)); + t.deepEqual(encodeCrock(myEcdhePub), pub_ecdhe); + + const myEddsaPub = eddsaGetPublic(decodeCrock(priv_eddsa)); + t.deepEqual(encodeCrock(myEddsaPub), pub_eddsa); + + const myKm1 = keyExchangeEddsaEcdhe( + decodeCrock(priv_eddsa), + decodeCrock(pub_ecdhe), + ); + t.deepEqual(encodeCrock(myKm1), key_material); + + const myKm2 = keyExchangeEcdheEddsa( + decodeCrock(priv_ecdhe), + decodeCrock(pub_eddsa), + ); + t.deepEqual(encodeCrock(myKm2), key_material); +}); diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts @@ -0,0 +1,452 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Native implementation of GNU Taler crypto. + */ + +/** + * Imports. + */ +import * as nacl from "./nacl-fast.js"; +import { kdf } from "./kdf.js"; +import bigint from "big-integer"; +import { initNodePrng } from "./prng-node.js"; + +initNodePrng(); + +export function getRandomBytes(n: number): Uint8Array { + return nacl.randomBytes(n); +} + +const encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; + +class EncodingError extends Error { + constructor() { + super("Encoding error"); + Object.setPrototypeOf(this, EncodingError.prototype); + } +} + +function getValue(chr: string): number { + let a = chr; + switch (chr) { + case "O": + case "o": + a = "0;"; + break; + case "i": + case "I": + case "l": + case "L": + a = "1"; + break; + case "u": + case "U": + a = "V"; + } + + if (a >= "0" && a <= "9") { + return a.charCodeAt(0) - "0".charCodeAt(0); + } + + if (a >= "a" && a <= "z") a = a.toUpperCase(); + let dec = 0; + if (a >= "A" && a <= "Z") { + if ("I" < a) dec++; + if ("L" < a) dec++; + if ("O" < a) dec++; + if ("U" < a) dec++; + return a.charCodeAt(0) - "A".charCodeAt(0) + 10 - dec; + } + throw new EncodingError(); +} + +export function encodeCrock(data: ArrayBuffer): string { + const dataBytes = new Uint8Array(data); + let sb = ""; + const size = data.byteLength; + let bitBuf = 0; + let numBits = 0; + let pos = 0; + while (pos < size || numBits > 0) { + if (pos < size && numBits < 5) { + const d = dataBytes[pos++]; + bitBuf = (bitBuf << 8) | d; + numBits += 8; + } + if (numBits < 5) { + // zero-padding + bitBuf = bitBuf << (5 - numBits); + numBits = 5; + } + const v = (bitBuf >>> (numBits - 5)) & 31; + sb += encTable[v]; + numBits -= 5; + } + return sb; +} + +export function decodeCrock(encoded: string): Uint8Array { + const size = encoded.length; + let bitpos = 0; + let bitbuf = 0; + let readPosition = 0; + const outLen = Math.floor((size * 5) / 8); + const out = new Uint8Array(outLen); + let outPos = 0; + + while (readPosition < size || bitpos > 0) { + if (readPosition < size) { + const v = getValue(encoded[readPosition++]); + bitbuf = (bitbuf << 5) | v; + bitpos += 5; + } + while (bitpos >= 8) { + const d = (bitbuf >>> (bitpos - 8)) & 0xff; + out[outPos++] = d; + bitpos -= 8; + } + if (readPosition == size && bitpos > 0) { + bitbuf = (bitbuf << (8 - bitpos)) & 0xff; + bitpos = bitbuf == 0 ? 0 : 8; + } + } + return out; +} + +export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array { + const pair = nacl.sign_keyPair_fromSeed(eddsaPriv); + return pair.publicKey; +} + +export function ecdheGetPublic(ecdhePriv: Uint8Array): Uint8Array { + return nacl.scalarMult_base(ecdhePriv); +} + +export function keyExchangeEddsaEcdhe( + eddsaPriv: Uint8Array, + ecdhePub: Uint8Array, +): Uint8Array { + const ph = nacl.hash(eddsaPriv); + const a = new Uint8Array(32); + for (let i = 0; i < 32; i++) { + a[i] = ph[i]; + } + const x = nacl.scalarMult(a, ecdhePub); + return nacl.hash(x); +} + +export function keyExchangeEcdheEddsa( + ecdhePriv: Uint8Array, + eddsaPub: Uint8Array, +): Uint8Array { + const curve25519Pub = nacl.sign_ed25519_pk_to_curve25519(eddsaPub); + const x = nacl.scalarMult(ecdhePriv, curve25519Pub); + return nacl.hash(x); +} + +interface RsaPub { + N: bigint.BigInteger; + e: bigint.BigInteger; +} + +interface RsaBlindingKey { + r: bigint.BigInteger; +} + +/** + * KDF modulo a big integer. + */ +function kdfMod( + n: bigint.BigInteger, + ikm: Uint8Array, + salt: Uint8Array, + info: Uint8Array, +): bigint.BigInteger { + const nbits = n.bitLength().toJSNumber(); + const buflen = Math.floor((nbits - 1) / 8 + 1); + const mask = (1 << (8 - (buflen * 8 - nbits))) - 1; + let counter = 0; + while (true) { + const ctx = new Uint8Array(info.byteLength + 2); + ctx.set(info, 0); + ctx[ctx.length - 2] = (counter >>> 8) & 0xff; + ctx[ctx.length - 1] = counter & 0xff; + const buf = kdf(buflen, ikm, salt, ctx); + const arr = Array.from(buf); + arr[0] = arr[0] & mask; + const r = bigint.fromArray(arr, 256, false); + if (r.lt(n)) { + return r; + } + counter++; + } +} + +// Newer versions of node have TextEncoder and TextDecoder as a global, +// just like modern browsers. +// In older versions of node or environments that do not have these +// globals, they must be polyfilled (by adding them to globa/globalThis) +// before stringToBytes or bytesToString is called the first time. + +let encoder: any; +let decoder: any; + +export function stringToBytes(s: string): Uint8Array { + if (!encoder) { + // @ts-ignore + encoder = new TextEncoder(); + } + return encoder.encode(s); +} + +export function bytesToString(b: Uint8Array): string { + if (!decoder) { + // @ts-ignore + decoder = new TextDecoder(); + } + return decoder.decode(b); +} + +function loadBigInt(arr: Uint8Array): bigint.BigInteger { + return bigint.fromArray(Array.from(arr), 256, false); +} + +function rsaBlindingKeyDerive( + rsaPub: RsaPub, + bks: Uint8Array, +): bigint.BigInteger { + const salt = stringToBytes("Blinding KDF extractor HMAC key"); + const info = stringToBytes("Blinding KDF"); + return kdfMod(rsaPub.N, bks, salt, info); +} + +/* + * Test for malicious RSA key. + * + * Assuming n is an RSA modulous and r is generated using a call to + * GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a + * malicious RSA key designed to deanomize the user. + * + * @param r KDF result + * @param n RSA modulus of the public key + */ +function rsaGcdValidate(r: bigint.BigInteger, n: bigint.BigInteger): void { + const t = bigint.gcd(r, n); + if (!t.equals(bigint.one)) { + throw Error("malicious RSA public key"); + } +} + +function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger { + const info = stringToBytes("RSA-FDA FTpsW!"); + const salt = rsaPubEncode(rsaPub); + const r = kdfMod(rsaPub.N, hm, salt, info); + rsaGcdValidate(r, rsaPub.N); + return r; +} + +function rsaPubDecode(rsaPub: Uint8Array): RsaPub { + const modulusLength = (rsaPub[0] << 8) | rsaPub[1]; + const exponentLength = (rsaPub[2] << 8) | rsaPub[3]; + if (4 + exponentLength + modulusLength != rsaPub.length) { + throw Error("invalid RSA public key (format wrong)"); + } + const modulus = rsaPub.slice(4, 4 + modulusLength); + const exponent = rsaPub.slice( + 4 + modulusLength, + 4 + modulusLength + exponentLength, + ); + const res = { + N: loadBigInt(modulus), + e: loadBigInt(exponent), + }; + return res; +} + +function rsaPubEncode(rsaPub: RsaPub): Uint8Array { + const mb = rsaPub.N.toArray(256).value; + const eb = rsaPub.e.toArray(256).value; + const out = new Uint8Array(4 + mb.length + eb.length); + out[0] = (mb.length >>> 8) & 0xff; + out[1] = mb.length & 0xff; + out[2] = (eb.length >>> 8) & 0xff; + out[3] = eb.length & 0xff; + out.set(mb, 4); + out.set(eb, 4 + mb.length); + return out; +} + +export function rsaBlind( + hm: Uint8Array, + bks: Uint8Array, + rsaPubEnc: Uint8Array, +): Uint8Array { + const rsaPub = rsaPubDecode(rsaPubEnc); + const data = rsaFullDomainHash(hm, rsaPub); + const r = rsaBlindingKeyDerive(rsaPub, bks); + const r_e = r.modPow(rsaPub.e, rsaPub.N); + const bm = r_e.multiply(data).mod(rsaPub.N); + return new Uint8Array(bm.toArray(256).value); +} + +export function rsaUnblind( + sig: Uint8Array, + rsaPubEnc: Uint8Array, + bks: Uint8Array, +): Uint8Array { + const rsaPub = rsaPubDecode(rsaPubEnc); + const blinded_s = loadBigInt(sig); + const r = rsaBlindingKeyDerive(rsaPub, bks); + const r_inv = r.modInv(rsaPub.N); + const s = blinded_s.multiply(r_inv).mod(rsaPub.N); + return new Uint8Array(s.toArray(256).value); +} + +export function rsaVerify( + hm: Uint8Array, + rsaSig: Uint8Array, + rsaPubEnc: Uint8Array, +): boolean { + const rsaPub = rsaPubDecode(rsaPubEnc); + const d = rsaFullDomainHash(hm, rsaPub); + const sig = loadBigInt(rsaSig); + const sig_e = sig.modPow(rsaPub.e, rsaPub.N); + return sig_e.equals(d); +} + +export interface EddsaKeyPair { + eddsaPub: Uint8Array; + eddsaPriv: Uint8Array; +} + +export interface EcdheKeyPair { + ecdhePub: Uint8Array; + ecdhePriv: Uint8Array; +} + +export function createEddsaKeyPair(): EddsaKeyPair { + const eddsaPriv = nacl.randomBytes(32); + const eddsaPub = eddsaGetPublic(eddsaPriv); + return { eddsaPriv, eddsaPub }; +} + +export function createEcdheKeyPair(): EcdheKeyPair { + const ecdhePriv = nacl.randomBytes(32); + const ecdhePub = ecdheGetPublic(ecdhePriv); + return { ecdhePriv, ecdhePub }; +} + +export function hash(d: Uint8Array): Uint8Array { + return nacl.hash(d); +} + +export function eddsaSign(msg: Uint8Array, eddsaPriv: Uint8Array): Uint8Array { + const pair = nacl.sign_keyPair_fromSeed(eddsaPriv); + return nacl.sign_detached(msg, pair.secretKey); +} + +export function eddsaVerify( + msg: Uint8Array, + sig: Uint8Array, + eddsaPub: Uint8Array, +): boolean { + return nacl.sign_detached_verify(msg, sig, eddsaPub); +} + +export function createHashContext(): nacl.HashState { + return new nacl.HashState(); +} + +export interface FreshCoin { + coinPub: Uint8Array; + coinPriv: Uint8Array; + bks: Uint8Array; +} + +export function setupRefreshPlanchet( + secretSeed: Uint8Array, + coinNumber: number, +): FreshCoin { + const info = stringToBytes("taler-coin-derivation"); + const saltArrBuf = new ArrayBuffer(4); + const salt = new Uint8Array(saltArrBuf); + const saltDataView = new DataView(saltArrBuf); + saltDataView.setUint32(0, coinNumber); + const out = kdf(64, secretSeed, salt, info); + const coinPriv = out.slice(0, 32); + const bks = out.slice(32, 64); + return { + bks, + coinPriv, + coinPub: eddsaGetPublic(coinPriv), + }; +} + +export function setupWithdrawPlanchet( + secretSeed: Uint8Array, + coinNumber: number, +): FreshCoin { + const info = stringToBytes("taler-withdrawal-coin-derivation"); + const saltArrBuf = new ArrayBuffer(4); + const salt = new Uint8Array(saltArrBuf); + const saltDataView = new DataView(saltArrBuf); + saltDataView.setUint32(0, coinNumber); + const out = kdf(64, secretSeed, salt, info); + const coinPriv = out.slice(0, 32); + const bks = out.slice(32, 64); + return { + bks, + coinPriv, + coinPub: eddsaGetPublic(coinPriv), + }; +} + +export function setupTipPlanchet( + secretSeed: Uint8Array, + coinNumber: number, +): FreshCoin { + const info = stringToBytes("taler-tip-coin-derivation"); + const saltArrBuf = new ArrayBuffer(4); + const salt = new Uint8Array(saltArrBuf); + const saltDataView = new DataView(saltArrBuf); + saltDataView.setUint32(0, coinNumber); + const out = kdf(64, secretSeed, salt, info); + const coinPriv = out.slice(0, 32); + const bks = out.slice(32, 64); + return { + bks, + coinPriv, + coinPub: eddsaGetPublic(coinPriv), + }; +} + +export function setupRefreshTransferPub( + secretSeed: Uint8Array, + transferPubIndex: number, +): EcdheKeyPair { + const info = stringToBytes("taler-transfer-pub-derivation"); + const saltArrBuf = new ArrayBuffer(4); + const salt = new Uint8Array(saltArrBuf); + const saltDataView = new DataView(saltArrBuf); + saltDataView.setUint32(0, transferPubIndex); + const out = kdf(32, secretSeed, salt, info); + return { + ecdhePriv: out, + ecdhePub: ecdheGetPublic(out), + }; +} diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts @@ -40,14 +40,14 @@ import { codecForString, Logger, Configuration, + decodeCrock, + rsaBlind, } from "@gnu-taler/taler-util"; import { NodeHttpLib, getDefaultNodeWallet, OperationFailedAndReportedError, OperationFailedError, - decodeCrock, - rsaBlind, NodeThreadCryptoWorkerFactory, CryptoApi, walletCoreDebugFlags, @@ -810,7 +810,7 @@ advancedCli coinPubList = coinPubListCodec.decode( JSON.parse(args.suspendCoins.coinPubSpec), ); - } catch (e) { + } catch (e: any) { console.log("could not parse coin list:", e.message); process.exit(1); } @@ -835,7 +835,7 @@ advancedCli coinPubList = coinPubListCodec.decode( JSON.parse(args.unsuspendCoins.coinPubSpec), ); - } catch (e) { + } catch (e: any) { console.log("could not parse coin list:", e.message); process.exit(1); } diff --git a/packages/taler-wallet-cli/src/integrationtests/harness.ts b/packages/taler-wallet-cli/src/integrationtests/harness.ts @@ -44,11 +44,6 @@ import { MerchantInstancesResponse, } from "./merchantApiTypes"; import { - createEddsaKeyPair, - eddsaGetPublic, - EddsaKeyPair, - encodeCrock, - getRandomBytes, openPromise, OperationFailedError, WalletCoreApiClient, @@ -64,6 +59,11 @@ import { Duration, parsePaytoUri, CoreApiResponse, + createEddsaKeyPair, + eddsaGetPublic, + EddsaKeyPair, + encodeCrock, + getRandomBytes, } from "@gnu-taler/taler-util"; import { CoinConfig } from "./denomStructures.js"; @@ -441,7 +441,7 @@ export async function pingProc( const resp = await axios.get(url); console.log(`service ${serviceName} available`); return; - } catch (e) { + } catch (e: any) { console.log(`service ${serviceName} not ready:`, e.toString()); await delayMs(1000); } @@ -1074,8 +1074,12 @@ export class ExchangeService implements ExchangeServiceInterface { async purgeSecmodKeys(): Promise<void> { const cfg = Configuration.load(this.configFilename); - const rsaKeydir = cfg.getPath("taler-exchange-secmod-rsa", "KEY_DIR").required(); - const eddsaKeydir = cfg.getPath("taler-exchange-secmod-eddsa", "KEY_DIR").required(); + const rsaKeydir = cfg + .getPath("taler-exchange-secmod-rsa", "KEY_DIR") + .required(); + const eddsaKeydir = cfg + .getPath("taler-exchange-secmod-eddsa", "KEY_DIR") + .required(); // Be *VERY* careful when changing this, or you will accidentally delete user data. await sh(this.globalState, "rm-secmod-keys", `rm -rf ${rsaKeydir}/COIN_*`); await sh(this.globalState, "rm-secmod-keys", `rm ${eddsaKeydir}/*`); @@ -1119,11 +1123,7 @@ export class ExchangeService implements ExchangeServiceInterface { this.exchangeHttpProc = this.globalState.spawnService( "taler-exchange-httpd", - [ - "-c", - this.configFilename, - ...this.timetravelArgArr, - ], + ["-c", this.configFilename, ...this.timetravelArgArr], `exchange-httpd-${this.name}`, ); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts @@ -28,7 +28,7 @@ import { BankAccessApi, CreditDebitIndicator, } from "./harness"; -import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-wallet-core"; +import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util"; import { defaultCoinConfig } from "./denomStructures"; /** diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-spec-public-orders.ts @@ -21,13 +21,10 @@ import { ConfirmPayResultType, PreparePayResultType, URL, -} from "@gnu-taler/taler-util"; -import { encodeCrock, getRandomBytes, - NodeHttpLib, - WalletApiOperation, -} from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-util"; +import { NodeHttpLib, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { BankService, ExchangeService, @@ -566,11 +563,9 @@ async function testWithoutClaimToken( * specification of the endpoint. */ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { - const { - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); + const { bank, exchange, merchant } = await createSimpleTestkudosEnvironment( + t, + ); // Base URL for the default instance. const merchantBaseUrl = merchant.makeInstanceBaseUrl(); diff --git a/packages/taler-wallet-cli/src/lint.ts b/packages/taler-wallet-cli/src/lint.ts @@ -35,9 +35,9 @@ import { codecForExchangeKeysJson, codecForKeysManagementResponse, Configuration, + decodeCrock, } from "@gnu-taler/taler-util"; import { - decodeCrock, NodeHttpLib, readSuccessResponseJsonOrThrow, } from "@gnu-taler/taler-wallet-core"; diff --git a/packages/taler-wallet-core/src/crypto/talerCrypto-test.ts b/packages/taler-wallet-core/src/crypto/talerCrypto-test.ts @@ -1,189 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Imports - */ -import test from "ava"; -import { - encodeCrock, - decodeCrock, - ecdheGetPublic, - eddsaGetPublic, - keyExchangeEddsaEcdhe, - keyExchangeEcdheEddsa, - rsaBlind, - rsaUnblind, - stringToBytes, - bytesToString, - rsaVerify, -} from "./talerCrypto.js"; -import { sha512, kdf } from "./primitives/kdf.js"; -import * as nacl from "./primitives/nacl-fast.js"; - -test("encoding", (t) => { - const s = "Hello, World"; - const encStr = encodeCrock(stringToBytes(s)); - const outBuf = decodeCrock(encStr); - const sOut = bytesToString(outBuf); - t.deepEqual(s, sOut); -}); - -test("taler-exchange-tvg hash code", (t) => { - const input = "91JPRV3F5GG4EKJN41A62V35E8"; - const output = - "CW96WR74JS8T53EC8GKSGD49QKH4ZNFTZXDAWMMV5GJ1E4BM6B8GPN5NVHDJ8ZVXNCW7Q4WBYCV61HCA3PZC2YJD850DT29RHHN7ESR"; - - const myOutput = encodeCrock(sha512(decodeCrock(input))); - - t.deepEqual(myOutput, output); -}); - -test("taler-exchange-tvg ecdhe key", (t) => { - const priv1 = "X4T4N0M8PVQXQEBW2BA7049KFSM7J437NSDFC6GDNM3N5J9367A0"; - const pub1 = "M997P494MS6A95G1P0QYWW2VNPSHSX5Q6JBY5B9YMNYWP0B50X3G"; - const priv2 = "14A0MMQ64DCV8HE0CS3WBC9DHFJAHXRGV7NEARFJPC5R5E1697E0"; - const skm = - "NXRY2YCY7H9B6KM928ZD55WG964G59YR0CPX041DYXKBZZ85SAWNPQ8B30QRM5FMHYCXJAN0EAADJYWEF1X3PAC2AJN28626TR5A6AR"; - - const myPub1 = nacl.scalarMult_base(decodeCrock(priv1)); - t.deepEqual(encodeCrock(myPub1), pub1); - - const mySkm = nacl.hash( - nacl.scalarMult(decodeCrock(priv2), decodeCrock(pub1)), - ); - t.deepEqual(encodeCrock(mySkm), skm); -}); - -test("taler-exchange-tvg eddsa key", (t) => { - const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40"; - const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0"; - - const pair = nacl.sign_keyPair_fromSeed(decodeCrock(priv)); - t.deepEqual(encodeCrock(pair.publicKey), pub); -}); - -test("taler-exchange-tvg kdf", (t) => { - const salt = "94KPT83PCNS7J83KC5P78Y8"; - const ikm = "94KPT83MD1JJ0WV5CDS6AX10D5Q70XBM41NPAY90DNGQ8SBJD5GPR"; - const ctx = - "94KPT83141HPYVKMCNW78833D1TPWTSC41GPRWVF41NPWVVQDRG62WS04XMPWSKF4WG6JVH0EHM6A82J8S1G"; - const outLen = 64; - const out = - "GTMR4QT05Z9WF5HKVG0WK9RPXGHSMHJNW377G9GJXCA8B0FEKPF4D27RJMSJZYWSQNTBJ5EYVV7ZW18B48Z0JVJJ80RHB706Y96Q358"; - - const myOut = kdf( - outLen, - decodeCrock(ikm), - decodeCrock(salt), - decodeCrock(ctx), - ); - - t.deepEqual(encodeCrock(myOut), out); -}); - -test("taler-exchange-tvg eddsa_ecdh", (t) => { - const priv_ecdhe = "4AFZWMSGTVCHZPQ0R81NWXDCK4N58G7SDBBE5KXE080Y50370JJG"; - const pub_ecdhe = "FXFN5GPAFTKVPWJDPVXQ87167S8T82T5ZV8CDYC0NH2AE14X0M30"; - const priv_eddsa = "1KG54M8T3X8BSFSZXCR3SQBSR7Y9P53NX61M864S7TEVMJ2XVPF0"; - const pub_eddsa = "7BXWKG6N224C57RTDV8XEAHR108HG78NMA995BE8QAT5GC1S7E80"; - const key_material = - "PKZ42Z56SVK2796HG1QYBRJ6ZQM2T9QGA3JA4AAZ8G7CWK9FPX175Q9JE5P0ZAX3HWWPHAQV4DPCK10R9X3SAXHRV0WF06BHEC2ZTKR"; - - const myEcdhePub = ecdheGetPublic(decodeCrock(priv_ecdhe)); - t.deepEqual(encodeCrock(myEcdhePub), pub_ecdhe); - - const myEddsaPub = eddsaGetPublic(decodeCrock(priv_eddsa)); - t.deepEqual(encodeCrock(myEddsaPub), pub_eddsa); - - const myKm1 = keyExchangeEddsaEcdhe( - decodeCrock(priv_eddsa), - decodeCrock(pub_ecdhe), - ); - t.deepEqual(encodeCrock(myKm1), key_material); - - const myKm2 = keyExchangeEcdheEddsa( - decodeCrock(priv_ecdhe), - decodeCrock(pub_eddsa), - ); - t.deepEqual(encodeCrock(myKm2), key_material); -}); - -test("incremental hashing #1", (t) => { - const n = 1024; - const d = nacl.randomBytes(n); - - const h1 = nacl.hash(d); - const h2 = new nacl.HashState().update(d).finish(); - - const s = new nacl.HashState(); - for (let i = 0; i < n; i++) { - const b = new Uint8Array(1); - b[0] = d[i]; - s.update(b); - } - - const h3 = s.finish(); - - t.deepEqual(encodeCrock(h1), encodeCrock(h2)); - t.deepEqual(encodeCrock(h1), encodeCrock(h3)); -}); - -test("incremental hashing #2", (t) => { - const n = 10; - const d = nacl.randomBytes(n); - - const h1 = nacl.hash(d); - const h2 = new nacl.HashState().update(d).finish(); - const s = new nacl.HashState(); - for (let i = 0; i < n; i++) { - const b = new Uint8Array(1); - b[0] = d[i]; - s.update(b); - } - - const h3 = s.finish(); - - t.deepEqual(encodeCrock(h1), encodeCrock(h3)); - t.deepEqual(encodeCrock(h1), encodeCrock(h2)); -}); - -test("taler-exchange-tvg eddsa_ecdh #2", (t) => { - const priv_ecdhe = "W5FH9CFS3YPGSCV200GE8TH6MAACPKKGEG2A5JTFSD1HZ5RYT7Q0"; - const pub_ecdhe = "FER9CRS2T8783TAANPZ134R704773XT0ZT1XPFXZJ9D4QX67ZN00"; - const priv_eddsa = "MSZ1TBKC6YQ19ZFP3NTJVKWNVGFP35BBRW8FTAQJ9Z2B96VC9P4G"; - const pub_eddsa = "Y7MKG85PBT8ZEGHF08JBVZXEV70TS0PY5Y2CMEN1WXEDN63KP1A0"; - const key_material = - "G6RA58N61K7MT3WA13Q7VRTE1FQS6H43RX9HK8Z5TGAB61601GEGX51JRHHQMNKNM2R9AVC1STSGQDRHGKWVYP584YGBCTVMMJYQF30"; - - const myEcdhePub = ecdheGetPublic(decodeCrock(priv_ecdhe)); - t.deepEqual(encodeCrock(myEcdhePub), pub_ecdhe); - - const myEddsaPub = eddsaGetPublic(decodeCrock(priv_eddsa)); - t.deepEqual(encodeCrock(myEddsaPub), pub_eddsa); - - const myKm1 = keyExchangeEddsaEcdhe( - decodeCrock(priv_eddsa), - decodeCrock(pub_ecdhe), - ); - t.deepEqual(encodeCrock(myKm1), key_material); - - const myKm2 = keyExchangeEcdheEddsa( - decodeCrock(priv_ecdhe), - decodeCrock(pub_eddsa), - ); - t.deepEqual(encodeCrock(myKm2), key_material); -}); diff --git a/packages/taler-wallet-core/src/crypto/talerCrypto.ts b/packages/taler-wallet-core/src/crypto/talerCrypto.ts @@ -1,449 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Native implementation of GNU Taler crypto. - */ - -/** - * Imports. - */ -import * as nacl from "./primitives/nacl-fast.js"; -import bigint from "big-integer"; -import { kdf } from "./primitives/kdf.js"; - -export function getRandomBytes(n: number): Uint8Array { - return nacl.randomBytes(n); -} - -const encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; - -class EncodingError extends Error { - constructor() { - super("Encoding error"); - Object.setPrototypeOf(this, EncodingError.prototype); - } -} - -function getValue(chr: string): number { - let a = chr; - switch (chr) { - case "O": - case "o": - a = "0;"; - break; - case "i": - case "I": - case "l": - case "L": - a = "1"; - break; - case "u": - case "U": - a = "V"; - } - - if (a >= "0" && a <= "9") { - return a.charCodeAt(0) - "0".charCodeAt(0); - } - - if (a >= "a" && a <= "z") a = a.toUpperCase(); - let dec = 0; - if (a >= "A" && a <= "Z") { - if ("I" < a) dec++; - if ("L" < a) dec++; - if ("O" < a) dec++; - if ("U" < a) dec++; - return a.charCodeAt(0) - "A".charCodeAt(0) + 10 - dec; - } - throw new EncodingError(); -} - -export function encodeCrock(data: ArrayBuffer): string { - const dataBytes = new Uint8Array(data); - let sb = ""; - const size = data.byteLength; - let bitBuf = 0; - let numBits = 0; - let pos = 0; - while (pos < size || numBits > 0) { - if (pos < size && numBits < 5) { - const d = dataBytes[pos++]; - bitBuf = (bitBuf << 8) | d; - numBits += 8; - } - if (numBits < 5) { - // zero-padding - bitBuf = bitBuf << (5 - numBits); - numBits = 5; - } - const v = (bitBuf >>> (numBits - 5)) & 31; - sb += encTable[v]; - numBits -= 5; - } - return sb; -} - -export function decodeCrock(encoded: string): Uint8Array { - const size = encoded.length; - let bitpos = 0; - let bitbuf = 0; - let readPosition = 0; - const outLen = Math.floor((size * 5) / 8); - const out = new Uint8Array(outLen); - let outPos = 0; - - while (readPosition < size || bitpos > 0) { - if (readPosition < size) { - const v = getValue(encoded[readPosition++]); - bitbuf = (bitbuf << 5) | v; - bitpos += 5; - } - while (bitpos >= 8) { - const d = (bitbuf >>> (bitpos - 8)) & 0xff; - out[outPos++] = d; - bitpos -= 8; - } - if (readPosition == size && bitpos > 0) { - bitbuf = (bitbuf << (8 - bitpos)) & 0xff; - bitpos = bitbuf == 0 ? 0 : 8; - } - } - return out; -} - -export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array { - const pair = nacl.sign_keyPair_fromSeed(eddsaPriv); - return pair.publicKey; -} - -export function ecdheGetPublic(ecdhePriv: Uint8Array): Uint8Array { - return nacl.scalarMult_base(ecdhePriv); -} - -export function keyExchangeEddsaEcdhe( - eddsaPriv: Uint8Array, - ecdhePub: Uint8Array, -): Uint8Array { - const ph = nacl.hash(eddsaPriv); - const a = new Uint8Array(32); - for (let i = 0; i < 32; i++) { - a[i] = ph[i]; - } - const x = nacl.scalarMult(a, ecdhePub); - return nacl.hash(x); -} - -export function keyExchangeEcdheEddsa( - ecdhePriv: Uint8Array, - eddsaPub: Uint8Array, -): Uint8Array { - const curve25519Pub = nacl.sign_ed25519_pk_to_curve25519(eddsaPub); - const x = nacl.scalarMult(ecdhePriv, curve25519Pub); - return nacl.hash(x); -} - -interface RsaPub { - N: bigint.BigInteger; - e: bigint.BigInteger; -} - -interface RsaBlindingKey { - r: bigint.BigInteger; -} - -/** - * KDF modulo a big integer. - */ -function kdfMod( - n: bigint.BigInteger, - ikm: Uint8Array, - salt: Uint8Array, - info: Uint8Array, -): bigint.BigInteger { - const nbits = n.bitLength().toJSNumber(); - const buflen = Math.floor((nbits - 1) / 8 + 1); - const mask = (1 << (8 - (buflen * 8 - nbits))) - 1; - let counter = 0; - while (true) { - const ctx = new Uint8Array(info.byteLength + 2); - ctx.set(info, 0); - ctx[ctx.length - 2] = (counter >>> 8) & 0xff; - ctx[ctx.length - 1] = counter & 0xff; - const buf = kdf(buflen, ikm, salt, ctx); - const arr = Array.from(buf); - arr[0] = arr[0] & mask; - const r = bigint.fromArray(arr, 256, false); - if (r.lt(n)) { - return r; - } - counter++; - } -} - -// Newer versions of node have TextEncoder and TextDecoder as a global, -// just like modern browsers. -// In older versions of node or environments that do not have these -// globals, they must be polyfilled (by adding them to globa/globalThis) -// before stringToBytes or bytesToString is called the first time. - -let encoder: any; -let decoder: any; - -export function stringToBytes(s: string): Uint8Array { - if (!encoder) { - // @ts-ignore - encoder = new TextEncoder(); - } - return encoder.encode(s); -} - -export function bytesToString(b: Uint8Array): string { - if (!decoder) { - // @ts-ignore - decoder = new TextDecoder(); - } - return decoder.decode(b); -} - -function loadBigInt(arr: Uint8Array): bigint.BigInteger { - return bigint.fromArray(Array.from(arr), 256, false); -} - -function rsaBlindingKeyDerive( - rsaPub: RsaPub, - bks: Uint8Array, -): bigint.BigInteger { - const salt = stringToBytes("Blinding KDF extractor HMAC key"); - const info = stringToBytes("Blinding KDF"); - return kdfMod(rsaPub.N, bks, salt, info); -} - -/* - * Test for malicious RSA key. - * - * Assuming n is an RSA modulous and r is generated using a call to - * GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a - * malicious RSA key designed to deanomize the user. - * - * @param r KDF result - * @param n RSA modulus of the public key - */ -function rsaGcdValidate(r: bigint.BigInteger, n: bigint.BigInteger): void { - const t = bigint.gcd(r, n); - if (!t.equals(bigint.one)) { - throw Error("malicious RSA public key"); - } -} - -function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger { - const info = stringToBytes("RSA-FDA FTpsW!"); - const salt = rsaPubEncode(rsaPub); - const r = kdfMod(rsaPub.N, hm, salt, info); - rsaGcdValidate(r, rsaPub.N); - return r; -} - -function rsaPubDecode(rsaPub: Uint8Array): RsaPub { - const modulusLength = (rsaPub[0] << 8) | rsaPub[1]; - const exponentLength = (rsaPub[2] << 8) | rsaPub[3]; - if (4 + exponentLength + modulusLength != rsaPub.length) { - throw Error("invalid RSA public key (format wrong)"); - } - const modulus = rsaPub.slice(4, 4 + modulusLength); - const exponent = rsaPub.slice( - 4 + modulusLength, - 4 + modulusLength + exponentLength, - ); - const res = { - N: loadBigInt(modulus), - e: loadBigInt(exponent), - }; - return res; -} - -function rsaPubEncode(rsaPub: RsaPub): Uint8Array { - const mb = rsaPub.N.toArray(256).value; - const eb = rsaPub.e.toArray(256).value; - const out = new Uint8Array(4 + mb.length + eb.length); - out[0] = (mb.length >>> 8) & 0xff; - out[1] = mb.length & 0xff; - out[2] = (eb.length >>> 8) & 0xff; - out[3] = eb.length & 0xff; - out.set(mb, 4); - out.set(eb, 4 + mb.length); - return out; -} - -export function rsaBlind( - hm: Uint8Array, - bks: Uint8Array, - rsaPubEnc: Uint8Array, -): Uint8Array { - const rsaPub = rsaPubDecode(rsaPubEnc); - const data = rsaFullDomainHash(hm, rsaPub); - const r = rsaBlindingKeyDerive(rsaPub, bks); - const r_e = r.modPow(rsaPub.e, rsaPub.N); - const bm = r_e.multiply(data).mod(rsaPub.N); - return new Uint8Array(bm.toArray(256).value); -} - -export function rsaUnblind( - sig: Uint8Array, - rsaPubEnc: Uint8Array, - bks: Uint8Array, -): Uint8Array { - const rsaPub = rsaPubDecode(rsaPubEnc); - const blinded_s = loadBigInt(sig); - const r = rsaBlindingKeyDerive(rsaPub, bks); - const r_inv = r.modInv(rsaPub.N); - const s = blinded_s.multiply(r_inv).mod(rsaPub.N); - return new Uint8Array(s.toArray(256).value); -} - -export function rsaVerify( - hm: Uint8Array, - rsaSig: Uint8Array, - rsaPubEnc: Uint8Array, -): boolean { - const rsaPub = rsaPubDecode(rsaPubEnc); - const d = rsaFullDomainHash(hm, rsaPub); - const sig = loadBigInt(rsaSig); - const sig_e = sig.modPow(rsaPub.e, rsaPub.N); - return sig_e.equals(d); -} - -export interface EddsaKeyPair { - eddsaPub: Uint8Array; - eddsaPriv: Uint8Array; -} - -export interface EcdheKeyPair { - ecdhePub: Uint8Array; - ecdhePriv: Uint8Array; -} - -export function createEddsaKeyPair(): EddsaKeyPair { - const eddsaPriv = nacl.randomBytes(32); - const eddsaPub = eddsaGetPublic(eddsaPriv); - return { eddsaPriv, eddsaPub }; -} - -export function createEcdheKeyPair(): EcdheKeyPair { - const ecdhePriv = nacl.randomBytes(32); - const ecdhePub = ecdheGetPublic(ecdhePriv); - return { ecdhePriv, ecdhePub }; -} - -export function hash(d: Uint8Array): Uint8Array { - return nacl.hash(d); -} - -export function eddsaSign(msg: Uint8Array, eddsaPriv: Uint8Array): Uint8Array { - const pair = nacl.sign_keyPair_fromSeed(eddsaPriv); - return nacl.sign_detached(msg, pair.secretKey); -} - -export function eddsaVerify( - msg: Uint8Array, - sig: Uint8Array, - eddsaPub: Uint8Array, -): boolean { - return nacl.sign_detached_verify(msg, sig, eddsaPub); -} - -export function createHashContext(): nacl.HashState { - return new nacl.HashState(); -} - -export interface FreshCoin { - coinPub: Uint8Array; - coinPriv: Uint8Array; - bks: Uint8Array; -} - -export function setupRefreshPlanchet( - secretSeed: Uint8Array, - coinNumber: number, -): FreshCoin { - const info = stringToBytes("taler-coin-derivation"); - const saltArrBuf = new ArrayBuffer(4); - const salt = new Uint8Array(saltArrBuf); - const saltDataView = new DataView(saltArrBuf); - saltDataView.setUint32(0, coinNumber); - const out = kdf(64, secretSeed, salt, info); - const coinPriv = out.slice(0, 32); - const bks = out.slice(32, 64); - return { - bks, - coinPriv, - coinPub: eddsaGetPublic(coinPriv), - }; -} - -export function setupWithdrawPlanchet( - secretSeed: Uint8Array, - coinNumber: number, -): FreshCoin { - const info = stringToBytes("taler-withdrawal-coin-derivation"); - const saltArrBuf = new ArrayBuffer(4); - const salt = new Uint8Array(saltArrBuf); - const saltDataView = new DataView(saltArrBuf); - saltDataView.setUint32(0, coinNumber); - const out = kdf(64, secretSeed, salt, info); - const coinPriv = out.slice(0, 32); - const bks = out.slice(32, 64); - return { - bks, - coinPriv, - coinPub: eddsaGetPublic(coinPriv), - }; -} - -export function setupTipPlanchet( - secretSeed: Uint8Array, - coinNumber: number, -): FreshCoin { - const info = stringToBytes("taler-tip-coin-derivation"); - const saltArrBuf = new ArrayBuffer(4); - const salt = new Uint8Array(saltArrBuf); - const saltDataView = new DataView(saltArrBuf); - saltDataView.setUint32(0, coinNumber); - const out = kdf(64, secretSeed, salt, info); - const coinPriv = out.slice(0, 32); - const bks = out.slice(32, 64); - return { - bks, - coinPriv, - coinPub: eddsaGetPublic(coinPriv), - }; -} - -export function setupRefreshTransferPub( - secretSeed: Uint8Array, - transferPubIndex: number, -): EcdheKeyPair { - const info = stringToBytes("taler-transfer-pub-derivation"); - const saltArrBuf = new ArrayBuffer(4); - const salt = new Uint8Array(saltArrBuf); - const saltDataView = new DataView(saltArrBuf); - saltDataView.setUint32(0, transferPubIndex); - const out = kdf(32, secretSeed, salt, info); - return { - ecdhePriv: out, - ecdhePub: ecdheGetPublic(out), - }; -} diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -63,9 +63,9 @@ import { setupTipPlanchet, setupWithdrawPlanchet, eddsaGetPublic, -} from "../talerCrypto.js"; -import { randomBytes } from "../primitives/nacl-fast.js"; -import { kdf } from "../primitives/kdf.js"; +} from "@gnu-taler/taler-util"; +import { randomBytes } from "@gnu-taler/taler-util"; +import { kdf } from "@gnu-taler/taler-util"; import { Timestamp, timestampTruncateToSecond } from "@gnu-taler/taler-util"; import { Logger } from "@gnu-taler/taler-util"; diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts @@ -28,8 +28,7 @@ import { import { RequestThrottler } from "../util/RequestThrottler.js"; import Axios, { AxiosResponse } from "axios"; import { OperationFailedError, makeErrorDetails } from "../errors.js"; -import { Logger } from "@gnu-taler/taler-util"; -import { bytesToString } from "../crypto/talerCrypto.js"; +import { Logger, bytesToString } from "@gnu-taler/taler-util"; import { TalerErrorCode, URL } from "@gnu-taler/taler-util"; const logger = new Logger("NodeHttpLib.ts"); @@ -83,7 +82,7 @@ export class NodeHttpLib implements HttpRequestLibrary { timeout, maxRedirects: 0, }); - } catch (e) { + } catch (e: any) { throw OperationFailedError.fromCode( TalerErrorCode.WALLET_NETWORK_ERROR, `${e.message}`, diff --git a/packages/taler-wallet-core/src/headless/helpers.ts b/packages/taler-wallet-core/src/headless/helpers.ts @@ -92,7 +92,7 @@ export async function getDefaultNodeWallet( }); const dbContent = JSON.parse(dbContentStr); myBackend.importDump(dbContent); - } catch (e) { + } catch (e: any) { const code: string = e.code; if (code === "ENOENT") { logger.trace("wallet file doesn't exist yet"); diff --git a/packages/taler-wallet-core/src/index.browser.ts b/packages/taler-wallet-core/src/index.browser.ts @@ -15,58 +15,3 @@ */ export * from "./index.js"; - -import { setPRNG } from './crypto/primitives/nacl-fast.js'; -// export default API; - -function cleanup(arr: Uint8Array): void { - for (let i = 0; i < arr.length; i++) arr[i] = 0; -} - -// Initialize PRNG if environment provides CSPRNG. -// If not, methods calling randombytes will throw. -// @ts-ignore-error -const cr = typeof self !== "undefined" ? self.crypto || self.msCrypto : null; - -const QUOTA = 65536; -setPRNG(function (x: Uint8Array, n: number) { - let i; - const v = new Uint8Array(n); - for (i = 0; i < n; i += QUOTA) { - cr.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA))); - } - for (i = 0; i < n; i++) x[i] = v[i]; - cleanup(v); -}); -// function initPRNG() { -// // Initialize PRNG if environment provides CSPRNG. -// // If not, methods calling randombytes will throw. -// // @ts-ignore-error -// const cr = typeof self !== "undefined" ? self.crypto || self.msCrypto : null; -// if (cr && cr.getRandomValues) { -// // Browsers. -// const QUOTA = 65536; -// setPRNG(function (x: Uint8Array, n: number) { -// let i; -// const v = new Uint8Array(n); -// for (i = 0; i < n; i += QUOTA) { -// cr.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA))); -// } -// for (i = 0; i < n; i++) x[i] = v[i]; -// cleanup(v); -// }); -// } else if (typeof require !== "undefined") { -// // Node.js. -// // eslint-disable-next-line @typescript-eslint/no-var-requires -// const cr = require("crypto"); -// if (cr && cr.randomBytes) { -// setPRNG(function (x: Uint8Array, n: number) { -// const v = cr.randomBytes(n); -// for (let i = 0; i < n; i++) x[i] = v[i]; -// cleanup(v); -// }); -// } -// } -// } - -// initPRNG(); -\ No newline at end of file diff --git a/packages/taler-wallet-core/src/index.node.ts b/packages/taler-wallet-core/src/index.node.ts @@ -22,22 +22,4 @@ export { getDefaultNodeWallet, DefaultNodeWalletArgs, } from "./headless/helpers.js"; - -import { setPRNG } from './crypto/primitives/nacl-fast.js'; -import cr from 'crypto'; - -function cleanup(arr: Uint8Array): void { - for (let i = 0; i < arr.length; i++) arr[i] = 0; -} - -// Initialize PRNG if environment provides CSPRNG. -// If not, methods calling randombytes will throw. -if (cr && cr.randomBytes) { - setPRNG(function (x: Uint8Array, n: number) { - const v = cr.randomBytes(n); - for (let i = 0; i < n; i++) x[i] = v[i]; - cleanup(v); - }); -} - export * from "./crypto/workers/nodeThreadWorker.js"; diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts @@ -36,7 +36,6 @@ export * from "./db-utils.js"; export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js"; export type { CryptoWorker } from "./crypto/workers/cryptoWorker.js"; export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js"; -export * from "./crypto/talerCrypto.js"; export * from "./pending-types.js"; diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -53,14 +53,12 @@ import { Logger, timestampToIsoString, WalletBackupContentV1, -} from "@gnu-taler/taler-util"; -import { InternalWalletState } from "../../common.js"; -import { hash } from "../../crypto/primitives/nacl-fast.js"; -import { + hash, encodeCrock, getRandomBytes, stringToBytes, -} from "../../crypto/talerCrypto.js"; +} from "@gnu-taler/taler-util"; +import { InternalWalletState } from "../../common.js"; import { AbortStatus, CoinSourceType, diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -55,11 +55,11 @@ import { } from "@gnu-taler/taler-util"; import { gunzipSync, gzipSync } from "fflate"; import { InternalWalletState } from "../../common.js"; -import { kdf } from "../../crypto/primitives/kdf.js"; +import { kdf } from "@gnu-taler/taler-util"; import { secretbox, secretbox_open, -} from "../../crypto/primitives/nacl-fast.js"; +} from "@gnu-taler/taler-util"; import { bytesToString, decodeCrock, @@ -70,7 +70,7 @@ import { hash, rsaBlind, stringToBytes, -} from "../../crypto/talerCrypto.js"; +} from "@gnu-taler/taler-util"; import { CryptoApi } from "../../crypto/workers/cryptoApi.js"; import { BackupProviderRecord, diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { encodeCrock, getRandomBytes } from "../../crypto/talerCrypto.js"; +import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { ConfigRecord, WalletBackupConfState, diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts @@ -39,12 +39,12 @@ import { URL, } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../common.js"; -import { kdf } from "../crypto/primitives/kdf.js"; +import { kdf } from "@gnu-taler/taler-util"; import { encodeCrock, getRandomBytes, stringToBytes, -} from "../crypto/talerCrypto.js"; +} from "@gnu-taler/taler-util"; import { DepositGroupRecord } from "../db.js"; import { guardOperationException } from "../errors.js"; import { selectPayCoins } from "../util/coinSelection.js"; diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -40,7 +40,7 @@ import { TalerErrorDetails, Timestamp, } from "@gnu-taler/taler-util"; -import { decodeCrock, encodeCrock, hash } from "../crypto/talerCrypto.js"; +import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; import { CryptoApi } from "../crypto/workers/cryptoApi.js"; import { DenominationRecord, diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts @@ -54,7 +54,7 @@ import { URL, getDurationRemaining, } from "@gnu-taler/taler-util"; -import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto.js"; +import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { PayCoinSelection, CoinCandidateSelection, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts @@ -32,7 +32,7 @@ import { RefreshReason, TalerErrorDetails, } from "@gnu-taler/taler-util"; -import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto.js"; +import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { CoinRecord, CoinSourceType, diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto.js"; +import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { CoinRecord, CoinSourceType, diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts @@ -32,7 +32,7 @@ import { TalerErrorCode, addPaytoQueryParams, } from "@gnu-taler/taler-util"; -import { randomBytes } from "../crypto/primitives/nacl-fast.js"; +import { randomBytes } from "@gnu-taler/taler-util"; import { ReserveRecordStatus, ReserveBankInfo, @@ -63,7 +63,7 @@ import { processWithdrawGroup, getBankWithdrawalInfo, } from "./withdraw.js"; -import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto.js"; +import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { Logger, URL } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrErrorCode, diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts @@ -55,7 +55,7 @@ import { getHttpResponseErrorDetails, readSuccessResponseJsonOrThrow, } from "../util/http.js"; -import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto.js"; +import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; const logger = new Logger("operations/tip.ts"); diff --git a/packages/taler-wallet-core/src/util/contractTerms.ts b/packages/taler-wallet-core/src/util/contractTerms.ts @@ -15,14 +15,14 @@ */ import { canonicalJson } from "@gnu-taler/taler-util"; -import { kdf } from "../crypto/primitives/kdf.js"; +import { kdf } from "@gnu-taler/taler-util"; import { decodeCrock, encodeCrock, getRandomBytes, hash, stringToBytes, -} from "../crypto/talerCrypto.js"; +} from "@gnu-taler/taler-util"; export namespace ContractTermsUtil { export type PathPredicate = (path: string[]) => boolean; diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts @@ -198,7 +198,7 @@ export async function readSuccessResponseJsonOrErrorCode<T>( let parsedResponse: T; try { parsedResponse = codec.decode(respJson); - } catch (e) { + } catch (e: any) { throw OperationFailedError.fromCode( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, "Response invalid", diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx @@ -14,13 +14,24 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, BackupBackupProviderTerms, canonicalizeBaseUrl, i18n } from "@gnu-taler/taler-util"; -import { verify } from "@gnu-taler/taler-wallet-core/src/crypto/primitives/nacl-fast"; +import { + Amounts, + BackupBackupProviderTerms, + canonicalizeBaseUrl, + i18n, +} from "@gnu-taler/taler-util"; import { VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Checkbox } from "../components/Checkbox"; import { ErrorMessage } from "../components/ErrorMessage"; -import { Button, ButtonPrimary, Input, LightText, PopupBox, SmallLightText } from "../components/styled/index"; +import { + Button, + ButtonPrimary, + Input, + LightText, + PopupBox, + SmallLightText, +} from "../components/styled/index"; import * as wxApi from "../wxApi"; interface Props { @@ -30,52 +41,65 @@ interface Props { function getJsonIfOk(r: Response) { if (r.ok) { - return r.json() + return r.json(); } else { if (r.status >= 400 && r.status < 500) { - throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`) + throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`); } else { - throw new Error(`Try another server: (${r.status}) ${r.statusText || 'internal server error'}`) + throw new Error( + `Try another server: (${r.status}) ${ + r.statusText || "internal server error" + }`, + ); } } } - export function ProviderAddPage({ onBack }: Props): VNode { - const [verifying, setVerifying] = useState<{ url: string, name: string, provider: BackupBackupProviderTerms } | undefined>(undefined) - - async function getProviderInfo(url: string): Promise<BackupBackupProviderTerms> { + const [verifying, setVerifying] = useState< + | { url: string; name: string; provider: BackupBackupProviderTerms } + | undefined + >(undefined); + + async function getProviderInfo( + url: string, + ): Promise<BackupBackupProviderTerms> { return fetch(`${url}config`) - .catch(e => { throw new Error(`Network error`) }) - .then(getJsonIfOk) + .catch((e) => { + throw new Error(`Network error`); + }) + .then(getJsonIfOk); } if (!verifying) { - return <SetUrlView - onCancel={onBack} - onVerify={(url) => getProviderInfo(url)} - onConfirm={(url, name) => getProviderInfo(url) - .then((provider) => { - setVerifying({ url, name, provider }); - }) - .catch(e => e.message) - } - /> + return ( + <SetUrlView + onCancel={onBack} + onVerify={(url) => getProviderInfo(url)} + onConfirm={(url, name) => + getProviderInfo(url) + .then((provider) => { + setVerifying({ url, name, provider }); + }) + .catch((e) => e.message) + } + /> + ); } - return <ConfirmProviderView - provider={verifying.provider} - url={verifying.url} - onCancel={() => { - setVerifying(undefined); - }} - onConfirm={() => { - wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack) - }} - - /> + return ( + <ConfirmProviderView + provider={verifying.provider} + url={verifying.url} + onCancel={() => { + setVerifying(undefined); + }} + onConfirm={() => { + wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack); + }} + /> + ); } - export interface SetUrlViewProps { initialValue?: string; onCancel: () => void; @@ -84,83 +108,137 @@ export interface SetUrlViewProps { withError?: string; } -export function SetUrlView({ initialValue, onCancel, onVerify, onConfirm, withError }: SetUrlViewProps) { - const [value, setValue] = useState<string>(initialValue || "") - const [urlError, setUrlError] = useState(false) - const [name, setName] = useState<string|undefined>(undefined) - const [error, setError] = useState<string | undefined>(withError) +export function SetUrlView({ + initialValue, + onCancel, + onVerify, + onConfirm, + withError, +}: SetUrlViewProps) { + const [value, setValue] = useState<string>(initialValue || ""); + const [urlError, setUrlError] = useState(false); + const [name, setName] = useState<string | undefined>(undefined); + const [error, setError] = useState<string | undefined>(withError); useEffect(() => { try { - const url = canonicalizeBaseUrl(value) - onVerify(url).then(r => { - setUrlError(false) - setName(new URL(url).hostname) - }).catch(() => { - setUrlError(true) - setName(undefined) - }) + const url = canonicalizeBaseUrl(value); + onVerify(url) + .then((r) => { + setUrlError(false); + setName(new URL(url).hostname); + }) + .catch(() => { + setUrlError(true); + setName(undefined); + }); } catch { - setUrlError(true) - setName(undefined) + setUrlError(true); + setName(undefined); } - }, [value]) - return <PopupBox> - <section> - <h1> Add backup provider</h1> - <ErrorMessage title={error && "Could not get provider information"} description={error} /> - <LightText> Backup providers may charge for their service</LightText> - <p> - <Input invalid={urlError}> - <label>URL</label> - <input type="text" placeholder="https://" value={value} onChange={(e) => setValue(e.currentTarget.value)} /> - </Input> - <Input> - <label>Name</label> - <input type="text" disabled={name === undefined} value={name} onChange={e => setName(e.currentTarget.value)}/> - </Input> - </p> - </section> - <footer> - <Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button> - <ButtonPrimary - disabled={!value && !urlError} - onClick={() => { - const url = canonicalizeBaseUrl(value) - return onConfirm(url, name!).then(r => r ? setError(r) : undefined) - }}><i18n.Translate>Next</i18n.Translate></ButtonPrimary> - </footer> - </PopupBox> + }, [value]); + return ( + <PopupBox> + <section> + <h1> Add backup provider</h1> + <ErrorMessage + title={error && "Could not get provider information"} + description={error} + /> + <LightText> Backup providers may charge for their service</LightText> + <p> + <Input invalid={urlError}> + <label>URL</label> + <input + type="text" + placeholder="https://" + value={value} + onChange={(e) => setValue(e.currentTarget.value)} + /> + </Input> + <Input> + <label>Name</label> + <input + type="text" + disabled={name === undefined} + value={name} + onChange={(e) => setName(e.currentTarget.value)} + /> + </Input> + </p> + </section> + <footer> + <Button onClick={onCancel}> + <i18n.Translate> &lt; Back</i18n.Translate> + </Button> + <ButtonPrimary + disabled={!value && !urlError} + onClick={() => { + const url = canonicalizeBaseUrl(value); + return onConfirm(url, name!).then((r) => + r ? setError(r) : undefined, + ); + }} + > + <i18n.Translate>Next</i18n.Translate> + </ButtonPrimary> + </footer> + </PopupBox> + ); } export interface ConfirmProviderViewProps { - provider: BackupBackupProviderTerms, - url: string, + provider: BackupBackupProviderTerms; + url: string; onCancel: () => void; onConfirm: () => void; } -export function ConfirmProviderView({ url, provider, onCancel, onConfirm }: ConfirmProviderViewProps) { +export function ConfirmProviderView({ + url, + provider, + onCancel, + onConfirm, +}: ConfirmProviderViewProps) { const [accepted, setAccepted] = useState(false); - return <PopupBox> - <section> - <h1>Review terms of service</h1> - <div>Provider URL: <a href={url} target="_blank">{url}</a></div> - <SmallLightText>Please review and accept this provider's terms of service</SmallLightText> - <h2>1. Pricing</h2> - <p> - {Amounts.isZero(provider.annual_fee) ? 'free of charge' : `${provider.annual_fee} per year of service`} - </p> - <h2>2. Storage</h2> - <p> - {provider.storage_limit_in_megabytes} megabytes of storage per year of service - </p> - <Checkbox label="Accept terms of service" name="terms" onToggle={() => setAccepted(old => !old)} enabled={accepted} /> - </section> - <footer> - <Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button> - <ButtonPrimary - disabled={!accepted} - onClick={onConfirm}><i18n.Translate>Add provider</i18n.Translate></ButtonPrimary> - </footer> - </PopupBox> + return ( + <PopupBox> + <section> + <h1>Review terms of service</h1> + <div> + Provider URL:{" "} + <a href={url} target="_blank"> + {url} + </a> + </div> + <SmallLightText> + Please review and accept this provider's terms of service + </SmallLightText> + <h2>1. Pricing</h2> + <p> + {Amounts.isZero(provider.annual_fee) + ? "free of charge" + : `${provider.annual_fee} per year of service`} + </p> + <h2>2. Storage</h2> + <p> + {provider.storage_limit_in_megabytes} megabytes of storage per year of + service + </p> + <Checkbox + label="Accept terms of service" + name="terms" + onToggle={() => setAccepted((old) => !old)} + enabled={accepted} + /> + </section> + <footer> + <Button onClick={onCancel}> + <i18n.Translate> &lt; Back</i18n.Translate> + </Button> + <ButtonPrimary disabled={!accepted} onClick={onConfirm}> + <i18n.Translate>Add provider</i18n.Translate> + </ButtonPrimary> + </footer> + </PopupBox> + ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx @@ -14,13 +14,24 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, BackupBackupProviderTerms, canonicalizeBaseUrl, i18n } from "@gnu-taler/taler-util"; -import { verify } from "@gnu-taler/taler-wallet-core/src/crypto/primitives/nacl-fast"; +import { + Amounts, + BackupBackupProviderTerms, + canonicalizeBaseUrl, + i18n, +} from "@gnu-taler/taler-util"; import { VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Checkbox } from "../components/Checkbox"; import { ErrorMessage } from "../components/ErrorMessage"; -import { Button, ButtonPrimary, Input, LightText, WalletBox, SmallLightText } from "../components/styled/index"; +import { + Button, + ButtonPrimary, + Input, + LightText, + WalletBox, + SmallLightText, +} from "../components/styled/index"; import * as wxApi from "../wxApi"; interface Props { @@ -30,52 +41,65 @@ interface Props { function getJsonIfOk(r: Response) { if (r.ok) { - return r.json() + return r.json(); } else { if (r.status >= 400 && r.status < 500) { - throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`) + throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`); } else { - throw new Error(`Try another server: (${r.status}) ${r.statusText || 'internal server error'}`) + throw new Error( + `Try another server: (${r.status}) ${ + r.statusText || "internal server error" + }`, + ); } } } - export function ProviderAddPage({ onBack }: Props): VNode { - const [verifying, setVerifying] = useState<{ url: string, name: string, provider: BackupBackupProviderTerms } | undefined>(undefined) - - async function getProviderInfo(url: string): Promise<BackupBackupProviderTerms> { + const [verifying, setVerifying] = useState< + | { url: string; name: string; provider: BackupBackupProviderTerms } + | undefined + >(undefined); + + async function getProviderInfo( + url: string, + ): Promise<BackupBackupProviderTerms> { return fetch(`${url}config`) - .catch(e => { throw new Error(`Network error`) }) - .then(getJsonIfOk) + .catch((e) => { + throw new Error(`Network error`); + }) + .then(getJsonIfOk); } if (!verifying) { - return <SetUrlView - onCancel={onBack} - onVerify={(url) => getProviderInfo(url)} - onConfirm={(url, name) => getProviderInfo(url) - .then((provider) => { - setVerifying({ url, name, provider }); - }) - .catch(e => e.message) - } - /> + return ( + <SetUrlView + onCancel={onBack} + onVerify={(url) => getProviderInfo(url)} + onConfirm={(url, name) => + getProviderInfo(url) + .then((provider) => { + setVerifying({ url, name, provider }); + }) + .catch((e) => e.message) + } + /> + ); } - return <ConfirmProviderView - provider={verifying.provider} - url={verifying.url} - onCancel={() => { - setVerifying(undefined); - }} - onConfirm={() => { - wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack) - }} - - /> + return ( + <ConfirmProviderView + provider={verifying.provider} + url={verifying.url} + onCancel={() => { + setVerifying(undefined); + }} + onConfirm={() => { + wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack); + }} + /> + ); } - export interface SetUrlViewProps { initialValue?: string; onCancel: () => void; @@ -84,83 +108,137 @@ export interface SetUrlViewProps { withError?: string; } -export function SetUrlView({ initialValue, onCancel, onVerify, onConfirm, withError }: SetUrlViewProps) { - const [value, setValue] = useState<string>(initialValue || "") - const [urlError, setUrlError] = useState(false) - const [name, setName] = useState<string|undefined>(undefined) - const [error, setError] = useState<string | undefined>(withError) +export function SetUrlView({ + initialValue, + onCancel, + onVerify, + onConfirm, + withError, +}: SetUrlViewProps) { + const [value, setValue] = useState<string>(initialValue || ""); + const [urlError, setUrlError] = useState(false); + const [name, setName] = useState<string | undefined>(undefined); + const [error, setError] = useState<string | undefined>(withError); useEffect(() => { try { - const url = canonicalizeBaseUrl(value) - onVerify(url).then(r => { - setUrlError(false) - setName(new URL(url).hostname) - }).catch(() => { - setUrlError(true) - setName(undefined) - }) + const url = canonicalizeBaseUrl(value); + onVerify(url) + .then((r) => { + setUrlError(false); + setName(new URL(url).hostname); + }) + .catch(() => { + setUrlError(true); + setName(undefined); + }); } catch { - setUrlError(true) - setName(undefined) + setUrlError(true); + setName(undefined); } - }, [value]) - return <WalletBox> - <section> - <h1> Add backup provider</h1> - <ErrorMessage title={error && "Could not get provider information"} description={error} /> - <LightText> Backup providers may charge for their service</LightText> - <p> - <Input invalid={urlError}> - <label>URL</label> - <input type="text" placeholder="https://" value={value} onChange={(e) => setValue(e.currentTarget.value)} /> - </Input> - <Input> - <label>Name</label> - <input type="text" disabled={name === undefined} value={name} onChange={e => setName(e.currentTarget.value)}/> - </Input> - </p> - </section> - <footer> - <Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button> - <ButtonPrimary - disabled={!value && !urlError} - onClick={() => { - const url = canonicalizeBaseUrl(value) - return onConfirm(url, name!).then(r => r ? setError(r) : undefined) - }}><i18n.Translate>Next</i18n.Translate></ButtonPrimary> - </footer> - </WalletBox> + }, [value]); + return ( + <WalletBox> + <section> + <h1> Add backup provider</h1> + <ErrorMessage + title={error && "Could not get provider information"} + description={error} + /> + <LightText> Backup providers may charge for their service</LightText> + <p> + <Input invalid={urlError}> + <label>URL</label> + <input + type="text" + placeholder="https://" + value={value} + onChange={(e) => setValue(e.currentTarget.value)} + /> + </Input> + <Input> + <label>Name</label> + <input + type="text" + disabled={name === undefined} + value={name} + onChange={(e) => setName(e.currentTarget.value)} + /> + </Input> + </p> + </section> + <footer> + <Button onClick={onCancel}> + <i18n.Translate> &lt; Back</i18n.Translate> + </Button> + <ButtonPrimary + disabled={!value && !urlError} + onClick={() => { + const url = canonicalizeBaseUrl(value); + return onConfirm(url, name!).then((r) => + r ? setError(r) : undefined, + ); + }} + > + <i18n.Translate>Next</i18n.Translate> + </ButtonPrimary> + </footer> + </WalletBox> + ); } export interface ConfirmProviderViewProps { - provider: BackupBackupProviderTerms, - url: string, + provider: BackupBackupProviderTerms; + url: string; onCancel: () => void; onConfirm: () => void; } -export function ConfirmProviderView({ url, provider, onCancel, onConfirm }: ConfirmProviderViewProps) { +export function ConfirmProviderView({ + url, + provider, + onCancel, + onConfirm, +}: ConfirmProviderViewProps) { const [accepted, setAccepted] = useState(false); - return <WalletBox> - <section> - <h1>Review terms of service</h1> - <div>Provider URL: <a href={url} target="_blank">{url}</a></div> - <SmallLightText>Please review and accept this provider's terms of service</SmallLightText> - <h2>1. Pricing</h2> - <p> - {Amounts.isZero(provider.annual_fee) ? 'free of charge' : `${provider.annual_fee} per year of service`} - </p> - <h2>2. Storage</h2> - <p> - {provider.storage_limit_in_megabytes} megabytes of storage per year of service - </p> - <Checkbox label="Accept terms of service" name="terms" onToggle={() => setAccepted(old => !old)} enabled={accepted} /> - </section> - <footer> - <Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button> - <ButtonPrimary - disabled={!accepted} - onClick={onConfirm}><i18n.Translate>Add provider</i18n.Translate></ButtonPrimary> - </footer> - </WalletBox> + return ( + <WalletBox> + <section> + <h1>Review terms of service</h1> + <div> + Provider URL:{" "} + <a href={url} target="_blank"> + {url} + </a> + </div> + <SmallLightText> + Please review and accept this provider's terms of service + </SmallLightText> + <h2>1. Pricing</h2> + <p> + {Amounts.isZero(provider.annual_fee) + ? "free of charge" + : `${provider.annual_fee} per year of service`} + </p> + <h2>2. Storage</h2> + <p> + {provider.storage_limit_in_megabytes} megabytes of storage per year of + service + </p> + <Checkbox + label="Accept terms of service" + name="terms" + onToggle={() => setAccepted((old) => !old)} + enabled={accepted} + /> + </section> + <footer> + <Button onClick={onCancel}> + <i18n.Translate> &lt; Back</i18n.Translate> + </Button> + <ButtonPrimary disabled={!accepted} onClick={onConfirm}> + <i18n.Translate>Add provider</i18n.Translate> + </ButtonPrimary> + </footer> + </WalletBox> + ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml @@ -12,6 +12,19 @@ importers: '@linaria/shaker': 3.0.0-beta.7 esbuild: 0.12.21 + packages/anastasis-core: + specifiers: + '@gnu-taler/taler-util': workspace:^0.8.3 + ava: ^3.15.0 + hash-wasm: ^4.9.0 + typescript: ^4.4.3 + dependencies: + '@gnu-taler/taler-util': link:../taler-util + hash-wasm: 4.9.0 + devDependencies: + ava: 3.15.0 + typescript: 4.4.3 + packages/idb-bridge: specifiers: '@rollup/plugin-commonjs': ^17.1.0 @@ -52,6 +65,7 @@ importers: specifiers: '@types/node': ^14.14.22 ava: ^3.15.0 + big-integer: ^1.6.48 esbuild: ^0.9.2 jed: ^1.1.1 prettier: ^2.2.1 @@ -59,6 +73,7 @@ importers: tslib: ^2.1.0 typescript: ^4.2.3 dependencies: + big-integer: 1.6.48 jed: 1.1.1 tslib: 2.1.0 devDependencies: @@ -11813,6 +11828,10 @@ packages: safe-buffer: 5.2.1 dev: true + /hash-wasm/4.9.0: + resolution: {integrity: sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==} + dev: false + /hash.js/1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} dependencies: @@ -19292,6 +19311,12 @@ packages: hasBin: true dev: true + /typescript/4.4.3: + resolution: {integrity: sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /uglify-js/3.12.5: resolution: {integrity: sha512-SgpgScL4T7Hj/w/GexjnBHi3Ien9WS1Rpfg5y91WXMj9SY997ZCQU76mH4TpLwwfmMvoOU8wiaRkIf6NaH3mtg==} engines: {node: '>=0.8.0'}