summaryrefslogtreecommitdiff
path: root/src/cryptoWorker.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/cryptoWorker.ts')
-rw-r--r--src/cryptoWorker.ts365
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});
+}