[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-wallet-kotlin] 02/02: Add signing of RecoupRequest with tests
From: |
gnunet |
Subject: |
[taler-wallet-kotlin] 02/02: Add signing of RecoupRequest with tests |
Date: |
Mon, 29 Jun 2020 21:00:48 +0200 |
This is an automated email from the git hooks/post-receive script.
torsten-grote pushed a commit to branch master
in repository wallet-kotlin.
commit ba7e1cce382b338a746ae3f1c6358f2e60530384
Author: Torsten Grote <t@grobox.de>
AuthorDate: Mon Jun 29 16:00:19 2020 -0300
Add signing of RecoupRequest with tests
---
.../kotlin/net/taler/wallet/kotlin/Types.kt | 78 ++++++++++++++++++++++
.../net/taler/wallet/kotlin/crypto/Recoup.kt | 67 +++++++++++++++++++
.../net/taler/wallet/kotlin/crypto/RecoupTest.kt | 74 ++++++++++++++++++++
3 files changed, 219 insertions(+)
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
index c8aa990..2aa44da 100644
--- a/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt
@@ -112,3 +112,81 @@ enum class DenominationStatus {
*/
VerifiedBad
}
+
+class CoinRecord(
+ /**
+ * Where did the coin come from? Used for recouping coins.
+ */
+ val coinSource: CoinSourceType,
+
+ /**
+ * Public key of the coin.
+ */
+ val coinPub: String,
+
+ /**
+ * Private key to authorize operations on the coin.
+ */
+ val coinPriv: String,
+
+ /**
+ * Key used by the exchange used to sign the coin.
+ */
+ val denomPub: String,
+
+ /**
+ * Hash of the public key that signs the coin.
+ */
+ val denomPubHash: String,
+
+ /**
+ * Unblinded signature by the exchange.
+ */
+ val denomSig: String,
+
+ /**
+ * Amount that's left on the coin.
+ */
+ val currentAmount: Amount,
+
+ /**
+ * Base URL that identifies the exchange from which we got the coin.
+ */
+ val exchangeBaseUrl: String,
+
+ /**
+ * The coin is currently suspended, and will not be used for payments.
+ */
+ val suspended: Boolean,
+
+ /**
+ * Blinding key used when withdrawing the coin.
+ * Potentially send again during payback.
+ */
+ val blindingKey: String,
+
+ /**
+ * Status of the coin.
+ */
+ val status: CoinStatus
+)
+
+enum class CoinSourceType(val value: String) {
+ WITHDRAW("withdraw"),
+ REFRESH("refresh"),
+ TIP("tip")
+}
+
+enum class CoinStatus(val value: String) {
+
+ /**
+ * Withdrawn and never shown to anybody.
+ */
+ FRESH("fresh"),
+
+ /**
+ * A coin that has been spent and refreshed.
+ */
+ DORMANT("dormant")
+
+}
diff --git a/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Recoup.kt
b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Recoup.kt
new file mode 100644
index 0000000..79612a8
--- /dev/null
+++ b/src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Recoup.kt
@@ -0,0 +1,67 @@
+package net.taler.wallet.kotlin.crypto
+
+import net.taler.wallet.kotlin.Base32Crockford
+import net.taler.wallet.kotlin.CoinRecord
+import net.taler.wallet.kotlin.CoinSourceType.REFRESH
+import net.taler.wallet.kotlin.crypto.Signature.Companion.WALLET_COIN_RECOUP
+
+internal class Recoup(private val crypto: Crypto) {
+
+ /**
+ * Request that we send to the exchange to get a payback.
+ */
+ data class Request(
+ /**
+ * Hashed denomination public key of the coin we want to get
+ * paid back.
+ */
+ val denomPubHash: String,
+
+ /**
+ * Signature over the coin public key by the denomination.
+ */
+ val denomSig: String,
+
+ /**
+ * Coin public key of the coin we want to refund.
+ */
+ val coinPub: String,
+
+ /**
+ * Blinding key that was used during withdraw,
+ * used to prove that we were actually withdrawing the coin.
+ */
+ val coinBlindKeySecret: String,
+
+ /**
+ * Signature made by the coin, authorizing the payback.
+ */
+ val coinSig: String,
+
+ /**
+ * Was the coin refreshed (and thus the recoup should go to the old
coin)?
+ */
+ val refreshed: Boolean
+ )
+
+ /**
+ * Create and sign a message to recoup a coin.
+ */
+ fun createRequest(coin: CoinRecord): Request {
+ val p = Signature.PurposeBuilder(WALLET_COIN_RECOUP)
+ .put(Base32Crockford.decode(coin.coinPub))
+ .put(Base32Crockford.decode(coin.denomPubHash))
+ .put(Base32Crockford.decode(coin.blindingKey))
+ .build()
+ val coinSig = crypto.eddsaSign(p,
Base32Crockford.decode(coin.coinPriv))
+ return Request(
+ coinBlindKeySecret = coin.blindingKey,
+ coinPub = coin.coinPub,
+ coinSig = Base32Crockford.encode(coinSig),
+ denomPubHash = coin.denomPubHash,
+ denomSig = coin.denomSig,
+ refreshed = coin.coinSource === REFRESH
+ )
+ }
+
+}
diff --git a/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/RecoupTest.kt
b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/RecoupTest.kt
new file mode 100644
index 0000000..865eaa9
--- /dev/null
+++ b/src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/RecoupTest.kt
@@ -0,0 +1,74 @@
+package net.taler.wallet.kotlin.crypto
+
+import net.taler.wallet.kotlin.Amount
+import net.taler.wallet.kotlin.CoinRecord
+import net.taler.wallet.kotlin.CoinSourceType.REFRESH
+import net.taler.wallet.kotlin.CoinSourceType.WITHDRAW
+import net.taler.wallet.kotlin.CoinStatus.FRESH
+import net.taler.wallet.kotlin.crypto.Recoup.Request
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class RecoupTest {
+
+ private val crypto = CryptoFactory.getCrypto()
+ private val recoup = Recoup(crypto)
+
+ private class RecoupRequestVector(val record: CoinRecord, val request:
Request)
+
+ @Test
+ fun test() {
+ val vectors = listOf(
+ RecoupRequestVector(
+ CoinRecord(
+ coinSource = WITHDRAW,
+ coinPub =
"9YW99NYH54FWG87TP3SKCGR9MRWYSVR75X42FN4YAJC9579CQBJ0",
+ coinPriv =
"EPPYWTDVWM4CXW75J8AAGWW620C7DCC3B45TM31KHKPYMT9VM7DG",
+ denomPub =
"020000X3T40FNGSM3Y1QFKX9H4JY5EP70Y2CKDHD29B5BEZCTWRMT6AC9SA0G5YJ1XVYY580K6S93SFCKM5PFKP96H3KXDNP58EVQPTYDB5S0QY4V8B873NYA7EYRH25NJ8MR2VP6F7WWVMBK3NR3FSFP17PHPGF279NBSRTXWZSJZFX6RCTR6VS5WMSYFHZCR0P8R6MGHDCB3QW4M3G2001",
+ denomPubHash =
"DG3114X57XKHQ1XM6AN0P7D2B6J96SVFG09S3SF43ZXCYYK9PGX84XP3ZY7WY3QD9JE1BWS2T8DGR78QXZZAVGED79HES10HAPTWBX8",
+ denomSig =
"AHE8DGMTTKNWGCVQYTV56CBWA81DH10BQEBAM0A5YGRAZXRPVHMZ5FH0XW1523QXSTXT3WMS1X7FDMEZ3BR898YEDTXDTHEMX6RS11KCPBAZCGTNPHKYF6RH9414Q0PYT5BZKGKWJNAFPWQS715NXEFZBY1D6RPTAN520REJ4RTREC9PP5D8WVQ3B66Q4ARYQ3CK49K0ZDME0",
+ currentAmount = Amount("TESTKUDOS", 0, 0),
+ exchangeBaseUrl = "example.org",
+ suspended = false,
+ blindingKey =
"1Y29A3ABERGYJR8Y9HS7XS8AYYDAKV6BZSXMZ0WS5VDTS150C100",
+ status = FRESH
+ ),
+ Request(
+ denomPubHash =
"DG3114X57XKHQ1XM6AN0P7D2B6J96SVFG09S3SF43ZXCYYK9PGX84XP3ZY7WY3QD9JE1BWS2T8DGR78QXZZAVGED79HES10HAPTWBX8",
+ denomSig =
"AHE8DGMTTKNWGCVQYTV56CBWA81DH10BQEBAM0A5YGRAZXRPVHMZ5FH0XW1523QXSTXT3WMS1X7FDMEZ3BR898YEDTXDTHEMX6RS11KCPBAZCGTNPHKYF6RH9414Q0PYT5BZKGKWJNAFPWQS715NXEFZBY1D6RPTAN520REJ4RTREC9PP5D8WVQ3B66Q4ARYQ3CK49K0ZDME0",
+ coinPub =
"9YW99NYH54FWG87TP3SKCGR9MRWYSVR75X42FN4YAJC9579CQBJ0",
+ coinBlindKeySecret =
"1Y29A3ABERGYJR8Y9HS7XS8AYYDAKV6BZSXMZ0WS5VDTS150C100",
+ coinSig =
"GBN5MVEY6JATGGSTX5YF32G3G204Y1PF9ASVXQFN895DWN5ZK3CBY2NHC8ATB1E9JWSV1QD4ECM0XHP8Y6DFZ1S02MYD5NBKZ45B018",
+ refreshed = false
+ )
+ ),
+ RecoupRequestVector(
+ CoinRecord(
+ coinSource = REFRESH,
+ coinPub =
"2YE003173JB6WNQ9HS73Z468F11KDHWWVGCPDHDTD6AY5AVJPQPG",
+ coinPriv =
"GCR4R26XTCFNS109ZYC0G6M374K1ACNES1YH2CESWD86JBAE22WG",
+ denomPub =
"020000X9M8MQVNH28D4J4YFA5ZZNKGNR0423BXQZV00RRN754XTDQMS5YKWQ3KSN8NV4V7CHDM22CRJ4WWQW05FDZC7VN0KK4S8VK9PYPPXNW6FJKHBSEZ2X1FCJKRC3T6PK2BKQ422Y2ASE76ZZAH6RRQT4SQGZTV3TRTSBC5AECJ5Z6C4RX7XFBERKVB45DA7H3V53YCYX1C41ZY5G2001",
+ denomPubHash =
"J0G3G880JJJD09923AAWNQQZHJVRQT71ZK8KZGYW7T1P18PCPZ72FBAKDW3EFZ3QFZEW72EYJ9K0FG3RFZTFADQKZDDN9YT6BT2PE70",
+ denomSig =
"8HVKAGMKRQRWB1HX9WCPX3FJ0SVE24DCAWQSHX4ZMXZ1KFZDNF4F0Z4K6ZCW142B2WDEH0W848W8WKH8P6A6EJR7J635QEF78CSJFF0EX1FRS5VY484GEX0HH3BDRDFGTHXNQRTTF1DD5ETMEG1QNKA3SAB24XZXZNQ6RDGTK02MRETP859NGMDD2F94F58JH4HYGXMAY0X32",
+ currentAmount = Amount("TESTKUDOS", 0, 0),
+ exchangeBaseUrl = "example.org",
+ suspended = false,
+ blindingKey =
"C5VPT5F925ADJWK48PR07KV2W66EZQN4KYE146NY77DFM8GFCTXG",
+ status = FRESH
+ ),
+ Request(
+ denomPubHash =
"J0G3G880JJJD09923AAWNQQZHJVRQT71ZK8KZGYW7T1P18PCPZ72FBAKDW3EFZ3QFZEW72EYJ9K0FG3RFZTFADQKZDDN9YT6BT2PE70",
+ denomSig =
"8HVKAGMKRQRWB1HX9WCPX3FJ0SVE24DCAWQSHX4ZMXZ1KFZDNF4F0Z4K6ZCW142B2WDEH0W848W8WKH8P6A6EJR7J635QEF78CSJFF0EX1FRS5VY484GEX0HH3BDRDFGTHXNQRTTF1DD5ETMEG1QNKA3SAB24XZXZNQ6RDGTK02MRETP859NGMDD2F94F58JH4HYGXMAY0X32",
+ coinPub =
"2YE003173JB6WNQ9HS73Z468F11KDHWWVGCPDHDTD6AY5AVJPQPG",
+ coinBlindKeySecret =
"C5VPT5F925ADJWK48PR07KV2W66EZQN4KYE146NY77DFM8GFCTXG",
+ coinSig =
"HGPAWTM2ZXVBZWYVSPS6S9DMSQWSVEJCQ78BN6WG2VND3PA7BQNHVE6142CGYX0VA82G5YP9SAV5YDNPNQJH2FTY5M6VM92QF6CB228",
+ refreshed = true
+ )
+ )
+ )
+ for (v in vectors) {
+ assertEquals(v.request, recoup.createRequest(v.record))
+ }
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.