gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-kotlin] branch master updated (561eaa7 -> 3e1418b)


From: gnunet
Subject: [taler-wallet-kotlin] branch master updated (561eaa7 -> 3e1418b)
Date: Wed, 15 Jul 2020 22:12:31 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a change to branch master
in repository wallet-kotlin.

    from 561eaa7  Select denominations for withdrawal (with tests)
     new 3704cda  Get withdrawal details from exchange with selected 
denominations
     new 3e1418b  Add payto URI parsing with basic tests

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../net/taler/wallet/kotlin/crypto/RefreshTest.kt  |  12 +-
 .../kotlin/net/taler/wallet/kotlin/PaytoUri.kt     |  45 ++++
 .../net/taler/wallet/kotlin/crypto/Refresh.kt      |  11 +-
 .../taler/wallet/kotlin/exchange/Denomination.kt   | 234 +++++++++++++++++++++
 .../net/taler/wallet/kotlin/exchange/Exchange.kt   |   5 +-
 .../taler/wallet/kotlin/exchange/ExchangeRecord.kt |  95 +--------
 .../net/taler/wallet/kotlin/exchange/Keys.kt       |  91 --------
 .../net/taler/wallet/kotlin/operations/Withdraw.kt | 146 +++++++++++--
 .../kotlin/net/taler/wallet/kotlin/DbTest.kt       |   4 +-
 .../kotlin/net/taler/wallet/kotlin/PaytoUriTest.kt |  58 +++++
 .../wallet/kotlin/exchange/DenominationTest.kt     |  91 ++++++++
 .../wallet/kotlin/{ => exchange}/Denominations.kt  |   8 +-
 .../taler/wallet/kotlin/operations/WithdrawTest.kt |  58 ++---
 13 files changed, 603 insertions(+), 255 deletions(-)
 create mode 100644 src/commonMain/kotlin/net/taler/wallet/kotlin/PaytoUri.kt
 create mode 100644 
src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Denomination.kt
 create mode 100644 
src/commonTest/kotlin/net/taler/wallet/kotlin/PaytoUriTest.kt
 create mode 100644 
src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/DenominationTest.kt
 rename src/commonTest/kotlin/net/taler/wallet/kotlin/{ => 
exchange}/Denominations.kt (97%)

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 4ed903e..6cdad75 100644
--- a/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
+++ b/src/androidTest/kotlin/net/taler/wallet/kotlin/crypto/RefreshTest.kt
@@ -22,12 +22,12 @@ 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.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 net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
@@ -83,7 +83,7 @@ class RefreshTest {
                                 stampExpireLegal = Timestamp(1688304984000),
                                 stampExpireWithdraw = Timestamp(1594301784000),
                                 stampStart = Timestamp(1593696984000),
-                                status = DenominationStatus.VerifiedGood,
+                                status = VerifiedGood,
                                 value = Amount("TESTKUDOS", fraction = 0, 
value = 1)
                             )
                         ),
@@ -104,7 +104,7 @@ class RefreshTest {
                                 stampExpireLegal = Timestamp(1688304984000),
                                 stampExpireWithdraw = Timestamp(1594301784000),
                                 stampStart = Timestamp(1593696984000),
-                                status = DenominationStatus.VerifiedGood,
+                                status = VerifiedGood,
                                 value = Amount("TESTKUDOS", fraction = 
10000000, value = 0)
                             )
                         ),
