diff options
Diffstat (limited to 'src/cryptoWorker.ts')
-rw-r--r-- | src/cryptoWorker.ts | 365 |
1 files changed, 330 insertions, 35 deletions
diff --git a/src/cryptoWorker.ts b/src/cryptoWorker.ts index 661b6e174..0abcb36ff 100644 --- a/src/cryptoWorker.ts +++ b/src/cryptoWorker.ts @@ -21,44 +21,339 @@ "use strict"; +import * as native from "./emscriptif"; +import { + PreCoinRecord, PayCoinInfo, AmountJson, + RefreshSessionRecord, RefreshPreCoinRecord, ReserveRecord, CoinStatus, +} from "./types"; +import create = chrome.alarms.create; +import {OfferRecord} from "./wallet"; +import {CoinWithDenom} from "./wallet"; +import {CoinPaySig, CoinRecord} from "./types"; +import {DenominationRecord, Amounts} from "./types"; +import {Amount} from "./emscriptif"; +import {HashContext} from "./emscriptif"; +import {RefreshMeltCoinAffirmationPS} from "./emscriptif"; +import {EddsaPublicKey} from "./emscriptif"; +import {HashCode} from "./emscriptif"; -importScripts("/src/emscripten/taler-emscripten-lib.js", - "/src/vendor/system-csp-production.src.js"); - - -// TypeScript does not allow ".js" extensions in the -// module name, so SystemJS must add it. -System.config({ - defaultJSExtensions: true, - map: { - "src": "/src", - }, -}); - -// We expect that in the manifest, the emscripten js is loaded -// becore the background page. -// Currently it is not possible to use SystemJS to load the emscripten js. -declare var Module: any; -if ("object" !== typeof Module) { - throw Error("emscripten not loaded, no 'Module' defined"); -} +namespace RpcFunctions { -// Manually register the emscripten js as a SystemJS, so that -// we can use it from TypeScript by importing it. + /** + * Create a pre-coin of the given denomination to be withdrawn from then given + * reserve. + */ + export function createPreCoin(denom: DenominationRecord, + reserve: ReserveRecord): PreCoinRecord { + 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.denomPub); + 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); -{ - let mod = System.newModule({Module: Module, default: Module}); - let modName = System.normalizeSync("/src/emscripten/taler-emscripten-lib"); - console.log("registering", modName); - System.set(modName, mod); -} + if (!ev) { + throw Error("couldn't blind (malicious exchange key?)"); + } + + if (!denom.feeWithdraw) { + throw Error("Field fee_withdraw missing"); + } + + let amountWithFee = new native.Amount(denom.value); + amountWithFee.add(new native.Amount(denom.feeWithdraw)); + let withdrawFee = new native.Amount(denom.feeWithdraw); + + // 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: PreCoinRecord = { + 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: DenominationRecord, + masterPub: string): boolean { + let p = new native.DenominationKeyValidityPS({ + master: native.EddsaPublicKey.fromCrock(masterPub), + denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub) + .encode() + .hash(), + expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireLegal), + expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireDeposit), + expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireWithdraw), + start: native.AbsoluteTimeNbo.fromTalerString(denom.stampStart), + value: (new native.Amount(denom.value)).toNbo(), + fee_deposit: (new native.Amount(denom.feeDeposit)).toNbo(), + fee_refresh: (new native.Amount(denom.feeRefresh)).toNbo(), + fee_withdraw: (new native.Amount(denom.feeWithdraw)).toNbo(), + fee_refund: (new native.Amount(denom.feeRefund)).toNbo(), + }); + + let nativeSig = new native.EddsaSignature(); + nativeSig.loadCrock(denom.masterSig); + + 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()}; + } -System.import("/src/cryptoLib") - .then((m: any) => { - m.main(self); - }) - .catch((e: Error) => { - console.log("crypto worker failed"); - console.error(e.stack); + + 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: OfferRecord, + cds: CoinWithDenom[]): PayCoinInfo { + let ret: PayCoinInfo = []; + + + let 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, offer.contract.max_fee).amount; + let total = Amounts.add(fees, offer.contract.amount).amount; + + let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency); + let amountRemaining = new native.Amount(total); + 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 feeDeposit: Amount = new native.Amount(cd.denom.feeDeposit); + + // Give the merchant at least the deposit fee, otherwise it'll reject + // the coin. + if (coinSpend.cmp(feeDeposit) < 0) { + coinSpend = feeDeposit; + } + + let newAmount = new native.Amount(cd.coin.currentAmount); + newAmount.sub(coinSpend); + cd.coin.currentAmount = newAmount.toJson(); + cd.coin.status = CoinStatus.TransactionPending; + + 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.feeDeposit).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), }); + + 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: CoinRecord, + newCoinDenoms: DenominationRecord[], + meltFee: AmountJson): RefreshSessionRecord { + + let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency); + + for (let ncd of newCoinDenoms) { + valueWithFee = Amounts.add(valueWithFee, + ncd.value, + ncd.feeWithdraw).amount; + } + + // melt fee + valueWithFee = Amounts.add(valueWithFee, meltFee).amount; + + let sessionHc = new HashContext(); + + let transferPubs: string[] = []; + let transferPrivs: string[] = []; + + let preCoinsForGammas: RefreshPreCoinRecord[][] = []; + + 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].denomPub); + 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: RefreshPreCoinRecord[] = []; + 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].denomPub); + let ev = native.rsaBlind(pubHash, + blindingFactor, + denomPub); + if (!ev) { + throw Error("couldn't blind (malicious exchange key?)"); + } + let preCoin: RefreshPreCoinRecord = { + 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: RefreshSessionRecord = { + meltCoinPub: meltCoin.coinPub, + newDenoms: newCoinDenoms.map((d) => d.denomPub), + 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(); + } +} + + +let worker: Worker = (self as any) as 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}); +} |