summaryrefslogtreecommitdiff
path: root/wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-08-14 17:33:22 -0300
committerTorsten Grote <t@grobox.de>2020-08-14 17:33:22 -0300
commita307a498dc8a42df129e8eaff591e9144ed96298 (patch)
treea55182b4793b673e433d0e431e9d971d45c9a6a7 /wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt
parent2ac13b19a5c7fc3531447333fe1772a78ca35795 (diff)
downloadwallet-kotlin-a307a498dc8a42df129e8eaff591e9144ed96298.tar.gz
wallet-kotlin-a307a498dc8a42df129e8eaff591e9144ed96298.tar.bz2
wallet-kotlin-a307a498dc8a42df129e8eaff591e9144ed96298.zip
Make the wallet lib actually use the common lib
Diffstat (limited to 'wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt')
-rw-r--r--wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt266
1 files changed, 266 insertions, 0 deletions
diff --git a/wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt b/wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt
new file mode 100644
index 0000000..90478ef
--- /dev/null
+++ b/wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt
@@ -0,0 +1,266 @@
+/*
+ * 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.lib.wallet.crypto
+
+import net.taler.lib.common.Amount
+import net.taler.lib.wallet.Base32Crockford
+import net.taler.lib.wallet.CoinRecord
+import net.taler.lib.common.Timestamp
+import net.taler.lib.wallet.crypto.Signature.Companion.WALLET_COIN_LINK
+import net.taler.lib.wallet.crypto.Signature.Companion.WALLET_COIN_MELT
+import net.taler.lib.wallet.crypto.Signature.PurposeBuilder
+import net.taler.lib.wallet.exchange.DenominationSelectionInfo
+import net.taler.lib.wallet.exchange.SelectedDenomination
+import net.taler.lib.wallet.toByteArray
+
+internal class Refresh(private val crypto: Crypto) {
+
+ data class RefreshSessionRecord(
+
+ /**
+ * Public key that's being melted in this session.
+ */
+ val meltCoinPub: String,
+
+ /**
+ * How much of the coin's value is melted away with this refresh session?
+ */
+ val amountRefreshInput: Amount,
+
+ /**
+ * Sum of the value of denominations we want to withdraw in this session, without fees.
+ */
+ val amountRefreshOutput: Amount,
+
+ /**
+ * Signature to confirm the melting.
+ */
+ val confirmSig: String,
+
+ /**
+ * Hashed denominations of the newly requested coins.
+ */
+ val newDenominationHashes: List<String>,
+
+ /**
+ * Denominations of the newly requested coins.
+ */
+ val newDenominations: List<String>,
+
+ /**
+ * Planchets for each cut-and-choose instance.
+ */
+ val planchetsForGammas: List<List<RefreshPlanchetRecord>>,
+
+ /**
+ * The transfer public keys, kappa of them.
+ */
+ val transferPublicKeys: List<String>,
+
+ /**
+ * Private keys for the transfer public keys.
+ */
+ val transferPrivateKeys: List<String>,
+
+ /**
+ * The no-reveal-index after we've done the melting.
+ */
+ val noRevealIndex: Int?,
+
+ /**
+ * Hash of the session.
+ */
+ val hash: String,
+
+ /**
+ * Timestamp when the refresh session finished.
+ */
+ val finishedTimestamp: Timestamp?,
+
+ /**
+ * When was this refresh session created?
+ */
+ val timestampCreated: Timestamp,
+
+ /**
+ * Base URL for the exchange we're doing the refresh with.
+ */
+ val exchangeBaseUrl: String
+ )
+
+ data class RefreshPlanchetRecord(
+ /**
+ * Public key for the coin.
+ */
+ val publicKey: String,
+ /**
+ * Private key for the coin.
+ */
+ val privateKey: String,
+ /**
+ * Blinded public key.
+ */
+ val coinEv: String,
+ /**
+ * Blinding key used.
+ */
+ val blindingKey: String
+ )
+
+ /**
+ * Create a new refresh session.
+ */
+ fun createRefreshSession(
+ exchangeBaseUrl: String,
+ meltCoin: CoinRecord,
+ meltFee: Amount,
+ newCoinDenominations: DenominationSelectionInfo,
+ kappa: Int = newCoinDenominations.selectedDenominations.size
+ ) : RefreshSessionRecord {
+ return createRefreshSession(exchangeBaseUrl, meltCoin, meltFee, newCoinDenominations, kappa) {
+ crypto.createEcdheKeyPair()
+ }
+ }
+
+ /**
+ * Create a new refresh session and allow to provide transfer key pairs for testing.
+ */
+ fun createRefreshSession(
+ exchangeBaseUrl: String,
+ meltCoin: CoinRecord,
+ meltFee: Amount,
+ newCoinDenominations: DenominationSelectionInfo,
+ kappa: Int = newCoinDenominations.selectedDenominations.size,
+ kappaKeys: (Int) -> EcdheKeyPair
+ ): RefreshSessionRecord {
+ val sessionHashState = crypto.getHashSha512State()
+
+ // create fresh transfer keys, one pair for each selected denomination (kappa-many)
+ val transferPublicKeys = ArrayList<String>()
+ val transferPrivateKeys = ArrayList<String>()
+ for (i in 0 until kappa) {
+ val transferKeyPair = kappaKeys(i)
+ sessionHashState.update(transferKeyPair.publicKey)
+ transferPrivateKeys.add(Base32Crockford.encode(transferKeyPair.privateKey))
+ transferPublicKeys.add(Base32Crockford.encode(transferKeyPair.publicKey))
+ }
+
+ // add denomination public keys to session hash
+ val newDenominations = ArrayList<String>()
+ val newDenominationHashes = ArrayList<String>()
+ for (selectedDenomination in newCoinDenominations.selectedDenominations) {
+ for (i in 0 until selectedDenomination.count) {
+ newDenominations.add(selectedDenomination.denominationRecord.denomPub)
+ newDenominationHashes.add(selectedDenomination.denominationRecord.denomPubHash)
+ sessionHashState.update(Base32Crockford.decode(selectedDenomination.denominationRecord.denomPub))
+ }
+ }
+
+ // add public key of melted coin to session hash
+ sessionHashState.update(Base32Crockford.decode(meltCoin.coinPub))
+
+ // calculate total value with all fees and add to session hash
+ val (totalOutput, withdrawFee) = calculateOutputAndWithdrawFee(newCoinDenominations.selectedDenominations)
+ val valueWithFee = totalOutput + withdrawFee + meltFee
+ sessionHashState.update(valueWithFee.toByteArray())
+
+ val planchetsForGammas = ArrayList<ArrayList<RefreshPlanchetRecord>>()
+ for (i in 0 until kappa) {
+ val planchets = ArrayList<RefreshPlanchetRecord>()
+ for (selectedDenomination in newCoinDenominations.selectedDenominations) {
+ for (k in 0 until selectedDenomination.count) {
+ val coinNumber = planchets.size
+ val transferPrivateKey = Base32Crockford.decode(transferPrivateKeys[i])
+ val oldCoinPub = Base32Crockford.decode(meltCoin.coinPub)
+ val transferSecret = crypto.keyExchangeEcdheEddsa(transferPrivateKey, oldCoinPub)
+ val fresh = crypto.setupRefreshPlanchet(transferSecret, coinNumber)
+ val publicKeyHash = crypto.sha512(fresh.coinPublicKey)
+ val denominationPub = Base32Crockford.decode(selectedDenomination.denominationRecord.denomPub)
+ val ev = crypto.rsaBlind(publicKeyHash, fresh.bks, denominationPub)
+ val planchet = RefreshPlanchetRecord(
+ blindingKey = Base32Crockford.encode(fresh.bks),
+ coinEv = Base32Crockford.encode(ev),
+ privateKey = Base32Crockford.encode(fresh.coinPrivateKey),
+ publicKey = Base32Crockford.encode(fresh.coinPublicKey)
+ )
+ planchets.add(planchet)
+ sessionHashState.update(ev)
+ }
+ }
+ planchetsForGammas.add(planchets)
+ }
+
+ val sessionHash = sessionHashState.final()
+
+ // make a signature over sessionHash, value (again?), meltFee and meltCoin public key with meltCoin private key
+ val confirmData = PurposeBuilder(WALLET_COIN_MELT)
+ .put(sessionHash)
+ .put(valueWithFee.toByteArray())
+ .put(meltFee.toByteArray())
+ .put(Base32Crockford.decode(meltCoin.coinPub))
+ .build()
+ val confirmSignature = crypto.eddsaSign(confirmData, Base32Crockford.decode(meltCoin.coinPriv))
+
+ return RefreshSessionRecord(
+ confirmSig = Base32Crockford.encode(confirmSignature),
+ exchangeBaseUrl = exchangeBaseUrl,
+ hash = Base32Crockford.encode(sessionHash),
+ meltCoinPub = meltCoin.coinPub,
+ newDenominationHashes = newDenominationHashes,
+ newDenominations = newDenominations,
+ noRevealIndex = null,
+ planchetsForGammas = planchetsForGammas,
+ transferPrivateKeys = transferPrivateKeys,
+ transferPublicKeys = transferPublicKeys,
+ amountRefreshOutput = totalOutput,
+ amountRefreshInput = valueWithFee,
+ timestampCreated = Timestamp.now(),
+ finishedTimestamp = null
+ )
+ }
+
+ private fun calculateOutputAndWithdrawFee(selectedDenomination: List<SelectedDenomination>): Pair<Amount, Amount> {
+ val currency = selectedDenomination[0].denominationRecord.value.currency
+ var total = Amount.zero(currency)
+ var fee = Amount.zero(currency)
+ for (ncd in selectedDenomination) {
+ total += ncd.denominationRecord.value * ncd.count
+ fee += ncd.denominationRecord.feeWithdraw * ncd.count
+ }
+ return Pair(total, fee)
+ }
+
+ fun signCoinLink(
+ oldCoinPrivateKey: String,
+ newDenominationHash: String,
+ oldCoinPublicKey: String,
+ transferPublicKey: String,
+ coinEv: String
+ ): String {
+ val coinEvHash = crypto.sha512(Base32Crockford.decode(coinEv))
+ val coinLink = PurposeBuilder(WALLET_COIN_LINK)
+ .put(Base32Crockford.decode(newDenominationHash))
+ .put(Base32Crockford.decode(oldCoinPublicKey))
+ .put(Base32Crockford.decode(transferPublicKey))
+ .put(coinEvHash)
+ .build()
+ val coinPrivateKey = Base32Crockford.decode(oldCoinPrivateKey)
+ val sig = crypto.eddsaSign(coinLink, coinPrivateKey)
+ return Base32Crockford.encode(sig)
+ }
+
+}