@@ -125,7 +125,7 @@ class RefreshTest {
                                 stampExpireLegal = Timestamp(1688304984000),
                                 stampExpireWithdraw = Timestamp(1594301784000),
                                 stampStart = Timestamp(1593696984000),
-                                status = DenominationStatus.VerifiedGood,
+                                status = VerifiedGood,
                                 value = Amount("TESTKUDOS", fraction = 
1000000, value = 0)
                             )
                         )
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/PaytoUri.kt 
b/src/commonMain/kotlin/net/taler/wallet/kotlin/PaytoUri.kt
new file mode 100644
index 0000000..f6b11d2
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/PaytoUri.kt
@@ -0,0 +1,45 @@
+/*
+ * 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
+
+data class PaytoUri(
+    val targetType: String,
+    val targetPath: String,
+    val params: Map<String, String>
+) {
+    companion object {
+        private const val SCHEMA = "payto://"
+        fun fromString(s: String): PaytoUri? {
+            if (!s.startsWith(SCHEMA)) return null
+            val rest = s.slice(SCHEMA.length until s.length).split('?')
+            val account = rest[0]
+            val query = if (rest.size > 1) rest[1] else null
+            val firstSlashPos = account.indexOf('/')
+            if (firstSlashPos == -1) return null
+            return PaytoUri(
+                targetType = account.slice(0 until firstSlashPos),
+                targetPath = account.slice((firstSlashPos + 1) until 
account.length),
+                params = HashMap<String, String>().apply {
+                    query?.split('&')?.forEach {
+                        val field = it.split('=')
+                        if (field.size > 1) put(field[0], field[1])
+                    }
+                }
+            )
+        } // end fromString()
+    }
+}
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 602a1ab..cd24b07 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Refresh.kt
@@ -23,7 +23,8 @@ 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
+import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
 
 internal class Refresh(private val crypto: Crypto) {
 
@@ -119,14 +120,6 @@ internal class Refresh(private val crypto: Crypto) {
         val blindingKey: String
     )
 
-    data class DenominationSelectionInfo(
-        val totalCoinValue: Amount,
-        val totalWithdrawCost: Amount,
-        val selectedDenominations: List<SelectedDenomination>
-    )
-
-    data class SelectedDenomination(val count: Int, val denominationRecord: 
DenominationRecord)
-
     /**
      * Create a new refresh session.
      */
