summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-06-29 16:00:19 -0300
committerTorsten Grote <t@grobox.de>2020-06-29 16:00:19 -0300
commitba7e1cce382b338a746ae3f1c6358f2e60530384 (patch)
tree6703317b5a2148a8d0459169d7b2aba143408dd0 /src
parentcd46828dd54c9dc17e0c5f5fe0817d1407cc8bd9 (diff)
downloadwallet-kotlin-ba7e1cce382b338a746ae3f1c6358f2e60530384.tar.gz
wallet-kotlin-ba7e1cce382b338a746ae3f1c6358f2e60530384.tar.bz2
wallet-kotlin-ba7e1cce382b338a746ae3f1c6358f2e60530384.zip
Add signing of RecoupRequest with tests
Diffstat (limited to 'src')
-rw-r--r--src/commonMain/kotlin/net/taler/wallet/kotlin/Types.kt78
-rw-r--r--src/commonMain/kotlin/net/taler/wallet/kotlin/crypto/Recoup.kt67
-rw-r--r--src/commonTest/kotlin/net/taler/wallet/kotlin/crypto/RecoupTest.kt74
3 files changed, 219 insertions, 0 deletions
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))
+ }
+ }
+
+}