diff --git a/extension/lib/wallet/cryptoLib.ts b/extension/lib/wallet/cryptoLib.ts
new file mode 100644
index 000000000..2c32b3a63
--- /dev/null
+++ b/extension/lib/wallet/cryptoLib.ts
@@ -0,0 +1,137 @@
+/*
+ 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, If not, see
+ */
+
+import {Denomination} from "./types";
+/**
+ * Web worker for crypto operations.
+ * @author Florian Dold
+ */
+
+"use strict";
+
+import * as native from "./emscriptif";
+import {PreCoin, Reserve} from "./types";
+import create = chrome.alarms.create;
+
+
+export function main(worker: Worker) {
+ worker.onmessage = (msg: MessageEvent) => {
+ console.log("got data", msg.data);
+ 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[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});
+ }
+}
+
+console.log("hello, this is the crypto lib");
+
+namespace RpcFunctions {
+
+ /**
+ * Create a pre-coin of the given denomination to be withdrawn from then given
+ * reserve.
+ */
+ export function createPreCoin(denom: Denomination, reserve: Reserve): 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.RsaBlindingKey.create(1024);
+ let pubHash: native.HashCode = coinPub.hash();
+ let ev: native.ByteArray = native.rsaBlind(pubHash,
+ blindingFactor,
+ denomPub);
+
+ 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(),
+ mintBaseUrl: reserve.mint_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(),
+ });
+
+ 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);
+
+ }
+}
diff --git a/extension/lib/wallet/cryptoWorker.ts b/extension/lib/wallet/cryptoWorker.ts
new file mode 100644
index 000000000..958c2de74
--- /dev/null
+++ b/extension/lib/wallet/cryptoWorker.ts
@@ -0,0 +1,65 @@
+/*
+ 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, If not, see
+ */
+
+/**
+ * Web worker for crypto operations.
+ * @author Florian Dold
+ */
+
+"use strict";
+
+
+importScripts("../emscripten/libwrapper.js",
+ "../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,
+ });
+
+// 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");
+}
+
+
+// Manually register the emscripten js as a SystemJS, so that
+// we can use it from TypeScript by importing it.
+
+{
+ let mod = System.newModule({Module: Module});
+ let modName = System.normalizeSync("../emscripten/emsc");
+ console.log("registering", modName);
+ System.set(modName, mod);
+}
+
+System.import("./cryptoLib")
+ .then((m) => {
+ m.main(self);
+ console.log("loaded");
+ })
+ .catch((e) => {
+ console.log("crypto worker failed");
+ console.error(e.stack);
+ });
+
+console.log("in worker thread");
+
diff --git a/extension/lib/wallet/types.ts b/extension/lib/wallet/types.ts
index 3b0cb2638..d18bd95f4 100644
--- a/extension/lib/wallet/types.ts
+++ b/extension/lib/wallet/types.ts
@@ -107,6 +107,25 @@ export interface ReserveCreationInfo {
withdrawFee: AmountJson;
}
+
+export interface PreCoin {
+ coinPub: string;
+ coinPriv: string;
+ reservePub: string;
+ denomPub: string;
+ blindingKey: string;
+ withdrawSig: string;
+ coinEv: string;
+ mintBaseUrl: string;
+ coinValue: AmountJson;
+}
+
+export interface Reserve {
+ mint_base_url: string
+ reserve_priv: string;
+ reserve_pub: string;
+}
+
export interface Notifier {
notify();
}
\ No newline at end of file
diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts
index 2d4a29c8d..475d316e3 100644
--- a/extension/lib/wallet/wallet.ts
+++ b/extension/lib/wallet/wallet.ts
@@ -28,6 +28,8 @@ import {Query} from "./query";
import {Checkable} from "./checkable";
import {canonicalizeBaseUrl} from "./helpers";
import {ReserveCreationInfo} from "./types";
+import {PreCoin} from "./types";
+import {Reserve} from "./types";
"use strict";
@@ -65,18 +67,6 @@ export class KeysJson {
}
-export interface PreCoin {
- coinPub: string;
- coinPriv: string;
- reservePub: string;
- denomPub: string;
- blindingKey: string;
- withdrawSig: string;
- coinEv: string;
- mintBaseUrl: string;
- coinValue: AmountJson;
-}
-
export interface Coin {
coinPub: string;
coinPriv: string;
@@ -87,34 +77,6 @@ export interface Coin {
}
-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(),
- });
-
- 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);
-
-}
-
-
class MintInfo implements IMintInfo {
baseUrl: string;
masterPublicKey: string;
@@ -145,53 +107,61 @@ class MintInfo implements IMintInfo {
* mint info is updated with the new information up until
* the first error.
*/
- mergeKeys(newKeys: KeysJson) {
- if (!this.masterPublicKey) {
- this.masterPublicKey = newKeys.master_public_key;
- }
+ mergeKeys(newKeys: KeysJson, wallet: Wallet): Promise {
+ return Promise.resolve().then(() => {
+ if (!this.masterPublicKey) {
+ this.masterPublicKey = newKeys.master_public_key;
+ }
- if (this.masterPublicKey != newKeys.master_public_key) {
- throw Error("public keys do not match");
- }
+ if (this.masterPublicKey != newKeys.master_public_key) {
+ throw Error("public keys do not match");
+ }
- for (let newDenom of newKeys.denoms) {
- let found = false;
- for (let oldDenom of this.denoms) {
- if (oldDenom.denom_pub === newDenom.denom_pub) {
- let a = Object.assign({}, oldDenom);
- let b = Object.assign({}, newDenom);
- // pub hash is only there for convenience in the wallet
- delete a["pub_hash"];
- delete b["pub_hash"];
- if (!_.isEqual(a, b)) {
- console.log("old/new:");
- console.dir(a);
- console.dir(b);
- throw Error("denomination modified");
+ for (let newDenom of newKeys.denoms) {
+ let found = false;
+ for (let oldDenom of this.denoms) {
+ if (oldDenom.denom_pub === newDenom.denom_pub) {
+ let a = Object.assign({}, oldDenom);
+ let b = Object.assign({}, newDenom);
+ // pub hash is only there for convenience in the wallet
+ delete a["pub_hash"];
+ delete b["pub_hash"];
+ if (!_.isEqual(a, b)) {
+ console.log("old/new:");
+ console.dir(a);
+ console.dir(b);
+ throw Error("denomination modified");
+ }
+ // TODO: check if info still matches
+ found = true;
+ break;
}
- // TODO: check if info still matches
- found = true;
- break;
}
+
+ if (found) {
+ continue;
+ }
+
+ console.log("validating denomination");
+
+ return wallet.isValidDenom(newDenom, this.masterPublicKey)
+ .then((valid) => {
+ if (!valid) {
+ throw Error("signature on denomination invalid");
+ }
+
+ let d: Denomination = Object.assign({}, newDenom);
+ d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub)
+ .encode()
+ .hash()
+ .toCrock();
+ this.denoms.push(d);
+
+ });
+
}
-
- if (found) {
- continue;
- }
-
- console.log("validating denomination");
-
- if (!isValidDenom(newDenom, this.masterPublicKey)) {
- throw Error("signature on denomination invalid");
- }
-
- let d: Denomination = Object.assign({}, newDenom);
- d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub)
- .encode()
- .hash()
- .toCrock();
- this.denoms.push(d);
- }
+ return;
+ });
}
}
@@ -330,13 +300,6 @@ interface Transaction {
}
-interface Reserve {
- mint_base_url: string
- reserve_priv: string;
- reserve_pub: string;
-}
-
-
export interface Badge {
setText(s: string): void;
setColor(c: string): void;
@@ -395,62 +358,6 @@ function rankDenom(denom1: any, denom2: any) {
}
-function mergeMintKeys(oldKeys: KeysJson, newKeys: KeysJson) {
-}
-
-
-/**
- * Create a pre-coin of the given denomination to be withdrawn from then given
- * reserve.
- */
-function createPreCoin(denom: Denomination, reserve: Reserve): 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.RsaBlindingKey.create(1024);
- let pubHash: native.HashCode = coinPub.hash();
- let ev: native.ByteArray = native.rsaBlind(pubHash,
- blindingFactor,
- denomPub);
-
- 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(),
- mintBaseUrl: reserve.mint_base_url,
- withdrawSig: sig.toCrock(),
- coinEv: ev.toCrock(),
- coinValue: denom.value
- };
- return preCoin;
-}
-
-
/**
* Get a list of denominations (with repetitions possible)
* whose total value is as close as possible to the available
@@ -493,6 +400,9 @@ export class Wallet {
private http: HttpRequestLibrary;
private badge: Badge;
private notifier: Notifier;
+ private cryptoWorker: Worker;
+ private nextRpcId: number = 1;
+ private rpcRegistry = {};
constructor(db: IDBDatabase,
@@ -503,6 +413,21 @@ export class Wallet {
this.http = http;
this.badge = badge;
this.notifier = notifier;
+ this.cryptoWorker = new Worker("/lib/wallet/cryptoWorker.js");
+
+ this.cryptoWorker.onmessage = (msg: MessageEvent) => {
+ let id = msg.data.id;
+ if (typeof id !== "number") {
+ console.error("rpc id must be number");
+ return;
+ }
+ if (!this.rpcRegistry[id]) {
+ console.error(`RPC with id ${id} has no registry entry`);
+ return;
+ }
+ let {resolve, reject} = this.rpcRegistry[id];
+ resolve(msg.data.result);
+ }
}
@@ -693,7 +618,9 @@ export class Wallet {
.put("history", historyEntry)
.putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
.finish()
- .then(() => { this.notifier.notify(); });
+ .then(() => {
+ this.notifier.notify();
+ });
}
@@ -903,7 +830,9 @@ export class Wallet {
.add("coins", coin)
.add("history", historyEntry)
.finish()
- .then(() => { this.notifier.notify(); });
+ .then(() => {
+ this.notifier.notify();
+ });
}
@@ -912,12 +841,15 @@ export class Wallet {
*/
private withdraw(denom: Denomination, reserve: Reserve): Promise {
console.log("creating pre coin at", new Date());
- let preCoin = createPreCoin(denom, reserve);
- return Query(this.db)
- .put("precoins", preCoin)
- .finish()
- .then(() => this.withdrawExecute(preCoin))
- .then((c) => this.storeCoin(c));
+ return this.createPreCoin(denom, reserve)
+ .then((preCoin) => {
+ return Query(this.db)
+ .put("precoins", preCoin)
+ .finish()
+ .then(() => this.withdrawExecute(preCoin))
+ .then((c) => this.storeCoin(c));
+ });
+
}
@@ -1028,8 +960,14 @@ export class Wallet {
console.log("using old mint");
}
- mint.mergeKeys(mintKeysJson);
- return Query(this.db).put("mints", mint).finish().then(() => mint);
+ return mint.mergeKeys(mintKeysJson)
+ .then(() => {
+ return Query(this.db)
+ .put("mints", mint)
+ .finish()
+ .then(() => mint);
+ });
+
});
});
}
@@ -1070,4 +1008,34 @@ export class Wallet {
.iter("history", {indexName: "timestamp"})
.reduce(collect, [])
}
+
+ registerRpcId(resolve, reject): number {
+ let id = this.nextRpcId++;
+ this.rpcRegistry[id] = {resolve, reject};
+ return id;
+ }
+
+
+ createPreCoin(denom: Denomination, reserve: Reserve): Promise {
+ return new Promise((resolve, reject) => {
+ let msg = {
+ operation: "createPreCoin",
+ id: this.registerRpcId(resolve, reject),
+ args: [denom, reserve]
+ };
+ this.cryptoWorker.postMessage(msg);
+ });
+ }
+
+ isValidDenom(denom: Denomination,
+ masterPub: string): Promise {
+ return new Promise((resolve, reject) => {
+ let msg = {
+ operation: "isValidDenom",
+ id: this.registerRpcId(resolve, reject),
+ args: [denom, masterPub]
+ };
+ this.cryptoWorker.postMessage(msg);
+ });
+ }
}
\ No newline at end of file
diff --git a/extension/tsconfig.json b/extension/tsconfig.json
index a3eea3a70..96f360370 100644
--- a/extension/tsconfig.json
+++ b/extension/tsconfig.json
@@ -13,6 +13,8 @@
"lib/i18n.ts",
"lib/refs.ts",
"lib/wallet/checkable.ts",
+ "lib/wallet/cryptoLib.ts",
+ "lib/wallet/cryptoWorker.ts",
"lib/wallet/db.ts",
"lib/wallet/emscriptif.ts",
"lib/wallet/helpers.ts",
@@ -29,4 +31,4 @@
"pages/confirm-create-reserve.tsx",
"test/tests/taler.ts"
]
-}
+}
\ No newline at end of file