diff options
author | Torsten Grote <t@grobox.de> | 2020-06-29 16:00:19 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-06-29 16:00:19 -0300 |
commit | ba7e1cce382b338a746ae3f1c6358f2e60530384 (patch) | |
tree | 6703317b5a2148a8d0459169d7b2aba143408dd0 /src | |
parent | cd46828dd54c9dc17e0c5f5fe0817d1407cc8bd9 (diff) | |
download | wallet-kotlin-ba7e1cce382b338a746ae3f1c6358f2e60530384.tar.gz wallet-kotlin-ba7e1cce382b338a746ae3f1c6358f2e60530384.tar.bz2 wallet-kotlin-ba7e1cce382b338a746ae3f1c6358f2e60530384.zip |
Add signing of RecoupRequest with tests
Diffstat (limited to 'src')
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)) + } + } + +} |