From f3fb8be7db6de87dae40d41bd5597a735c800ca1 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sun, 13 Nov 2016 23:30:18 +0100 Subject: restructuring --- src/cryptoLib.ts | 345 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 src/cryptoLib.ts (limited to 'src/cryptoLib.ts') diff --git a/src/cryptoLib.ts b/src/cryptoLib.ts new file mode 100644 index 000000000..1db686756 --- /dev/null +++ b/src/cryptoLib.ts @@ -0,0 +1,345 @@ +/* + This file is part of TALER + (C) 2016 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 + */ + +/** + * Web worker for crypto operations. + * @author Florian Dold + */ + +"use strict"; + +import * as native from "./emscriptif"; +import { + PreCoin, PayCoinInfo, AmountJson, + RefreshSession, RefreshPreCoin, ReserveRecord +} from "./types"; +import create = chrome.alarms.create; +import {Offer} from "./wallet"; +import {CoinWithDenom} from "./wallet"; +import {CoinPaySig, Coin} from "./types"; +import {Denomination, Amounts} from "./types"; +import {Amount} from "./emscriptif"; +import {HashContext} from "./emscriptif"; +import {RefreshMeltCoinAffirmationPS} from "./emscriptif"; +import {EddsaPublicKey} from "./emscriptif"; +import {HashCode} from "./emscriptif"; + + +export function main(worker: Worker) { + worker.onmessage = (msg: MessageEvent) => { + if (!Array.isArray(msg.data.args)) { + console.error("args must be array"); + return; + } + if (typeof msg.data.id != "number") { + console.error("RPC id must be number"); + } + if (typeof msg.data.operation != "string") { + console.error("RPC operation must be string"); + } + let f = (RpcFunctions as any)[msg.data.operation]; + if (!f) { + console.error(`unknown operation: '${msg.data.operation}'`); + return; + } + let res = f(...msg.data.args); + worker.postMessage({result: res, id: msg.data.id}); + } +} + + +namespace RpcFunctions { + + /** + * Create a pre-coin of the given denomination to be withdrawn from then given + * reserve. + */ + export function createPreCoin(denom: Denomination, + reserve: ReserveRecord): PreCoin { + let reservePriv = new native.EddsaPrivateKey(); + reservePriv.loadCrock(reserve.reserve_priv); + let reservePub = new native.EddsaPublicKey(); + reservePub.loadCrock(reserve.reserve_pub); + let denomPub = native.RsaPublicKey.fromCrock(denom.denom_pub); + let coinPriv = native.EddsaPrivateKey.create(); + let coinPub = coinPriv.getPublicKey(); + let blindingFactor = native.RsaBlindingKeySecret.create(); + let pubHash: native.HashCode = coinPub.hash(); + let ev = native.rsaBlind(pubHash, + blindingFactor, + denomPub); + + if (!ev) { + throw Error("couldn't blind (malicious exchange key?)"); + } + + if (!denom.fee_withdraw) { + throw Error("Field fee_withdraw missing"); + } + + let amountWithFee = new native.Amount(denom.value); + amountWithFee.add(new native.Amount(denom.fee_withdraw)); + let withdrawFee = new native.Amount(denom.fee_withdraw); + + // Signature + let withdrawRequest = new native.WithdrawRequestPS({ + reserve_pub: reservePub, + amount_with_fee: amountWithFee.toNbo(), + withdraw_fee: withdrawFee.toNbo(), + h_denomination_pub: denomPub.encode().hash(), + h_coin_envelope: ev.hash() + }); + + var sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv); + + let preCoin: PreCoin = { + reservePub: reservePub.toCrock(), + blindingKey: blindingFactor.toCrock(), + coinPub: coinPub.toCrock(), + coinPriv: coinPriv.toCrock(), + denomPub: denomPub.encode().toCrock(), + exchangeBaseUrl: reserve.exchange_base_url, + withdrawSig: sig.toCrock(), + coinEv: ev.toCrock(), + coinValue: denom.value + }; + return preCoin; + } + + + export function isValidDenom(denom: Denomination, + masterPub: string): boolean { + let p = new native.DenominationKeyValidityPS({ + master: native.EddsaPublicKey.fromCrock(masterPub), + denom_hash: native.RsaPublicKey.fromCrock(denom.denom_pub) + .encode() + .hash(), + expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_legal), + expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_deposit), + expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_withdraw), + start: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_start), + value: (new native.Amount(denom.value)).toNbo(), + fee_deposit: (new native.Amount(denom.fee_deposit)).toNbo(), + fee_refresh: (new native.Amount(denom.fee_refresh)).toNbo(), + fee_withdraw: (new native.Amount(denom.fee_withdraw)).toNbo(), + fee_refund: (new native.Amount(denom.fee_refund)).toNbo(), + }); + + let nativeSig = new native.EddsaSignature(); + nativeSig.loadCrock(denom.master_sig); + + let nativePub = native.EddsaPublicKey.fromCrock(masterPub); + + return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY, + p.toPurpose(), + nativeSig, + nativePub); + + } + + + export function createEddsaKeypair(): {priv: string, pub: string} { + const priv = native.EddsaPrivateKey.create(); + const pub = priv.getPublicKey(); + return {priv: priv.toCrock(), pub: pub.toCrock()}; + } + + + export function rsaUnblind(sig: string, bk: string, pk: string): string { + let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig), + native.RsaBlindingKeySecret.fromCrock(bk), + native.RsaPublicKey.fromCrock(pk)); + return denomSig.encode().toCrock() + } + + + /** + * Generate updated coins (to store in the database) + * and deposit permissions for each given coin. + */ + export function signDeposit(offer: Offer, + cds: CoinWithDenom[]): PayCoinInfo { + let ret: PayCoinInfo = []; + let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency); + let amountRemaining = new native.Amount(offer.contract.amount); + for (let cd of cds) { + let coinSpend: Amount; + + if (amountRemaining.value == 0 && amountRemaining.fraction == 0) { + break; + } + + if (amountRemaining.cmp(new native.Amount(cd.coin.currentAmount)) < 0) { + coinSpend = new native.Amount(amountRemaining.toJson()); + } else { + coinSpend = new native.Amount(cd.coin.currentAmount); + } + + amountSpent.add(coinSpend); + amountRemaining.sub(coinSpend); + + let newAmount = new native.Amount(cd.coin.currentAmount); + newAmount.sub(coinSpend); + cd.coin.currentAmount = newAmount.toJson(); + cd.coin.dirty = true; + cd.coin.transactionPending = true; + + let d = new native.DepositRequestPS({ + h_contract: native.HashCode.fromCrock(offer.H_contract), + h_wire: native.HashCode.fromCrock(offer.contract.H_wire), + amount_with_fee: coinSpend.toNbo(), + coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub), + deposit_fee: new native.Amount(cd.denom.fee_deposit).toNbo(), + merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub), + refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline), + timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp), + transaction_id: native.UInt64.fromNumber(offer.contract.transaction_id), + }); + + let coinSig = native.eddsaSign(d.toPurpose(), + native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv)) + .toCrock(); + + let s: CoinPaySig = { + coin_sig: coinSig, + coin_pub: cd.coin.coinPub, + ub_sig: cd.coin.denomSig, + denom_pub: cd.coin.denomPub, + f: coinSpend.toJson(), + }; + ret.push({sig: s, updatedCoin: cd.coin}); + } + return ret; + } + + + export function createRefreshSession(exchangeBaseUrl: string, + kappa: number, + meltCoin: Coin, + newCoinDenoms: Denomination[], + meltFee: AmountJson): RefreshSession { + + let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency); + + for (let ncd of newCoinDenoms) { + valueWithFee = Amounts.add(valueWithFee, + ncd.value, + ncd.fee_withdraw).amount; + } + + // melt fee + valueWithFee = Amounts.add(valueWithFee, meltFee).amount; + + let sessionHc = new HashContext(); + + let transferPubs: string[] = []; + let transferPrivs: string[] = []; + + let preCoinsForGammas: RefreshPreCoin[][] = []; + + for (let i = 0; i < kappa; i++) { + let t = native.EcdhePrivateKey.create(); + let pub = t.getPublicKey(); + sessionHc.read(pub); + transferPrivs.push(t.toCrock()); + transferPubs.push(pub.toCrock()); + } + + for (let i = 0; i < newCoinDenoms.length; i++) { + let r = native.RsaPublicKey.fromCrock(newCoinDenoms[i].denom_pub); + sessionHc.read(r.encode()); + } + + sessionHc.read(native.EddsaPublicKey.fromCrock(meltCoin.coinPub)); + sessionHc.read((new native.Amount(valueWithFee)).toNbo()); + + for (let i = 0; i < kappa; i++) { + let preCoins: RefreshPreCoin[] = []; + for (let j = 0; j < newCoinDenoms.length; j++) { + + let transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]); + let oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub); + let transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub); + + let fresh = native.setupFreshCoin(transferSecret, j); + + let coinPriv = fresh.priv; + let coinPub = coinPriv.getPublicKey(); + let blindingFactor = fresh.blindingKey; + let pubHash: native.HashCode = coinPub.hash(); + let denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[j].denom_pub); + let ev = native.rsaBlind(pubHash, + blindingFactor, + denomPub); + if (!ev) { + throw Error("couldn't blind (malicious exchange key?)"); + } + let preCoin: RefreshPreCoin = { + blindingKey: blindingFactor.toCrock(), + coinEv: ev.toCrock(), + publicKey: coinPub.toCrock(), + privateKey: coinPriv.toCrock(), + }; + preCoins.push(preCoin); + sessionHc.read(ev); + } + preCoinsForGammas.push(preCoins); + } + + let sessionHash = new HashCode(); + sessionHash.alloc(); + sessionHc.finish(sessionHash); + + let confirmData = new RefreshMeltCoinAffirmationPS({ + coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub), + amount_with_fee: (new Amount(valueWithFee)).toNbo(), + session_hash: sessionHash, + melt_fee: (new Amount(meltFee)).toNbo() + }); + + + let confirmSig: string = native.eddsaSign(confirmData.toPurpose(), + native.EddsaPrivateKey.fromCrock( + meltCoin.coinPriv)).toCrock(); + + let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency); + for (let denom of newCoinDenoms) { + valueOutput = Amounts.add(valueOutput, denom.value).amount; + } + + let refreshSession: RefreshSession = { + meltCoinPub: meltCoin.coinPub, + newDenoms: newCoinDenoms.map((d) => d.denom_pub), + confirmSig, + valueWithFee, + transferPubs, + preCoinsForGammas, + hash: sessionHash.toCrock(), + norevealIndex: undefined, + exchangeBaseUrl, + transferPrivs, + finished: false, + valueOutput, + }; + + return refreshSession; + } + + export function hashString(str: string): string { + const b = native.ByteArray.fromStringWithNull(str); + return b.hash().toCrock(); + } +} -- cgit v1.2.3