diff options
author | Torsten Grote <t@grobox.de> | 2020-07-08 12:00:53 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-07-08 12:00:53 -0300 |
commit | 4379d554c72d80328dff1e873d7cc007a1a5881a (patch) | |
tree | 354e4ef39c67a0e07e672bb0a09df850a2cc1561 /src | |
parent | 2ee6b0d4cc3871237d24e28d09d2f526d1304da1 (diff) | |
download | wallet-kotlin-4379d554c72d80328dff1e873d7cc007a1a5881a.tar.gz wallet-kotlin-4379d554c72d80328dff1e873d7cc007a1a5881a.tar.bz2 wallet-kotlin-4379d554c72d80328dff1e873d7cc007a1a5881a.zip |
Add initial code for updating an exchange
This introduces a common Database interface that gets currently only
implemented by a fake in-memmory database.
Diffstat (limited to 'src')
22 files changed, 1189 insertions, 105 deletions
diff --git a/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt b/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt new file mode 100644 index 0000000..45cbfc3 --- /dev/null +++ b/src/androidMain/kotlin/net/taler/wallet/kotlin/Db.kt @@ -0,0 +1,23 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin + +internal actual class DbFactory { + actual fun openDb(): Db { + return FakeDb() + } +} diff --git a/src/androidTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt b/src/androidTest/kotlin/net/taler/wallet/kotlin/TestUtilsAndroid.kt index 524da15..a362874 100644 --- a/src/androidTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt +++ b/src/androidTest/kotlin/net/taler/wallet/kotlin/TestUtilsAndroid.kt @@ -20,3 +20,5 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) = runBlocking { block(this) } + +actual fun getPlatformTarget(): PlatformTarget = PlatformTarget.ANDROID diff --git a/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt b/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt index 37cf10f..4ed903e 100644 --- a/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt +++ b/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt @@ -21,13 +21,13 @@ import net.taler.wallet.kotlin.Base32Crockford import net.taler.wallet.kotlin.CoinRecord import net.taler.wallet.kotlin.CoinSourceType.WITHDRAW import net.taler.wallet.kotlin.CoinStatus.DORMANT -import net.taler.wallet.kotlin.DenominationRecord -import net.taler.wallet.kotlin.DenominationStatus import net.taler.wallet.kotlin.Timestamp import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo import net.taler.wallet.kotlin.crypto.Refresh.RefreshPlanchetRecord import net.taler.wallet.kotlin.crypto.Refresh.RefreshSessionRecord import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination +import net.taler.wallet.kotlin.exchange.DenominationRecord +import net.taler.wallet.kotlin.exchange.DenominationStatus import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt new file mode 100644 index 0000000..303c526 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Db.kt @@ -0,0 +1,59 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin + +import net.taler.wallet.kotlin.exchange.DenominationRecord +import net.taler.wallet.kotlin.exchange.ExchangeRecord + +internal interface Db { + suspend fun put(exchange: ExchangeRecord) + suspend fun listExchanges(): List<ExchangeRecord> + suspend fun getExchangeByBaseUrl(baseUrl: String): ExchangeRecord? + suspend fun deleteExchangeByBaseUrl(baseUrl: String) + suspend fun put(denomination: DenominationRecord) +} + +internal expect class DbFactory() { + fun openDb(): Db +} + +internal class FakeDb: Db { + + private val exchanges = HashMap<String, ExchangeRecord>() + private val denominations = HashMap<String, DenominationRecord>() + + override suspend fun put(exchange: ExchangeRecord) { + exchanges[exchange.baseUrl] = exchange + } + + override suspend fun listExchanges(): List<ExchangeRecord> { + return exchanges.values.toList() + } + + override suspend fun getExchangeByBaseUrl(baseUrl: String): ExchangeRecord? { + return exchanges[baseUrl] + } + + override suspend fun deleteExchangeByBaseUrl(baseUrl: String) { + exchanges.remove(baseUrl) + } + + override suspend fun put(denomination: DenominationRecord) { + denominations[denomination.exchangeBaseUrl] = denomination + } + +} diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt index d0260cf..b5c850f 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Timestamp.kt @@ -17,11 +17,13 @@ package net.taler.wallet.kotlin import com.soywiz.klock.DateTime +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import net.taler.wallet.kotlin.crypto.CryptoImpl.Companion.toByteArray - -class Timestamp( - // @JsonProperty("t_ms") +@Serializable +data class Timestamp( + @SerialName("t_ms") val ms: Long ) { diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt index 4702a19..2365795 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt @@ -39,95 +39,6 @@ data class WireFee( val signature: String ) -data class DenominationRecord( - /** - * Value of one coin of the denomination. - */ - val value: Amount, - /** - * The denomination public key. - */ - val denomPub: String, - /** - * Hash of the denomination public key. - * Stored in the database for faster lookups. - */ - val denomPubHash: String, - /** - * Fee for withdrawing. - */ - val feeWithdraw: Amount, - /** - * Fee for depositing. - */ - val feeDeposit: Amount, - /** - * Fee for refreshing. - */ - val feeRefresh: Amount, - /** - * Fee for refunding. - */ - val feeRefund: Amount, - /** - * Validity start date of the denomination. - */ - val stampStart: Timestamp, - /** - * Date after which the currency can't be withdrawn anymore. - */ - val stampExpireWithdraw: Timestamp, - /** - * Date after the denomination officially doesn't exist anymore. - */ - val stampExpireLegal: Timestamp, - /** - * Data after which coins of this denomination can't be deposited anymore. - */ - val stampExpireDeposit: Timestamp, - /** - * Signature by the exchange's master key over the denomination - * information. - */ - val masterSig: String, - /** - * Did we verify the signature on the denomination? - */ - val status: DenominationStatus, - /** - * Was this denomination still offered by the exchange the last time - * we checked? - * Only false when the exchange redacts a previously published denomination. - */ - val isOffered: Boolean, - /** - * Did the exchange revoke the denomination? - * When this field is set to true in the database, the same transaction - * should also mark all affected coins as revoked. - */ - val isRevoked: Boolean, - /** - * Base URL of the exchange. - */ - val exchangeBaseUrl: String -) - -enum class DenominationStatus { - /** - * Verification was delayed. - */ - Unverified, - - /** - * Verified as valid. - */ - VerifiedGood, - - /** - * Verified as invalid. - */ - VerifiedBad -} class CoinRecord( /** diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt new file mode 100644 index 0000000..aa3fd91 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Utils.kt @@ -0,0 +1,35 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin + +import io.ktor.client.HttpClient +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.features.json.serializer.KotlinxSerializer +import kotlinx.serialization.UnstableDefault +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration + +fun getDefaultHttpClient(): HttpClient = HttpClient { + install(JsonFeature) { + serializer = KotlinxSerializer(Json(getJsonConfiguration())) + } +} + +@OptIn(UnstableDefault::class) +internal fun getJsonConfiguration() = JsonConfiguration( + ignoreUnknownKeys = true +) diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt index 4d6377a..602a1ab 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt @@ -19,11 +19,11 @@ package net.taler.wallet.kotlin.crypto import net.taler.wallet.kotlin.Amount import net.taler.wallet.kotlin.Base32Crockford import net.taler.wallet.kotlin.CoinRecord -import net.taler.wallet.kotlin.DenominationRecord import net.taler.wallet.kotlin.Timestamp import net.taler.wallet.kotlin.crypto.Signature.Companion.WALLET_COIN_LINK import net.taler.wallet.kotlin.crypto.Signature.Companion.WALLET_COIN_MELT import net.taler.wallet.kotlin.crypto.Signature.PurposeBuilder +import net.taler.wallet.kotlin.exchange.DenominationRecord internal class Refresh(private val crypto: Crypto) { diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt index 5e4a65e..ee1dff4 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Signature.kt @@ -17,9 +17,9 @@ package net.taler.wallet.kotlin.crypto import net.taler.wallet.kotlin.Base32Crockford -import net.taler.wallet.kotlin.DenominationRecord import net.taler.wallet.kotlin.WireFee import net.taler.wallet.kotlin.crypto.CryptoImpl.Companion.toByteArray +import net.taler.wallet.kotlin.exchange.DenominationRecord internal class Signature(private val crypto: Crypto) { diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Auditor.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Auditor.kt new file mode 100644 index 0000000..4df0bdf --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Auditor.kt @@ -0,0 +1,56 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin.exchange + +import kotlinx.serialization.Serializable + +/** + * Auditor information as given by the exchange in /keys. + */ +@Serializable +data class Auditor( + /** + * Auditor's public key. + */ + val auditor_pub: String, + + /** + * Base URL of the auditor. + */ + val auditor_url: String, + + /** + * List of signatures for denominations by the auditor. + */ + val denomination_keys: List<AuditorDenomSig> +) + +/** + * Signature by the auditor that a particular denomination key is audited. + */ +@Serializable +data class AuditorDenomSig( + /** + * Denomination public key's hash. + */ + val denom_pub_h: String, + + /** + * The signature. + */ + val auditor_sig: String +) diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt new file mode 100644 index 0000000..4124c30 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt @@ -0,0 +1,142 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin.exchange + +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import net.taler.wallet.kotlin.Amount +import net.taler.wallet.kotlin.Base32Crockford +import net.taler.wallet.kotlin.Db +import net.taler.wallet.kotlin.DbFactory +import net.taler.wallet.kotlin.Timestamp +import net.taler.wallet.kotlin.compareVersions +import net.taler.wallet.kotlin.crypto.Crypto +import net.taler.wallet.kotlin.crypto.CryptoFactory +import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified +import net.taler.wallet.kotlin.exchange.ExchangeUpdateReason.Initial +import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchKeys +import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchWire +import net.taler.wallet.kotlin.getDefaultHttpClient + +internal class Exchange( + private val crypto: Crypto = CryptoFactory.getCrypto(), + private val httpClient: HttpClient = getDefaultHttpClient(), + private val db: Db = DbFactory().openDb() +) { + + companion object { + const val PROTOCOL_VERSION = "7:0:0" + fun normalizeUrl(exchangeBaseUrl: String): String { + var url = exchangeBaseUrl + if (!url.startsWith("http")) url = "http://$url" + if (!url.endsWith("/")) url = "$url/" + // TODO also remove query and hash + return url + } + } + + suspend fun updateFromUrl(baseUrl: String): ExchangeRecord { + val now = Timestamp.now() + val url = normalizeUrl(baseUrl) + var record = db.getExchangeByBaseUrl(url) ?: ExchangeRecord( + baseUrl = url, + timestampAdded = now, + updateStatus = FetchKeys, + updateStarted = now, + updateReason = Initial + ).also { db.put(it) } + record = updateKeys(record) + // TODO update wire + // TODO update ToS + return record + } + + /** + * Fetch the exchange's /keys and update database accordingly. + * + * Exceptions thrown in this method must be caught and reported in the pending operations. + */ + internal suspend fun updateKeys(record: ExchangeRecord): ExchangeRecord { + val keys: Keys = fetchKeys(record.baseUrl) + // check if there are denominations offered + // TODO provide more error information for catcher + if (keys.denoms.isEmpty()) { + throw Error("Exchange doesn't offer any denominations") + } + // check if the exchange version is compatible + val versionMatch = compareVersions(PROTOCOL_VERSION, keys.version) + if (versionMatch == null || !versionMatch.compatible) { + throw Error("Exchange protocol version not compatible with wallet") + } + val currency = keys.denoms[0].value.currency + val newDenominations = keys.denoms.map { d -> + getDenominationRecord(record.baseUrl, currency, d) + } + // update exchange details + val details = ExchangeDetails( + auditors = keys.auditors, + currency = currency, + lastUpdateTime = keys.list_issue_date, + masterPublicKey = keys.master_public_key, + protocolVersion = keys.version, + signingKeys = keys.signkeys + ) + val updatedRecord = record.copy(details = details, updateStatus = FetchWire) + for (newDenomination in newDenominations) { + // TODO check oldDenominations and do consistency checks + db.put(newDenomination) + } + + // TODO handle keys.recoup + + return updatedRecord + } + + /** + * Fetch an exchange's /keys with the given normalized base URL. + * + * Visible for testing. + */ + internal suspend fun fetchKeys(baseUrl: String): Keys { + return httpClient.get("${baseUrl}keys") + } + + /** + * Turn an exchange's denominations from /keys into [DenominationRecord]s + * + * Visible for testing. + */ + internal fun getDenominationRecord(baseUrl: String, currency: String, d: Denomination): DenominationRecord { + checkCurrency(currency, d.value) + checkCurrency(currency, d.fee_refund) + checkCurrency(currency, d.fee_withdraw) + checkCurrency(currency, d.fee_refresh) + checkCurrency(currency, d.fee_deposit) + return d.toDenominationRecord( + baseUrl = baseUrl, + denomPubHash = crypto.sha512(Base32Crockford.decode(d.denom_pub)), + isOffered = true, + isRevoked = false, + status = Unverified + ) + } + + private fun checkCurrency(currency: String, amount: Amount) { + if (currency != amount.currency) throw Error("Expected currency $currency, but found ${amount.currency}") + } + +} diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt new file mode 100644 index 0000000..d882249 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt @@ -0,0 +1,234 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin.exchange + +import net.taler.wallet.kotlin.Amount +import net.taler.wallet.kotlin.Timestamp +import net.taler.wallet.kotlin.WireFee + +/** + * Exchange record as stored in the wallet's database. + */ +data class ExchangeRecord( + /** + * Base url of the exchange. + */ + val baseUrl: String, + + /** + * Was the exchange added as a built-in exchange? + */ + val builtIn: Boolean = false, + + /** + * Details, once known. + */ + val details: ExchangeDetails? = null, + + /** + * Mapping from wire method type to the wire fee. + */ + val wireInfo: ExchangeWireInfo? = null, + + /** + * When was the exchange added to the wallet? + */ + val timestampAdded: Timestamp, + + /** + * Terms of service text or undefined if not downloaded yet. + */ + val termsOfServiceText: String? = null, + + /** + * ETag for last terms of service download. + */ + val termsOfServiceLastEtag: String? = null, + + /** + * ETag for last terms of service download. + */ + val termsOfServiceAcceptedEtag: String? = null, + + /** + * ETag for last terms of service download. + */ + val termsOfServiceAcceptedTimestamp: Timestamp? = null, + + /** + * Time when the update to the exchange has been started or + * undefined if no update is in progress. + */ + val updateStarted: Timestamp? = null, + + val updateStatus: ExchangeUpdateStatus, + + val updateReason: ExchangeUpdateReason? = null +) { + init { + check(baseUrl == Exchange.normalizeUrl(baseUrl)) { "Base URL was not normalized" } + } +} + +/** + * Details about the exchange that we only know after querying /keys and /wire. + */ +data class ExchangeDetails( + /** + * Master public key of the exchange. + */ + val masterPublicKey: String, + + /** + * Auditors (partially) auditing the exchange. + */ + val auditors: List<Auditor>, + + /** + * Currency that the exchange offers. + */ + val currency: String, + + /** + * Last observed protocol version. + */ + val protocolVersion: String, + + /** + * Signing keys we got from the exchange, can also contain + * older signing keys that are not returned by /keys anymore. + */ + val signingKeys: List<SigningKey>, + + /** + * Timestamp for last update. + */ + val lastUpdateTime: Timestamp +) + +data class ExchangeWireInfo( + val feesForType: Map<String, List<WireFee>>, + val accounts: List<ExchangeBankAccount> +) + +data class ExchangeBankAccount( + val paytoUri: String +) + +sealed class ExchangeUpdateStatus(val value: String) { + object FetchKeys: ExchangeUpdateStatus("fetch-keys") + object FetchWire: ExchangeUpdateStatus("fetch-wire") + object FetchTerms: ExchangeUpdateStatus("fetch-terms") + object FinalizeUpdate: ExchangeUpdateStatus("finalize-update") + object Finished: ExchangeUpdateStatus("finished") +} + +sealed class ExchangeUpdateReason(val value: String) { + object Initial: ExchangeUpdateReason("initial") + object Forced: ExchangeUpdateReason("forced") + object Scheduled: ExchangeUpdateReason("scheduled") +} + +data class DenominationRecord( + /** + * Value of one coin of the denomination. + */ + val value: Amount, + /** + * The denomination public key. + */ + val denomPub: String, + /** + * Hash of the denomination public key. + * Stored in the database for faster lookups. + */ + val denomPubHash: String, + /** + * Fee for withdrawing. + */ + val feeWithdraw: Amount, + /** + * Fee for depositing. + */ + val feeDeposit: Amount, + /** + * Fee for refreshing. + */ + val feeRefresh: Amount, + /** + * Fee for refunding. + */ + val feeRefund: Amount, + /** + * Validity start date of the denomination. + */ + val stampStart: Timestamp, + /** + * Date after which the currency can't be withdrawn anymore. + */ + val stampExpireWithdraw: Timestamp, + /** + * Date after the denomination officially doesn't exist anymore. + */ + val stampExpireLegal: Timestamp, + /** + * Data after which coins of this denomination can't be deposited anymore. + */ + val stampExpireDeposit: Timestamp, + /** + * Signature by the exchange's master key over the denomination + * information. + */ + val masterSig: String, + /** + * Did we verify the signature on the denomination? + */ + val status: DenominationStatus, + /** + * Was this denomination still offered by the exchange the last time + * we checked? + * Only false when the exchange redacts a previously published denomination. + */ + val isOffered: Boolean, + /** + * Did the exchange revoke the denomination? + * When this field is set to true in the database, the same transaction + * should also mark all affected coins as revoked. + */ + val isRevoked: Boolean, + /** + * Base URL of the exchange. + */ + val exchangeBaseUrl: String +) + +enum class DenominationStatus { + /** + * Verification was delayed. + */ + Unverified, + + /** + * Verified as valid. + */ + VerifiedGood, + + /** + * Verified as invalid. + */ + VerifiedBad +} diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt new file mode 100644 index 0000000..c7ea966 --- /dev/null +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt @@ -0,0 +1,177 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin.exchange + +import kotlinx.serialization.Serializable +import net.taler.wallet.kotlin.Amount +import net.taler.wallet.kotlin.Base32Crockford +import net.taler.wallet.kotlin.Timestamp + +/** + * Structure that the exchange gives us in /keys. + */ +@Serializable +internal data class Keys( + /** + * List of offered denominations. + */ + val denoms: List<Denomination>, + + /** + * The exchange's master public key. + */ + val master_public_key: String, + + /** + * The list of auditors (partially) auditing the exchange. + */ + val auditors: List<Auditor>, + + /** + * Timestamp when this response was issued. + */ + val list_issue_date: Timestamp, + + /** + * List of revoked denominations. + */ + val recoup: List<Recoup>?, + + /** + * Short-lived signing keys used to sign online + * responses. + */ + val signkeys: List<SigningKey>, + + /** + * Protocol version. + */ + val version: String +) + +/** + * Structure of one exchange signing key in the /keys response. + */ +@Serializable +data class SigningKey( + val stamp_start: Timestamp, + val stamp_expire: Timestamp, + val stamp_end: Timestamp, + val key: String, + val master_sig: String +) + +/** + * Denomination as found in the /keys response from the exchange. + */ +@Serializable +internal data class Denomination( + /** + * Value of one coin of the denomination. + */ + val value: Amount, + + /** + * Public signing key of the denomination. + */ + val denom_pub: String, + + /** + * Fee for withdrawing. + */ + val fee_withdraw: Amount, + + /** + * Fee for depositing. + */ + val fee_deposit: Amount, + + /** + * Fee for refreshing. + */ + val fee_refresh: Amount, + + /** + * Fee for refunding. + */ + val fee_refund: Amount, + + /** + * Start date from which withdraw is allowed. + */ + val stamp_start: Timestamp, + + /** + * End date for withdrawing. + */ + val stamp_expire_withdraw: Timestamp, + + /** + * Expiration date after which the exchange can forget about + * the currency. + */ + val stamp_expire_legal: Timestamp, + + /** + * Date after which the coins of this denomination can't be + * deposited anymore. + */ + val stamp_expire_deposit: Timestamp, + + /** + * Signature over the denomination information by the exchange's master + * signing key. + */ + val master_sig: String +) { + fun toDenominationRecord( + baseUrl: String, + denomPubHash: ByteArray, + isOffered: Boolean, + isRevoked: Boolean, + status: DenominationStatus + ): DenominationRecord = DenominationRecord( + denomPub = denom_pub, + denomPubHash = Base32Crockford.encode(denomPubHash), + exchangeBaseUrl = baseUrl, + feeDeposit = fee_deposit, + feeRefresh = fee_refresh, + feeRefund = fee_refund, + feeWithdraw = fee_withdraw, + isOffered = isOffered, + isRevoked = isRevoked, + masterSig = master_sig, + stampExpireDeposit = stamp_expire_deposit, + stampExpireLegal = stamp_expire_legal, + stampExpireWithdraw = stamp_expire_withdraw, + stampStart = stamp_start, + status = status, + value = value + ) +} + +/** + * Element of the payback list that the + * exchange gives us in /keys. + */ +@Serializable +data class Recoup( + /** + * The hash of the denomination public key for which the payback is offered. + */ + val h_denom_pub: String +) diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt index b002e32..f7064bf 100644 --- a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt +++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt @@ -17,17 +17,14 @@ package net.taler.wallet.kotlin.operations import io.ktor.client.HttpClient -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.json.serializer.KotlinxSerializer import io.ktor.client.request.get import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.taler.wallet.kotlin.Amount import net.taler.wallet.kotlin.TalerUri.parseWithdrawUri +import net.taler.wallet.kotlin.getDefaultHttpClient -class Withdraw( - private val httpClient: HttpClient = HttpClient { install(JsonFeature) { serializer = KotlinxSerializer() } } -) { +class Withdraw(private val httpClient: HttpClient = getDefaultHttpClient()) { data class BankDetails( val amount: Amount, diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt new file mode 100644 index 0000000..7acc2a5 --- /dev/null +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt @@ -0,0 +1,67 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin + +import net.taler.wallet.kotlin.exchange.ExchangeRecord +import net.taler.wallet.kotlin.exchange.ExchangeUpdateReason.Initial +import net.taler.wallet.kotlin.exchange.ExchangeUpdateStatus.FetchKeys +import kotlin.test.Test +import kotlin.test.assertEquals + +class DbTest { + + private val dbFactory = DbFactory() + + private val exchange1 = ExchangeRecord( + baseUrl = "https://example1.org/", + timestampAdded = Timestamp.now(), + updateStatus = FetchKeys, + updateStarted = Timestamp.now(), + updateReason = Initial + ) + private val exchange2 = ExchangeRecord( + baseUrl = "https://example2.org/", + timestampAdded = Timestamp.now(), + updateStatus = FetchKeys, + updateStarted = Timestamp.now(), + updateReason = Initial + ) + + @Test + fun test() = runCoroutine { + val db = dbFactory.openDb() + var exchanges = db.listExchanges() + assertEquals(0, exchanges.size) + + db.put(exchange1) + exchanges = db.listExchanges() + assertEquals(1, exchanges.size) + assertEquals(exchange1, exchanges[0]) + + db.put(exchange2) + exchanges = db.listExchanges() + assertEquals(2, exchanges.size) + assertEquals(exchange1, db.getExchangeByBaseUrl(exchange1.baseUrl)) + assertEquals(exchange2, db.getExchangeByBaseUrl(exchange2.baseUrl)) + + db.deleteExchangeByBaseUrl(exchange1.baseUrl) + exchanges = db.listExchanges() + assertEquals(1, exchanges.size) + assertEquals(exchange2, exchanges[0]) + } + +}
\ No newline at end of file diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt index 857c56c..72e4b4b 100644 --- a/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt @@ -22,6 +22,7 @@ import io.ktor.client.engine.mock.MockEngineConfig import io.ktor.client.engine.mock.respond import io.ktor.client.engine.mock.respondError import io.ktor.client.features.json.JsonFeature +import io.ktor.client.features.json.serializer.KotlinxSerializer import io.ktor.http.ContentType.Application import io.ktor.http.HttpStatusCode.Companion.InternalServerError import io.ktor.http.Url @@ -29,14 +30,25 @@ import io.ktor.http.fullPath import io.ktor.http.headersOf import io.ktor.http.hostWithPort import kotlinx.coroutines.CoroutineScope +import kotlinx.serialization.json.Json + +enum class PlatformTarget { + ANDROID, + JS, + NATIVE_LINUX, +} /** * Workaround to use suspending functions in unit tests */ expect fun runCoroutine(block: suspend (scope: CoroutineScope) -> Unit) +expect fun getPlatformTarget(): PlatformTarget + fun getMockHttpClient(): HttpClient = HttpClient(MockEngine) { - install(JsonFeature) + install(JsonFeature) { + serializer = KotlinxSerializer(Json(getJsonConfiguration())) + } engine { addHandler { error("No test handler added") } } diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt index 48cbc8d..02d9b1d 100644 --- a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/SignatureTest.kt @@ -18,12 +18,12 @@ package net.taler.wallet.kotlin.crypto import net.taler.wallet.kotlin.Amount import net.taler.wallet.kotlin.Base32Crockford -import net.taler.wallet.kotlin.DenominationRecord -import net.taler.wallet.kotlin.DenominationStatus.Unverified -import net.taler.wallet.kotlin.DenominationStatus.VerifiedBad import net.taler.wallet.kotlin.Timestamp import net.taler.wallet.kotlin.WireFee import net.taler.wallet.kotlin.crypto.Signature.PurposeBuilder +import net.taler.wallet.kotlin.exchange.DenominationRecord +import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified +import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt new file mode 100644 index 0000000..e7a2c48 --- /dev/null +++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/ExchangeTest.kt @@ -0,0 +1,317 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin.exchange + +import net.taler.wallet.kotlin.Amount +import net.taler.wallet.kotlin.Timestamp +import net.taler.wallet.kotlin.getMockHttpClient +import net.taler.wallet.kotlin.giveJsonResponse +import net.taler.wallet.kotlin.runCoroutine +import kotlin.test.Test +import kotlin.test.assertEquals + +class ExchangeTest { + + private val httpClient = getMockHttpClient() + private val exchange = Exchange(httpClient = httpClient) + + @Test + fun testFetchKeys() { + val expectedKeys = Keys( + denoms = listOf( + Denomination( + value = Amount.fromJSONString("TESTKUDOS:5"), + denom_pub = "040000Z9TH9RPTA1BXF6Z89HM7JGXTPD5G8NNBWQWF7RWQGNAATN84QBWME1TGSWZ79WPQ62S2W2VHG2XBH66JDJ0KM8Q2FQ3FGBZQGNJVFNA9F66E6S3P36KTMWMKWDXWM9EX1YHSHQ841AHRR8JVDY96CZ13AJF6JW95K59AE8CSTH5ZS9NVS0102X92GK8JW2QX2S4EE25QNHK6XMXH3944QMXPFS7SFCMV623BM62VNPVX8JM424YXPJ09TXZAH2CF3QM5HDVRSTDRDGVBF6KZVRFM852TMVMYPVGFA9YQF6HWNJ8H5VCQ3Z9WWNMQ3T76X4F1P6W2J266K8B3W9HKW2WJNK3XHRAVC4GCF07TC0ZNAT0EDAAKV429YAXWSK952BPTY98GVP5XZQG2SE0Q5CF3PV04002", + fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"), + stamp_start = Timestamp(1582484881000), + stamp_expire_withdraw = Timestamp(1677092881000), + stamp_expire_legal = Timestamp(1897844881000), + stamp_expire_deposit = Timestamp(1740164881000), + master_sig = "2SDD44VVBD52XEV0A9R878BC60J51VKK0H5ZS6CPJ7Z738A8V4KPXCF70KFZAY2567400C2GEWNNVXF6PYD7HKX3D2M63WCNPJSE010" + ), + Denomination( + value = Amount.fromJSONString("TESTKUDOS:2"), + denom_pub = "040000XV91V0M7H906Y7R371YX2XAK1V5B2TRFD8ZM9WYJ495TP08NCVEDNFXS2KZBJR4808VZ52PNNQSYVQ2T3J7867MZQY1QZ9N8YQWQWCKSYAY8A07E5SYAK0G0KRTCN5VZ7JXE2YCNT7Q3RT9TGAZBSK5V1ZRRK6HX4C1YFKPWWP4TBVJ8DJMS43WKR4CR4S9T02YXVGR6GSDMR7GHBD89JHCEQ8V2K58Y5XVDGRRQYNBG9Q5XWDMV7GKN24JCPCEKSZZP5XYPXYJX2Z2JZ179M9FQV0PEWFJ4DP7AP14XE54FH97YP9398KA31ECVDY7PHMKMZ6E79Z9FSCXH3WSCXMHBWFGWPRG5ZG1P6HR71VKH4F0Y998JNE4G40JH2VSXP3035AR7HAMJGB56CYHH60EWD904002", + fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"), + stamp_start = Timestamp(1582484881000), + stamp_expire_withdraw = Timestamp(1677092881000), + stamp_expire_legal = Timestamp(1897844881000), + stamp_expire_deposit = Timestamp(1740164881000), + master_sig = "WX1RDTW1W1K7FPFQQ9MCJ8V5CJP8ZX2SGHQG2YS34BY8AMSR3YQ92HE2HT1JMP4W06J63RZ0BR2MKDBX54GV6QKD4R00N9HXN2S283R" + ), + Denomination( + value = Amount.fromJSONString("TESTKUDOS:1"), + denom_pub = "040000XCBJE9TDDZATYSDR51D0DKMY5NW8FMJ8YQ1Y4F40SPPTKFMD3FWH38NSQZ1YB621TCFH5RBN5J3SFR5SG4789G27FA90E605GG9AXYTXXPJ9HYPAVAVS6V4XCSC17HKX2M2NSX5D0PPETDGKQD04G498VS36YY4WTB5SYG4SV9MKPVZ5WG2WNP3MA77TFZSHK5HBHZBEW0S1TRKGSCDNBRHYB240M84YM1Y7EJ7BXKJK4GRR1GS16DJ2RA1YEQ1AAXH0GP6RRAEJE8D2JFSH05P3KR1GB97NMX6VD8DCAX45416F888EYQR4M6R820FJVZ6FYV9CCMZ3M10B64N6G4QFNKFNAV2ENPVVG4A3R0AAA6STJ7E5Z05GEKW35SHM14HY9CEGM7D1ZEKHZJYA9P6WH504002", + fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"), + stamp_start = Timestamp(1582484881000), + stamp_expire_withdraw = Timestamp(1677092881000), + stamp_expire_legal = Timestamp(1897844881000), + stamp_expire_deposit = Timestamp(1740164881000), + master_sig = "65G9FWQPA4YKJEM7D37079D4MY81D47KD1280RG7BRH85XZQ2N13FJPV9N8AEASK9CTGNX1HKX0GTRBJ5C49H4YRY0E4CYVPNH06W18" + ), + Denomination( + value = Amount.fromJSONString("TESTKUDOS:10"), + denom_pub = "040000XZDZK4BPPPXR7MYKK2AF4WF95EH3VF8WEX7WDX4HEWXSB5XX10N4V5RHFSK0TSBKNC9CRNVGK3WJ42S3Z9SB4Q3M4DQQ7DKCGKED6WBKENHT8JX51K1VR5JKCMAFBNM6DR5MNRGKFC2MDRQ0Y4BCXHKEMRD65C6JPBKYW9HJH66FGT22WMBV0AV7P60CKR13MQG6FKWW3TZW3XXHVY2VX9MJN6VQFPS6NQGGTNXZV2SK2X5MJAJME7RN9BNZ5ZBTW1CYMVCHBSVGBFPRC68W78PW44VP402VD12KG2AWKPD4DRBAA85HM1DN1KADYQ498QHYGEB3T3HH990HRV8PSNBGYCHB87JTVYMJ4N2PSP2FCX0H6FRTW1FQY05EB7D8BFXM95DNRCHVQSHBZ9RP7NZFA304002", + fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"), + stamp_start = Timestamp(1582484881000), + stamp_expire_withdraw = Timestamp(1677092881000), + stamp_expire_legal = Timestamp(1897844881000), + stamp_expire_deposit = Timestamp(1740164881000), + master_sig = "M423J7CJPACTPBYCFVR87B44JAJKAB2ME8C263WGHJSA8V8444SX428MVC9NF4GD08CKS9HY0WB4B8SEZ3HJFWKXNSH80RBJXQC822G" + ), + Denomination( + value = Amount.fromJSONString("TESTKUDOS:0.1"), + denom_pub = "040000YKYFF6GX979JS10MEZ16BQ7TT6XBTE0TBX6VJ9KSG7K4D91SWJVDETNKQJXAFK9SAB3S31FZFA0Y0X22TKRXKCT7Z4GZCCRJJ12T1A5M4DWRTZDFRD3FE495NXHVPFM96KXMKH1HABTDDFZN0NWQ3NBJ6GNXD40NJ95E955X948JHBDJZWM3TEAK4XFJX8056XFDHVNXSF4VN14RR1WD1J5K7JPS61SKRNF3HT6NZA823PZW2KPV2KVBMMP615A922ZNJGVQDTW5TYWTK5DCBGG1YEKQRYF39NX9X722FZK98BTMHHH6WZFCKBT096G9BKSHSJW3VE8KKPCN8XGWYYPD3158HRKSA28BJQ9XJVVB6FDCGZ154WWGGSGW82BDYDH7ZHJBMS046AG0ND4ZCVR2JQ04002", + fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"), + stamp_start = Timestamp(1582484881000), + stamp_expire_withdraw = Timestamp(1677092881000), + stamp_expire_legal = Timestamp(1897844881000), + stamp_expire_deposit = Timestamp(1740164881000), + master_sig = "RKZKGR0KYKY0VZ26339DZKV8EZJ2HRRQMFSAJDHBG3YHEQNZFHKM8WPYCH9WHXTWBB10GQN9QJKFDJJF2H6D5FT801GF87G153PTJ18" + ), + Denomination( + value = Amount.fromJSONString("TESTKUDOS:1000"), + denom_pub = "040000Y9PBY1HPBDD4KSK9PBA86TRY13JQ4284T4E0G4SEREQ8YM88PZHKW1ACKT1RTWVTBXX83G54NFVYRJQX9PTDXDJ1CXSS42G8NYMW97NA6NNNASV69W1JX39NTS1NVKXPW4WMBASATSNBTXHRT92FFN2NAJFGK876BNN3TPTH57C76ADAQV43VFF7CYAWWNYZAYGQQ1XY1NK34FJD778VFGYCZ1G9J8XPNB92ZKJBZEZKSNBRNH27GM5A736AFSGP7B4JSCGD0F4FMD1PDVB26MM9ZK8C1TDKXQ5DJ09AQQ55P7Q3A133ASPGBH6SCJTJYH8C9A451B0SP4GDX2ZFRSX5FP93PY4VKEB36KCAQ5E2MRZNWFB6T0JK0W7Z7NXP5FW2VQ4PNV7B2NQ3WFMCVRSDSV04002", + fee_withdraw = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_deposit = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refresh = Amount.fromJSONString("TESTKUDOS:0.01"), + fee_refund = Amount.fromJSONString("TESTKUDOS:0.01"), + stamp_start = Timestamp(1582484881000), + stamp_expire_withdraw = Timestamp(1677092881000), + stamp_expire_legal = Timestamp(1897844881000), + stamp_expire_deposit = Timestamp(1740164881000), + master_sig = "FJPQKECRKVQSTB9Y983KDGD65Z1JHQKNSCC6YPMBN3Z4VW0AGC5MQM9BPB0YYD1SCMETPD6QB4X80HWE0ZDGWNZB1KND5TP567T4G3G" + ) + ), + master_public_key = "DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG", + auditors = emptyList(), + list_issue_date = Timestamp(1592161681000), + recoup = emptyList(), + signkeys = listOf( + SigningKey( + stamp_start = Timestamp(1592161681000), + stamp_expire = Timestamp(1594580881000), + stamp_end = Timestamp(1655233681000), + key = "0FMRBH8FZYYMSQ2RHTYYGK2BV33JVSW6MTYCV7Y833GVNXFDYK10", + master_sig = "368HV41Z4FNDXQ7EP6TNAMBSKP44PJAZW27EPH7XJNVG2A6HZQM7ZPMCB6B30HG50S95YD1K2BAJVPEYMGF2DR7EEY0NFBQZZ1B8P1G" + ), + SigningKey( + stamp_start = Timestamp(1594580881000), + stamp_expire = Timestamp(1597000081000), + stamp_end = Timestamp(1657652881000), + key = "XMNYM62DQW0XDQACCYDMFTM5GY7SZST60NH7XS9GY18H8Q9N7QN0", + master_sig = "4HRJN36VVJ87ZC2HZXP7QDSZN30YQE8FCNWZS3RCA1HGNY9Q0JPMVJZ79RDHKS4GYXV29PM27DGCN0VB0BCZFF2FC6FMF3A6ZNKC238" + ) + ), + version = "7:0:0" + ) + + httpClient.giveJsonResponse("https://exchange.test.taler.net/keys") { + """{ + "version": "7:0:0", + "master_public_key": "DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG", + "reserve_closing_delay": { + "d_ms": 2419200000 + }, + "signkeys": [ + { + "stamp_start": { + "t_ms": 1592161681000 + }, + "stamp_expire": { + "t_ms": 1594580881000 + }, + "stamp_end": { + "t_ms": 1655233681000 + }, + "master_sig": "368HV41Z4FNDXQ7EP6TNAMBSKP44PJAZW27EPH7XJNVG2A6HZQM7ZPMCB6B30HG50S95YD1K2BAJVPEYMGF2DR7EEY0NFBQZZ1B8P1G", + "key": "0FMRBH8FZYYMSQ2RHTYYGK2BV33JVSW6MTYCV7Y833GVNXFDYK10" + }, + { + "stamp_start": { + "t_ms": 1594580881000 + }, + "stamp_expire": { + "t_ms": 1597000081000 + }, + "stamp_end": { + "t_ms": 1657652881000 + }, + "master_sig": "4HRJN36VVJ87ZC2HZXP7QDSZN30YQE8FCNWZS3RCA1HGNY9Q0JPMVJZ79RDHKS4GYXV29PM27DGCN0VB0BCZFF2FC6FMF3A6ZNKC238", + "key": "XMNYM62DQW0XDQACCYDMFTM5GY7SZST60NH7XS9GY18H8Q9N7QN0" + } + ], + "recoup": [], + "denoms": [ + { + "master_sig": "2SDD44VVBD52XEV0A9R878BC60J51VKK0H5ZS6CPJ7Z738A8V4KPXCF70KFZAY2567400C2GEWNNVXF6PYD7HKX3D2M63WCNPJSE010", + "stamp_start": { + "t_ms": 1582484881000 + }, + "stamp_expire_withdraw": { + "t_ms": 1677092881000 + }, + "stamp_expire_deposit": { + "t_ms": 1740164881000 + }, + "stamp_expire_legal": { + "t_ms": 1897844881000 + }, + "denom_pub": "040000Z9TH9RPTA1BXF6Z89HM7JGXTPD5G8NNBWQWF7RWQGNAATN84QBWME1TGSWZ79WPQ62S2W2VHG2XBH66JDJ0KM8Q2FQ3FGBZQGNJVFNA9F66E6S3P36KTMWMKWDXWM9EX1YHSHQ841AHRR8JVDY96CZ13AJF6JW95K59AE8CSTH5ZS9NVS0102X92GK8JW2QX2S4EE25QNHK6XMXH3944QMXPFS7SFCMV623BM62VNPVX8JM424YXPJ09TXZAH2CF3QM5HDVRSTDRDGVBF6KZVRFM852TMVMYPVGFA9YQF6HWNJ8H5VCQ3Z9WWNMQ3T76X4F1P6W2J266K8B3W9HKW2WJNK3XHRAVC4GCF07TC0ZNAT0EDAAKV429YAXWSK952BPTY98GVP5XZQG2SE0Q5CF3PV04002", + "value": "TESTKUDOS:5", + "fee_withdraw": "TESTKUDOS:0.01", + "fee_deposit": "TESTKUDOS:0.01", + "fee_refresh": "TESTKUDOS:0.01", + "fee_refund": "TESTKUDOS:0.01" + }, + { + "master_sig": "WX1RDTW1W1K7FPFQQ9MCJ8V5CJP8ZX2SGHQG2YS34BY8AMSR3YQ92HE2HT1JMP4W06J63RZ0BR2MKDBX54GV6QKD4R00N9HXN2S283R", + "stamp_start": { + "t_ms": 1582484881000 + }, + "stamp_expire_withdraw": { + "t_ms": 1677092881000 + }, + "stamp_expire_deposit": { + "t_ms": 1740164881000 + }, + "stamp_expire_legal": { + "t_ms": 1897844881000 + }, + "denom_pub": "040000XV91V0M7H906Y7R371YX2XAK1V5B2TRFD8ZM9WYJ495TP08NCVEDNFXS2KZBJR4808VZ52PNNQSYVQ2T3J7867MZQY1QZ9N8YQWQWCKSYAY8A07E5SYAK0G0KRTCN5VZ7JXE2YCNT7Q3RT9TGAZBSK5V1ZRRK6HX4C1YFKPWWP4TBVJ8DJMS43WKR4CR4S9T02YXVGR6GSDMR7GHBD89JHCEQ8V2K58Y5XVDGRRQYNBG9Q5XWDMV7GKN24JCPCEKSZZP5XYPXYJX2Z2JZ179M9FQV0PEWFJ4DP7AP14XE54FH97YP9398KA31ECVDY7PHMKMZ6E79Z9FSCXH3WSCXMHBWFGWPRG5ZG1P6HR71VKH4F0Y998JNE4G40JH2VSXP3035AR7HAMJGB56CYHH60EWD904002", + "value": "TESTKUDOS:2", + "fee_withdraw": "TESTKUDOS:0.01", + "fee_deposit": "TESTKUDOS:0.01", + "fee_refresh": "TESTKUDOS:0.01", + "fee_refund": "TESTKUDOS:0.01" + }, + { + "master_sig": "65G9FWQPA4YKJEM7D37079D4MY81D47KD1280RG7BRH85XZQ2N13FJPV9N8AEASK9CTGNX1HKX0GTRBJ5C49H4YRY0E4CYVPNH06W18", + "stamp_start": { + "t_ms": 1582484881000 + }, + "stamp_expire_withdraw": { + "t_ms": 1677092881000 + }, + "stamp_expire_deposit": { + "t_ms": 1740164881000 + }, + "stamp_expire_legal": { + "t_ms": 1897844881000 + }, + "denom_pub": "040000XCBJE9TDDZATYSDR51D0DKMY5NW8FMJ8YQ1Y4F40SPPTKFMD3FWH38NSQZ1YB621TCFH5RBN5J3SFR5SG4789G27FA90E605GG9AXYTXXPJ9HYPAVAVS6V4XCSC17HKX2M2NSX5D0PPETDGKQD04G498VS36YY4WTB5SYG4SV9MKPVZ5WG2WNP3MA77TFZSHK5HBHZBEW0S1TRKGSCDNBRHYB240M84YM1Y7EJ7BXKJK4GRR1GS16DJ2RA1YEQ1AAXH0GP6RRAEJE8D2JFSH05P3KR1GB97NMX6VD8DCAX45416F888EYQR4M6R820FJVZ6FYV9CCMZ3M10B64N6G4QFNKFNAV2ENPVVG4A3R0AAA6STJ7E5Z05GEKW35SHM14HY9CEGM7D1ZEKHZJYA9P6WH504002", + "value": "TESTKUDOS:1", + "fee_withdraw": "TESTKUDOS:0.01", + "fee_deposit": "TESTKUDOS:0.01", + "fee_refresh": "TESTKUDOS:0.01", + "fee_refund": "TESTKUDOS:0.01" + }, + { + "master_sig": "M423J7CJPACTPBYCFVR87B44JAJKAB2ME8C263WGHJSA8V8444SX428MVC9NF4GD08CKS9HY0WB4B8SEZ3HJFWKXNSH80RBJXQC822G", + "stamp_start": { + "t_ms": 1582484881000 + }, + "stamp_expire_withdraw": { + "t_ms": 1677092881000 + }, + "stamp_expire_deposit": { + "t_ms": 1740164881000 + }, + "stamp_expire_legal": { + "t_ms": 1897844881000 + }, + "denom_pub": "040000XZDZK4BPPPXR7MYKK2AF4WF95EH3VF8WEX7WDX4HEWXSB5XX10N4V5RHFSK0TSBKNC9CRNVGK3WJ42S3Z9SB4Q3M4DQQ7DKCGKED6WBKENHT8JX51K1VR5JKCMAFBNM6DR5MNRGKFC2MDRQ0Y4BCXHKEMRD65C6JPBKYW9HJH66FGT22WMBV0AV7P60CKR13MQG6FKWW3TZW3XXHVY2VX9MJN6VQFPS6NQGGTNXZV2SK2X5MJAJME7RN9BNZ5ZBTW1CYMVCHBSVGBFPRC68W78PW44VP402VD12KG2AWKPD4DRBAA85HM1DN1KADYQ498QHYGEB3T3HH990HRV8PSNBGYCHB87JTVYMJ4N2PSP2FCX0H6FRTW1FQY05EB7D8BFXM95DNRCHVQSHBZ9RP7NZFA304002", + "value": "TESTKUDOS:10", + "fee_withdraw": "TESTKUDOS:0.01", + "fee_deposit": "TESTKUDOS:0.01", + "fee_refresh": "TESTKUDOS:0.01", + "fee_refund": "TESTKUDOS:0.01" + }, + { + "master_sig": "RKZKGR0KYKY0VZ26339DZKV8EZJ2HRRQMFSAJDHBG3YHEQNZFHKM8WPYCH9WHXTWBB10GQN9QJKFDJJF2H6D5FT801GF87G153PTJ18", + "stamp_start": { + "t_ms": 1582484881000 + }, + "stamp_expire_withdraw": { + "t_ms": 1677092881000 + }, + "stamp_expire_deposit": { + "t_ms": 1740164881000 + }, + "stamp_expire_legal": { + "t_ms": 1897844881000 + }, + "denom_pub": "040000YKYFF6GX979JS10MEZ16BQ7TT6XBTE0TBX6VJ9KSG7K4D91SWJVDETNKQJXAFK9SAB3S31FZFA0Y0X22TKRXKCT7Z4GZCCRJJ12T1A5M4DWRTZDFRD3FE495NXHVPFM96KXMKH1HABTDDFZN0NWQ3NBJ6GNXD40NJ95E955X948JHBDJZWM3TEAK4XFJX8056XFDHVNXSF4VN14RR1WD1J5K7JPS61SKRNF3HT6NZA823PZW2KPV2KVBMMP615A922ZNJGVQDTW5TYWTK5DCBGG1YEKQRYF39NX9X722FZK98BTMHHH6WZFCKBT096G9BKSHSJW3VE8KKPCN8XGWYYPD3158HRKSA28BJQ9XJVVB6FDCGZ154WWGGSGW82BDYDH7ZHJBMS046AG0ND4ZCVR2JQ04002", + "value": "TESTKUDOS:0.1", + "fee_withdraw": "TESTKUDOS:0.01", + "fee_deposit": "TESTKUDOS:0.01", + "fee_refresh": "TESTKUDOS:0.01", + "fee_refund": "TESTKUDOS:0.01" + }, + { + "master_sig": "FJPQKECRKVQSTB9Y983KDGD65Z1JHQKNSCC6YPMBN3Z4VW0AGC5MQM9BPB0YYD1SCMETPD6QB4X80HWE0ZDGWNZB1KND5TP567T4G3G", + "stamp_start": { + "t_ms": 1582484881000 + }, + "stamp_expire_withdraw": { + "t_ms": 1677092881000 + }, + "stamp_expire_deposit": { + "t_ms": 1740164881000 + }, + "stamp_expire_legal": { + "t_ms": 1897844881000 + }, + "denom_pub": "040000Y9PBY1HPBDD4KSK9PBA86TRY13JQ4284T4E0G4SEREQ8YM88PZHKW1ACKT1RTWVTBXX83G54NFVYRJQX9PTDXDJ1CXSS42G8NYMW97NA6NNNASV69W1JX39NTS1NVKXPW4WMBASATSNBTXHRT92FFN2NAJFGK876BNN3TPTH57C76ADAQV43VFF7CYAWWNYZAYGQQ1XY1NK34FJD778VFGYCZ1G9J8XPNB92ZKJBZEZKSNBRNH27GM5A736AFSGP7B4JSCGD0F4FMD1PDVB26MM9ZK8C1TDKXQ5DJ09AQQ55P7Q3A133ASPGBH6SCJTJYH8C9A451B0SP4GDX2ZFRSX5FP93PY4VKEB36KCAQ5E2MRZNWFB6T0JK0W7Z7NXP5FW2VQ4PNV7B2NQ3WFMCVRSDSV04002", + "value": "TESTKUDOS:1000", + "fee_withdraw": "TESTKUDOS:0.01", + "fee_deposit": "TESTKUDOS:0.01", + "fee_refresh": "TESTKUDOS:0.01", + "fee_refund": "TESTKUDOS:0.01" + } + ], + "auditors": [], + "list_issue_date": { + "t_ms": 1592161681000 + }, + "eddsa_pub": "0FMRBH8FZYYMSQ2RHTYYGK2BV33JVSW6MTYCV7Y833GVNXFDYK10", + "eddsa_sig": "2GB384567SZM9CM7RJT51N04D2ZK7NAHWZRT6BA0FFNXTAB71D4T1WVQTXZEPDM07X1MJ46ZBC189SCM4EG4V8TQJRP2WAZCKPAJJ2R" + }""".trimIndent() + } + runCoroutine { + val keys = exchange.fetchKeys("https://exchange.test.taler.net/") + assertEquals(expectedKeys, keys) + } + } + + +}
\ No newline at end of file diff --git a/src/jsMain/kotlin/net/taler/wallet/kotlin/Db.kt b/src/jsMain/kotlin/net/taler/wallet/kotlin/Db.kt new file mode 100644 index 0000000..45cbfc3 --- /dev/null +++ b/src/jsMain/kotlin/net/taler/wallet/kotlin/Db.kt @@ -0,0 +1,23 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin + +internal actual class DbFactory { + actual fun openDb(): Db { + return FakeDb() + } +} diff --git a/src/jsTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt b/src/jsTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt index da5a183..49466e0 100644 --- a/src/jsTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt +++ b/src/jsTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt @@ -21,3 +21,5 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.promise actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit): dynamic = GlobalScope.promise { block(this) } + +actual fun getPlatformTarget(): PlatformTarget = PlatformTarget.JS diff --git a/src/linuxMain/kotlin/net/taler/wallet/kotlin/Db.kt b/src/linuxMain/kotlin/net/taler/wallet/kotlin/Db.kt new file mode 100644 index 0000000..45cbfc3 --- /dev/null +++ b/src/linuxMain/kotlin/net/taler/wallet/kotlin/Db.kt @@ -0,0 +1,23 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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/> + */ + +package net.taler.wallet.kotlin + +internal actual class DbFactory { + actual fun openDb(): Db { + return FakeDb() + } +} diff --git a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt b/src/linuxTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt index 524da15..162ce4e 100644 --- a/src/linuxTest/kotlin/net/taler/wallet/kotlin/runCoroutine.kt +++ b/src/linuxTest/kotlin/net/taler/wallet/kotlin/TestUtils.kt @@ -20,3 +20,5 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking actual fun runCoroutine(block: suspend (scope : CoroutineScope) -> Unit) = runBlocking { block(this) } + +actual fun getPlatformTarget(): PlatformTarget = PlatformTarget.NATIVE_LINUX |