diff --git 
a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Denomination.kt 
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Denomination.kt
new file mode 100644
index 0000000..88a81fd
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Denomination.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 kotlinx.serialization.Serializable
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Base32Crockford
+import net.taler.wallet.kotlin.Duration
+import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+
+/**
+ * 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
+        )
+}
+
+enum class DenominationStatus {
+    /**
+     * Verification was delayed.
+     */
+    Unverified,
+
+    /**
+     * Verified as valid.
+     */
+    VerifiedGood,
+
+    /**
+     * Verified as invalid.
+     */
+    VerifiedBad
+}
+
+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
+) {
+    fun isWithdrawable(now: Timestamp = Timestamp.now()): Boolean {
+        if (isRevoked) return false // can not use revoked denomination
+        if (status != Unverified && status != VerifiedGood) return false // 
verified to be bad
+        if (now < stampStart) return false // denomination has not yet started
+        val lastPossibleWithdraw = stampExpireWithdraw - Duration(50 * 1000)
+        if ((lastPossibleWithdraw - now).ms == 0L) return false // 
denomination has expired
+        return true
+    }
+}
+
+data class DenominationSelectionInfo(
+    val totalCoinValue: Amount,
+    val totalWithdrawCost: Amount,
+    val selectedDenominations: List<SelectedDenomination>
+) {
+    fun getEarliestDepositExpiry(): Timestamp {
+        if (selectedDenominations.isEmpty()) return Timestamp(
+            Timestamp.NEVER
+        )
+        var earliest = 
selectedDenominations[0].denominationRecord.stampExpireDeposit
+        for (i in 1 until selectedDenominations.size) {
+            val stampExpireDeposit = 
selectedDenominations[i].denominationRecord.stampExpireDeposit
+            if (stampExpireDeposit < earliest) earliest = stampExpireDeposit
+        }
+        return earliest
+    }
+}
+
+data class SelectedDenomination(val count: Int, val denominationRecord: 
DenominationRecord)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt 
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
index c8a89ef..e4a99b7 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Exchange.kt
@@ -53,7 +53,8 @@ internal class Exchange(
 ) {
 
     companion object {
-        const val PROTOCOL_VERSION = "7:0:0"
+        private const val PROTOCOL_VERSION = "7:0:0"
+        fun getVersionMatch(version: String) = 
compareVersions(PROTOCOL_VERSION, version)
         fun normalizeUrl(exchangeBaseUrl: String): String {
             var url = exchangeBaseUrl
             if (!url.startsWith("http")) url = "http://$url";
@@ -103,7 +104,7 @@ internal class Exchange(
             throw Error("Exchange doesn't offer any denominations")
         }
         // check if the exchange version is compatible
-        val versionMatch = compareVersions(PROTOCOL_VERSION, keys.version)
+        val versionMatch = getVersionMatch(keys.version)
         if (versionMatch == null || !versionMatch.compatible) {
             throw Error("Exchange protocol version not compatible with wallet")
         }
diff --git 
a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt 
b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
index 38d85ec..9bfd649 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/ExchangeRecord.kt
@@ -16,7 +16,6 @@
 
 package net.taler.wallet.kotlin.exchange
 
-import net.taler.wallet.kotlin.Amount
 import net.taler.wallet.kotlin.Timestamp
 
 /**
@@ -86,6 +85,9 @@ data class ExchangeRecord(
     init {
         check(baseUrl == Exchange.normalizeUrl(baseUrl)) { "Base URL was not 
normalized" }
     }
+
+    val termsOfServiceAccepted: Boolean
+        get() = termsOfServiceAcceptedTimestamp != null && 
termsOfServiceAcceptedEtag == termsOfServiceLastEtag
 }
 
 /**
@@ -129,6 +131,7 @@ data class ExchangeWireInfo(
     val accounts: List<ExchangeBankAccount>
 )
 
+// TODO is this class needed?
 data class ExchangeBankAccount(
     val paytoUri: String
 )
@@ -146,93 +149,3 @@ sealed class ExchangeUpdateReason(val value: String) {
     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
index 016f957..54806f9 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/exchange/Keys.kt
@@ -19,8 +19,6 @@ package net.taler.wallet.kotlin.exchange
 import io.ktor.client.HttpClient
 import io.ktor.client.request.get
 import kotlinx.serialization.Serializable
-import net.taler.wallet.kotlin.Amount
-import net.taler.wallet.kotlin.Base32Crockford
 import net.taler.wallet.kotlin.Timestamp
 
 /**
@@ -86,95 +84,6 @@ data class SigningKey(
     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.
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 b73688c..1fa2822 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/operations/Withdraw.kt
@@ -23,22 +23,29 @@ import kotlinx.serialization.Serializable
 import net.taler.wallet.kotlin.Amount
 import net.taler.wallet.kotlin.Db
 import net.taler.wallet.kotlin.DbFactory
-import net.taler.wallet.kotlin.Duration
 import net.taler.wallet.kotlin.TalerUri.parseWithdrawUri
 import net.taler.wallet.kotlin.Timestamp
+import net.taler.wallet.kotlin.VersionMatchResult
+import net.taler.wallet.kotlin.crypto.Crypto
 import net.taler.wallet.kotlin.crypto.CryptoFactory
-import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
-import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
 import net.taler.wallet.kotlin.crypto.Signature
 import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
 import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
 import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.Exchange
+import net.taler.wallet.kotlin.exchange.ExchangeRecord
+import net.taler.wallet.kotlin.exchange.ExchangeWireInfo
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
 import net.taler.wallet.kotlin.getDefaultHttpClient
 
 internal class Withdraw(
     private val httpClient: HttpClient = getDefaultHttpClient(),
     private val db: Db = DbFactory().openDb(),
-    private val signature: Signature = Signature(CryptoFactory.getCrypto())
+    private val crypto: Crypto = CryptoFactory.getCrypto(),
+    private val signature: Signature = Signature(crypto),
+    private val exchange: Exchange = Exchange(crypto, signature, httpClient, 
db = db)
 ) {
 
     data class BankDetails(
@@ -86,6 +93,123 @@ internal class Withdraw(
         return response.toBankDetails(url)
     }
 
+    /**
+     * Information about what will happen when creating a reserve.
+     *
+     * Sent to the wallet frontend to be rendered and shown to the user.
+     */
+    data class WithdrawalDetails(
+        /**
+         * Exchange that the reserve will be created at.
+         */
+        // TODO we probably don't need to include our internal exchange record 
in here
+        val exchange: ExchangeRecord,
+
+        /**
+         * Selected denominations for withdraw.
+         */
+        val selectedDenominations: DenominationSelectionInfo,
+
+        /**
+         * Fees for withdraw.
+         */
+        val withdrawFee: Amount,
+
+        /**
+         * Remaining balance that is too small to be withdrawn.
+         */
+        val overhead: Amount,
+
+        /**
+         * The earliest deposit expiration of the selected coins.
+         */
+        // TODO what is this needed for?
+        val earliestDepositExpiration: Timestamp,
+
+        /**
+         * Number of currently offered denominations.
+         */
+        // TODO what is this needed for?
+        val numOfferedDenoms: Int
+    ) {
+        init {
+            check(exchange.details != null)
+            check(exchange.wireInfo != null)
+        }
+
+        /**
+         * Filtered wire info to send to the bank.
+         */
+        val exchangeWireAccounts: List<String> get() = 
exchange.wireInfo!!.accounts.map { it.paytoUri }
+
+        /**
+         * Wire fees from the exchange.
+         */
+        val wireFees: ExchangeWireInfo get() = exchange.wireInfo!!
+
+        /**
+         * Did the user already accept the current terms of service for the 
exchange?
+         */
+        val termsOfServiceAccepted: Boolean get() = 
exchange.termsOfServiceAccepted
+
+        /**
+         * Result of checking the wallet's version against the exchange's 
version.
+         */
+        val versionMatch: VersionMatchResult?
+            get() = 
Exchange.getVersionMatch(exchange.details!!.protocolVersion)
+
+    }
+
+    internal suspend fun getWithdrawalDetails(exchangeBaseUrl: String, amount: 
Amount): WithdrawalDetails {
+        val exchange = exchange.updateFromUrl(exchangeBaseUrl)
+        check(exchange.details != null)
+        check(exchange.wireInfo != null)
+        val selectedDenominations = selectDenominations(exchange, amount)
+        val possibleDenominations = 
db.getDenominationsByBaseUrl(exchangeBaseUrl).filter { it.isOffered }
+        // TODO determine trust and audit status
+        return WithdrawalDetails(
+            exchange = exchange,
+            selectedDenominations = selectedDenominations,
+            withdrawFee = selectedDenominations.totalWithdrawCost - 
selectedDenominations.totalCoinValue,
+            overhead = amount - selectedDenominations.totalWithdrawCost,
+            earliestDepositExpiration = 
selectedDenominations.getEarliestDepositExpiry(),
+            numOfferedDenoms = possibleDenominations.size
+        )
+    }
+
+    /**
+     * Get a list of denominations to withdraw from the given exchange for the 
given amount,
+     * making sure that all denominations' signatures are verified.
+     */
+    internal suspend fun selectDenominations(exchange: ExchangeRecord, amount: 
Amount): DenominationSelectionInfo {
+        val exchangeDetails = exchange.details ?: throw Error("Exchange 
$exchange details not available.")
+
+        val possibleDenominations = getPossibleDenominations(exchange.baseUrl)
+        val selectedDenominations = getDenominationSelection(amount, 
possibleDenominations)
+        // TODO consider validating denominations before writing them into the 
DB
+        for (selectedDenomination in 
selectedDenominations.selectedDenominations) {
+            var denomination = selectedDenomination.denominationRecord
+            if (denomination.status == Unverified) {
+                val valid = signature.verifyDenominationRecord(denomination, 
exchangeDetails.masterPublicKey)
+                denomination = if (!valid) {
+                    denomination.copy(status = VerifiedBad)
+                } else {
+                    denomination.copy(status = VerifiedGood)
+                }
+                db.put(denomination)
+            }
+            if (denomination.status == VerifiedBad) throw Error("Exchange 
$exchange has bad denomination.")
+        }
+        return selectedDenominations
+    }
+
+    suspend fun getPossibleDenominations(exchangeBaseUrl: String): 
List<DenominationRecord> {
+        return db.getDenominationsByBaseUrl(exchangeBaseUrl).filter { 
denomination ->
+            (denomination.status == Unverified || denomination.status == 
VerifiedGood) &&
+                    !denomination.isRevoked
+        }
+    }
+
     /**
      * Get a list of denominations (with repetitions possible)
      * whose total value is as close as possible to the available amount, but 
never larger.
@@ -98,7 +222,8 @@ internal class Withdraw(
         var totalWithdrawCost = Amount.zero(amount.currency)
 
         // denominations need to be sorted, so we try the highest ones first
-        val denominations = 
denoms.filter(this::isWithdrawableDenomination).sortedByDescending { it.value }
+        val now = Timestamp.now()
+        val denominations = denoms.filter { it.isWithdrawable(now) 
}.sortedByDescending { it.value }
         var remainingAmount = amount.copy()
         val zero = Amount.zero(amount.currency)
         for (d in denominations) {
@@ -125,15 +250,4 @@ internal class Withdraw(
         )
     }
 
-    // TODO move into DenominationRecord?
-    fun isWithdrawableDenomination(d: DenominationRecord): Boolean {
-        if (d.isRevoked) return false // can not use revoked denomination
-        if (d.status != Unverified && d.status != VerifiedGood) return false 
// verified to be bad
-        val now = Timestamp.now()
-        if (now < d.stampStart) return false // denomination has not yet 
started
-        val lastPossibleWithdraw = d.stampExpireWithdraw - Duration(50 * 1000)
-        if ((lastPossibleWithdraw - now).ms == 0L) return false // 
denomination has expired
-        return true
-    }
-
 }
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
index 32a8d88..ab4770d 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/DbTest.kt
@@ -16,8 +16,8 @@
 
 package net.taler.wallet.kotlin
 
-import net.taler.wallet.kotlin.Denominations.denomination10
-import net.taler.wallet.kotlin.Denominations.denomination5
+import net.taler.wallet.kotlin.exchange.Denominations.denomination10
+import net.taler.wallet.kotlin.exchange.Denominations.denomination5
 import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
 import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
 import net.taler.wallet.kotlin.exchange.ExchangeRecord
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/PaytoUriTest.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/PaytoUriTest.kt
new file mode 100644
index 0000000..4f080e3
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/PaytoUriTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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 kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+
+class PaytoUriTest {
+
+    @Test
+    fun testFromString() {
+        // wrong scheme
+        var uri = "https://example.com/";
+        assertNull(PaytoUri.fromString(uri))
+
+        // incomplete scheme
+        uri = "payto:blabla"
+        assertNull(PaytoUri.fromString(uri))
+
+        // proper URI
+        uri = "payto://x-taler-bank/123"
+        var parsedUri = PaytoUri.fromString(uri)
+        assertNotNull(parsedUri)
+        assertEquals("x-taler-bank", parsedUri.targetType)
+        assertEquals("123", parsedUri.targetPath)
+
+        // proper URI with incomplete query
+        uri = "payto://x-taler-bank/123?foo"
+        parsedUri = PaytoUri.fromString(uri)
+        assertNotNull(parsedUri)
+        assertEquals(0, parsedUri.params.size)
+
+        // proper URI with two query param
+        uri = "payto://x-taler-bank/123?foo=bar&hip=hop"
+        parsedUri = PaytoUri.fromString(uri)
+        assertNotNull(parsedUri)
+        assertEquals(2, parsedUri.params.size)
+        assertEquals("bar", parsedUri.params["foo"])
+        assertEquals("hop", parsedUri.params["hip"])
+    }
+
+}
diff --git 
a/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/DenominationTest.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/DenominationTest.kt
new file mode 100644
index 0000000..f48c97d
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/DenominationTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.Timestamp.Companion.NEVER
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
+import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.Denominations.denomination1
+import net.taler.wallet.kotlin.exchange.Denominations.denomination10
+import net.taler.wallet.kotlin.exchange.Denominations.denomination2
+import net.taler.wallet.kotlin.exchange.Denominations.denomination5
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class DenominationTest {
+
+    @Test
+    fun testGetEarliestDepositExpiry() {
+        // empty selection info never expires
+        val infoEmpty = DenominationSelectionInfo(
+            totalCoinValue = Amount.zero("TESTKUDOS"),
+            totalWithdrawCost = Amount.zero("TESTKUDOS"),
+            selectedDenominations = emptyList()
+        )
+        assertEquals(Timestamp(NEVER), infoEmpty.getEarliestDepositExpiry())
+
+        // earliest expiry of single denomination is that of the denomination
+        val info1 = infoEmpty.copy(
+            selectedDenominations = listOf(SelectedDenomination(1, 
denomination10))
+        )
+        assertEquals(denomination10.stampExpireDeposit, 
info1.getEarliestDepositExpiry())
+
+        // denomination that expires earlier gets selected
+        val info2 = infoEmpty.copy(
+            selectedDenominations = listOf(
+                SelectedDenomination(3, denomination5.copy(stampExpireDeposit 
= Timestamp(42))),
+                SelectedDenomination(2, denomination2.copy(stampExpireDeposit 
= Timestamp(2))),
+                SelectedDenomination(1, denomination1.copy(stampExpireDeposit 
= Timestamp(1)))
+            )
+        )
+        assertEquals(Timestamp(1), info2.getEarliestDepositExpiry())
+
+        // denomination that expires at all is earlier than the one that never 
expires
+        val info3 = infoEmpty.copy(
+            selectedDenominations = listOf(
+                SelectedDenomination(2, denomination2.copy(stampExpireDeposit 
= Timestamp(NEVER))),
+                SelectedDenomination(1, denomination1.copy(stampExpireDeposit 
= Timestamp(1)))
+            )
+        )
+        assertEquals(Timestamp(1), info3.getEarliestDepositExpiry())
+    }
+
+    @Test
+    fun testIsWithdrawableDenomination() {
+        // denomination is withdrawable
+        assertTrue(denomination1.isWithdrawable())
+        // denomination is withdrawable when VerifiedGood
+        assertTrue(denomination1.copy(status = VerifiedGood).isWithdrawable())
+        // fails with VerifiedBad
+        assertFalse(denomination1.copy(status = VerifiedBad).isWithdrawable())
+        // fails when revoked
+        assertFalse(denomination1.copy(isRevoked = true).isWithdrawable())
+        // fails when not started
+        assertFalse(denomination1.copy(stampStart = 
Timestamp(Timestamp.now().ms + 9999)).isWithdrawable())
+        // fails when expired
+        assertFalse(denomination1.copy(stampExpireWithdraw = 
Timestamp.now()).isWithdrawable())
+        // fails when almost expired
+        assertFalse(denomination1.copy(stampExpireWithdraw = 
Timestamp(Timestamp.now().ms + 5000)).isWithdrawable())
+        // succeeds when not quite expired
+        assertTrue(denomination1.copy(stampExpireWithdraw = 
Timestamp(Timestamp.now().ms + 51000)).isWithdrawable())
+    }
+
+}
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/Denominations.kt
similarity index 97%
rename from src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt
rename to 
src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/Denominations.kt
index 2049cf4..8cfd7fe 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/Denominations.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/exchange/Denominations.kt
@@ -14,16 +14,18 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.kotlin
+package net.taler.wallet.kotlin.exchange
 
-import net.taler.wallet.kotlin.exchange.DenominationRecord
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.Timestamp
 import net.taler.wallet.kotlin.exchange.DenominationStatus.Unverified
 import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
 
 object Denominations {
 
     private val validStart = Timestamp.now()
-    private val validExpireWithdraw = Timestamp(Timestamp.now().ms + 1000L * 
60L * 60L * 24L * 365L)
+    private val validExpireWithdraw =
+        Timestamp(Timestamp.now().ms + 1000L * 60L * 60L * 24L * 365L)
     val denomination10 = DenominationRecord(
         denomPub = 
"020000X0X3G1EBB22XJ4HD6R8545R294TMCMA13ZRW7R101KJENFGTNTZSPGA0XP898FJEVHY4SJTC0SM264K0Y7Q6E24S35JSFZXD6VAJDJX8FCERBTNFV5DZR8V4GV7DAD062CPZBEVGNDEJQCTHVFJP84QWVPYJFNZSS3EJEK3WKJVG5EM3X2JPM1C97AB26VSZXWNYNC2CNJN7KG2001",
         denomPubHash = 
"7GB2YKDWKQ3DS2GA9XCVXVPMPJQA9M7Q0DFDHCX5M71J4E2PEHAJK3QF3KTJTWJA33KG0BX6XX0TTRMMZ8CEBM4GSE2N5FSV7GYRGH0",
diff --git 
a/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt 
b/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
index 03b629e..413deef 100644
--- a/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/operations/WithdrawTest.kt
@@ -17,33 +17,41 @@
 package net.taler.wallet.kotlin.operations
 
 import net.taler.wallet.kotlin.Amount
-import net.taler.wallet.kotlin.Denominations.denomination0d01
-import net.taler.wallet.kotlin.Denominations.denomination0d1
-import net.taler.wallet.kotlin.Denominations.denomination1
-import net.taler.wallet.kotlin.Denominations.denomination10
-import net.taler.wallet.kotlin.Denominations.denomination2
-import net.taler.wallet.kotlin.Denominations.denomination4
-import net.taler.wallet.kotlin.Denominations.denomination5
-import net.taler.wallet.kotlin.Denominations.denomination8
-import net.taler.wallet.kotlin.Timestamp
-import net.taler.wallet.kotlin.crypto.Refresh.DenominationSelectionInfo
-import net.taler.wallet.kotlin.crypto.Refresh.SelectedDenomination
-import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedBad
-import net.taler.wallet.kotlin.exchange.DenominationStatus.VerifiedGood
+import net.taler.wallet.kotlin.exchange.DenominationSelectionInfo
+import net.taler.wallet.kotlin.exchange.Denominations.denomination0d01
+import net.taler.wallet.kotlin.exchange.Denominations.denomination0d1
+import net.taler.wallet.kotlin.exchange.Denominations.denomination1
+import net.taler.wallet.kotlin.exchange.Denominations.denomination10
+import net.taler.wallet.kotlin.exchange.Denominations.denomination2
+import net.taler.wallet.kotlin.exchange.Denominations.denomination4
+import net.taler.wallet.kotlin.exchange.Denominations.denomination5
+import net.taler.wallet.kotlin.exchange.Denominations.denomination8
+import net.taler.wallet.kotlin.exchange.SelectedDenomination
 import net.taler.wallet.kotlin.getMockHttpClient
 import net.taler.wallet.kotlin.giveJsonResponse
 import net.taler.wallet.kotlin.operations.Withdraw.BankDetails
 import net.taler.wallet.kotlin.runCoroutine
+import kotlin.test.Ignore
 import kotlin.test.Test
 import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
 
 internal class WithdrawTest {
 
     private val httpClient = getMockHttpClient()
     private val withdraw = Withdraw(httpClient)
 
+    @Ignore // live test that requires internet connectivity and a working 
exchange
+    @Test
+    fun testLiveUpdate() {
+        runCoroutine {
+            val withdraw = Withdraw() // use own instance without mocked HTTP 
client
+            val url = "http://exchange.test.taler.net/";
+            val amount = Amount("TESTKUDOS", 5, 0)
+            val details = withdraw.getWithdrawalDetails(url, amount)
+            assertEquals(url, details.exchange.baseUrl)
+        }
+    }
+
     @Test
     fun getBankWithdrawalInfo() {
         val bankDetails = BankDetails(
@@ -74,26 +82,6 @@ internal class WithdrawTest {
         }
     }
 
-    @Test
-    fun testIsWithdrawableDenomination() {
-        // denomination is withdrawable
-        assertTrue(withdraw.isWithdrawableDenomination(denomination1))
-        // denomination is withdrawable when VerifiedGood
-        
assertTrue(withdraw.isWithdrawableDenomination(denomination1.copy(status = 
VerifiedGood)))
-        // fails with VerifiedBad
-        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(status = 
VerifiedBad)))
-        // fails when revoked
-        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(isRevoked = 
true)))
-        // fails when not started
-        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampStart = 
Timestamp(Timestamp.now().ms + 9999))))
-        // fails when expired
-        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
 = Timestamp.now())))
-        // fails when almost expired
-        
assertFalse(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
 = Timestamp(Timestamp.now().ms + 5000))))
-        // succeeds when not quite expired
-        
assertTrue(withdraw.isWithdrawableDenomination(denomination1.copy(stampExpireWithdraw
 = Timestamp(Timestamp.now().ms + 51000))))
-    }
-
     @Test
     fun testGetDenominationSelection() {
         val allDenominations = listOf(

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]