diff options
Diffstat (limited to 'src/crypto/cryptoImplementation.ts')
-rw-r--r-- | src/crypto/cryptoImplementation.ts | 596 |
1 files changed, 0 insertions, 596 deletions
diff --git a/src/crypto/cryptoImplementation.ts b/src/crypto/cryptoImplementation.ts deleted file mode 100644 index faebbaa4a..000000000 --- a/src/crypto/cryptoImplementation.ts +++ /dev/null @@ -1,596 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 GNUnet e.V. - - 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. - - 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 - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Synchronous implementation of crypto-related functions for the wallet. - * - * The functionality is parameterized over an Emscripten environment. - */ - -/** - * Imports. - */ - -import { - CoinRecord, - CoinStatus, - DenominationRecord, - RefreshPlanchetRecord, - RefreshSessionRecord, - ReserveRecord, - TipPlanchet, - WireFee, -} from "../dbTypes"; - -import { CoinPaySig, ContractTerms, PaybackRequest } from "../talerTypes"; -import { - BenchmarkResult, - CoinWithDenom, - PayCoinInfo, - Timestamp, - PlanchetCreationResult, - PlanchetCreationRequest, -} from "../walletTypes"; -import { canonicalJson, getTalerStampSec } from "../util/helpers"; -import { AmountJson } from "../util/amounts"; -import * as Amounts from "../util/amounts"; -import * as timer from "../util/timer"; -import { - getRandomBytes, - encodeCrock, - decodeCrock, - createEddsaKeyPair, - createBlindingKeySecret, - hash, - rsaBlind, - eddsaVerify, - eddsaSign, - rsaUnblind, - stringToBytes, - createHashContext, - createEcdheKeyPair, - keyExchangeEcdheEddsa, - setupRefreshPlanchet, -} from "./talerCrypto"; -import { randomBytes } from "./primitives/nacl-fast"; - -enum SignaturePurpose { - RESERVE_WITHDRAW = 1200, - WALLET_COIN_DEPOSIT = 1201, - MASTER_DENOMINATION_KEY_VALIDITY = 1025, - WALLET_COIN_MELT = 1202, - TEST = 4242, - MERCHANT_PAYMENT_OK = 1104, - MASTER_WIRE_FEES = 1028, - WALLET_COIN_PAYBACK = 1203, - WALLET_COIN_LINK = 1204, -} - -function amountToBuffer(amount: AmountJson): Uint8Array { - const buffer = new ArrayBuffer(8 + 4 + 12); - const dvbuf = new DataView(buffer); - const u8buf = new Uint8Array(buffer); - const te = new TextEncoder(); - const curr = te.encode(amount.currency); - dvbuf.setBigUint64(0, BigInt(amount.value)); - dvbuf.setUint32(8, amount.fraction); - u8buf.set(curr, 8 + 4); - - return u8buf; -} - -function timestampToBuffer(ts: Timestamp): Uint8Array { - const b = new ArrayBuffer(8); - const v = new DataView(b); - const s = BigInt(ts.t_ms) * BigInt(1000); - v.setBigUint64(0, s); - return new Uint8Array(b); -} - -function talerTimestampStringToBuffer(ts: string): Uint8Array { - const t_sec = getTalerStampSec(ts); - if (t_sec === null || t_sec === undefined) { - // Should have been validated before! - throw Error("invalid timestamp"); - } - const buffer = new ArrayBuffer(8); - const dvbuf = new DataView(buffer); - const s = BigInt(t_sec) * BigInt(1000 * 1000); - dvbuf.setBigUint64(0, s); - return new Uint8Array(buffer); -} - -class SignaturePurposeBuilder { - private chunks: Uint8Array[] = []; - - constructor(private purposeNum: number) {} - - put(bytes: Uint8Array): SignaturePurposeBuilder { - this.chunks.push(Uint8Array.from(bytes)); - return this; - } - - build(): Uint8Array { - let payloadLen = 0; - for (let c of this.chunks) { - payloadLen += c.byteLength; - } - const buf = new ArrayBuffer(4 + 4 + payloadLen); - const u8buf = new Uint8Array(buf); - let p = 8; - for (let c of this.chunks) { - u8buf.set(c, p); - p += c.byteLength; - } - const dvbuf = new DataView(buf); - dvbuf.setUint32(0, payloadLen + 4 + 4); - dvbuf.setUint32(4, this.purposeNum); - return u8buf; - } -} - -function buildSigPS(purposeNum: number): SignaturePurposeBuilder { - return new SignaturePurposeBuilder(purposeNum); -} - -export class CryptoImplementation { - static enableTracing: boolean = false; - - constructor() {} - - /** - * Create a pre-coin of the given denomination to be withdrawn from then given - * reserve. - */ - createPlanchet( - req: PlanchetCreationRequest, - ): PlanchetCreationResult { - const reservePub = decodeCrock(req.reservePub); - const reservePriv = decodeCrock(req.reservePriv); - const denomPub = decodeCrock(req.denomPub); - const coinKeyPair = createEddsaKeyPair(); - const blindingFactor = createBlindingKeySecret(); - const coinPubHash = hash(coinKeyPair.eddsaPub); - const ev = rsaBlind(coinPubHash, blindingFactor, denomPub); - const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount; - const denomPubHash = hash(denomPub); - const evHash = hash(ev); - - const withdrawRequest = buildSigPS(SignaturePurpose.RESERVE_WITHDRAW) - .put(reservePub) - .put(amountToBuffer(amountWithFee)) - .put(amountToBuffer(req.feeWithdraw)) - .put(denomPubHash) - .put(evHash) - .build(); - - const sig = eddsaSign(withdrawRequest, reservePriv); - - const planchet: PlanchetCreationResult = { - blindingKey: encodeCrock(blindingFactor), - coinEv: encodeCrock(ev), - coinPriv: encodeCrock(coinKeyPair.eddsaPriv), - coinPub: encodeCrock(coinKeyPair.eddsaPub), - coinValue: req.value, - denomPub: encodeCrock(denomPub), - denomPubHash: encodeCrock(denomPubHash), - reservePub: encodeCrock(reservePub), - withdrawSig: encodeCrock(sig), - }; - return planchet; - } - - /** - * Create a planchet used for tipping, including the private keys. - */ - createTipPlanchet(denom: DenominationRecord): TipPlanchet { - const denomPub = decodeCrock(denom.denomPub); - const coinKeyPair = createEddsaKeyPair(); - const blindingFactor = createBlindingKeySecret(); - const coinPubHash = hash(coinKeyPair.eddsaPub); - const ev = rsaBlind(coinPubHash, blindingFactor, denomPub); - - const tipPlanchet: TipPlanchet = { - blindingKey: encodeCrock(blindingFactor), - coinEv: encodeCrock(ev), - coinPriv: encodeCrock(coinKeyPair.eddsaPriv), - coinPub: encodeCrock(coinKeyPair.eddsaPub), - coinValue: denom.value, - denomPub: encodeCrock(denomPub), - denomPubHash: encodeCrock(hash(denomPub)), - }; - return tipPlanchet; - } - - /** - * Create and sign a message to request payback for a coin. - */ - createPaybackRequest(coin: CoinRecord): PaybackRequest { - const p = buildSigPS(SignaturePurpose.WALLET_COIN_PAYBACK) - .put(decodeCrock(coin.coinPub)) - .put(decodeCrock(coin.denomPubHash)) - .put(decodeCrock(coin.blindingKey)) - .build(); - - const coinPriv = decodeCrock(coin.coinPriv); - const coinSig = eddsaSign(p, coinPriv); - const paybackRequest: PaybackRequest = { - coin_blind_key_secret: coin.blindingKey, - coin_pub: coin.coinPub, - coin_sig: encodeCrock(coinSig), - denom_pub: coin.denomPub, - denom_sig: coin.denomSig, - }; - return paybackRequest; - } - - /** - * Check if a payment signature is valid. - */ - isValidPaymentSignature( - sig: string, - contractHash: string, - merchantPub: string, - ): boolean { - const p = buildSigPS(SignaturePurpose.MERCHANT_PAYMENT_OK) - .put(decodeCrock(contractHash)) - .build(); - const sigBytes = decodeCrock(sig); - const pubBytes = decodeCrock(merchantPub); - return eddsaVerify(p, sigBytes, pubBytes); - } - - /** - * Check if a wire fee is correctly signed. - */ - isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean { - const p = buildSigPS(SignaturePurpose.MASTER_WIRE_FEES) - .put(hash(stringToBytes(type + "\0"))) - .put(timestampToBuffer(wf.startStamp)) - .put(timestampToBuffer(wf.endStamp)) - .put(amountToBuffer(wf.wireFee)) - .build(); - const sig = decodeCrock(wf.sig); - const pub = decodeCrock(masterPub); - return eddsaVerify(p, sig, pub); - } - - /** - * Check if the signature of a denomination is valid. - */ - isValidDenom(denom: DenominationRecord, masterPub: string): boolean { - const p = buildSigPS(SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) - .put(decodeCrock(masterPub)) - .put(timestampToBuffer(denom.stampStart)) - .put(timestampToBuffer(denom.stampExpireWithdraw)) - .put(timestampToBuffer(denom.stampExpireDeposit)) - .put(timestampToBuffer(denom.stampExpireLegal)) - .put(amountToBuffer(denom.value)) - .put(amountToBuffer(denom.feeWithdraw)) - .put(amountToBuffer(denom.feeDeposit)) - .put(amountToBuffer(denom.feeRefresh)) - .put(amountToBuffer(denom.feeRefund)) - .put(decodeCrock(denom.denomPubHash)) - .build(); - const sig = decodeCrock(denom.masterSig); - const pub = decodeCrock(masterPub); - return eddsaVerify(p, sig, pub); - } - - /** - * Create a new EdDSA key pair. - */ - createEddsaKeypair(): { priv: string; pub: string } { - const pair = createEddsaKeyPair(); - return { - priv: encodeCrock(pair.eddsaPriv), - pub: encodeCrock(pair.eddsaPub), - }; - } - - /** - * Unblind a blindly signed value. - */ - rsaUnblind(sig: string, bk: string, pk: string): string { - const denomSig = rsaUnblind( - decodeCrock(sig), - decodeCrock(pk), - decodeCrock(bk), - ); - return encodeCrock(denomSig); - } - - /** - * Generate updated coins (to store in the database) - * and deposit permissions for each given coin. - */ - signDeposit( - contractTerms: ContractTerms, - cds: CoinWithDenom[], - totalAmount: AmountJson, - ): PayCoinInfo { - const ret: PayCoinInfo = { - originalCoins: [], - sigs: [], - updatedCoins: [], - }; - - const contractTermsHash = this.hashString(canonicalJson(contractTerms)); - - const feeList: AmountJson[] = cds.map(x => x.denom.feeDeposit); - let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList) - .amount; - // okay if saturates - fees = Amounts.sub(fees, Amounts.parseOrThrow(contractTerms.max_fee)) - .amount; - const total = Amounts.add(fees, totalAmount).amount; - - let amountSpent = Amounts.getZero(cds[0].coin.currentAmount.currency); - let amountRemaining = total; - - for (const cd of cds) { - const originalCoin = { ...cd.coin }; - - if (amountRemaining.value === 0 && amountRemaining.fraction === 0) { - break; - } - - let coinSpend: AmountJson; - if (Amounts.cmp(amountRemaining, cd.coin.currentAmount) < 0) { - coinSpend = amountRemaining; - } else { - coinSpend = cd.coin.currentAmount; - } - - amountSpent = Amounts.add(amountSpent, coinSpend).amount; - - const feeDeposit = cd.denom.feeDeposit; - - // Give the merchant at least the deposit fee, otherwise it'll reject - // the coin. - - if (Amounts.cmp(coinSpend, feeDeposit) < 0) { - coinSpend = feeDeposit; - } - - const newAmount = Amounts.sub(cd.coin.currentAmount, coinSpend).amount; - cd.coin.currentAmount = newAmount; - cd.coin.status = CoinStatus.Dirty; - - const d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT) - .put(decodeCrock(contractTermsHash)) - .put(decodeCrock(contractTerms.H_wire)) - .put(talerTimestampStringToBuffer(contractTerms.timestamp)) - .put(talerTimestampStringToBuffer(contractTerms.refund_deadline)) - .put(amountToBuffer(coinSpend)) - .put(amountToBuffer(cd.denom.feeDeposit)) - .put(decodeCrock(contractTerms.merchant_pub)) - .put(decodeCrock(cd.coin.coinPub)) - .build(); - const coinSig = eddsaSign(d, decodeCrock(cd.coin.coinPriv)); - - const s: CoinPaySig = { - coin_pub: cd.coin.coinPub, - coin_sig: encodeCrock(coinSig), - contribution: Amounts.toString(coinSpend), - denom_pub: cd.coin.denomPub, - exchange_url: cd.denom.exchangeBaseUrl, - ub_sig: cd.coin.denomSig, - }; - ret.sigs.push(s); - ret.updatedCoins.push(cd.coin); - ret.originalCoins.push(originalCoin); - } - return ret; - } - - /** - * Create a new refresh session. - */ - createRefreshSession( - exchangeBaseUrl: string, - kappa: number, - meltCoin: CoinRecord, - newCoinDenoms: DenominationRecord[], - meltFee: AmountJson, - ): RefreshSessionRecord { - let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency); - - for (const ncd of newCoinDenoms) { - valueWithFee = Amounts.add(valueWithFee, ncd.value, ncd.feeWithdraw) - .amount; - } - - // melt fee - valueWithFee = Amounts.add(valueWithFee, meltFee).amount; - - const sessionHc = createHashContext(); - - const transferPubs: string[] = []; - const transferPrivs: string[] = []; - - const planchetsForGammas: RefreshPlanchetRecord[][] = []; - - for (let i = 0; i < kappa; i++) { - const transferKeyPair = createEcdheKeyPair(); - sessionHc.update(transferKeyPair.ecdhePub); - transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv)); - transferPubs.push(encodeCrock(transferKeyPair.ecdhePub)); - } - - for (const denom of newCoinDenoms) { - const r = decodeCrock(denom.denomPub); - sessionHc.update(r); - } - - sessionHc.update(decodeCrock(meltCoin.coinPub)); - sessionHc.update(amountToBuffer(valueWithFee)); - - for (let i = 0; i < kappa; i++) { - const planchets: RefreshPlanchetRecord[] = []; - for (let j = 0; j < newCoinDenoms.length; j++) { - const transferPriv = decodeCrock(transferPrivs[i]); - const oldCoinPub = decodeCrock(meltCoin.coinPub); - const transferSecret = keyExchangeEcdheEddsa(transferPriv, oldCoinPub); - - const fresh = setupRefreshPlanchet(transferSecret, j); - - const coinPriv = fresh.coinPriv; - const coinPub = fresh.coinPub; - const blindingFactor = fresh.bks; - const pubHash = hash(coinPub); - const denomPub = decodeCrock(newCoinDenoms[j].denomPub); - const ev = rsaBlind(pubHash, blindingFactor, denomPub); - const planchet: RefreshPlanchetRecord = { - blindingKey: encodeCrock(blindingFactor), - coinEv: encodeCrock(ev), - privateKey: encodeCrock(coinPriv), - publicKey: encodeCrock(coinPub), - }; - planchets.push(planchet); - sessionHc.update(ev); - } - planchetsForGammas.push(planchets); - } - - const sessionHash = sessionHc.finish(); - - const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT) - .put(sessionHash) - .put(amountToBuffer(valueWithFee)) - .put(amountToBuffer(meltFee)) - .put(decodeCrock(meltCoin.coinPub)) - .build(); - - const confirmSig = eddsaSign(confirmData, decodeCrock(meltCoin.coinPriv)); - - let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency); - for (const denom of newCoinDenoms) { - valueOutput = Amounts.add(valueOutput, denom.value).amount; - } - - const refreshSessionId = encodeCrock(getRandomBytes(32)); - - const refreshSession: RefreshSessionRecord = { - refreshSessionId, - confirmSig: encodeCrock(confirmSig), - exchangeBaseUrl, - finished: false, - hash: encodeCrock(sessionHash), - meltCoinPub: meltCoin.coinPub, - newDenomHashes: newCoinDenoms.map(d => d.denomPubHash), - newDenoms: newCoinDenoms.map(d => d.denomPub), - norevealIndex: undefined, - planchetsForGammas: planchetsForGammas, - transferPrivs, - transferPubs, - valueOutput, - valueWithFee, - }; - - return refreshSession; - } - - /** - * Hash a string including the zero terminator. - */ - hashString(str: string): string { - const ts = new TextEncoder(); - const b = ts.encode(str + "\0"); - return encodeCrock(hash(b)); - } - - /** - * Hash a denomination public key. - */ - hashDenomPub(denomPub: string): string { - return encodeCrock(hash(decodeCrock(denomPub))); - } - - signCoinLink( - oldCoinPriv: string, - newDenomHash: string, - oldCoinPub: string, - transferPub: string, - coinEv: string, - ): string { - const coinEvHash = hash(decodeCrock(coinEv)); - const coinLink = buildSigPS(SignaturePurpose.WALLET_COIN_LINK) - .put(decodeCrock(newDenomHash)) - .put(decodeCrock(oldCoinPub)) - .put(decodeCrock(transferPub)) - .put(coinEvHash) - .build(); - const coinPriv = decodeCrock(oldCoinPriv); - const sig = eddsaSign(coinLink, coinPriv); - return encodeCrock(sig); - } - - benchmark(repetitions: number): BenchmarkResult { - let time_hash = 0; - for (let i = 0; i < repetitions; i++) { - const start = timer.performanceNow(); - this.hashString("hello world"); - time_hash += timer.performanceNow() - start; - } - - let time_hash_big = 0; - for (let i = 0; i < repetitions; i++) { - const ba = randomBytes(4096); - const start = timer.performanceNow(); - hash(ba); - time_hash_big += timer.performanceNow() - start; - } - - let time_eddsa_create = 0; - for (let i = 0; i < repetitions; i++) { - const start = timer.performanceNow(); - const pair = createEddsaKeyPair(); - time_eddsa_create += timer.performanceNow() - start; - } - - let time_eddsa_sign = 0; - const p = randomBytes(4096); - - const pair = createEddsaKeyPair(); - - for (let i = 0; i < repetitions; i++) { - const start = timer.performanceNow(); - eddsaSign(p, pair.eddsaPriv); - time_eddsa_sign += timer.performanceNow() - start; - } - - const sig = eddsaSign(p, pair.eddsaPriv); - - let time_eddsa_verify = 0; - for (let i = 0; i < repetitions; i++) { - const start = timer.performanceNow(); - eddsaVerify(p, sig, pair.eddsaPub); - time_eddsa_verify += timer.performanceNow() - start; - } - - return { - repetitions, - time: { - hash_small: time_hash, - hash_big: time_hash_big, - eddsa_create: time_eddsa_create, - eddsa_sign: time_eddsa_sign, - eddsa_verify: time_eddsa_verify, - }, - }; - } -} |