From a307a498dc8a42df129e8eaff591e9144ed96298 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 14 Aug 2020 17:33:22 -0300 Subject: Make the wallet lib actually use the common lib --- .../kotlin/net/taler/lib/wallet/crypto/Refresh.kt | 266 +++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt (limited to 'wallet/src/commonMain/kotlin/net/taler/lib/wallet/crypto/Refresh.kt') 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 + */ + +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, + + /** + * Denominations of the newly requested coins. + */ + val newDenominations: List, + + /** + * Planchets for each cut-and-choose instance. + */ + val planchetsForGammas: List>, + + /** + * The transfer public keys, kappa of them. + */ + val transferPublicKeys: List, + + /** + * Private keys for the transfer public keys. + */ + val transferPrivateKeys: List, + + /** + * 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() + val transferPrivateKeys = ArrayList() + 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() + val newDenominationHashes = ArrayList() + 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>() + for (i in 0 until kappa) { + val planchets = ArrayList() + 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): Pair { + 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) + } + +} -- cgit v1.2.3