aboutsummaryrefslogtreecommitdiff
path: root/lib/wallet
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-11-13 23:30:18 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-11-13 23:31:17 +0100
commitf3fb8be7db6de87dae40d41bd5597a735c800ca1 (patch)
tree1a061db04de8f5bb5a6b697fa56a9948f67fac2f /lib/wallet
parent200d83c3886149ebb3f018530302079e12a81f6b (diff)
restructuring
Diffstat (limited to 'lib/wallet')
-rw-r--r--lib/wallet/checkable.ts262
-rw-r--r--lib/wallet/chromeBadge.ts227
-rw-r--r--lib/wallet/cryptoApi-test.ts79
-rw-r--r--lib/wallet/cryptoApi.ts256
-rw-r--r--lib/wallet/cryptoLib.ts346
-rw-r--r--lib/wallet/cryptoWorker.ts61
-rw-r--r--lib/wallet/db.ts117
-rw-r--r--lib/wallet/emscriptif-test.ts21
-rw-r--r--lib/wallet/emscriptif.ts1245
-rw-r--r--lib/wallet/helpers.ts140
-rw-r--r--lib/wallet/http.ts97
-rw-r--r--lib/wallet/query.ts612
-rw-r--r--lib/wallet/renderHtml.tsx63
-rw-r--r--lib/wallet/types-test.ts38
-rw-r--r--lib/wallet/types.ts554
-rw-r--r--lib/wallet/wallet.ts1657
-rw-r--r--lib/wallet/wxApi.ts75
-rw-r--r--lib/wallet/wxMessaging.ts439
18 files changed, 0 insertions, 6289 deletions
diff --git a/lib/wallet/checkable.ts b/lib/wallet/checkable.ts
deleted file mode 100644
index 89d0c7150..000000000
--- a/lib/wallet/checkable.ts
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-
-"use strict";
-
-/**
- * Decorators for type-checking JSON into
- * an object.
- * @module Checkable
- * @author Florian Dold
- */
-
-export namespace Checkable {
-
- type Path = (number | string)[];
-
- interface SchemaErrorConstructor {
- new (err: string): SchemaError;
- }
-
- interface SchemaError {
- name: string;
- message: string;
- }
-
- interface Prop {
- propertyKey: any;
- checker: any;
- type: any;
- elementChecker?: any;
- elementProp?: any;
- }
-
- export let SchemaError = (function SchemaError(message: string) {
- this.name = 'SchemaError';
- this.message = message;
- this.stack = (<any>new Error()).stack;
- }) as any as SchemaErrorConstructor;
-
-
- SchemaError.prototype = new Error;
-
- let chkSym = Symbol("checkable");
-
-
- function checkNumber(target: any, prop: Prop, path: Path): any {
- if ((typeof target) !== "number") {
- throw new SchemaError(`expected number for ${path}`);
- }
- return target;
- }
-
-
- function checkString(target: any, prop: Prop, path: Path): any {
- if (typeof target !== "string") {
- throw new SchemaError(`expected string for ${path}, got ${typeof target} instead`);
- }
- return target;
- }
-
-
- function checkAnyObject(target: any, prop: Prop, path: Path): any {
- if (typeof target !== "object") {
- throw new SchemaError(`expected (any) object for ${path}, got ${typeof target} instead`);
- }
- return target;
- }
-
-
- function checkAny(target: any, prop: Prop, path: Path): any {
- return target;
- }
-
-
- function checkList(target: any, prop: Prop, path: Path): any {
- if (!Array.isArray(target)) {
- throw new SchemaError(`array expected for ${path}, got ${typeof target} instead`);
- }
- for (let i = 0; i < target.length; i++) {
- let v = target[i];
- prop.elementChecker(v, prop.elementProp, path.concat([i]));
- }
- return target;
- }
-
-
- function checkOptional(target: any, prop: Prop, path: Path): any {
- console.assert(prop.propertyKey);
- prop.elementChecker(target,
- prop.elementProp,
- path.concat([prop.propertyKey]));
- return target;
- }
-
-
- function checkValue(target: any, prop: Prop, path: Path): any {
- let type = prop.type;
- if (!type) {
- throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`);
- }
- let v = target;
- if (!v || typeof v !== "object") {
- throw new SchemaError(
- `expected object for ${path.join(".")}, got ${typeof v} instead`);
- }
- let props = type.prototype[chkSym].props;
- let remainingPropNames = new Set(Object.getOwnPropertyNames(v));
- let obj = new type();
- for (let prop of props) {
- if (!remainingPropNames.has(prop.propertyKey)) {
- if (prop.optional) {
- continue;
- }
- throw new SchemaError("Property missing: " + prop.propertyKey);
- }
- if (!remainingPropNames.delete(prop.propertyKey)) {
- throw new SchemaError("assertion failed");
- }
- let propVal = v[prop.propertyKey];
- obj[prop.propertyKey] = prop.checker(propVal,
- prop,
- path.concat([prop.propertyKey]));
- }
-
- if (remainingPropNames.size != 0) {
- throw new SchemaError("superfluous properties " + JSON.stringify(Array.from(
- remainingPropNames.values())));
- }
- return obj;
- }
-
-
- export function Class(target: any) {
- target.checked = (v: any) => {
- return checkValue(v, {
- propertyKey: "(root)",
- type: target,
- checker: checkValue
- }, ["(root)"]);
- };
- return target;
- }
-
-
- export function Value(type: any) {
- if (!type) {
- throw Error("Type does not exist yet (wrong order of definitions?)");
- }
- function deco(target: Object, propertyKey: string | symbol): void {
- let chk = mkChk(target);
- chk.props.push({
- propertyKey: propertyKey,
- checker: checkValue,
- type: type
- });
- }
-
- return deco;
- }
-
-
- export function List(type: any) {
- let stub = {};
- type(stub, "(list-element)");
- let elementProp = mkChk(stub).props[0];
- let elementChecker = elementProp.checker;
- if (!elementChecker) {
- throw Error("assertion failed");
- }
- function deco(target: Object, propertyKey: string | symbol): void {
- let chk = mkChk(target);
- chk.props.push({
- elementChecker,
- elementProp,
- propertyKey: propertyKey,
- checker: checkList,
- });
- }
-
- return deco;
- }
-
-
- export function Optional(type: any) {
- let stub = {};
- type(stub, "(optional-element)");
- let elementProp = mkChk(stub).props[0];
- let elementChecker = elementProp.checker;
- if (!elementChecker) {
- throw Error("assertion failed");
- }
- function deco(target: Object, propertyKey: string | symbol): void {
- let chk = mkChk(target);
- chk.props.push({
- elementChecker,
- elementProp,
- propertyKey: propertyKey,
- checker: checkOptional,
- optional: true,
- });
- }
-
- return deco;
- }
-
-
- export function Number(target: Object, propertyKey: string | symbol): void {
- let chk = mkChk(target);
- chk.props.push({ propertyKey: propertyKey, checker: checkNumber });
- }
-
-
- export function AnyObject(target: Object,
- propertyKey: string | symbol): void {
- let chk = mkChk(target);
- chk.props.push({
- propertyKey: propertyKey,
- checker: checkAnyObject
- });
- }
-
-
- export function Any(target: Object,
- propertyKey: string | symbol): void {
- let chk = mkChk(target);
- chk.props.push({
- propertyKey: propertyKey,
- checker: checkAny,
- optional: true
- });
- }
-
-
- export function String(target: Object, propertyKey: string | symbol): void {
- let chk = mkChk(target);
- chk.props.push({ propertyKey: propertyKey, checker: checkString });
- }
-
-
- function mkChk(target: any) {
- let chk = target[chkSym];
- if (!chk) {
- chk = { props: [] };
- target[chkSym] = chk;
- }
- return chk;
- }
-} \ No newline at end of file
diff --git a/lib/wallet/chromeBadge.ts b/lib/wallet/chromeBadge.ts
deleted file mode 100644
index df12fba83..000000000
--- a/lib/wallet/chromeBadge.ts
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 INRIA
-
- 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/>
- */
-
-import {
- Badge
-} from "./wallet";
-
-
-/**
- * Polyfill for requestAnimationFrame, which
- * doesn't work from a background page.
- */
-function rAF(cb: (ts: number) => void) {
- window.setTimeout(() => {
- cb(performance.now());
- }, 100 /* 100 ms delay between frames */);
-}
-
-
-export class ChromeBadge implements Badge {
- canvas: HTMLCanvasElement;
- ctx: CanvasRenderingContext2D;
- /**
- * True if animation running. The animation
- * might still be running even if we're not busy anymore,
- * just to transition to the "normal" state in a animated way.
- */
- animationRunning: boolean = false;
-
- /**
- * Is the wallet still busy? Note that we do not stop the
- * animation immediately when the wallet goes idle, but
- * instead slowly close the gap.
- */
- isBusy: boolean = false;
-
- /**
- * Current rotation angle, ranges from 0 to rotationAngleMax.
- */
- rotationAngle: number = 0;
-
- /**
- * While animating, how wide is the current gap in the circle?
- * Ranges from 0 to openMax.
- */
- gapWidth: number = 0;
-
- /**
- * Maximum value for our rotationAngle, corresponds to 2 Pi.
- */
- static rotationAngleMax = 1000;
-
- /**
- * How fast do we rotate? Given in rotation angle (relative to rotationAngleMax) per millisecond.
- */
- static rotationSpeed = 0.5;
-
- /**
- * How fast to we open? Given in rotation angle (relative to rotationAngleMax) per millisecond.
- */
- static openSpeed = 0.15;
-
- /**
- * How fast to we close? Given as a multiplication factor per frame update.
- */
- static closeSpeed = 0.7;
-
- /**
- * How far do we open? Given relative to rotationAngleMax.
- */
- static openMax = 100;
-
- constructor(window?: Window) {
- // Allow injecting another window for testing
- let bg = window || chrome.extension.getBackgroundPage();
- if (!bg) {
- throw Error("no window available");
- }
- this.canvas = bg.document.createElement("canvas");
- // Note: changing the width here means changing the font
- // size in draw() as well!
- this.canvas.width = 32;
- this.canvas.height = 32;
- this.ctx = this.canvas.getContext("2d")!;
- this.draw();
- }
-
- /**
- * Draw the badge based on the current state.
- */
- private draw() {
- this.ctx.setTransform(1, 0, 0, 1, 0, 0);
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
-
- this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2);
-
- this.ctx.beginPath();
- this.ctx.arc(0, 0, this.canvas.width / 2 - 2, 0, 2 * Math.PI);
- this.ctx.fillStyle = "white";
- this.ctx.fill();
-
- // move into the center, off by 2 for aligning the "T" with the bottom
- // of the circle.
- this.ctx.translate(0, 2);
-
- // pick sans-serif font; note: 14px is based on the 32px width above!
- this.ctx.font = "bold 24px sans-serif";
- // draw the "T" perfectly centered (x and y) to the current position
- this.ctx.textAlign = "center";
- this.ctx.textBaseline = "middle";
- this.ctx.fillStyle = "black";
- this.ctx.fillText("T", 0, 0);
- // now move really into the center
- this.ctx.translate(0, -2);
- // start drawing the (possibly open) circle
- this.ctx.beginPath();
- this.ctx.lineWidth = 2.5;
- if (this.animationRunning) {
- /* Draw circle around the "T" with an opening of this.gapWidth */
- this.ctx.arc(0, 0,
- this.canvas.width / 2 - 2, /* radius */
- this.rotationAngle / ChromeBadge.rotationAngleMax * Math.PI * 2,
- ((this.rotationAngle + ChromeBadge.rotationAngleMax - this.gapWidth) / ChromeBadge.rotationAngleMax) * Math.PI * 2,
- false);
- }
- else {
- /* Draw full circle */
- this.ctx.arc(0, 0,
- this.canvas.width / 2 - 2, /* radius */
- 0,
- Math.PI * 2,
- false);
- }
- this.ctx.stroke();
- // go back to the origin
- this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2);
-
- // Allow running outside the extension for testing
- if (window["chrome"] && window.chrome["browserAction"]) {
- let imageData = this.ctx.getImageData(0,
- 0,
- this.canvas.width,
- this.canvas.height);
- chrome.browserAction.setIcon({imageData});
- }
- }
-
- private animate() {
- if (this.animationRunning) {
- return;
- }
- this.animationRunning = true;
- let start: number|undefined = undefined;
- let step = (timestamp: number) => {
- if (!this.animationRunning) {
- return;
- }
- if (!start) {
- start = timestamp;
- }
- let delta = (timestamp - start);
- if (!this.isBusy && 0 == this.gapWidth) {
- // stop if we're close enough to origin
- this.rotationAngle = 0;
- } else {
- this.rotationAngle = (this.rotationAngle + (timestamp - start) * ChromeBadge.rotationSpeed) % ChromeBadge.rotationAngleMax;
- }
- if (this.isBusy) {
- if (this.gapWidth < ChromeBadge.openMax) {
- this.gapWidth += ChromeBadge.openSpeed * (timestamp - start);
- }
- if (this.gapWidth > ChromeBadge.openMax) {
- this.gapWidth = ChromeBadge.openMax;
- }
- }
- else {
- if (this.gapWidth > 0) {
- this.gapWidth--;
- this.gapWidth *= ChromeBadge.closeSpeed;
- }
- }
-
-
- if (this.isBusy || this.gapWidth > 0) {
- start = timestamp;
- rAF(step);
- } else {
- this.animationRunning = false;
- }
- this.draw();
- };
- rAF(step);
- }
-
- setText(s: string) {
- chrome.browserAction.setBadgeText({text: s});
- }
-
- setColor(c: string) {
- chrome.browserAction.setBadgeBackgroundColor({color: c});
- }
-
- startBusy() {
- if (this.isBusy) {
- return;
- }
- this.isBusy = true;
- this.animate();
- }
-
- stopBusy() {
- this.isBusy = false;
- }
-}
diff --git a/lib/wallet/cryptoApi-test.ts b/lib/wallet/cryptoApi-test.ts
deleted file mode 100644
index 38ecdb634..000000000
--- a/lib/wallet/cryptoApi-test.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import {CryptoApi} from "./cryptoApi";
-import {ReserveRecord, Denomination} from "lib/wallet/types";
-import {test, TestLib} from "testlib/talertest";
-
-let masterPub1: string = "CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
-
-let denomValid1: Denomination = {
- "master_sig": "CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
- "stamp_start": "/Date(1473148381)/",
- "stamp_expire_withdraw": "/Date(2482300381)/",
- "stamp_expire_deposit": "/Date(1851580381)/",
- "denom_pub": "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
- "stamp_expire_legal": "/Date(1567756381)/",
- "value": {
- "currency": "PUDOS",
- "value": 0,
- "fraction": 100000
- },
- "fee_withdraw": {
- "currency": "PUDOS",
- "value": 0,
- "fraction": 10000
- },
- "fee_deposit": {
- "currency": "PUDOS",
- "value": 0,
- "fraction": 10000
- },
- "fee_refresh": {
- "currency": "PUDOS",
- "value": 0,
- "fraction": 10000
- },
- "fee_refund": {
- "currency": "PUDOS",
- "value": 0,
- "fraction": 10000
- }
-};
-
-let denomInvalid1 = JSON.parse(JSON.stringify(denomValid1));
-denomInvalid1.value.value += 1;
-
-test("string hashing", async (t: TestLib) => {
- let crypto = new CryptoApi();
- let s = await crypto.hashString("hello taler");
- let sh = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
- t.assert(s == sh);
- t.pass();
-});
-
-test("precoin creation", async (t: TestLib) => {
- let crypto = new CryptoApi();
- let {priv, pub} = await crypto.createEddsaKeypair();
- let r: ReserveRecord = {
- reserve_pub: pub,
- reserve_priv: priv,
- exchange_base_url: "https://example.com/exchange",
- created: 0,
- requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
- precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
- current_amount: null,
- confirmed: false,
- last_query: null,
- };
-
- let precoin = await crypto.createPreCoin(denomValid1, r);
- t.pass();
-});
-
-test("denom validation", async (t: TestLib) => {
- let crypto = new CryptoApi();
- let v: boolean;
- v = await crypto.isValidDenom(denomValid1, masterPub1);
- t.assert(v);
- v = await crypto.isValidDenom(denomInvalid1, masterPub1);
- t.assert(!v);
- t.pass();
-});
diff --git a/lib/wallet/cryptoApi.ts b/lib/wallet/cryptoApi.ts
deleted file mode 100644
index 31407b74a..000000000
--- a/lib/wallet/cryptoApi.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-
-/**
- * API to access the Taler crypto worker thread.
- * @author Florian Dold
- */
-
-
-import {PreCoin, Coin, ReserveRecord, AmountJson} from "./types";
-import {Denomination} from "./types";
-import {Offer} from "./wallet";
-import {CoinWithDenom} from "./wallet";
-import {PayCoinInfo} from "./types";
-import {RefreshSession} from "./types";
-
-
-interface WorkerState {
- /**
- * The actual worker thread.
- */
- w: Worker|null;
-
- /**
- * Work we're currently executing or null if not busy.
- */
- currentWorkItem: WorkItem|null;
-
- /**
- * Timer to terminate the worker if it's not busy enough.
- */
- terminationTimerHandle: number|null;
-}
-
-interface WorkItem {
- operation: string;
- args: any[];
- resolve: any;
- reject: any;
-
- /**
- * Serial id to identify a matching response.
- */
- rpcId: number;
-}
-
-
-/**
- * Number of different priorities. Each priority p
- * must be 0 <= p < NUM_PRIO.
- */
-const NUM_PRIO = 5;
-
-export class CryptoApi {
- private nextRpcId: number = 1;
- private workers: WorkerState[];
- private workQueues: WorkItem[][];
- /**
- * Number of busy workers.
- */
- private numBusy: number = 0;
-
- /**
- * Start a worker (if not started) and set as busy.
- */
- wake<T>(ws: WorkerState, work: WorkItem): void {
- if (ws.currentWorkItem != null) {
- throw Error("assertion failed");
- }
- ws.currentWorkItem = work;
- this.numBusy++;
- if (!ws.w) {
- let w = new Worker("/lib/wallet/cryptoWorker.js");
- w.onmessage = (m: MessageEvent) => this.handleWorkerMessage(ws, m);
- w.onerror = (e: ErrorEvent) => this.handleWorkerError(ws, e);
- ws.w = w;
- }
-
- let msg: any = {
- operation: work.operation, args: work.args,
- id: work.rpcId
- };
- this.resetWorkerTimeout(ws);
- ws.w!.postMessage(msg);
- }
-
- resetWorkerTimeout(ws: WorkerState) {
- if (ws.terminationTimerHandle != null) {
- clearTimeout(ws.terminationTimerHandle);
- }
- let destroy = () => {
- // terminate worker if it's idle
- if (ws.w && ws.currentWorkItem == null) {
- ws.w!.terminate();
- ws.w = null;
- }
- };
- ws.terminationTimerHandle = setTimeout(destroy, 20 * 1000);
- }
-
- handleWorkerError(ws: WorkerState, e: ErrorEvent) {
- if (ws.currentWorkItem) {
- console.error(`error in worker during ${ws.currentWorkItem!.operation}`,
- e);
- } else {
- console.error("error in worker", e);
- }
- console.error(e.message);
- try {
- ws.w!.terminate();
- ws.w = null;
- } catch (e) {
- console.error(e);
- }
- if (ws.currentWorkItem != null) {
- ws.currentWorkItem.reject(e);
- ws.currentWorkItem = null;
- this.numBusy--;
- }
- this.findWork(ws);
- }
-
- findWork(ws: WorkerState) {
- // try to find more work for this worker
- for (let i = 0; i < NUM_PRIO; i++) {
- let q = this.workQueues[NUM_PRIO - i - 1];
- if (q.length != 0) {
- let work: WorkItem = q.shift()!;
- this.wake(ws, work);
- return;
- }
- }
- }
-
- handleWorkerMessage(ws: WorkerState, msg: MessageEvent) {
- let id = msg.data.id;
- if (typeof id !== "number") {
- console.error("rpc id must be number");
- return;
- }
- let currentWorkItem = ws.currentWorkItem;
- ws.currentWorkItem = null;
- this.numBusy--;
- this.findWork(ws);
- if (!currentWorkItem) {
- console.error("unsolicited response from worker");
- return;
- }
- if (id != currentWorkItem.rpcId) {
- console.error(`RPC with id ${id} has no registry entry`);
- return;
- }
- currentWorkItem.resolve(msg.data.result);
- }
-
- constructor() {
- this.workers = new Array<WorkerState>((navigator as any)["hardwareConcurrency"] || 2);
-
- for (let i = 0; i < this.workers.length; i++) {
- this.workers[i] = {
- w: null,
- terminationTimerHandle: null,
- currentWorkItem: null,
- };
- }
- this.workQueues = [];
- for (let i = 0; i < NUM_PRIO; i++) {
- this.workQueues.push([]);
- }
- }
-
- private doRpc<T>(operation: string, priority: number,
- ...args: any[]): Promise<T> {
-
- return new Promise((resolve, reject) => {
- let rpcId = this.nextRpcId++;
- let workItem: WorkItem = {operation, args, resolve, reject, rpcId};
-
- if (this.numBusy == this.workers.length) {
- let q = this.workQueues[priority];
- if (!q) {
- throw Error("assertion failed");
- }
- this.workQueues[priority].push(workItem);
- return;
- }
-
- for (let i = 0; i < this.workers.length; i++) {
- let ws = this.workers[i];
- if (ws.currentWorkItem != null) {
- continue;
- }
-
- this.wake<T>(ws, workItem);
- return;
- }
-
- throw Error("assertion failed");
- });
- }
-
-
- createPreCoin(denom: Denomination, reserve: ReserveRecord): Promise<PreCoin> {
- return this.doRpc("createPreCoin", 1, denom, reserve);
- }
-
- hashString(str: string): Promise<string> {
- return this.doRpc("hashString", 1, str);
- }
-
- isValidDenom(denom: Denomination,
- masterPub: string): Promise<boolean> {
- return this.doRpc("isValidDenom", 2, denom, masterPub);
- }
-
- signDeposit(offer: Offer,
- cds: CoinWithDenom[]): Promise<PayCoinInfo> {
- return this.doRpc("signDeposit", 3, offer, cds);
- }
-
- createEddsaKeypair(): Promise<{priv: string, pub: string}> {
- return this.doRpc("createEddsaKeypair", 1);
- }
-
- rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
- return this.doRpc("rsaUnblind", 4, sig, bk, pk);
- }
-
- createRefreshSession(exchangeBaseUrl: string,
- kappa: number,
- meltCoin: Coin,
- newCoinDenoms: Denomination[],
- meltFee: AmountJson): Promise<RefreshSession> {
- return this.doRpc("createRefreshSession",
- 4,
- exchangeBaseUrl,
- kappa,
- meltCoin,
- newCoinDenoms,
- meltFee);
- }
-}
diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts
deleted file mode 100644
index 6cb5b79d1..000000000
--- a/lib/wallet/cryptoLib.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-/**
- * 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} from "./types";
-import {Denomination, Amounts} from "./types";
-import {Amount} from "./emscriptif";
-import {Coin} from "../../background/lib/wallet/types";
-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();
- }
-}
diff --git a/lib/wallet/cryptoWorker.ts b/lib/wallet/cryptoWorker.ts
deleted file mode 100644
index 0689c910e..000000000
--- a/lib/wallet/cryptoWorker.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-/**
- * Web worker for crypto operations.
- * @author Florian Dold
- */
-
-"use strict";
-
-
-importScripts("../emscripten/taler-emscripten-lib.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, default: Module});
- let modName = System.normalizeSync("../emscripten/taler-emscripten-lib");
- console.log("registering", modName);
- System.set(modName, mod);
-}
-
-System.import("./cryptoLib")
- .then((m) => {
- m.main(self);
- })
- .catch((e) => {
- console.log("crypto worker failed");
- console.error(e.stack);
- });
diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts
deleted file mode 100644
index 9cffc164c..000000000
--- a/lib/wallet/db.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-"use strict";
-import {IExchangeInfo} from "./types";
-
-/**
- * Declarations and helpers for
- * things that are stored in the wallet's
- * database.
- * @module Db
- * @author Florian Dold
- */
-
-const DB_NAME = "taler";
-const DB_VERSION = 11;
-
-import {Stores} from "./wallet";
-import {Store, Index} from "./query";
-
-
-
-
-
-/**
- * Return a promise that resolves
- * to the taler wallet db.
- */
-export function openTalerDb(): Promise<IDBDatabase> {
- return new Promise((resolve, reject) => {
- const req = indexedDB.open(DB_NAME, DB_VERSION);
- req.onerror = (e) => {
- reject(e);
- };
- req.onsuccess = (e) => {
- resolve(req.result);
- };
- req.onupgradeneeded = (e) => {
- const db = req.result;
- console.log("DB: upgrade needed: oldVersion = " + e.oldVersion);
- switch (e.oldVersion) {
- case 0: // DB does not exist yet
-
- for (let n in Stores) {
- if ((Stores as any)[n] instanceof Store) {
- let si: Store<any> = (Stores as any)[n];
- const s = db.createObjectStore(si.name, si.storeParams);
- for (let indexName in (si as any)) {
- if ((si as any)[indexName] instanceof Index) {
- let ii: Index<any,any> = (si as any)[indexName];
- s.createIndex(ii.indexName, ii.keyPath);
- }
- }
- }
- }
- break;
- default:
- if (e.oldVersion != DB_VERSION) {
- window.alert("Incompatible wallet dababase version, please reset" +
- " db.");
- chrome.browserAction.setBadgeText({text: "err"});
- chrome.browserAction.setBadgeBackgroundColor({color: "#F00"});
- throw Error("incompatible DB");
- }
- break;
- }
- };
- });
-}
-
-
-export function exportDb(db: IDBDatabase): Promise<any> {
- let dump = {
- name: db.name,
- version: db.version,
- stores: {} as {[s: string]: any},
- };
-
- return new Promise((resolve, reject) => {
-
- let tx = db.transaction(Array.from(db.objectStoreNames));
- tx.addEventListener("complete", () => {
- resolve(dump);
- });
- for (let i = 0; i < db.objectStoreNames.length; i++) {
- let name = db.objectStoreNames[i];
- let storeDump = {} as {[s: string]: any};
- dump.stores[name] = storeDump;
- let store = tx.objectStore(name)
- .openCursor()
- .addEventListener("success", (e: Event) => {
- let cursor = (e.target as any).result;
- if (cursor) {
- storeDump[cursor.key] = cursor.value;
- cursor.continue();
- }
- });
- }
- });
-}
-
-export function deleteDb() {
- indexedDB.deleteDatabase(DB_NAME);
-}
diff --git a/lib/wallet/emscriptif-test.ts b/lib/wallet/emscriptif-test.ts
deleted file mode 100644
index ddafa32bc..000000000
--- a/lib/wallet/emscriptif-test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import {test, TestLib} from "testlib/talertest";
-import * as native from "./emscriptif";
-
-test("string hashing", (t: TestLib) => {
- let x = native.ByteArray.fromStringWithNull("hello taler");
- let h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR"
- let hc = x.hash().toCrock();
- console.log(`# hc ${hc}`);
- t.assert(h === hc, "must equal");
- t.pass();
-});
-
-test("signing", (t: TestLib) => {
- let x = native.ByteArray.fromStringWithNull("hello taler");
- let priv = native.EddsaPrivateKey.create();
- let pub = priv.getPublicKey();
- let purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x);
- let sig = native.eddsaSign(purpose, priv);
- t.assert(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
- t.pass();
-});
diff --git a/lib/wallet/emscriptif.ts b/lib/wallet/emscriptif.ts
deleted file mode 100644
index b26f35ebd..000000000
--- a/lib/wallet/emscriptif.ts
+++ /dev/null
@@ -1,1245 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015 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/>
- */
-
-import {AmountJson} from "./types";
-import Module, {EmscFunGen} from "../emscripten/taler-emscripten-lib";
-
-/**
- * High-level interface to emscripten-compiled modules used
- * by the wallet.
- *
- * @author Florian Dold
- */
-
-"use strict";
-
-// Size of a native pointer.
-const PTR_SIZE = 4;
-
-const GNUNET_OK = 1;
-const GNUNET_YES = 1;
-const GNUNET_NO = 0;
-const GNUNET_SYSERR = -1;
-
-
-const getEmsc: EmscFunGen = (name: string, ret: any, argTypes: any[]) => {
- return (...args: any[]) => {
- return Module.ccall(name, ret, argTypes, args);
- }
-};
-
-
-/**
- * Wrapped emscripten functions that do not allocate any memory.
- */
-const emsc = {
- free: (ptr: number) => Module._free(ptr),
- get_value: getEmsc("TALER_WR_get_value",
- "number",
- ["number"]),
- get_fraction: getEmsc("TALER_WR_get_fraction",
- "number",
- ["number"]),
- get_currency: getEmsc("TALER_WR_get_currency",
- "string",
- ["number"]),
- amount_add: getEmsc("TALER_amount_add",
- "number",
- ["number", "number", "number"]),
- amount_subtract: getEmsc("TALER_amount_subtract",
- "number",
- ["number", "number", "number"]),
- amount_normalize: getEmsc("TALER_amount_normalize",
- "void",
- ["number"]),
- amount_get_zero: getEmsc("TALER_amount_get_zero",
- "number",
- ["string", "number"]),
- amount_cmp: getEmsc("TALER_amount_cmp",
- "number",
- ["number", "number"]),
- amount_hton: getEmsc("TALER_amount_hton",
- "void",
- ["number", "number"]),
- amount_ntoh: getEmsc("TALER_amount_ntoh",
- "void",
- ["number", "number"]),
- hash: getEmsc("GNUNET_CRYPTO_hash",
- "void",
- ["number", "number", "number"]),
- memmove: getEmsc("memmove",
- "number",
- ["number", "number", "number"]),
- rsa_public_key_free: getEmsc("GNUNET_CRYPTO_rsa_public_key_free",
- "void",
- ["number"]),
- rsa_signature_free: getEmsc("GNUNET_CRYPTO_rsa_signature_free",
- "void",
- ["number"]),
- string_to_data: getEmsc("GNUNET_STRINGS_string_to_data",
- "number",
- ["number", "number", "number", "number"]),
- eddsa_sign: getEmsc("GNUNET_CRYPTO_eddsa_sign",
- "number",
- ["number", "number", "number"]),
- eddsa_verify: getEmsc("GNUNET_CRYPTO_eddsa_verify",
- "number",
- ["number", "number", "number", "number"]),
- hash_create_random: getEmsc("GNUNET_CRYPTO_hash_create_random",
- "void",
- ["number", "number"]),
- rsa_blinding_key_destroy: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_free",
- "void",
- ["number"]),
- random_block: getEmsc("GNUNET_CRYPTO_random_block",
- "void",
- ["number", "number", "number"]),
- hash_context_abort: getEmsc("GNUNET_CRYPTO_hash_context_abort",
- "void",
- ["number"]),
- hash_context_read: getEmsc("GNUNET_CRYPTO_hash_context_read",
- "void",
- ["number", "number", "number"]),
- hash_context_finish: getEmsc("GNUNET_CRYPTO_hash_context_finish",
- "void",
- ["number", "number"]),
- ecdh_eddsa: getEmsc("GNUNET_CRYPTO_ecdh_eddsa",
- "number",
- ["number", "number", "number"]),
-
- setup_fresh_coin: getEmsc(
- "TALER_setup_fresh_coin",
- "void",
- ["number", "number", "number"]),
-};
-
-const emscAlloc = {
- get_amount: getEmsc("TALER_WRALL_get_amount",
- "number",
- ["number", "number", "number", "string"]),
- eddsa_key_create: getEmsc("GNUNET_CRYPTO_eddsa_key_create",
- "number", []),
- ecdsa_key_create: getEmsc("GNUNET_CRYPTO_ecdsa_key_create",
- "number", []),
- ecdhe_key_create: getEmsc("GNUNET_CRYPTO_ecdhe_key_create",
- "number", []),
- eddsa_public_key_from_private: getEmsc(
- "TALER_WRALL_eddsa_public_key_from_private",
- "number",
- ["number"]),
- ecdsa_public_key_from_private: getEmsc(
- "TALER_WRALL_ecdsa_public_key_from_private",
- "number",
- ["number"]),
- ecdhe_public_key_from_private: getEmsc(
- "TALER_WRALL_ecdhe_public_key_from_private",
- "number",
- ["number"]),
- data_to_string_alloc: getEmsc("GNUNET_STRINGS_data_to_string_alloc",
- "number",
- ["number", "number"]),
- purpose_create: getEmsc("TALER_WRALL_purpose_create",
- "number",
- ["number", "number", "number"]),
- rsa_blind: getEmsc("GNUNET_CRYPTO_rsa_blind",
- "number",
- ["number", "number", "number", "number", "number"]),
- rsa_blinding_key_create: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_create",
- "number",
- ["number"]),
- rsa_blinding_key_encode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_encode",
- "number",
- ["number", "number"]),
- rsa_signature_encode: getEmsc("GNUNET_CRYPTO_rsa_signature_encode",
- "number",
- ["number", "number"]),
- rsa_blinding_key_decode: getEmsc("GNUNET_CRYPTO_rsa_blinding_key_decode",
- "number",
- ["number", "number"]),
- rsa_public_key_decode: getEmsc("GNUNET_CRYPTO_rsa_public_key_decode",
- "number",
- ["number", "number"]),
- rsa_signature_decode: getEmsc("GNUNET_CRYPTO_rsa_signature_decode",
- "number",
- ["number", "number"]),
- rsa_public_key_encode: getEmsc("GNUNET_CRYPTO_rsa_public_key_encode",
- "number",
- ["number", "number"]),
- rsa_unblind: getEmsc("GNUNET_CRYPTO_rsa_unblind",
- "number",
- ["number", "number", "number"]),
- hash_context_start: getEmsc("GNUNET_CRYPTO_hash_context_start",
- "number",
- []),
- malloc: (size: number) => Module._malloc(size),
-};
-
-
-export enum SignaturePurpose {
- RESERVE_WITHDRAW = 1200,
- WALLET_COIN_DEPOSIT = 1201,
- MASTER_DENOMINATION_KEY_VALIDITY = 1025,
- WALLET_COIN_MELT = 1202,
- TEST = 4242,
-}
-
-export enum RandomQuality {
- WEAK = 0,
- STRONG = 1,
- NONCE = 2
-}
-
-interface ArenaObject {
- destroy(): void;
-}
-
-
-export class HashContext implements ArenaObject {
- private hashContextPtr: number | undefined;
-
- constructor() {
- this.hashContextPtr = emscAlloc.hash_context_start();
- }
-
- read(obj: PackedArenaObject): void {
- if (!this.hashContextPtr) {
- throw Error("assertion failed");
- }
- emsc.hash_context_read(this.hashContextPtr, obj.nativePtr, obj.size());
- }
-
- finish(h: HashCode) {
- if (!this.hashContextPtr) {
- throw Error("assertion failed");
- }
- h.alloc();
- emsc.hash_context_finish(this.hashContextPtr, h.nativePtr);
- }
-
- destroy(): void {
- if (this.hashContextPtr) {
- emsc.hash_context_abort(this.hashContextPtr);
- }
- this.hashContextPtr = undefined;
- }
-}
-
-
-abstract class MallocArenaObject implements ArenaObject {
- protected _nativePtr: number | undefined = undefined;
-
- /**
- * Is this a weak reference to the underlying memory?
- */
- isWeak = false;
- arena: Arena;
-
- destroy(): void {
- if (this._nativePtr && !this.isWeak) {
- emsc.free(this.nativePtr);
- this._nativePtr = undefined;
- }
- }
-
- constructor(arena?: Arena) {
- if (!arena) {
- if (arenaStack.length == 0) {
- throw Error("No arena available")
- }
- arena = arenaStack[arenaStack.length - 1];
- }
- arena.put(this);
- this.arena = arena;
- }
-
- alloc(size: number) {
- if (this._nativePtr !== undefined) {
- throw Error("Double allocation");
- }
- this.nativePtr = emscAlloc.malloc(size);
- }
-
- set nativePtr(v: number) {
- if (v === undefined) {
- throw Error("Native pointer must be a number or null");
- }
- this._nativePtr = v;
- }
-
- get nativePtr() {
- // We want to allow latent allocation
- // of native wrappers, but we never want to
- // pass 'undefined' to emscripten.
- if (this._nativePtr === undefined) {
- throw Error("Native pointer not initialized");
- }
- return this._nativePtr;
- }
-}
-
-
-interface Arena {
- put(obj: ArenaObject): void;
- destroy(): void;
-}
-
-
-/**
- * Arena that must be manually destroyed.
- */
-class SimpleArena implements Arena {
- heap: Array<ArenaObject>;
-
- constructor() {
- this.heap = [];
- }
-
- put(obj: ArenaObject) {
- this.heap.push(obj);
- }
-
- destroy() {
- for (let obj of this.heap) {
- obj.destroy();
- }
- this.heap = []
- }
-}
-
-
-/**
- * Arena that destroys all its objects once control has returned to the message
- * loop.
- */
-class SyncArena extends SimpleArena {
- private isScheduled: boolean;
-
- constructor() {
- super();
- }
-
- pub(obj: MallocArenaObject) {
- super.put(obj);
- if (!this.isScheduled) {
- this.schedule();
- }
- this.heap.push(obj);
- }
-
- private schedule() {
- this.isScheduled = true;
- Promise.resolve().then(() => {
- this.isScheduled = false;
- this.destroy();
- });
- }
-}
-
-let arenaStack: Arena[] = [];
-arenaStack.push(new SyncArena());
-
-
-export class Amount extends MallocArenaObject {
- constructor(args?: AmountJson, arena?: Arena) {
- super(arena);
- if (args) {
- this.nativePtr = emscAlloc.get_amount(args.value,
- 0,
- args.fraction,
- args.currency);
- } else {
- this.nativePtr = emscAlloc.get_amount(0, 0, 0, "");
- }
- }
-
- static getZero(currency: string, a?: Arena): Amount {
- let am = new Amount(undefined, a);
- let r = emsc.amount_get_zero(currency, am.nativePtr);
- if (r != GNUNET_OK) {
- throw Error("invalid currency");
- }
- return am;
- }
-
-
- toNbo(a?: Arena): AmountNbo {
- let x = new AmountNbo(a);
- x.alloc();
- emsc.amount_hton(x.nativePtr, this.nativePtr);
- return x;
- }
-
- fromNbo(nbo: AmountNbo): void {
- emsc.amount_ntoh(this.nativePtr, nbo.nativePtr);
- }
-
- get value() {
- return emsc.get_value(this.nativePtr);
- }
-
- get fraction() {
- return emsc.get_fraction(this.nativePtr);
- }
-
- get currency(): String {
- return emsc.get_currency(this.nativePtr);
- }
-
- toJson(): AmountJson {
- return {
- value: emsc.get_value(this.nativePtr),
- fraction: emsc.get_fraction(this.nativePtr),
- currency: emsc.get_currency(this.nativePtr)
- };
- }
-
- /**
- * Add an amount to this amount.
- */
- add(a: Amount) {
- let res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr);
- if (res < 1) {
- // Overflow
- return false;
- }
- return true;
- }
-
- /**
- * Perform saturating subtraction on amounts.
- */
- sub(a: Amount) {
- // this = this - a
- let res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr);
- if (res == 0) {
- // Underflow
- return false;
- }
- if (res > 0) {
- return true;
- }
- throw Error("Incompatible currencies");
- }
-
- cmp(a: Amount) {
- // If we don't check this, the c code aborts.
- if (this.currency !== a.currency) {
- throw Error(`incomparable currencies (${this.currency} and ${a.currency})`);
- }
- return emsc.amount_cmp(this.nativePtr, a.nativePtr);
- }
-
- normalize() {
- emsc.amount_normalize(this.nativePtr);
- }
-}
-
-
-/**
- * Count the UTF-8 characters in a JavaScript string.
- */
-function countUtf8Bytes(str: string): number {
- var s = str.length;
- // JavaScript strings are UTF-16 arrays
- for (let i = str.length - 1; i >= 0; i--) {
- var code = str.charCodeAt(i);
- if (code > 0x7f && code <= 0x7ff) {
- // We need an extra byte in utf-8 here
- s++;
- } else if (code > 0x7ff && code <= 0xffff) {
- // We need two extra bytes in utf-8 here
- s += 2;
- }
- // Skip over the other surrogate
- if (code >= 0xDC00 && code <= 0xDFFF) {
- i--;
- }
- }
- return s;
-}
-
-
-/**
- * Managed reference to a contiguous block of memory in the Emscripten heap.
- * Can be converted from / to a serialized representation.
- * Should contain only data, not pointers.
- */
-abstract class PackedArenaObject extends MallocArenaObject {
- abstract size(): number;
-
- constructor(a?: Arena) {
- super(a);
- }
-
- randomize(qual: RandomQuality = RandomQuality.STRONG): void {
- emsc.random_block(qual, this.nativePtr, this.size());
- }
-
- toCrock(): string {
- var d = emscAlloc.data_to_string_alloc(this.nativePtr, this.size());
- var s = Module.Pointer_stringify(d);
- emsc.free(d);
- return s;
- }
-
- toJson(): any {
- // Per default, the json encoding of
- // packed arena objects is just the crockford encoding.
- // Subclasses typically want to override this.
- return this.toCrock();
- }
-
- loadCrock(s: string) {
- this.alloc();
- // We need to get the javascript string
- // to the emscripten heap first.
- let buf = ByteArray.fromStringWithNull(s);
- let res = emsc.string_to_data(buf.nativePtr,
- s.length,
- this.nativePtr,
- this.size());
- buf.destroy();
- if (res < 1) {
- throw {error: "wrong encoding"};
- }
- }
-
- alloc() {
- // FIXME: should the client be allowed to call alloc multiple times?
- if (!this._nativePtr) {
- this.nativePtr = emscAlloc.malloc(this.size());
- }
- }
-
- hash(): HashCode {
- var x = new HashCode();
- x.alloc();
- emsc.hash(this.nativePtr, this.size(), x.nativePtr);
- return x;
- }
-
- hexdump() {
- let bytes: string[] = [];
- for (let i = 0; i < this.size(); i++) {
- let b = Module.getValue(this.nativePtr + i, "i8");
- b = (b + 256) % 256;
- bytes.push("0".concat(b.toString(16)).slice(-2));
- }
- let lines: string[] = [];
- for (let i = 0; i < bytes.length; i += 8) {
- lines.push(bytes.slice(i, i + 8).join(","));
- }
- return lines.join("\n");
- }
-}
-
-
-export class AmountNbo extends PackedArenaObject {
- size() {
- return 24;
- }
-
- toJson(): any {
- let a = new SimpleArena();
- let am = new Amount(undefined, a);
- am.fromNbo(this);
- let json = am.toJson();
- a.destroy();
- return json;
- }
-}
-
-
-export class EddsaPrivateKey extends PackedArenaObject {
- static create(a?: Arena): EddsaPrivateKey {
- let obj = new EddsaPrivateKey(a);
- obj.nativePtr = emscAlloc.eddsa_key_create();
- return obj;
- }
-
- size() {
- return 32;
- }
-
- getPublicKey(a?: Arena): EddsaPublicKey {
- let obj = new EddsaPublicKey(a);
- obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr);
- return obj;
- }
-
- static fromCrock: (s: string) => EddsaPrivateKey;
-}
-mixinStatic(EddsaPrivateKey, fromCrock);
-
-
-export class EcdsaPrivateKey extends PackedArenaObject {
- static create(a?: Arena): EcdsaPrivateKey {
- let obj = new EcdsaPrivateKey(a);
- obj.nativePtr = emscAlloc.ecdsa_key_create();
- return obj;
- }
-
- size() {
- return 32;
- }
-
- getPublicKey(a?: Arena): EcdsaPublicKey {
- let obj = new EcdsaPublicKey(a);
- obj.nativePtr = emscAlloc.ecdsa_public_key_from_private(this.nativePtr);
- return obj;
- }
-
- static fromCrock: (s: string) => EcdsaPrivateKey;
-}
-mixinStatic(EcdsaPrivateKey, fromCrock);
-
-
-export class EcdhePrivateKey extends PackedArenaObject {
- static create(a?: Arena): EcdhePrivateKey {
- let obj = new EcdhePrivateKey(a);
- obj.nativePtr = emscAlloc.ecdhe_key_create();
- return obj;
- }
-
- size() {
- return 32;
- }
-
- getPublicKey(a?: Arena): EcdhePublicKey {
- let obj = new EcdhePublicKey(a);
- obj.nativePtr = emscAlloc.ecdhe_public_key_from_private(this.nativePtr);
- return obj;
- }
-
- static fromCrock: (s: string) => EcdhePrivateKey;
-}
-mixinStatic(EcdhePrivateKey, fromCrock);
-
-
-function fromCrock(s: string) {
- let x = new this();
- x.alloc();
- x.loadCrock(s);
- return x;
-}
-
-
-function mixin(obj: any, method: any, name?: string) {
- if (!name) {
- name = method.name;
- }
- if (!name) {
- throw Error("Mixin needs a name.");
- }
- obj.prototype[method.name] = method;
-}
-
-
-function mixinStatic(obj: any, method: any, name?: string) {
- if (!name) {
- name = method.name;
- }
- if (!name) {
- throw Error("Mixin needs a name.");
- }
- obj[method.name] = method;
-}
-
-
-export class EddsaPublicKey extends PackedArenaObject {
- size() {
- return 32;
- }
-
- static fromCrock: (s: string) => EddsaPublicKey;
-}
-mixinStatic(EddsaPublicKey, fromCrock);
-
-export class EcdsaPublicKey extends PackedArenaObject {
- size() {
- return 32;
- }
-
- static fromCrock: (s: string) => EcdsaPublicKey;
-}
-mixinStatic(EcdsaPublicKey, fromCrock);
-
-
-export class EcdhePublicKey extends PackedArenaObject {
- size() {
- return 32;
- }
-
- static fromCrock: (s: string) => EcdhePublicKey;
-}
-mixinStatic(EcdhePublicKey, fromCrock);
-
-
-function makeFromCrock(decodeFn: (p: number, s: number) => number) {
- function fromCrock(s: string, a?: Arena) {
- let obj = new this(a);
- let buf = ByteArray.fromCrock(s);
- obj.setNative(decodeFn(buf.nativePtr,
- buf.size()));
- buf.destroy();
- return obj;
- }
-
- return fromCrock;
-}
-
-function makeToCrock(encodeFn: (po: number,
- ps: number) => number): () => string {
- function toCrock() {
- let ptr = emscAlloc.malloc(PTR_SIZE);
- let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr);
- let res = new ByteArray(size, Module.getValue(ptr, '*'));
- let s = res.toCrock();
- emsc.free(ptr);
- res.destroy();
- return s;
- }
-
- return toCrock;
-}
-
-export class RsaBlindingKeySecret extends PackedArenaObject {
- size() {
- return 32;
- }
-
- /**
- * Create a random blinding key secret.
- */
- static create(a?: Arena): RsaBlindingKeySecret {
- let o = new RsaBlindingKeySecret(a);
- o.alloc();
- o.randomize();
- return o;
- }
-
- static fromCrock: (s: string) => RsaBlindingKeySecret;
-}
-mixinStatic(RsaBlindingKeySecret, fromCrock);
-
-
-export class HashCode extends PackedArenaObject {
- size() {
- return 64;
- }
-
- static fromCrock: (s: string) => HashCode;
-
- random(qual: RandomQuality = RandomQuality.STRONG) {
- this.alloc();
- emsc.hash_create_random(qual, this.nativePtr);
- }
-}
-mixinStatic(HashCode, fromCrock);
-
-
-export class ByteArray extends PackedArenaObject {
- private allocatedSize: number;
-
- size() {
- return this.allocatedSize;
- }
-
- constructor(desiredSize: number, init?: number, a?: Arena) {
- super(a);
- if (init === undefined) {
- this.nativePtr = emscAlloc.malloc(desiredSize);
- } else {
- this.nativePtr = init;
- }
- this.allocatedSize = desiredSize;
- }
-
- static fromStringWithoutNull(s: string, a?: Arena): ByteArray {
- // UTF-8 bytes, including 0-terminator
- let terminatedByteLength = countUtf8Bytes(s) + 1;
- let hstr = emscAlloc.malloc(terminatedByteLength);
- Module.stringToUTF8(s, hstr, terminatedByteLength);
- return new ByteArray(terminatedByteLength - 1, hstr, a);
- }
-
- static fromStringWithNull(s: string, a?: Arena): ByteArray {
- // UTF-8 bytes, including 0-terminator
- let terminatedByteLength = countUtf8Bytes(s) + 1;
- let hstr = emscAlloc.malloc(terminatedByteLength);
- Module.stringToUTF8(s, hstr, terminatedByteLength);
- return new ByteArray(terminatedByteLength, hstr, a);
- }
-
- static fromCrock(s: string, a?: Arena): ByteArray {
- let byteLength = countUtf8Bytes(s);
- let hstr = emscAlloc.malloc(byteLength + 1);
- Module.stringToUTF8(s, hstr, byteLength + 1);
- let decodedLen = Math.floor((byteLength * 5) / 8);
- let ba = new ByteArray(decodedLen, undefined, a);
- let res = emsc.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen);
- emsc.free(hstr);
- if (res != GNUNET_OK) {
- throw Error("decoding failed");
- }
- return ba;
- }
-}
-
-
-export class EccSignaturePurpose extends PackedArenaObject {
- size() {
- return this.payloadSize + 8;
- }
-
- payloadSize: number;
-
- constructor(purpose: SignaturePurpose,
- payload: PackedArenaObject,
- a?: Arena) {
- super(a);
- this.nativePtr = emscAlloc.purpose_create(purpose,
- payload.nativePtr,
- payload.size());
- this.payloadSize = payload.size();
- }
-}
-
-
-abstract class SignatureStruct {
- abstract fieldTypes(): Array<any>;
-
- abstract purpose(): SignaturePurpose;
-
- private members: any = {};
-
- constructor(x: { [name: string]: any }) {
- for (let k in x) {
- this.set(k, x[k]);
- }
- }
-
- toPurpose(a?: Arena): EccSignaturePurpose {
- let totalSize = 0;
- for (let f of this.fieldTypes()) {
- let name = f[0];
- let member = this.members[name];
- if (!member) {
- throw Error(`Member ${name} not set`);
- }
- totalSize += member.size();
- }
-
- let buf = emscAlloc.malloc(totalSize);
- let ptr = buf;
- for (let f of this.fieldTypes()) {
- let name = f[0];
- let member = this.members[name];
- let size = member.size();
- emsc.memmove(ptr, member.nativePtr, size);
- ptr += size;
- }
- let ba = new ByteArray(totalSize, buf, a);
- return new EccSignaturePurpose(this.purpose(), ba);
- }
-
-
- toJson() {
- let res: any = {};
- for (let f of this.fieldTypes()) {
- let name = f[0];
- let member = this.members[name];
- if (!member) {
- throw Error(`Member ${name} not set`);
- }
- res[name] = member.toJson();
- }
- res["purpose"] = this.purpose();
- return res;
- }
-
- protected set(name: string, value: PackedArenaObject) {
- let typemap: any = {};
- for (let f of this.fieldTypes()) {
- typemap[f[0]] = f[1];
- }
- if (!(name in typemap)) {
- throw Error(`Key ${name} not found`);
- }
- if (!(value instanceof typemap[name])) {
- throw Error("Wrong type for ${name}");
- }
- this.members[name] = value;
- }
-}
-
-
-// It's redundant, but more type safe.
-export interface WithdrawRequestPS_Args {
- reserve_pub: EddsaPublicKey;
- amount_with_fee: AmountNbo;
- withdraw_fee: AmountNbo;
- h_denomination_pub: HashCode;
- h_coin_envelope: HashCode;
-}
-
-
-export class WithdrawRequestPS extends SignatureStruct {
- constructor(w: WithdrawRequestPS_Args) {
- super(w);
- }
-
- purpose() {
- return SignaturePurpose.RESERVE_WITHDRAW;
- }
-
- fieldTypes() {
- return [
- ["reserve_pub", EddsaPublicKey],
- ["amount_with_fee", AmountNbo],
- ["withdraw_fee", AmountNbo],
- ["h_denomination_pub", HashCode],
- ["h_coin_envelope", HashCode]
- ];
- }
-}
-
-
-interface RefreshMeltCoinAffirmationPS_Args {
- session_hash: HashCode;
- amount_with_fee: AmountNbo;
- melt_fee: AmountNbo;
- coin_pub: EddsaPublicKey;
-}
-
-export class RefreshMeltCoinAffirmationPS extends SignatureStruct {
-
- constructor(w: RefreshMeltCoinAffirmationPS_Args) {
- super(w);
- }
-
- purpose() {
- return SignaturePurpose.WALLET_COIN_MELT;
- }
-
- fieldTypes() {
- return [
- ["session_hash", HashCode],
- ["amount_with_fee", AmountNbo],
- ["melt_fee", AmountNbo],
- ["coin_pub", EddsaPublicKey]
- ];
- }
-}
-
-
-export class AbsoluteTimeNbo extends PackedArenaObject {
- static fromTalerString(s: string): AbsoluteTimeNbo {
- let x = new AbsoluteTimeNbo();
- x.alloc();
- let r = /Date\(([0-9]+)\)/;
- let m = r.exec(s);
- if (!m || m.length != 2) {
- throw Error();
- }
- let n = parseInt(m[1]) * 1000000;
- // XXX: This only works up to 54 bit numbers.
- set64(x.nativePtr, n);
- return x;
- }
-
- size() {
- return 8;
- }
-}
-
-
-// XXX: This only works up to 54 bit numbers.
-function set64(p: number, n: number) {
- for (let i = 0; i < 8; ++i) {
- Module.setValue(p + (7 - i), n & 0xFF, "i8");
- n = Math.floor(n / 256);
- }
-}
-
-// XXX: This only works up to 54 bit numbers.
-function set32(p: number, n: number) {
- for (let i = 0; i < 4; ++i) {
- Module.setValue(p + (3 - i), n & 0xFF, "i8");
- n = Math.floor(n / 256);
- }
-}
-
-
-export class UInt64 extends PackedArenaObject {
- static fromNumber(n: number): UInt64 {
- let x = new UInt64();
- x.alloc();
- set64(x.nativePtr, n);
- return x;
- }
-
- size() {
- return 8;
- }
-}
-
-
-export class UInt32 extends PackedArenaObject {
- static fromNumber(n: number): UInt64 {
- let x = new UInt32();
- x.alloc();
- set32(x.nativePtr, n);
- return x;
- }
-
- size() {
- return 4;
- }
-}
-
-
-// It's redundant, but more type safe.
-export interface DepositRequestPS_Args {
- h_contract: HashCode;
- h_wire: HashCode;
- timestamp: AbsoluteTimeNbo;
- refund_deadline: AbsoluteTimeNbo;
- transaction_id: UInt64;
- amount_with_fee: AmountNbo;
- deposit_fee: AmountNbo;
- merchant: EddsaPublicKey;
- coin_pub: EddsaPublicKey;
-}
-
-
-export class DepositRequestPS extends SignatureStruct {
- constructor(w: DepositRequestPS_Args) {
- super(w);
- }
-
- purpose() {
- return SignaturePurpose.WALLET_COIN_DEPOSIT;
- }
-
- fieldTypes() {
- return [
- ["h_contract", HashCode],
- ["h_wire", HashCode],
- ["timestamp", AbsoluteTimeNbo],
- ["refund_deadline", AbsoluteTimeNbo],
- ["transaction_id", UInt64],
- ["amount_with_fee", AmountNbo],
- ["deposit_fee", AmountNbo],
- ["merchant", EddsaPublicKey],
- ["coin_pub", EddsaPublicKey],
- ];
- }
-}
-
-export interface DenominationKeyValidityPS_args {
- master: EddsaPublicKey;
- start: AbsoluteTimeNbo;
- expire_withdraw: AbsoluteTimeNbo;
- expire_spend: AbsoluteTimeNbo;
- expire_legal: AbsoluteTimeNbo;
- value: AmountNbo;
- fee_withdraw: AmountNbo;
- fee_deposit: AmountNbo;
- fee_refresh: AmountNbo;
- fee_refund: AmountNbo;
- denom_hash: HashCode;
-}
-
-export class DenominationKeyValidityPS extends SignatureStruct {
- constructor(w: DenominationKeyValidityPS_args) {
- super(w);
- }
-
- purpose() {
- return SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY;
- }
-
- fieldTypes() {
- return [
- ["master", EddsaPublicKey],
- ["start", AbsoluteTimeNbo],
- ["expire_withdraw", AbsoluteTimeNbo],
- ["expire_spend", AbsoluteTimeNbo],
- ["expire_legal", AbsoluteTimeNbo],
- ["value", AmountNbo],
- ["fee_withdraw", AmountNbo],
- ["fee_deposit", AmountNbo],
- ["fee_refresh", AmountNbo],
- ["fee_refund", AmountNbo],
- ["denom_hash", HashCode]
- ];
- }
-}
-
-
-interface Encodeable {
- encode(arena?: Arena): ByteArray;
-}
-
-function makeEncode(encodeFn: any) {
- function encode(arena?: Arena) {
- let ptr = emscAlloc.malloc(PTR_SIZE);
- let len = encodeFn(this.nativePtr, ptr);
- let res = new ByteArray(len, undefined, arena);
- res.nativePtr = Module.getValue(ptr, '*');
- emsc.free(ptr);
- return res;
- }
-
- return encode;
-}
-
-
-export class RsaPublicKey extends MallocArenaObject implements Encodeable {
- static fromCrock: (s: string, a?: Arena) => RsaPublicKey;
-
- toCrock() {
- return this.encode().toCrock();
- }
-
- destroy() {
- emsc.rsa_public_key_free(this.nativePtr);
- this.nativePtr = 0;
- }
-
- encode: (arena?: Arena) => ByteArray;
-}
-mixinStatic(RsaPublicKey, makeFromCrock(emscAlloc.rsa_public_key_decode));
-mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode));
-
-
-export class EddsaSignature extends PackedArenaObject {
- size() {
- return 64;
- }
-}
-
-
-export class RsaSignature extends MallocArenaObject implements Encodeable {
- static fromCrock: (s: string, a?: Arena) => RsaSignature;
-
- encode: (arena?: Arena) => ByteArray;
-
- destroy() {
- emsc.rsa_signature_free(this.nativePtr);
- this.nativePtr = 0;
- }
-}
-mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode));
-mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode));
-
-
-export function rsaBlind(hashCode: HashCode,
- blindingKey: RsaBlindingKeySecret,
- pkey: RsaPublicKey,
- arena?: Arena): ByteArray|null {
- let buf_ptr_out = emscAlloc.malloc(PTR_SIZE);
- let buf_size_out = emscAlloc.malloc(PTR_SIZE);
- let res = emscAlloc.rsa_blind(hashCode.nativePtr,
- blindingKey.nativePtr,
- pkey.nativePtr,
- buf_ptr_out,
- buf_size_out);
- let buf_ptr = Module.getValue(buf_ptr_out, '*');
- let buf_size = Module.getValue(buf_size_out, '*');
- emsc.free(buf_ptr_out);
- emsc.free(buf_size_out);
- if (res != GNUNET_OK) {
- // malicious key
- return null;
- }
- return new ByteArray(buf_size, buf_ptr, arena);
-}
-
-
-export function eddsaSign(purpose: EccSignaturePurpose,
- priv: EddsaPrivateKey,
- a?: Arena): EddsaSignature {
- let sig = new EddsaSignature(a);
- sig.alloc();
- let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr);
- if (res < 1) {
- throw Error("EdDSA signing failed");
- }
- return sig;
-}
-
-
-export function eddsaVerify(purposeNum: number,
- verify: EccSignaturePurpose,
- sig: EddsaSignature,
- pub: EddsaPublicKey,
- a?: Arena): boolean {
- let r = emsc.eddsa_verify(purposeNum,
- verify.nativePtr,
- sig.nativePtr,
- pub.nativePtr);
- return r === GNUNET_OK;
-}
-
-
-export function rsaUnblind(sig: RsaSignature,
- bk: RsaBlindingKeySecret,
- pk: RsaPublicKey,
- a?: Arena): RsaSignature {
- let x = new RsaSignature(a);
- x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr,
- bk.nativePtr,
- pk.nativePtr);
- return x;
-}
-
-
-type TransferSecretP = HashCode;
-
-
-export interface FreshCoin {
- priv: EddsaPrivateKey;
- blindingKey: RsaBlindingKeySecret;
-}
-
-export function ecdhEddsa(priv: EcdhePrivateKey,
- pub: EddsaPublicKey): HashCode {
- let h = new HashCode();
- h.alloc();
- let res = emsc.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr);
- if (res != GNUNET_OK) {
- throw Error("ecdh_eddsa failed");
- }
- return h;
-}
-
-export function setupFreshCoin(secretSeed: TransferSecretP,
- coinIndex: number): FreshCoin {
- let priv = new EddsaPrivateKey();
- priv.isWeak = true;
- let blindingKey = new RsaBlindingKeySecret();
- blindingKey.isWeak = true;
- let buf = new ByteArray(priv.size() + blindingKey.size());
-
- emsc.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr);
-
- priv.nativePtr = buf.nativePtr;
- blindingKey.nativePtr = buf.nativePtr + priv.size();
-
- return {priv, blindingKey};
-}
diff --git a/lib/wallet/helpers.ts b/lib/wallet/helpers.ts
deleted file mode 100644
index 26cd350ee..000000000
--- a/lib/wallet/helpers.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-/**
- * Smaller helper functions that do not depend
- * on the emscripten machinery.
- *
- * @author Florian Dold
- */
-
-/// <reference path="../decl/urijs/URIjs.d.ts" />
-
-import {AmountJson} from "./types";
-import URI = uri.URI;
-
-export function substituteFulfillmentUrl(url: string, vars: any) {
- url = url.replace("${H_contract}", vars.H_contract);
- url = url.replace("${$}", "$");
- return url;
-}
-
-
-export function amountToPretty(amount: AmountJson): string {
- let x = amount.value + amount.fraction / 1e6;
- return `${x} ${amount.currency}`;
-}
-
-
-/**
- * Canonicalize a base url, typically for the exchange.
- *
- * See http://api.taler.net/wallet.html#general
- */
-export function canonicalizeBaseUrl(url: string) {
- let x: URI = new URI(url);
- if (!x.protocol()) {
- x.protocol("https");
- }
- x.path(x.path() + "/").normalizePath();
- x.fragment();
- x.query();
- return x.href()
-}
-
-
-export function parsePrettyAmount(pretty: string): AmountJson|undefined {
- const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty);
- if (!res) {
- return undefined;
- }
- return {
- value: parseInt(res[1], 10),
- fraction: res[2] ? (parseFloat(`0.${res[2]}`) * 1e-6) : 0,
- currency: res[3]
- }
-}
-
-
-
-/**
- * Convert object to JSON with canonical ordering of keys
- * and whitespace omitted.
- */
-export function canonicalJson(obj: any): string {
- // Check for cycles, etc.
- JSON.stringify(obj);
- if (typeof obj == "string" || typeof obj == "number" || obj === null) {
- return JSON.stringify(obj)
- }
- if (Array.isArray(obj)) {
- let objs: string[] = obj.map((e) => canonicalJson(e));
- return `[${objs.join(',')}]`;
- }
- let keys: string[] = [];
- for (let key in obj) {
- keys.push(key);
- }
- keys.sort();
- let s = "{";
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
- if (i != keys.length - 1) {
- s += ",";
- }
- }
- return s + "}";
-}
-
-
-export function deepEquals(x: any, y: any): boolean {
- if (x === y) {
- return true;
- }
-
- if (Array.isArray(x) && x.length !== y.length) {
- return false;
- }
-
- var p = Object.keys(x);
- return Object.keys(y).every((i) => p.indexOf(i) !== -1) &&
- p.every((i) => deepEquals(x[i], y[i]));
-}
-
-
-export function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
- return xs.reduce((acc: U[], next: T) => [...f(next), ...acc], []);
-}
-
-
-export function getTalerStampSec(stamp: string): number | null {
- const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
- if (!m) {
- return null;
- }
- return parseInt(m[1]);
-}
-
-
-export function getTalerStampDate(stamp: string): Date | null {
- let sec = getTalerStampSec(stamp);
- if (sec == null) {
- return null;
- }
- return new Date(sec * 1000);
-}
-
diff --git a/lib/wallet/http.ts b/lib/wallet/http.ts
deleted file mode 100644
index 1d22c4eb2..000000000
--- a/lib/wallet/http.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-/**
- * Helpers for doing XMLHttpRequest-s that are based on ES6 promises.
- * @module Http
- * @author Florian Dold
- */
-
-"use strict";
-
-
-export interface HttpResponse {
- status: number;
- responseText: string;
-}
-
-
-export interface HttpRequestLibrary {
- req(method: string,
- url: string | uri.URI,
- options?: any): Promise<HttpResponse>;
-
- get(url: string | uri.URI): Promise<HttpResponse>;
-
- postJson(url: string | uri.URI, body: any): Promise<HttpResponse>;
-
- postForm(url: string | uri.URI, form: any): Promise<HttpResponse>;
-}
-
-
-export class BrowserHttpLib {
- req(method: string,
- url: string|uri.URI,
- options?: any): Promise<HttpResponse> {
- let urlString: string;
- if (url instanceof URI) {
- urlString = url.href();
- } else if (typeof url === "string") {
- urlString = url;
- }
-
- return new Promise((resolve, reject) => {
- let myRequest = new XMLHttpRequest();
- myRequest.open(method, urlString);
- if (options && options.req) {
- myRequest.send(options.req);
- } else {
- myRequest.send();
- }
- myRequest.addEventListener("readystatechange", (e) => {
- if (myRequest.readyState == XMLHttpRequest.DONE) {
- let resp = {
- status: myRequest.status,
- responseText: myRequest.responseText
- };
- resolve(resp);
- }
- });
- });
- }
-
-
- get(url: string|uri.URI) {
- return this.req("get", url);
- }
-
-
- postJson(url: string|uri.URI, body: any) {
- return this.req("post", url, {req: JSON.stringify(body)});
- }
-
-
- postForm(url: string|uri.URI, form: any) {
- return this.req("post", url, {req: form});
- }
-}
-
-
-export class RequestException {
- constructor(detail: any) {
-
- }
-}
diff --git a/lib/wallet/query.ts b/lib/wallet/query.ts
deleted file mode 100644
index 08e270ea6..000000000
--- a/lib/wallet/query.ts
+++ /dev/null
@@ -1,612 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-
-/**
- * Database query abstractions.
- * @module Query
- * @author Florian Dold
- */
-
-"use strict";
-
-
-export interface JoinResult<L,R> {
- left: L;
- right: R;
-}
-
-
-export class Store<T> {
- name: string;
- validator?: (v: T) => T;
- storeParams: IDBObjectStoreParameters;
-
- constructor(name: string, storeParams: IDBObjectStoreParameters,
- validator?: (v: T) => T) {
- this.name = name;
- this.validator = validator;
- this.storeParams = storeParams;
- }
-}
-
-export class Index<S extends IDBValidKey,T> {
- indexName: string;
- storeName: string;
- keyPath: string | string[];
-
- constructor(s: Store<T>, indexName: string, keyPath: string | string[]) {
- this.storeName = s.name;
- this.indexName = indexName;
- this.keyPath = keyPath;
- }
-}
-
-/**
- * Stream that can be filtered, reduced or joined
- * with indices.
- */
-export interface QueryStream<T> {
- indexJoin<S,I extends IDBValidKey>(index: Index<I,S>,
- keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>>;
- keyJoin<S,I extends IDBValidKey>(store: Store<S>,
- keyFn: (obj: T) => I): QueryStream<JoinResult<T,S>>;
- filter(f: (T: any) => boolean): QueryStream<T>;
- reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
- map<S>(f: (x:T) => S): QueryStream<S>;
- flatMap<S>(f: (x: T) => S[]): QueryStream<S>;
- toArray(): Promise<T[]>;
-
- then(onfulfill: any, onreject: any): any;
-}
-
-export let AbortTransaction = Symbol("abort_transaction");
-
-/**
- * Get an unresolved promise together with its extracted resolve / reject
- * function.
- */
-function openPromise<T>() {
- let resolve: ((value?: T | PromiseLike<T>) => void) | null = null;
- let reject: ((reason?: any) => void) | null = null;
- const promise = new Promise<T>((res, rej) => {
- resolve = res;
- reject = rej;
- });
- if (!(resolve && reject)) {
- // Never happens, unless JS implementation is broken
- throw Error();
- }
- return {resolve, reject, promise};
-}
-
-
-abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
- abstract subscribe(f: (isDone: boolean,
- value: any,
- tx: IDBTransaction) => void): void;
-
- root: QueryRoot;
-
- constructor(root: QueryRoot) {
- this.root = root;
- }
-
- then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
- return this.root.then(onfulfilled, onrejected);
- }
-
- flatMap<S>(f: (x: T) => S[]): QueryStream<S> {
- return new QueryStreamFlatMap<T,S>(this, f);
- }
-
- map<S>(f: (x: T) => S): QueryStream<S> {
- return new QueryStreamMap(this, f);
- }
-
- indexJoin<S,I extends IDBValidKey>(index: Index<I,S>,
- keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
- this.root.addStoreAccess(index.storeName, false);
- return new QueryStreamIndexJoin(this, index.storeName, index.indexName, keyFn);
- }
-
- keyJoin<S, I extends IDBValidKey>(store: Store<S>,
- keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
- this.root.addStoreAccess(store.name, false);
- return new QueryStreamKeyJoin(this, store.name, keyFn);
- }
-
- filter(f: (x: any) => boolean): QueryStream<T> {
- return new QueryStreamFilter(this, f);
- }
-
- toArray(): Promise<T[]> {
- let {resolve, promise} = openPromise();
- let values: T[] = [];
-
- this.subscribe((isDone, value) => {
- if (isDone) {
- resolve(values);
- return;
- }
- values.push(value);
- });
-
- return Promise.resolve()
- .then(() => this.root.finish())
- .then(() => promise);
- }
-
- reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> {
- let {resolve, promise} = openPromise();
- let acc = init;
-
- this.subscribe((isDone, value) => {
- if (isDone) {
- resolve(acc);
- return;
- }
- acc = f(value, acc);
- });
-
- return Promise.resolve()
- .then(() => this.root.finish())
- .then(() => promise);
- }
-}
-
-type FilterFn = (e: any) => boolean;
-type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void;
-
-interface FlatMapFn<T> {
- (v: T): T[];
-}
-
-class QueryStreamFilter<T> extends QueryStreamBase<T> {
- s: QueryStreamBase<T>;
- filterFn: FilterFn;
-
- constructor(s: QueryStreamBase<T>, filterFn: FilterFn) {
- super(s.root);
- this.s = s;
- this.filterFn = filterFn;
- }
-
- subscribe(f: SubscribeFn) {
- this.s.subscribe((isDone, value, tx) => {
- if (isDone) {
- f(true, undefined, tx);
- return;
- }
- if (this.filterFn(value)) {
- f(false, value, tx);
- }
- });
- }
-}
-
-
-class QueryStreamFlatMap<T,S> extends QueryStreamBase<S> {
- s: QueryStreamBase<T>;
- flatMapFn: (v: T) => S[];
-
- constructor(s: QueryStreamBase<T>, flatMapFn: (v: T) => S[]) {
- super(s.root);
- this.s = s;
- this.flatMapFn = flatMapFn;
- }
-
- subscribe(f: SubscribeFn) {
- this.s.subscribe((isDone, value, tx) => {
- if (isDone) {
- f(true, undefined, tx);
- return;
- }
- let values = this.flatMapFn(value);
- for (let v in values) {
- f(false, value, tx)
- }
- });
- }
-}
-
-
-class QueryStreamMap<S,T> extends QueryStreamBase<T> {
- s: QueryStreamBase<S>;
- mapFn: (v: S) => T;
-
- constructor(s: QueryStreamBase<S>, mapFn: (v: S) => T) {
- super(s.root);
- this.s = s;
- this.mapFn = mapFn;
- }
-
- subscribe(f: SubscribeFn) {
- this.s.subscribe((isDone, value, tx) => {
- if (isDone) {
- f(true, undefined, tx);
- return;
- }
- let mappedValue = this.mapFn(value);
- f(false, mappedValue, tx);
- });
- }
-}
-
-
-class QueryStreamIndexJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> {
- s: QueryStreamBase<T>;
- storeName: string;
- key: any;
- indexName: string;
-
- constructor(s: QueryStreamBase<T>, storeName: string, indexName: string,
- key: any) {
- super(s.root);
- this.s = s;
- this.storeName = storeName;
- this.key = key;
- this.indexName = indexName;
- }
-
- subscribe(f: SubscribeFn) {
- this.s.subscribe((isDone, value, tx) => {
- if (isDone) {
- f(true, undefined, tx);
- return;
- }
- console.log("joining on", this.key(value));
- let s = tx.objectStore(this.storeName).index(this.indexName);
- let req = s.openCursor(IDBKeyRange.only(this.key(value)));
- req.onsuccess = () => {
- let cursor = req.result;
- if (cursor) {
- f(false, {left: value, right: cursor.value}, tx);
- cursor.continue();
- } else {
- f(true, undefined, tx);
- }
- }
- });
- }
-}
-
-
-class QueryStreamKeyJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> {
- s: QueryStreamBase<T>;
- storeName: string;
- key: any;
-
- constructor(s: QueryStreamBase<T>, storeName: string,
- key: any) {
- super(s.root);
- this.s = s;
- this.storeName = storeName;
- this.key = key;
- }
-
- subscribe(f: SubscribeFn) {
- this.s.subscribe((isDone, value, tx) => {
- if (isDone) {
- f(true, undefined, tx);
- return;
- }
- console.log("joining on", this.key(value));
- let s = tx.objectStore(this.storeName);
- let req = s.openCursor(IDBKeyRange.only(this.key(value)));
- req.onsuccess = () => {
- let cursor = req.result;
- if (cursor) {
- f(false, {left:value, right: cursor.value}, tx);
- cursor.continue();
- } else {
- f(true, undefined, tx);
- }
- }
- });
- }
-}
-
-
-class IterQueryStream<T> extends QueryStreamBase<T> {
- private storeName: string;
- private options: any;
- private subscribers: SubscribeFn[];
-
- constructor(qr: QueryRoot, storeName: string, options: any) {
- super(qr);
- this.options = options;
- this.storeName = storeName;
- this.subscribers = [];
-
- let doIt = (tx: IDBTransaction) => {
- const {indexName = void 0, only = void 0} = this.options;
- let s: any;
- if (indexName !== void 0) {
- s = tx.objectStore(this.storeName)
- .index(this.options.indexName);
- } else {
- s = tx.objectStore(this.storeName);
- }
- let kr: IDBKeyRange | undefined = undefined;
- if (only !== undefined) {
- kr = IDBKeyRange.only(this.options.only);
- }
- let req = s.openCursor(kr);
- req.onsuccess = () => {
- let cursor: IDBCursorWithValue = req.result;
- if (cursor) {
- for (let f of this.subscribers) {
- f(false, cursor.value, tx);
- }
- cursor.continue();
- } else {
- for (let f of this.subscribers) {
- f(true, undefined, tx);
- }
- }
- }
- };
-
- this.root.addWork(doIt);
- }
-
- subscribe(f: SubscribeFn) {
- this.subscribers.push(f);
- }
-}
-
-
-export class QueryRoot implements PromiseLike<void> {
- private work: ((t: IDBTransaction) => void)[] = [];
- private db: IDBDatabase;
- private stores = new Set();
- private kickoffPromise: Promise<void>;
-
- /**
- * Some operations is a write operation,
- * and we need to do a "readwrite" transaction/
- */
- private hasWrite: boolean;
-
- private finishScheduled: boolean;
-
- constructor(db: IDBDatabase) {
- this.db = db;
- }
-
- then<R>(onfulfilled: (value: void) => R | PromiseLike<R>, onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
- return this.finish().then(onfulfilled, onrejected);
- }
-
- iter<T>(store: Store<T>): QueryStream<T> {
- this.stores.add(store.name);
- this.scheduleFinish();
- return new IterQueryStream(this, store.name, {});
- }
-
- iterIndex<S extends IDBValidKey,T>(index: Index<S,T>,
- only?: S): QueryStream<T> {
- this.stores.add(index.storeName);
- this.scheduleFinish();
- return new IterQueryStream(this, index.storeName, {
- only,
- indexName: index.indexName
- });
- }
-
- /**
- * Put an object into the given object store.
- * Overrides if an existing object with the same key exists
- * in the store.
- */
- put<T>(store: Store<T>, val: T): QueryRoot {
- let doPut = (tx: IDBTransaction) => {
- tx.objectStore(store.name).put(val);
- };
- this.scheduleFinish();
- this.addWork(doPut, store.name, true);
- return this;
- }
-
-
- putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> {
- const {resolve, promise} = openPromise();
- let doPutWithResult = (tx: IDBTransaction) => {
- let req = tx.objectStore(store.name).put(val);
- req.onsuccess = () => {
- resolve(req.result);
- }
- this.scheduleFinish();
- };
- this.addWork(doPutWithResult, store.name, true);
- return Promise.resolve()
- .then(() => this.finish())
- .then(() => promise);
- }
-
-
- mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
- let doPut = (tx: IDBTransaction) => {
- let reqGet = tx.objectStore(store.name).get(key);
- reqGet.onsuccess = () => {
- let r = reqGet.result;
- let m: T;
- try {
- m = f(r);
- } catch (e) {
- if (e == AbortTransaction) {
- tx.abort();
- return;
- }
- throw e;
- }
-
- tx.objectStore(store.name).put(m);
- }
- };
- this.scheduleFinish();
- this.addWork(doPut, store.name, true);
- return this;
- }
-
-
- /**
- * Add all object from an iterable to the given object store.
- * Fails if the object's key is already present
- * in the object store.
- */
- putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
- const doPutAll = (tx: IDBTransaction) => {
- for (let obj of iterable) {
- tx.objectStore(store.name).put(obj);
- }
- };
- this.scheduleFinish();
- this.addWork(doPutAll, store.name, true);
- return this;
- }
-
- /**
- * Add an object to the given object store.
- * Fails if the object's key is already present
- * in the object store.
- */
- add<T>(store: Store<T>, val: T): QueryRoot {
- const doAdd = (tx: IDBTransaction) => {
- tx.objectStore(store.name).add(val);
- };
- this.scheduleFinish();
- this.addWork(doAdd, store.name, true);
- return this;
- }
-
- /**
- * Get one object from a store by its key.
- */
- get<T>(store: Store<T>, key: any): Promise<T|undefined> {
- if (key === void 0) {
- throw Error("key must not be undefined");
- }
-
- const {resolve, promise} = openPromise();
-
- const doGet = (tx: IDBTransaction) => {
- const req = tx.objectStore(store.name).get(key);
- req.onsuccess = () => {
- resolve(req.result);
- };
- };
-
- this.addWork(doGet, store.name, false);
- return Promise.resolve()
- .then(() => this.finish())
- .then(() => promise);
- }
-
- /**
- * Get one object from a store by its key.
- */
- getIndexed<I extends IDBValidKey,T>(index: Index<I,T>,
- key: I): Promise<T|undefined> {
- if (key === void 0) {
- throw Error("key must not be undefined");
- }
-
- const {resolve, promise} = openPromise();
-
- const doGetIndexed = (tx: IDBTransaction) => {
- const req = tx.objectStore(index.storeName)
- .index(index.indexName)
- .get(key);
- req.onsuccess = () => {
- resolve(req.result);
- };
- };
-
- this.addWork(doGetIndexed, index.storeName, false);
- return Promise.resolve()
- .then(() => this.finish())
- .then(() => promise);
- }
-
- private scheduleFinish() {
- if (!this.finishScheduled) {
- Promise.resolve().then(() => this.finish());
- this.finishScheduled = true;
- }
- }
-
- /**
- * Finish the query, and start the query in the first place if necessary.
- */
- finish(): Promise<void> {
- if (this.kickoffPromise) {
- return this.kickoffPromise;
- }
- this.kickoffPromise = new Promise<void>((resolve, reject) => {
- if (this.work.length == 0) {
- resolve();
- return;
- }
- const mode = this.hasWrite ? "readwrite" : "readonly";
- const tx = this.db.transaction(Array.from(this.stores), mode);
- tx.oncomplete = () => {
- resolve();
- };
- tx.onabort = () => {
- reject(Error("transaction aborted"));
- };
- for (let w of this.work) {
- w(tx);
- }
- });
- return this.kickoffPromise;
- }
-
- /**
- * Delete an object by from the given object store.
- */
- delete(storeName: string, key: any): QueryRoot {
- const doDelete = (tx: IDBTransaction) => {
- tx.objectStore(storeName).delete(key);
- };
- this.scheduleFinish();
- this.addWork(doDelete, storeName, true);
- return this;
- }
-
- /**
- * Low-level function to add a task to the internal work queue.
- */
- addWork(workFn: (t: IDBTransaction) => void,
- storeName?: string,
- isWrite?: boolean) {
- this.work.push(workFn);
- if (storeName) {
- this.addStoreAccess(storeName, isWrite);
- }
- }
-
- addStoreAccess(storeName: string, isWrite?: boolean) {
- if (storeName) {
- this.stores.add(storeName);
- }
- if (isWrite) {
- this.hasWrite = true;
- }
- }
-}
diff --git a/lib/wallet/renderHtml.tsx b/lib/wallet/renderHtml.tsx
deleted file mode 100644
index 940d5c425..000000000
--- a/lib/wallet/renderHtml.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 INRIA
-
- 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/>
- */
-
-/**
- * Helpers functions to render Taler-related data structures to HTML.
- *
- * @author Florian Dold
- */
-
-
-import {AmountJson, Contract} from "./types";
-
-export function prettyAmount(amount: AmountJson) {
- let v = amount.value + amount.fraction / 1e6;
- return `${v.toFixed(2)} ${amount.currency}`;
-}
-
-export function renderContract(contract: Contract): JSX.Element {
- let merchantName = <strong>{contract.merchant.name}</strong>;
- let amount = <strong>{prettyAmount(contract.amount)}</strong>;
-
- return (
- <div>
- <p>
- The merchant {merchantName}
- wants to enter a contract over {amount}{" "}
- with you.
- </p>
- <p>{i18n`You are about to purchase:`}</p>
- <ul>
- {contract.products.map(
- (p: any, i: number) => (<li key={i}>{`${p.description}: ${prettyAmount(p.price)}`}</li>))
- }
- </ul>
- </div>
- );
-}
-
-
-export function abbrev(s: string, n: number = 5) {
- let sAbbrev = s;
- if (s.length > n) {
- sAbbrev = s.slice(0, n) + "..";
- }
- return (
- <span className="abbrev" title={s}>
- {sAbbrev}
- </span>
- );
-}
diff --git a/lib/wallet/types-test.ts b/lib/wallet/types-test.ts
deleted file mode 100644
index 3ebb1a5db..000000000
--- a/lib/wallet/types-test.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import {test, TestLib} from "testlib/talertest";
-import {Amounts} from "./types";
-import * as types from "./types";
-
-let amt = (value: number, fraction: number, currency: string): types.AmountJson => ({value, fraction, currency});
-
-test("amount addition (simple)", (t: TestLib) => {
- let a1 = amt(1,0,"EUR");
- let a2 = amt(1,0,"EUR");
- let a3 = amt(2,0,"EUR");
- t.assert(0 == types.Amounts.cmp(Amounts.add(a1, a2).amount, a3));
- t.pass();
-});
-
-test("amount addition (saturation)", (t: TestLib) => {
- let a1 = amt(1,0,"EUR");
- let res = Amounts.add(Amounts.getMaxAmount("EUR"), a1);
- t.assert(res.saturated);
- t.pass();
-});
-
-test("amount subtraction (simple)", (t: TestLib) => {
- let a1 = amt(2,5,"EUR");
- let a2 = amt(1,0,"EUR");
- let a3 = amt(1,5,"EUR");
- t.assert(0 == types.Amounts.cmp(Amounts.sub(a1, a2).amount, a3));
- t.pass();
-});
-
-test("amount subtraction (saturation)", (t: TestLib) => {
- let a1 = amt(0,0,"EUR");
- let a2 = amt(1,0,"EUR");
- let res = Amounts.sub(a1, a2);
- t.assert(res.saturated);
- res = Amounts.sub(a1, a1);
- t.assert(!res.saturated);
- t.pass();
-});
diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts
deleted file mode 100644
index 39d374069..000000000
--- a/lib/wallet/types.ts
+++ /dev/null
@@ -1,554 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015 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/>
- */
-
-/**
- * Common types that are used by Taler.
- *
- * Note most types are defined in wallet.ts, types that
- * are defined in types.ts are intended to be used by components
- * that do not depend on the whole wallet implementation (which depends on
- * emscripten).
- *
- * @author Florian Dold
- */
-
-import { Checkable } from "./checkable";
-
-@Checkable.Class
-export class AmountJson {
- @Checkable.Number
- value: number;
-
- @Checkable.Number
- fraction: number;
-
- @Checkable.String
- currency: string;
-
- static checked: (obj: any) => AmountJson;
-}
-
-
-export interface SignedAmountJson {
- amount: AmountJson;
- isNegative: boolean;
-}
-
-
-export interface ReserveRecord {
- reserve_pub: string;
- reserve_priv: string,
- exchange_base_url: string,
- created: number,
- last_query: number | null,
- /**
- * Current amount left in the reserve
- */
- current_amount: AmountJson | null,
- /**
- * Amount requested when the reserve was created.
- * When a reserve is re-used (rare!) the current_amount can
- * be higher than the requested_amount
- */
- requested_amount: AmountJson,
-
-
- /**
- * What's the current amount that sits
- * in precoins?
- */
- precoin_amount: AmountJson;
-
-
- confirmed: boolean,
-}
-
-
-@Checkable.Class
-export class CreateReserveResponse {
- /**
- * Exchange URL where the bank should create the reserve.
- * The URL is canonicalized in the response.
- */
- @Checkable.String
- exchange: string;
-
- @Checkable.String
- reservePub: string;
-
- static checked: (obj: any) => CreateReserveResponse;
-}
-
-
-@Checkable.Class
-export class Denomination {
- @Checkable.Value(AmountJson)
- value: AmountJson;
-
- @Checkable.String
- denom_pub: string;
-
- @Checkable.Value(AmountJson)
- fee_withdraw: AmountJson;
-
- @Checkable.Value(AmountJson)
- fee_deposit: AmountJson;
-
- @Checkable.Value(AmountJson)
- fee_refresh: AmountJson;
-
- @Checkable.Value(AmountJson)
- fee_refund: AmountJson;
-
- @Checkable.String
- stamp_start: string;
-
- @Checkable.String
- stamp_expire_withdraw: string;
-
- @Checkable.String
- stamp_expire_legal: string;
-
- @Checkable.String
- stamp_expire_deposit: string;
-
- @Checkable.String
- master_sig: string;
-
- static checked: (obj: any) => Denomination;
-}
-
-
-export interface IExchangeInfo {
- baseUrl: string;
- masterPublicKey: string;
-
- /**
- * All denominations we ever received from the exchange.
- * Expired denominations may be garbage collected.
- */
- all_denoms: Denomination[];
-
- /**
- * Denominations we received with the last update.
- * Subset of "denoms".
- */
- active_denoms: Denomination[];
-
- /**
- * Timestamp for last update.
- */
- last_update_time: number;
-}
-
-export interface WireInfo {
- [type: string]: any;
-}
-
-export interface ReserveCreationInfo {
- exchangeInfo: IExchangeInfo;
- wireInfo: WireInfo;
- selectedDenoms: Denomination[];
- withdrawFee: AmountJson;
- overhead: AmountJson;
-}
-
-
-/**
- * A coin that isn't yet signed by an exchange.
- */
-export interface PreCoin {
- coinPub: string;
- coinPriv: string;
- reservePub: string;
- denomPub: string;
- blindingKey: string;
- withdrawSig: string;
- coinEv: string;
- exchangeBaseUrl: string;
- coinValue: AmountJson;
-}
-
-export interface RefreshPreCoin {
- publicKey: string;
- privateKey: string;
- coinEv: string;
- blindingKey: string
-}
-
-
-/**
- * Ongoing refresh
- */
-export interface RefreshSession {
- /**
- * Public key that's being melted in this session.
- */
- meltCoinPub: string;
-
- /**
- * How much of the coin's value is melted away
- * with this refresh session?
- */
- valueWithFee: AmountJson
-
- /**
- * Sum of the value of denominations we want
- * to withdraw in this session, without fees.
- */
- valueOutput: AmountJson;
-
- /**
- * Signature to confirm the melting.
- */
- confirmSig: string;
-
- /**
- * Denominations of the newly requested coins
- */
- newDenoms: string[];
-
-
- preCoinsForGammas: RefreshPreCoin[][];
-
-
- /**
- * The transfer keys, kappa of them.
- */
- transferPubs: string[];
-
- transferPrivs: string[];
-
- /**
- * The no-reveal-index after we've done the melting.
- */
- norevealIndex?: number;
-
- /**
- * Hash of the session.
- */
- hash: string;
-
- exchangeBaseUrl: string;
-
- finished: boolean;
-}
-
-
-export interface CoinPaySig {
- coin_sig: string;
- coin_pub: string;
- ub_sig: string;
- denom_pub: string;
- f: AmountJson;
-}
-
-/**
- * Coin as stored in the "coins" data store
- * of the wallet database.
- */
-export interface Coin {
- /**
- * Public key of the coin.
- */
- coinPub: string;
-
- /**
- * Private key to authorize operations on the coin.
- */
- coinPriv: string;
-
- /**
- * Key used by the exchange used to sign the coin.
- */
- denomPub: string;
-
- /**
- * Unblinded signature by the exchange.
- */
- denomSig: string;
-
- /**
- * Amount that's left on the coin.
- */
- currentAmount: AmountJson;
-
- /**
- * Base URL that identifies the exchange from which we got the
- * coin.
- */
- exchangeBaseUrl: string;
-
- /**
- * We have withdrawn the coin, but it's not accepted by the exchange anymore.
- * We have to tell an auditor and wait for compensation or for the exchange
- * to fix it.
- */
- suspended?: boolean;
-
- /**
- * Was the coin revealed in a transaction?
- */
- dirty: boolean;
-
- /**
- * Is the coin currently involved in a transaction?
- *
- * This delays refreshing until the transaction is finished or
- * aborted.
- */
- transactionPending: boolean;
-}
-
-
-@Checkable.Class
-export class ExchangeHandle {
- @Checkable.String
- master_pub: string;
-
- @Checkable.String
- url: string;
-
- static checked: (obj: any) => ExchangeHandle;
-}
-
-export interface WalletBalance {
- [currency: string]: WalletBalanceEntry;
-}
-
-export interface WalletBalanceEntry {
- available: AmountJson;
- pendingIncoming: AmountJson;
- pendingPayment: AmountJson;
-}
-
-
-interface Merchant {
- /**
- * label for a location with the business address of the merchant
- */
- address: string;
-
- /**
- * the merchant's legal name of business
- */
- name: string;
-
- /**
- * label for a location that denotes the jurisdiction for disputes.
- * Some of the typical fields for a location (such as a street address) may be absent.
- */
- jurisdiction: string;
-
- /**
- * Instance of the merchant, in case one merchant
- * represents multiple receivers.
- */
- instance?: string;
-}
-
-@Checkable.Class
-export class Contract {
- @Checkable.String
- H_wire: string;
-
- @Checkable.String
- summary: string;
-
- @Checkable.Value(AmountJson)
- amount: AmountJson;
-
- @Checkable.List(Checkable.AnyObject)
- auditors: any[];
-
- /**
- * DEPRECATED alias for pay_deadline.
- */
- @Checkable.Optional(Checkable.String)
- expiry: string;
-
- @Checkable.Optional(Checkable.String)
- pay_deadline: string;
-
- @Checkable.Any
- locations: any;
-
- @Checkable.Value(AmountJson)
- max_fee: AmountJson;
-
- @Checkable.Any
- merchant: any;
-
- @Checkable.String
- merchant_pub: string;
-
- @Checkable.List(Checkable.Value(ExchangeHandle))
- exchanges: ExchangeHandle[];
-
- @Checkable.List(Checkable.AnyObject)
- products: any[];
-
- @Checkable.String
- refund_deadline: string;
-
- @Checkable.String
- timestamp: string;
-
- @Checkable.Number
- transaction_id: number;
-
- @Checkable.String
- fulfillment_url: string;
-
- @Checkable.Optional(Checkable.String)
- repurchase_correlation_id: string;
-
- /**
- * DEPRECATED alias for instance
- */
- @Checkable.Optional(Checkable.String)
- receiver: string;
-
- @Checkable.Optional(Checkable.String)
- instance: string;
-
- static checked: (obj: any) => Contract;
-}
-
-
-export type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig }>;
-
-
-export namespace Amounts {
- export interface Result {
- amount: AmountJson;
- // Was there an over-/underflow?
- saturated: boolean;
- }
-
- export function getMaxAmount(currency: string): AmountJson {
- return {
- currency,
- value: Number.MAX_SAFE_INTEGER,
- fraction: 2 ** 32,
- }
- }
-
- export function getZero(currency: string): AmountJson {
- return {
- currency,
- value: 0,
- fraction: 0,
- }
- }
-
- export function add(first: AmountJson, ...rest: AmountJson[]): Result {
- let currency = first.currency;
- let value = first.value + Math.floor(first.fraction / 1e6);
- if (value > Number.MAX_SAFE_INTEGER) {
- return { amount: getMaxAmount(currency), saturated: true };
- }
- let fraction = first.fraction % 1e6;
- for (let x of rest) {
- if (x.currency !== currency) {
- throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
- }
-
- value = value + x.value + Math.floor((fraction + x.fraction) / 1e6);
- fraction = (fraction + x.fraction) % 1e6;
- if (value > Number.MAX_SAFE_INTEGER) {
- return { amount: getMaxAmount(currency), saturated: true };
- }
- }
- return { amount: { currency, value, fraction }, saturated: false };
- }
-
-
- export function sub(a: AmountJson, ...rest: AmountJson[]): Result {
- let currency = a.currency;
- let value = a.value;
- let fraction = a.fraction;
-
- for (let b of rest) {
- if (b.currency !== currency) {
- throw Error(`Mismatched currency: ${b.currency} and ${currency}`);
- }
- if (fraction < b.fraction) {
- if (value < 1) {
- return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
- }
- value--;
- fraction += 1e6;
- }
- console.assert(fraction >= b.fraction);
- fraction -= b.fraction;
- if (value < b.value) {
- return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
- }
- value -= b.value;
- }
-
- return { amount: { currency, value, fraction }, saturated: false };
- }
-
- export function cmp(a: AmountJson, b: AmountJson): number {
- if (a.currency !== b.currency) {
- throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
- }
- let av = a.value + Math.floor(a.fraction / 1e6);
- let af = a.fraction % 1e6;
- let bv = b.value + Math.floor(b.fraction / 1e6);
- let bf = b.fraction % 1e6;
- switch (true) {
- case av < bv:
- return -1;
- case av > bv:
- return 1;
- case af < bf:
- return -1;
- case af > bf:
- return 1;
- case af == bf:
- return 0;
- default:
- throw Error("assertion failed");
- }
- }
-
- export function copy(a: AmountJson): AmountJson {
- return {
- value: a.value,
- fraction: a.fraction,
- currency: a.currency,
- }
- }
-
- export function isNonZero(a: AmountJson) {
- return a.value > 0 || a.fraction > 0;
- }
-}
-
-
-export interface CheckRepurchaseResult {
- isRepurchase: boolean;
- existingContractHash?: string;
- existingFulfillmentUrl?: string;
-}
-
-
-export interface Notifier {
- notify(): void;
-}
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
deleted file mode 100644
index 9fb6e5a27..000000000
--- a/lib/wallet/wallet.ts
+++ /dev/null
@@ -1,1657 +0,0 @@
-/*
- This file is part of TALER
- (C) 2015 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/>
- */
-
-/**
- * High-level wallet operations that should be indepentent from the underlying
- * browser extension interface.
- * @module Wallet
- * @author Florian Dold
- */
-
-import {
- AmountJson,
- Amounts,
- CheckRepurchaseResult,
- Coin,
- CoinPaySig,
- Contract,
- CreateReserveResponse,
- Denomination,
- ExchangeHandle,
- IExchangeInfo,
- Notifier,
- PayCoinInfo,
- PreCoin,
- RefreshSession,
- ReserveCreationInfo,
- ReserveRecord,
- WalletBalance,
- WalletBalanceEntry,
- WireInfo,
-} from "./types";
-import {
- HttpRequestLibrary,
- HttpResponse,
- RequestException,
-} from "./http";
-import {
- AbortTransaction,
- Index,
- JoinResult,
- QueryRoot,
- Store,
-} from "./query";
-import {Checkable} from "./checkable";
-import {
- amountToPretty,
- canonicalizeBaseUrl,
- canonicalJson,
- deepEquals,
- flatMap,
- getTalerStampSec,
-} from "./helpers";
-import {CryptoApi} from "./cryptoApi";
-
-"use strict";
-
-export interface CoinWithDenom {
- coin: Coin;
- denom: Denomination;
-}
-
-
-@Checkable.Class
-export class KeysJson {
- @Checkable.List(Checkable.Value(Denomination))
- denoms: Denomination[];
-
- @Checkable.String
- master_public_key: string;
-
- @Checkable.Any
- auditors: any[];
-
- @Checkable.String
- list_issue_date: string;
-
- @Checkable.Any
- signkeys: any;
-
- @Checkable.String
- eddsa_pub: string;
-
- @Checkable.String
- eddsa_sig: string;
-
- static checked: (obj: any) => KeysJson;
-}
-
-
-@Checkable.Class
-export class CreateReserveRequest {
- /**
- * The initial amount for the reserve.
- */
- @Checkable.Value(AmountJson)
- amount: AmountJson;
-
- /**
- * Exchange URL where the bank should create the reserve.
- */
- @Checkable.String
- exchange: string;
-
- static checked: (obj: any) => CreateReserveRequest;
-}
-
-
-@Checkable.Class
-export class ConfirmReserveRequest {
- /**
- * Public key of then reserve that should be marked
- * as confirmed.
- */
- @Checkable.String
- reservePub: string;
-
- static checked: (obj: any) => ConfirmReserveRequest;
-}
-
-
-@Checkable.Class
-export class Offer {
- @Checkable.Value(Contract)
- contract: Contract;
-
- @Checkable.String
- merchant_sig: string;
-
- @Checkable.String
- H_contract: string;
-
- @Checkable.Number
- offer_time: number;
-
- /**
- * Serial ID when the offer is stored in the wallet DB.
- */
- @Checkable.Optional(Checkable.Number)
- id?: number;
-
- static checked: (obj: any) => Offer;
-}
-
-export interface HistoryRecord {
- type: string;
- timestamp: number;
- subjectId?: string;
- detail: any;
- level: HistoryLevel;
-}
-
-
-interface ExchangeCoins {
- [exchangeUrl: string]: CoinWithDenom[];
-}
-
-interface PayReq {
- amount: AmountJson;
- coins: CoinPaySig[];
- H_contract: string;
- max_fee: AmountJson;
- merchant_sig: string;
- exchange: string;
- refund_deadline: string;
- timestamp: string;
- transaction_id: number;
- pay_deadline: string;
- /**
- * Merchant instance identifier that should receive the
- * payment, if applicable.
- */
- instance?: string;
-}
-
-interface Transaction {
- contractHash: string;
- contract: Contract;
- payReq: PayReq;
- merchantSig: string;
-
- /**
- * The transaction isn't active anymore, it's either successfully paid
- * or refunded/aborted.
- */
- finished: boolean;
-}
-
-export enum HistoryLevel {
- Trace = 1,
- Developer = 2,
- Expert = 3,
- User = 4,
-}
-
-
-export interface Badge {
- setText(s: string): void;
- setColor(c: string): void;
- startBusy(): void;
- stopBusy(): void;
-}
-
-
-function setTimeout(f: any, t: number) {
- return chrome.extension.getBackgroundPage().setTimeout(f, t);
-}
-
-
-function isWithdrawableDenom(d: Denomination) {
- const now_sec = (new Date).getTime() / 1000;
- const stamp_withdraw_sec = getTalerStampSec(d.stamp_expire_withdraw);
- const stamp_start_sec = getTalerStampSec(d.stamp_start);
- // Withdraw if still possible to withdraw within a minute
- if ((stamp_withdraw_sec + 60 > now_sec) && (now_sec >= stamp_start_sec)) {
- return true;
- }
- return false;
-}
-
-
-/**
- * Result of updating exisiting information
- * about an exchange with a new '/keys' response.
- */
-interface KeyUpdateInfo {
- updatedExchangeInfo: IExchangeInfo;
- addedDenominations: Denomination[];
- removedDenominations: Denomination[];
-}
-
-
-/**
- * Get a list of denominations (with repetitions possible)
- * whose total value is as close as possible to the available
- * amount, but never larger.
- */
-function getWithdrawDenomList(amountAvailable: AmountJson,
- denoms: Denomination[]): Denomination[] {
- let remaining = Amounts.copy(amountAvailable);
- const ds: Denomination[] = [];
-
- denoms = denoms.filter(isWithdrawableDenom);
- denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
-
- // This is an arbitrary number of coins
- // we can withdraw in one go. It's not clear if this limit
- // is useful ...
- for (let i = 0; i < 1000; i++) {
- let found = false;
- for (let d of denoms) {
- let cost = Amounts.add(d.value, d.fee_withdraw).amount;
- if (Amounts.cmp(remaining, cost) < 0) {
- continue;
- }
- found = true;
- remaining = Amounts.sub(remaining, cost).amount;
- ds.push(d);
- break;
- }
- if (!found) {
- break;
- }
- }
- return ds;
-}
-
-
-export namespace Stores {
- class ExchangeStore extends Store<IExchangeInfo> {
- constructor() {
- super("exchanges", {keyPath: "baseUrl"});
- }
-
- pubKeyIndex = new Index<string,IExchangeInfo>(this, "pubKey", "masterPublicKey");
- }
-
- class CoinsStore extends Store<Coin> {
- constructor() {
- super("coins", {keyPath: "coinPub"});
- }
-
- exchangeBaseUrlIndex = new Index<string,Coin>(this, "exchangeBaseUrl", "exchangeBaseUrl");
- }
-
- class HistoryStore extends Store<HistoryRecord> {
- constructor() {
- super("history", {
- keyPath: "id",
- autoIncrement: true
- });
- }
-
- timestampIndex = new Index<number,HistoryRecord>(this, "timestamp", "timestamp");
- }
-
- class OffersStore extends Store<Offer> {
- constructor() {
- super("offers", {
- keyPath: "id",
- autoIncrement: true
- });
- }
- }
-
- class TransactionsStore extends Store<Transaction> {
- constructor() {
- super("transactions", {keyPath: "contractHash"});
- }
-
- repurchaseIndex = new Index<[string,string],Transaction>(this, "repurchase", [
- "contract.merchant_pub",
- "contract.repurchase_correlation_id"
- ]);
- }
-
- export let exchanges: ExchangeStore = new ExchangeStore();
- export let transactions: TransactionsStore = new TransactionsStore();
- export let reserves: Store<ReserveRecord> = new Store<ReserveRecord>("reserves", {keyPath: "reserve_pub"});
- export let coins: CoinsStore = new CoinsStore();
- export let refresh: Store<RefreshSession> = new Store<RefreshSession>("refresh", {keyPath: "meltCoinPub"});
- export let history: HistoryStore = new HistoryStore();
- export let offers: OffersStore = new OffersStore();
- export let precoins: Store<PreCoin> = new Store<PreCoin>("precoins", {keyPath: "coinPub"});
-}
-
-
-export class Wallet {
- private db: IDBDatabase;
- private http: HttpRequestLibrary;
- private badge: Badge;
- private notifier: Notifier;
- public cryptoApi: CryptoApi;
-
- /**
- * Set of identifiers for running operations.
- */
- private runningOperations: Set<string> = new Set();
-
- q(): QueryRoot {
- return new QueryRoot(this.db);
- }
-
- constructor(db: IDBDatabase,
- http: HttpRequestLibrary,
- badge: Badge,
- notifier: Notifier) {
- this.db = db;
- this.http = http;
- this.badge = badge;
- this.notifier = notifier;
- this.cryptoApi = new CryptoApi();
-
- this.resumePendingFromDb();
- }
-
-
- private startOperation(operationId: string) {
- this.runningOperations.add(operationId);
- this.badge.startBusy();
- }
-
- private stopOperation(operationId: string) {
- this.runningOperations.delete(operationId);
- if (this.runningOperations.size == 0) {
- this.badge.stopBusy();
- }
- }
-
- async updateExchanges(): Promise<void> {
- console.log("updating exchanges");
-
- let exchangesUrls = await this.q()
- .iter(Stores.exchanges)
- .map((e) => e.baseUrl)
- .toArray();
-
- for (let url of exchangesUrls) {
- this.updateExchangeFromUrl(url)
- .catch((e) => {
- console.error("updating exchange failed", e);
- });
- }
- }
-
- /**
- * Resume various pending operations that are pending
- * by looking at the database.
- */
- private resumePendingFromDb(): void {
- console.log("resuming pending operations from db");
-
- this.q()
- .iter(Stores.reserves)
- .reduce((reserve) => {
- console.log("resuming reserve", reserve.reserve_pub);
- this.processReserve(reserve);
- });
-
- this.q()
- .iter(Stores.precoins)
- .reduce((preCoin) => {
- console.log("resuming precoin");
- this.processPreCoin(preCoin);
- });
-
- this.q()
- .iter(Stores.refresh)
- .reduce((r: RefreshSession) => {
- this.continueRefreshSession(r);
- });
-
- // FIXME: optimize via index
- this.q()
- .iter(Stores.coins)
- .reduce((c: Coin) => {
- if (c.dirty && !c.transactionPending) {
- this.refresh(c.coinPub);
- }
- });
- }
-
-
- /**
- * Get exchanges and associated coins that are still spendable,
- * but only if the sum the coins' remaining value exceeds the payment amount.
- */
- private async getPossibleExchangeCoins(paymentAmount: AmountJson,
- depositFeeLimit: AmountJson,
- allowedExchanges: ExchangeHandle[]): Promise<ExchangeCoins> {
- // Mapping from exchange base URL to list of coins together with their
- // denomination
- let m: ExchangeCoins = {};
-
- let x: number;
-
- function storeExchangeCoin(mc: JoinResult<IExchangeInfo, Coin>,
- url: string) {
- let exchange: IExchangeInfo = mc.left;
- console.log("got coin for exchange", url);
- let coin: Coin = mc.right;
- if (coin.suspended) {
- console.log("skipping suspended coin",
- coin.denomPub,
- "from exchange",
- exchange.baseUrl);
- return;
- }
- let denom = exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub);
- if (!denom) {
- console.warn("denom not found (database inconsistent)");
- return;
- }
- if (denom.value.currency !== paymentAmount.currency) {
- console.warn("same pubkey for different currencies");
- return;
- }
- let cd = {coin, denom};
- let x = m[url];
- if (!x) {
- m[url] = [cd];
- } else {
- x.push(cd);
- }
- }
-
- // Make sure that we don't look up coins
- // for the same URL twice ...
- let handledExchanges = new Set();
-
- let ps = flatMap(allowedExchanges, (info: ExchangeHandle) => {
- if (handledExchanges.has(info.url)) {
- return [];
- }
- handledExchanges.add(info.url);
- console.log("Checking for merchant's exchange", JSON.stringify(info));
- return [
- this.q()
- .iterIndex(Stores.exchanges.pubKeyIndex, info.master_pub)
- .indexJoin(Stores.coins.exchangeBaseUrlIndex,
- (exchange) => exchange.baseUrl)
- .reduce((x) => storeExchangeCoin(x, info.url))
- ];
- });
-
- await Promise.all(ps);
-
- let ret: ExchangeCoins = {};
-
- if (Object.keys(m).length == 0) {
- console.log("not suitable exchanges found");
- }
-
- console.log("exchange coins:");
- console.dir(m);
-
- // We try to find the first exchange where we have
- // enough coins to cover the paymentAmount with fees
- // under depositFeeLimit
-
- nextExchange:
- for (let key in m) {
- let coins = m[key];
- // Sort by ascending deposit fee
- coins.sort((o1, o2) => Amounts.cmp(o1.denom.fee_deposit,
- o2.denom.fee_deposit));
- let maxFee = Amounts.copy(depositFeeLimit);
- let minAmount = Amounts.copy(paymentAmount);
- let accFee = Amounts.copy(coins[0].denom.fee_deposit);
- let accAmount = Amounts.getZero(coins[0].coin.currentAmount.currency);
- let usableCoins: CoinWithDenom[] = [];
- nextCoin:
- for (let i = 0; i < coins.length; i++) {
- let coinAmount = Amounts.copy(coins[i].coin.currentAmount);
- let coinFee = coins[i].denom.fee_deposit;
- if (Amounts.cmp(coinAmount, coinFee) <= 0) {
- continue nextCoin;
- }
- accFee = Amounts.add(accFee, coinFee).amount;
- accAmount = Amounts.add(accAmount, coinAmount).amount;
- if (Amounts.cmp(accFee, maxFee) >= 0) {
- // FIXME: if the fees are too high, we have
- // to cover them ourselves ....
- console.log("too much fees");
- continue nextExchange;
- }
- usableCoins.push(coins[i]);
- if (Amounts.cmp(accAmount, minAmount) >= 0) {
- ret[key] = usableCoins;
- continue nextExchange;
- }
- }
- }
- return ret;
- }
-
-
- /**
- * Record all information that is necessary to
- * pay for a contract in the wallet's database.
- */
- private async recordConfirmPay(offer: Offer,
- payCoinInfo: PayCoinInfo,
- chosenExchange: string): Promise<void> {
- let payReq: PayReq = {
- amount: offer.contract.amount,
- coins: payCoinInfo.map((x) => x.sig),
- H_contract: offer.H_contract,
- max_fee: offer.contract.max_fee,
- merchant_sig: offer.merchant_sig,
- exchange: URI(chosenExchange).href(),
- refund_deadline: offer.contract.refund_deadline,
- pay_deadline: offer.contract.pay_deadline,
- timestamp: offer.contract.timestamp,
- transaction_id: offer.contract.transaction_id,
- instance: offer.contract.merchant.instance
- };
- let t: Transaction = {
- contractHash: offer.H_contract,
- contract: offer.contract,
- payReq: payReq,
- merchantSig: offer.merchant_sig,
- finished: false,
- };
-
- let historyEntry: HistoryRecord = {
- type: "pay",
- timestamp: (new Date).getTime(),
- subjectId: `contract-${offer.H_contract}`,
- detail: {
- merchantName: offer.contract.merchant.name,
- amount: offer.contract.amount,
- contractHash: offer.H_contract,
- fulfillmentUrl: offer.contract.fulfillment_url,
- },
- level: HistoryLevel.User
- };
-
- await this.q()
- .put(Stores.transactions, t)
- .put(Stores.history, historyEntry)
- .putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
- .finish();
-
- this.notifier.notify();
- }
-
-
- async putHistory(historyEntry: HistoryRecord): Promise<void> {
- await this.q().put(Stores.history, historyEntry).finish();
- this.notifier.notify();
- }
-
-
- async saveOffer(offer: Offer): Promise<number> {
- console.log(`saving offer in wallet.ts`);
- let id = await this.q().putWithResult(Stores.offers, offer);
- this.notifier.notify();
- console.log(`saved offer with id ${id}`);
- if (typeof id !== "number") {
- throw Error("db schema wrong");
- }
- return id;
- }
-
-
- /**
- * Add a contract to the wallet and sign coins,
- * but do not send them yet.
- */
- async confirmPay(offer: Offer): Promise<any> {
- console.log("executing confirmPay");
-
- let transaction = await this.q().get(Stores.transactions, offer.H_contract);
-
- if (transaction) {
- // Already payed ...
- return {};
- }
-
- let mcs = await this.getPossibleExchangeCoins(offer.contract.amount,
- offer.contract.max_fee,
- offer.contract.exchanges);
-
- if (Object.keys(mcs).length == 0) {
- console.log("not confirming payment, insufficient coins");
- return {
- error: "coins-insufficient",
- };
- }
- let exchangeUrl = Object.keys(mcs)[0];
-
- let ds = await this.cryptoApi.signDeposit(offer, mcs[exchangeUrl]);
- await this.recordConfirmPay(offer,
- ds,
- exchangeUrl);
- return {};
- }
-
-
- /**
- * Add a contract to the wallet and sign coins,
- * but do not send them yet.
- */
- async checkPay(offer: Offer): Promise<any> {
- // First check if we already payed for it.
- let transaction = await this.q().get(Stores.transactions, offer.H_contract);
- if (transaction) {
- return {isPayed: true};
- }
-
- // If not already payed, check if we could pay for it.
- let mcs = await this.getPossibleExchangeCoins(offer.contract.amount,
- offer.contract.max_fee,
- offer.contract.exchanges);
-
- if (Object.keys(mcs).length == 0) {
- console.log("not confirming payment, insufficient coins");
- return {
- error: "coins-insufficient",
- };
- }
- return {isPayed: false};
- }
-
-
- /**
- * Retrieve all necessary information for looking up the contract
- * with the given hash.
- */
- async executePayment(H_contract: string): Promise<any> {
- let t = await this.q().get<Transaction>(Stores.transactions, H_contract);
- if (!t) {
- return {
- success: false,
- contractFound: false,
- }
- }
- let resp = {
- success: true,
- payReq: t.payReq,
- contract: t.contract,
- };
- return resp;
- }
-
-
- /**
- * First fetch information requred to withdraw from the reserve,
- * then deplete the reserve, withdrawing coins until it is empty.
- */
- private async processReserve(reserveRecord: ReserveRecord,
- retryDelayMs: number = 250): Promise<void> {
- const opId = "reserve-" + reserveRecord.reserve_pub;
- this.startOperation(opId);
-
- try {
- let exchange = await this.updateExchangeFromUrl(reserveRecord.exchange_base_url);
- let reserve = await this.updateReserve(reserveRecord.reserve_pub,
- exchange);
- let n = await this.depleteReserve(reserve, exchange);
-
- if (n != 0) {
- let depleted: HistoryRecord = {
- type: "depleted-reserve",
- subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
- timestamp: (new Date).getTime(),
- detail: {
- exchangeBaseUrl: reserveRecord.exchange_base_url,
- reservePub: reserveRecord.reserve_pub,
- requestedAmount: reserveRecord.requested_amount,
- currentAmount: reserveRecord.current_amount,
- },
- level: HistoryLevel.User
- };
- await this.q().put(Stores.history, depleted).finish();
- }
- } catch (e) {
- // random, exponential backoff truncated at 3 minutes
- let nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(),
- 3000 * 60);
- console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`);
- setTimeout(() => this.processReserve(reserveRecord, nextDelay),
- retryDelayMs);
- } finally {
- this.stopOperation(opId);
- }
- }
-
-
- private async processPreCoin(preCoin: PreCoin,
- retryDelayMs = 100): Promise<void> {
-
- let exchange = await this.q().get(Stores.exchanges,
- preCoin.exchangeBaseUrl);
- if (!exchange) {
- console.error("db inconsistend: exchange for precoin not found");
- return;
- }
- let denom = exchange.all_denoms.find((d) => d.denom_pub == preCoin.denomPub);
- if (!denom) {
- console.error("db inconsistent: denom for precoin not found");
- return;
- }
-
- try {
- const coin = await this.withdrawExecute(preCoin);
-
- const mutateReserve = (r: ReserveRecord) => {
-
- console.log(`before committing coin: current ${amountToPretty(r.current_amount!)}, precoin: ${amountToPretty(
- r.precoin_amount)})}`);
-
- let x = Amounts.sub(r.precoin_amount,
- preCoin.coinValue,
- denom!.fee_withdraw);
- if (x.saturated) {
- console.error("database inconsistent");
- throw AbortTransaction;
- }
- r.precoin_amount = x.amount;
- return r;
- };
-
- let historyEntry: HistoryRecord = {
- type: "withdraw",
- timestamp: (new Date).getTime(),
- level: HistoryLevel.Expert,
- detail: {
- coinPub: coin.coinPub,
- }
- };
-
- await this.q()
- .mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
- .delete("precoins", coin.coinPub)
- .add(Stores.coins, coin)
- .add(Stores.history, historyEntry)
- .finish();
-
- this.notifier.notify();
- } catch (e) {
- console.error("Failed to withdraw coin from precoin, retrying in",
- retryDelayMs,
- "ms", e);
- // exponential backoff truncated at one minute
- let nextRetryDelayMs = Math.min(retryDelayMs * 2, 1000 * 60);
- setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs),
- retryDelayMs);
- }
- }
-
-
- /**
- * Create a reserve, but do not flag it as confirmed yet.
- */
- async createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
- let keypair = await this.cryptoApi.createEddsaKeypair();
- const now = (new Date).getTime();
- const canonExchange = canonicalizeBaseUrl(req.exchange);
-
- const reserveRecord: ReserveRecord = {
- reserve_pub: keypair.pub,
- reserve_priv: keypair.priv,
- exchange_base_url: canonExchange,
- created: now,
- last_query: null,
- current_amount: null,
- requested_amount: req.amount,
- confirmed: false,
- precoin_amount: Amounts.getZero(req.amount.currency),
- };
-
- const historyEntry = {
- type: "create-reserve",
- level: HistoryLevel.Expert,
- timestamp: now,
- subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
- detail: {
- requestedAmount: req.amount,
- reservePub: reserveRecord.reserve_pub,
- }
- };
-
- await this.q()
- .put(Stores.reserves, reserveRecord)
- .put(Stores.history, historyEntry)
- .finish();
-
- let r: CreateReserveResponse = {
- exchange: canonExchange,
- reservePub: keypair.pub,
- };
- return r;
- }
-
-
- /**
- * Mark an existing reserve as confirmed. The wallet will start trying
- * to withdraw from that reserve. This may not immediately succeed,
- * since the exchange might not know about the reserve yet, even though the
- * bank confirmed its creation.
- *
- * A confirmed reserve should be shown to the user in the UI, while
- * an unconfirmed reserve should be hidden.
- */
- async confirmReserve(req: ConfirmReserveRequest): Promise<void> {
- const now = (new Date).getTime();
- let reserve: ReserveRecord|undefined = await (
- this.q().get<ReserveRecord>(Stores.reserves,
- req.reservePub));
- if (!reserve) {
- console.error("Unable to confirm reserve, not found in DB");
- return;
- }
- console.log("reserve confirmed");
- const historyEntry: HistoryRecord = {
- type: "confirm-reserve",
- timestamp: now,
- subjectId: `reserve-progress-${reserve.reserve_pub}`,
- detail: {
- exchangeBaseUrl: reserve.exchange_base_url,
- reservePub: req.reservePub,
- requestedAmount: reserve.requested_amount,
- },
- level: HistoryLevel.User,
- };
- reserve.confirmed = true;
- await this.q()
- .put(Stores.reserves, reserve)
- .put(Stores.history, historyEntry)
- .finish();
- this.notifier.notify();
-
- this.processReserve(reserve);
- }
-
-
- private async withdrawExecute(pc: PreCoin): Promise<Coin> {
- let reserve = await this.q().get<ReserveRecord>(Stores.reserves,
- pc.reservePub);
-
- if (!reserve) {
- throw Error("db inconsistent");
- }
-
- let wd: any = {};
- wd.denom_pub = pc.denomPub;
- wd.reserve_pub = pc.reservePub;
- wd.reserve_sig = pc.withdrawSig;
- wd.coin_ev = pc.coinEv;
- let reqUrl = URI("reserve/withdraw").absoluteTo(reserve.exchange_base_url);
- let resp = await this.http.postJson(reqUrl, wd);
-
-
- if (resp.status != 200) {
- throw new RequestException({
- hint: "Withdrawal failed",
- status: resp.status
- });
- }
- let r = JSON.parse(resp.responseText);
- let denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig,
- pc.blindingKey,
- pc.denomPub);
- let coin: Coin = {
- coinPub: pc.coinPub,
- coinPriv: pc.coinPriv,
- denomPub: pc.denomPub,
- denomSig: denomSig,
- currentAmount: pc.coinValue,
- exchangeBaseUrl: pc.exchangeBaseUrl,
- dirty: false,
- transactionPending: false,
- };
- return coin;
- }
-
-
- /**
- * Withdraw coins from a reserve until it is empty.
- */
- private async depleteReserve(reserve: ReserveRecord,
- exchange: IExchangeInfo): Promise<number> {
- if (!reserve.current_amount) {
- throw Error("can't withdraw when amount is unknown");
- }
- let denomsAvailable: Denomination[] = Array.from(exchange.active_denoms);
- let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount!,
- denomsAvailable);
-
- let ps = denomsForWithdraw.map(async(denom) => {
- function mutateReserve(r: ReserveRecord): ReserveRecord {
- let currentAmount = r.current_amount;
- if (!currentAmount) {
- throw Error("can't withdraw when amount is unknown");
- }
- r.precoin_amount = Amounts.add(r.precoin_amount,
- denom.value,
- denom.fee_withdraw).amount;
- let result = Amounts.sub(currentAmount,
- denom.value,
- denom.fee_withdraw);
- if (result.saturated) {
- console.error("can't create precoin, saturated");
- throw AbortTransaction;
- }
- r.current_amount = result.amount;
-
- console.log(`after creating precoin: current ${amountToPretty(r.current_amount)}, precoin: ${amountToPretty(
- r.precoin_amount)})}`);
-
- return r;
- }
-
- let preCoin = await this.cryptoApi
- .createPreCoin(denom, reserve);
- await this.q()
- .put(Stores.precoins, preCoin)
- .mutate(Stores.reserves, reserve.reserve_pub, mutateReserve);
- await this.processPreCoin(preCoin);
- });
-
- await Promise.all(ps);
- return ps.length;
- }
-
-
- /**
- * Update the information about a reserve that is stored in the wallet
- * by quering the reserve's exchange.
- */
- private async updateReserve(reservePub: string,
- exchange: IExchangeInfo): Promise<ReserveRecord> {
- let reserve = await this.q()
- .get<ReserveRecord>(Stores.reserves, reservePub);
- if (!reserve) {
- throw Error("reserve not in db");
- }
- let reqUrl = URI("reserve/status").absoluteTo(exchange.baseUrl);
- reqUrl.query({'reserve_pub': reservePub});
- let resp = await this.http.get(reqUrl);
- if (resp.status != 200) {
- throw Error();
- }
- let reserveInfo = JSON.parse(resp.responseText);
- if (!reserveInfo) {
- throw Error();
- }
- let oldAmount = reserve.current_amount;
- let newAmount = reserveInfo.balance;
- reserve.current_amount = reserveInfo.balance;
- let historyEntry = {
- type: "reserve-update",
- timestamp: (new Date).getTime(),
- subjectId: `reserve-progress-${reserve.reserve_pub}`,
- detail: {
- reservePub,
- requestedAmount: reserve.requested_amount,
- oldAmount,
- newAmount
- }
- };
- await this.q()
- .put(Stores.reserves, reserve)
- .finish();
- this.notifier.notify();
- return reserve;
- }
-
-
- /**
- * Get the wire information for the exchange with the given base URL.
- */
- async getWireInfo(exchangeBaseUrl: string): Promise<WireInfo> {
- exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl);
- let reqUrl = URI("wire").absoluteTo(exchangeBaseUrl);
- let resp = await this.http.get(reqUrl);
-
- if (resp.status != 200) {
- throw Error("/wire request failed");
- }
-
- let wiJson = JSON.parse(resp.responseText);
- if (!wiJson) {
- throw Error("/wire response malformed")
- }
- return wiJson;
- }
-
- async getReserveCreationInfo(baseUrl: string,
- amount: AmountJson): Promise<ReserveCreationInfo> {
- let exchangeInfo = await this.updateExchangeFromUrl(baseUrl);
-
- let selectedDenoms = getWithdrawDenomList(amount,
- exchangeInfo.active_denoms);
- let acc = Amounts.getZero(amount.currency);
- for (let d of selectedDenoms) {
- acc = Amounts.add(acc, d.fee_withdraw).amount;
- }
- let actualCoinCost = selectedDenoms
- .map((d: Denomination) => Amounts.add(d.value,
- d.fee_withdraw).amount)
- .reduce((a, b) => Amounts.add(a, b).amount);
-
- let wireInfo = await this.getWireInfo(baseUrl);
-
- let ret: ReserveCreationInfo = {
- exchangeInfo,
- selectedDenoms,
- wireInfo,
- withdrawFee: acc,
- overhead: Amounts.sub(amount, actualCoinCost).amount,
- };
- return ret;
- }
-
-
- /**
- * Update or add exchange DB entry by fetching the /keys information.
- * Optionally link the reserve entry to the new or existing
- * exchange entry in then DB.
- */
- async updateExchangeFromUrl(baseUrl: string): Promise<IExchangeInfo> {
- baseUrl = canonicalizeBaseUrl(baseUrl);
- let reqUrl = URI("keys").absoluteTo(baseUrl);
- let resp = await this.http.get(reqUrl);
- if (resp.status != 200) {
- throw Error("/keys request failed");
- }
- let exchangeKeysJson = KeysJson.checked(JSON.parse(resp.responseText));
- return this.updateExchangeFromJson(baseUrl, exchangeKeysJson);
- }
-
- private async suspendCoins(exchangeInfo: IExchangeInfo): Promise<void> {
- let suspendedCoins = await (
- this.q()
- .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchangeInfo.baseUrl)
- .reduce((coin: Coin, suspendedCoins: Coin[]) => {
- if (!exchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) {
- return Array.prototype.concat(suspendedCoins, [coin]);
- }
- return Array.prototype.concat(suspendedCoins);
- }, []));
-
- let q = this.q();
- suspendedCoins.map((c) => {
- console.log("suspending coin", c);
- c.suspended = true;
- q.put(Stores.coins, c);
- });
- await q.finish();
- }
-
-
- private async updateExchangeFromJson(baseUrl: string,
- exchangeKeysJson: KeysJson): Promise<IExchangeInfo> {
- const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
- if (updateTimeSec === null) {
- throw Error("invalid update time");
- }
-
- let r = await this.q().get<IExchangeInfo>(Stores.exchanges, baseUrl);
-
- let exchangeInfo: IExchangeInfo;
-
- if (!r) {
- exchangeInfo = {
- baseUrl,
- all_denoms: [],
- active_denoms: [],
- last_update_time: updateTimeSec,
- masterPublicKey: exchangeKeysJson.master_public_key,
- };
- console.log("making fresh exchange");
- } else {
- if (updateTimeSec < r.last_update_time) {
- console.log("outdated /keys, not updating");
- return r
- }
- exchangeInfo = r;
- console.log("updating old exchange");
- }
-
- let updatedExchangeInfo = await this.updateExchangeInfo(exchangeInfo,
- exchangeKeysJson);
- await this.suspendCoins(updatedExchangeInfo);
-
- await this.q()
- .put(Stores.exchanges, updatedExchangeInfo)
- .finish();
-
- return updatedExchangeInfo;
- }
-
-
- private async updateExchangeInfo(exchangeInfo: IExchangeInfo,
- newKeys: KeysJson): Promise<IExchangeInfo> {
- if (exchangeInfo.masterPublicKey != newKeys.master_public_key) {
- throw Error("public keys do not match");
- }
-
- exchangeInfo.active_denoms = [];
-
- let denomsToCheck = newKeys.denoms.filter((newDenom) => {
- // did we find the new denom in the list of all (old) denoms?
- let found = false;
- for (let oldDenom of exchangeInfo.all_denoms) {
- if (oldDenom.denom_pub === newDenom.denom_pub) {
- let a: any = Object.assign({}, oldDenom);
- let b: any = Object.assign({}, newDenom);
- // pub hash is only there for convenience in the wallet
- delete a["pub_hash"];
- delete b["pub_hash"];
- if (!deepEquals(a, b)) {
- console.error("denomination parameters were modified, old/new:");
- console.dir(a);
- console.dir(b);
- // FIXME: report to auditors
- }
- found = true;
- break;
- }
- }
-
- if (found) {
- exchangeInfo.active_denoms.push(newDenom);
- // No need to check signatures
- return false;
- }
- return true;
- });
-
- let ps = denomsToCheck.map(async(denom) => {
- let valid = await this.cryptoApi
- .isValidDenom(denom,
- exchangeInfo.masterPublicKey);
- if (!valid) {
- console.error("invalid denomination",
- denom,
- "with key",
- exchangeInfo.masterPublicKey);
- // FIXME: report to auditors
- }
- exchangeInfo.active_denoms.push(denom);
- exchangeInfo.all_denoms.push(denom);
- });
-
- await Promise.all(ps);
-
- return exchangeInfo;
- }
-
-
- /**
- * Retrieve a mapping from currency name to the amount
- * that is currenctly available for spending in the wallet.
- */
- async getBalances(): Promise<WalletBalance> {
- function ensureEntry(balance: WalletBalance, currency: string) {
- let entry: WalletBalanceEntry|undefined = balance[currency];
- let z = Amounts.getZero(currency);
- if (!entry) {
- balance[currency] = entry = {
- available: z,
- pendingIncoming: z,
- pendingPayment: z,
- };
- }
- return entry;
- }
-
- function collectBalances(c: Coin, balance: WalletBalance) {
- if (c.suspended) {
- return balance;
- }
- let currency = c.currentAmount.currency;
- let entry = ensureEntry(balance, currency);
- entry.available = Amounts.add(entry.available, c.currentAmount).amount;
- return balance;
- }
-
- function collectPendingWithdraw(r: ReserveRecord, balance: WalletBalance) {
- if (!r.confirmed) {
- return balance;
- }
- let entry = ensureEntry(balance, r.requested_amount.currency);
- let amount = r.current_amount;
- if (!amount) {
- amount = r.requested_amount;
- }
- amount = Amounts.add(amount, r.precoin_amount).amount;
- if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) {
- entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
- amount).amount;
- }
- return balance;
- }
-
- function collectPendingRefresh(r: RefreshSession, balance: WalletBalance) {
- if (!r.finished) {
- return balance;
- }
- let entry = ensureEntry(balance, r.valueWithFee.currency);
- entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
- r.valueOutput).amount;
-
- return balance;
- }
-
- function collectPayments(t: Transaction, balance: WalletBalance) {
- if (t.finished) {
- return balance;
- }
- let entry = ensureEntry(balance, t.contract.amount.currency);
- entry.pendingPayment = Amounts.add(entry.pendingPayment,
- t.contract.amount).amount;
-
- return balance;
- }
-
- function collectSmallestWithdraw(e: IExchangeInfo, sw: any) {
- let min: AmountJson|undefined;
- for (let d of e.active_denoms) {
- let v = Amounts.add(d.value, d.fee_withdraw).amount;
- if (!min) {
- min = v;
- continue;
- }
- if (Amounts.cmp(v, min) < 0) {
- min = v;
- }
- }
- sw[e.baseUrl] = min;
- return sw;
- }
-
- let balance = {};
- // Mapping from exchange pub to smallest
- // possible amount we can withdraw
- let smallestWithdraw: {[baseUrl: string]: AmountJson} = {};
-
- smallestWithdraw = await (this.q()
- .iter(Stores.exchanges)
- .reduce(collectSmallestWithdraw, {}));
-
- console.log("smallest withdraws", smallestWithdraw);
-
- let tx = this.q();
- tx.iter(Stores.coins)
- .reduce(collectBalances, balance);
- tx.iter(Stores.refresh)
- .reduce(collectPendingRefresh, balance);
- tx.iter(Stores.reserves)
- .reduce(collectPendingWithdraw, balance);
- tx.iter(Stores.transactions)
- .reduce(collectPayments, balance);
- await tx.finish();
- return balance;
-
- }
-
-
- async createRefreshSession(oldCoinPub: string): Promise<RefreshSession|undefined> {
- let coin = await this.q().get<Coin>(Stores.coins, oldCoinPub);
-
- if (!coin) {
- throw Error("coin not found");
- }
-
- let exchange = await this.updateExchangeFromUrl(coin.exchangeBaseUrl);
-
- if (!exchange) {
- throw Error("db inconsistent");
- }
-
- let oldDenom = exchange.all_denoms.find((d) => d.denom_pub == coin!.denomPub);
-
- if (!oldDenom) {
- throw Error("db inconsistent");
- }
-
- let availableDenoms: Denomination[] = exchange.active_denoms;
-
- let availableAmount = Amounts.sub(coin.currentAmount,
- oldDenom.fee_refresh).amount;
-
- let newCoinDenoms = getWithdrawDenomList(availableAmount,
- availableDenoms);
-
- console.log("refreshing into", newCoinDenoms);
-
- if (newCoinDenoms.length == 0) {
- console.log("not refreshing, value too small");
- return undefined;
- }
-
-
- let refreshSession: RefreshSession = await (
- this.cryptoApi.createRefreshSession(exchange.baseUrl,
- 3,
- coin,
- newCoinDenoms,
- oldDenom.fee_refresh));
-
- function mutateCoin(c: Coin): Coin {
- let r = Amounts.sub(c.currentAmount,
- refreshSession.valueWithFee);
- if (r.saturated) {
- // Something else must have written the coin value
- throw AbortTransaction;
- }
- c.currentAmount = r.amount;
- return c;
- }
-
- await this.q()
- .put(Stores.refresh, refreshSession)
- .mutate(Stores.coins, coin.coinPub, mutateCoin)
- .finish();
-
- return refreshSession;
- }
-
-
- async refresh(oldCoinPub: string): Promise<void> {
- let refreshSession: RefreshSession|undefined;
- let oldSession = await this.q().get(Stores.refresh, oldCoinPub);
- if (oldSession) {
- refreshSession = oldSession;
- } else {
- refreshSession = await this.createRefreshSession(oldCoinPub);
- }
- if (!refreshSession) {
- // refreshing not necessary
- return;
- }
- this.continueRefreshSession(refreshSession);
- }
-
- async continueRefreshSession(refreshSession: RefreshSession) {
- if (refreshSession.finished) {
- return;
- }
- if (typeof refreshSession.norevealIndex !== "number") {
- let coinPub = refreshSession.meltCoinPub;
- await this.refreshMelt(refreshSession);
- let r = await this.q().get<RefreshSession>(Stores.refresh, coinPub);
- if (!r) {
- throw Error("refresh session does not exist anymore");
- }
- refreshSession = r;
- }
-
- await this.refreshReveal(refreshSession);
- }
-
-
- async refreshMelt(refreshSession: RefreshSession): Promise<void> {
- if (refreshSession.norevealIndex != undefined) {
- console.error("won't melt again");
- return;
- }
-
- let coin = await this.q().get<Coin>(Stores.coins,
- refreshSession.meltCoinPub);
- if (!coin) {
- console.error("can't melt coin, it does not exist");
- return;
- }
-
- let reqUrl = URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl);
- let meltCoin = {
- coin_pub: coin.coinPub,
- denom_pub: coin.denomPub,
- denom_sig: coin.denomSig,
- confirm_sig: refreshSession.confirmSig,
- value_with_fee: refreshSession.valueWithFee,
- };
- let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv));
- let req = {
- "new_denoms": refreshSession.newDenoms,
- "melt_coin": meltCoin,
- "transfer_pubs": refreshSession.transferPubs,
- "coin_evs": coinEvs,
- };
- console.log("melt request:", req);
- let resp = await this.http.postJson(reqUrl, req);
-
- console.log("melt request:", req);
- console.log("melt response:", resp.responseText);
-
- if (resp.status != 200) {
- console.error(resp.responseText);
- throw Error("refresh failed");
- }
-
- let respJson = JSON.parse(resp.responseText);
-
- if (!respJson) {
- throw Error("exchange responded with garbage");
- }
-
- let norevealIndex = respJson.noreveal_index;
-
- if (typeof norevealIndex != "number") {
- throw Error("invalid response");
- }
-
- refreshSession.norevealIndex = norevealIndex;
-
- await this.q().put(Stores.refresh, refreshSession).finish();
- }
-
-
- async refreshReveal(refreshSession: RefreshSession): Promise<void> {
- let norevealIndex = refreshSession.norevealIndex;
- if (norevealIndex == undefined) {
- throw Error("can't reveal without melting first");
- }
- let privs = Array.from(refreshSession.transferPrivs);
- privs.splice(norevealIndex, 1);
-
- let req = {
- "session_hash": refreshSession.hash,
- "transfer_privs": privs,
- };
-
- let reqUrl = URI("refresh/reveal")
- .absoluteTo(refreshSession.exchangeBaseUrl);
- console.log("reveal request:", req);
- let resp = await this.http.postJson(reqUrl, req);
-
- console.log("session:", refreshSession);
- console.log("reveal response:", resp);
-
- if (resp.status != 200) {
- console.log("error: /refresh/reveal returned status " + resp.status);
- return;
- }
-
- let respJson = JSON.parse(resp.responseText);
-
- if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) {
- console.log("/refresh/reveal did not contain ev_sigs");
- }
-
- let exchange = await this.q().get<IExchangeInfo>(Stores.exchanges,
- refreshSession.exchangeBaseUrl);
- if (!exchange) {
- console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`);
- return;
- }
-
- let coins: Coin[] = [];
-
- for (let i = 0; i < respJson.ev_sigs.length; i++) {
- let denom = exchange.all_denoms.find((d) => d.denom_pub == refreshSession.newDenoms[i]);
- if (!denom) {
- console.error("denom not found");
- continue;
- }
- let pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i];
- let denomSig = await this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig,
- pc.blindingKey,
- denom.denom_pub);
- let coin: Coin = {
- coinPub: pc.publicKey,
- coinPriv: pc.privateKey,
- denomPub: denom.denom_pub,
- denomSig: denomSig,
- currentAmount: denom.value,
- exchangeBaseUrl: refreshSession.exchangeBaseUrl,
- dirty: false,
- transactionPending: false,
- };
-
- coins.push(coin);
- }
-
- refreshSession.finished = true;
-
- await this.q()
- .putAll(Stores.coins, coins)
- .put(Stores.refresh, refreshSession)
- .finish();
- }
-
-
- /**
- * Retrive the full event history for this wallet.
- */
- async getHistory(): Promise<any> {
- function collect(x: any, acc: any) {
- acc.push(x);
- return acc;
- }
-
- let history = await (
- this.q()
- .iterIndex(Stores.history.timestampIndex)
- .reduce(collect, []));
-
- return {history};
- }
-
-
- async getOffer(offerId: number): Promise<any> {
- let offer = await this.q() .get(Stores.offers, offerId);
- return offer;
- }
-
- async getExchanges(): Promise<IExchangeInfo[]> {
- return this.q()
- .iter<IExchangeInfo>(Stores.exchanges)
- .flatMap((e) => [e])
- .toArray();
- }
-
- async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
- return this.q()
- .iter<ReserveRecord>(Stores.reserves)
- .filter((r: ReserveRecord) => r.exchange_base_url === exchangeBaseUrl)
- .toArray();
- }
-
- async getCoins(exchangeBaseUrl: string): Promise<Coin[]> {
- return this.q()
- .iter<Coin>(Stores.coins)
- .filter((c: Coin) => c.exchangeBaseUrl === exchangeBaseUrl)
- .toArray();
- }
-
- async getPreCoins(exchangeBaseUrl: string): Promise<PreCoin[]> {
- return this.q()
- .iter<PreCoin>(Stores.precoins)
- .filter((c: PreCoin) => c.exchangeBaseUrl === exchangeBaseUrl)
- .toArray();
- }
-
- async hashContract(contract: Contract): Promise<string> {
- return this.cryptoApi.hashString(canonicalJson(contract));
- }
-
- /**
- * Check if there's an equivalent contract we've already purchased.
- */
- async checkRepurchase(contract: Contract): Promise<CheckRepurchaseResult> {
- if (!contract.repurchase_correlation_id) {
- console.log("no repurchase: no correlation id");
- return {isRepurchase: false};
- }
- let result: Transaction|undefined = await (
- this.q()
- .getIndexed(Stores.transactions.repurchaseIndex,
- [
- contract.merchant_pub,
- contract.repurchase_correlation_id
- ]));
-
- if (result) {
- console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id);
- return {
- isRepurchase: true,
- existingContractHash: result.contractHash,
- existingFulfillmentUrl: result.contract.fulfillment_url,
- };
- } else {
- return {isRepurchase: false};
- }
- }
-
-
- async paymentSucceeded(contractHash: string): Promise<any> {
- const doPaymentSucceeded = async() => {
- let t = await this.q().get<Transaction>(Stores.transactions,
- contractHash);
- if (!t) {
- console.error("contract not found");
- return;
- }
- t.finished = true;
- let modifiedCoins: Coin[] = [];
- for (let pc of t.payReq.coins) {
- let c = await this.q().get<Coin>(Stores.coins, pc.coin_pub);
- if (!c) {
- console.error("coin not found");
- return;
- }
- c.transactionPending = false;
- modifiedCoins.push(c);
- }
-
- await this.q()
- .putAll(Stores.coins, modifiedCoins)
- .put(Stores.transactions, t)
- .finish();
- for (let c of t.payReq.coins) {
- this.refresh(c.coin_pub);
- }
- };
- doPaymentSucceeded();
- return;
- }
-}
diff --git a/lib/wallet/wxApi.ts b/lib/wallet/wxApi.ts
deleted file mode 100644
index a85b56c28..000000000
--- a/lib/wallet/wxApi.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-import {
- AmountJson,
- Coin,
- PreCoin,
- ReserveCreationInfo,
- IExchangeInfo,
- ReserveRecord
-} from "./types";
-
-/**
- * Interface to the wallet through WebExtension messaging.
- * @author Florian Dold
- */
-
-
-export function getReserveCreationInfo(baseUrl: string,
- amount: AmountJson): Promise<ReserveCreationInfo> {
- let m = { type: "reserve-creation-info", detail: { baseUrl, amount } };
- return new Promise((resolve, reject) => {
- chrome.runtime.sendMessage(m, (resp) => {
- if (resp.error) {
- console.error("error response", resp);
- let e = Error("call to reserve-creation-info failed");
- (e as any).errorResponse = resp;
- reject(e);
- return;
- }
- resolve(resp);
- });
- });
-}
-
-export async function callBackend(type: string, detail?: any): Promise<any> {
- return new Promise<IExchangeInfo[]>((resolve, reject) => {
- chrome.runtime.sendMessage({ type, detail }, (resp) => {
- resolve(resp);
- });
- });
-}
-
-export async function getExchanges(): Promise<IExchangeInfo[]> {
- return await callBackend("get-exchanges");
-}
-
-export async function getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
- return await callBackend("get-reserves", { exchangeBaseUrl });
-}
-
-export async function getCoins(exchangeBaseUrl: string): Promise<Coin[]> {
- return await callBackend("get-coins", { exchangeBaseUrl });
-}
-
-export async function getPreCoins(exchangeBaseUrl: string): Promise<PreCoin[]> {
- return await callBackend("get-precoins", { exchangeBaseUrl });
-}
-
-export async function refresh(coinPub: string): Promise<void> {
- return await callBackend("refresh-coin", { coinPub });
-}
diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts
deleted file mode 100644
index 07f16f24f..000000000
--- a/lib/wallet/wxMessaging.ts
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-
-import {
- Wallet,
- Offer,
- Badge,
- ConfirmReserveRequest,
- CreateReserveRequest
-} from "./wallet";
-import { deleteDb, exportDb, openTalerDb } from "./db";
-import { BrowserHttpLib } from "./http";
-import { Checkable } from "./checkable";
-import { AmountJson } from "./types";
-import Port = chrome.runtime.Port;
-import { Notifier } from "./types";
-import { Contract } from "./types";
-import MessageSender = chrome.runtime.MessageSender;
-import { ChromeBadge } from "./chromeBadge";
-
-"use strict";
-
-/**
- * Messaging for the WebExtensions wallet. Should contain
- * parts that are specific for WebExtensions, but as little business
- * logic as possible.
- *
- * @author Florian Dold
- */
-
-
-type Handler = (detail: any, sender: MessageSender) => Promise<any>;
-
-function makeHandlers(db: IDBDatabase,
- wallet: Wallet): { [msg: string]: Handler } {
- return {
- ["balances"]: function (detail, sender) {
- return wallet.getBalances();
- },
- ["dump-db"]: function (detail, sender) {
- return exportDb(db);
- },
- ["get-tab-cookie"]: function (detail, sender) {
- if (!sender || !sender.tab || !sender.tab.id) {
- return Promise.resolve();
- }
- let id: number = sender.tab.id;
- let info: any = <any>paymentRequestCookies[id];
- delete paymentRequestCookies[id];
- return Promise.resolve(info);
- },
- ["ping"]: function (detail, sender) {
- return Promise.resolve();
- },
- ["reset"]: function (detail, sender) {
- if (db) {
- let tx = db.transaction(Array.from(db.objectStoreNames), 'readwrite');
- for (let i = 0; i < db.objectStoreNames.length; i++) {
- tx.objectStore(db.objectStoreNames[i]).clear();
- }
- }
- deleteDb();
-
- chrome.browserAction.setBadgeText({ text: "" });
- console.log("reset done");
- // Response is synchronous
- return Promise.resolve({});
- },
- ["create-reserve"]: function (detail, sender) {
- const d = {
- exchange: detail.exchange,
- amount: detail.amount,
- };
- const req = CreateReserveRequest.checked(d);
- return wallet.createReserve(req);
- },
- ["confirm-reserve"]: function (detail, sender) {
- // TODO: make it a checkable
- const d = {
- reservePub: detail.reservePub
- };
- const req = ConfirmReserveRequest.checked(d);
- return wallet.confirmReserve(req);
- },
- ["confirm-pay"]: function (detail, sender) {
- let offer: Offer;
- try {
- offer = Offer.checked(detail.offer);
- } catch (e) {
- if (e instanceof Checkable.SchemaError) {
- console.error("schema error:", e.message);
- return Promise.resolve({
- error: "invalid contract",
- hint: e.message,
- detail: detail
- });
- } else {
- throw e;
- }
- }
-
- return wallet.confirmPay(offer);
- },
- ["check-pay"]: function (detail, sender) {
- let offer: Offer;
- try {
- offer = Offer.checked(detail.offer);
- } catch (e) {
- if (e instanceof Checkable.SchemaError) {
- console.error("schema error:", e.message);
- return Promise.resolve({
- error: "invalid contract",
- hint: e.message,
- detail: detail
- });
- } else {
- throw e;
- }
- }
- return wallet.checkPay(offer);
- },
- ["execute-payment"]: function (detail: any, sender: MessageSender) {
- if (sender.tab && sender.tab.id) {
- rateLimitCache[sender.tab.id]++;
- if (rateLimitCache[sender.tab.id] > 10) {
- console.warn("rate limit for execute payment exceeded");
- let msg = {
- error: "rate limit exceeded for execute-payment",
- rateLimitExceeded: true,
- hint: "Check for redirect loops",
- };
- return Promise.resolve(msg);
- }
- }
- return wallet.executePayment(detail.H_contract);
- },
- ["exchange-info"]: function (detail) {
- if (!detail.baseUrl) {
- return Promise.resolve({ error: "bad url" });
- }
- return wallet.updateExchangeFromUrl(detail.baseUrl);
- },
- ["hash-contract"]: function (detail) {
- if (!detail.contract) {
- return Promise.resolve({ error: "contract missing" });
- }
- return wallet.hashContract(detail.contract).then((hash) => {
- return { hash };
- });
- },
- ["put-history-entry"]: function (detail: any) {
- if (!detail.historyEntry) {
- return Promise.resolve({ error: "historyEntry missing" });
- }
- return wallet.putHistory(detail.historyEntry);
- },
- ["save-offer"]: function (detail: any) {
- let offer = detail.offer;
- if (!offer) {
- return Promise.resolve({ error: "offer missing" });
- }
- console.log("handling safe-offer");
- return wallet.saveOffer(offer);
- },
- ["reserve-creation-info"]: function (detail, sender) {
- if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
- return Promise.resolve({ error: "bad url" });
- }
- let amount = AmountJson.checked(detail.amount);
- return wallet.getReserveCreationInfo(detail.baseUrl, amount);
- },
- ["check-repurchase"]: function (detail, sender) {
- let contract = Contract.checked(detail.contract);
- return wallet.checkRepurchase(contract);
- },
- ["get-history"]: function (detail, sender) {
- // TODO: limit history length
- return wallet.getHistory();
- },
- ["get-offer"]: function (detail, sender) {
- return wallet.getOffer(detail.offerId);
- },
- ["get-exchanges"]: function (detail, sender) {
- return wallet.getExchanges();
- },
- ["get-reserves"]: function (detail, sender) {
- if (typeof detail.exchangeBaseUrl !== "string") {
- return Promise.reject(Error("exchangeBaseUrl missing"));
- }
- return wallet.getReserves(detail.exchangeBaseUrl);
- },
- ["get-coins"]: function (detail, sender) {
- if (typeof detail.exchangeBaseUrl !== "string") {
- return Promise.reject(Error("exchangBaseUrl missing"));
- }
- return wallet.getCoins(detail.exchangeBaseUrl);
- },
- ["get-precoins"]: function (detail, sender) {
- if (typeof detail.exchangeBaseUrl !== "string") {
- return Promise.reject(Error("exchangBaseUrl missing"));
- }
- return wallet.getPreCoins(detail.exchangeBaseUrl);
- },
- ["refresh-coin"]: function (detail, sender) {
- if (typeof detail.coinPub !== "string") {
- return Promise.reject(Error("coinPub missing"));
- }
- return wallet.refresh(detail.coinPub);
- },
- ["payment-failed"]: function (detail, sender) {
- // For now we just update exchanges (maybe the exchange did something
- // wrong and the keys were messed up).
- // FIXME: in the future we should look at what actually went wrong.
- console.error("payment reported as failed");
- wallet.updateExchanges();
- return Promise.resolve();
- },
- ["payment-succeeded"]: function (detail, sender) {
- let contractHash = detail.contractHash;
- if (!contractHash) {
- return Promise.reject(Error("contractHash missing"));
- }
- return wallet.paymentSucceeded(contractHash);
- },
- };
-}
-
-
-function dispatch(handlers: any, req: any, sender: any, sendResponse: any) {
- if (req.type in handlers) {
- Promise
- .resolve()
- .then(() => {
- const p = handlers[req.type](req.detail, sender);
-
- return p.then((r: any) => {
- try {
- sendResponse(r);
- } catch (e) {
- // might fail if tab disconnected
- }
- })
- })
- .catch((e) => {
- console.log(`exception during wallet handler for '${req.type}'`);
- console.log("request", req);
- console.error(e);
- try {
- sendResponse({
- error: "exception",
- hint: e.message,
- stack: e.stack.toString()
- });
-
- } catch (e) {
- // might fail if tab disconnected
- }
- });
- // The sendResponse call is async
- return true;
- } else {
- console.error(`Request type ${JSON.stringify(req)} unknown, req ${req.type}`);
- try {
- sendResponse({ error: "request unknown" });
- } catch (e) {
- // might fail if tab disconnected
- }
-
- // The sendResponse call is sync
- return false;
- }
-}
-
-class ChromeNotifier implements Notifier {
- ports: Port[] = [];
-
- constructor() {
- chrome.runtime.onConnect.addListener((port) => {
- console.log("got connect!");
- this.ports.push(port);
- port.onDisconnect.addListener(() => {
- let i = this.ports.indexOf(port);
- if (i >= 0) {
- this.ports.splice(i, 1);
- } else {
- console.error("port already removed");
- }
- });
- });
- }
-
- notify() {
- for (let p of this.ports) {
- p.postMessage({ notify: true });
- }
- }
-}
-
-
-/**
- * Mapping from tab ID to payment information (if any).
- */
-let paymentRequestCookies: { [n: number]: any } = {};
-
-function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[],
- url: string, tabId: number): any {
- const headers: { [s: string]: string } = {};
- for (let kv of headerList) {
- if (kv.value) {
- headers[kv.name.toLowerCase()] = kv.value;
- }
- }
-
- const contractUrl = headers["x-taler-contract-url"];
- if (contractUrl !== undefined) {
- paymentRequestCookies[tabId] = { type: "fetch", contractUrl };
- return;
- }
-
- const contractHash = headers["x-taler-contract-hash"];
-
- if (contractHash !== undefined) {
- const payUrl = headers["x-taler-pay-url"];
- if (payUrl === undefined) {
- console.log("malformed 402, X-Taler-Pay-Url missing");
- return;
- }
-
- // Offer URL is optional
- const offerUrl = headers["x-taler-offer-url"];
- paymentRequestCookies[tabId] = {
- type: "execute",
- offerUrl,
- payUrl,
- contractHash
- };
- return;
- }
-
- // looks like it's not a taler request, it might be
- // for a different payment system (or the shop is buggy)
- console.log("ignoring non-taler 402 response");
-}
-
-// Useful for debugging ...
-export let wallet: Wallet | undefined = undefined;
-export let badge: ChromeBadge | undefined = undefined;
-
-// Rate limit cache for executePayment operations, to break redirect loops
-let rateLimitCache: { [n: number]: number } = {};
-
-function clearRateLimitCache() {
- rateLimitCache = {};
-}
-
-export function wxMain() {
- chrome.browserAction.setBadgeText({ text: "" });
- badge = new ChromeBadge();
-
- chrome.tabs.query({}, function (tabs) {
- for (let tab of tabs) {
- if (!tab.url || !tab.id) {
- return;
- }
- let uri = URI(tab.url);
- if (uri.protocol() == "http" || uri.protocol() == "https") {
- console.log("injecting into existing tab", tab.id);
- chrome.tabs.executeScript(tab.id, { file: "lib/vendor/URI.js" });
- chrome.tabs.executeScript(tab.id, { file: "lib/taler-wallet-lib.js" });
- chrome.tabs.executeScript(tab.id, { file: "content_scripts/notify.js" });
- }
- }
- });
-
- chrome.extension.getBackgroundPage().setInterval(clearRateLimitCache, 5000);
-
- Promise.resolve()
- .then(() => {
- return openTalerDb();
- })
- .catch((e) => {
- console.error("could not open database");
- console.error(e);
- })
- .then((db: IDBDatabase) => {
- let http = new BrowserHttpLib();
- let notifier = new ChromeNotifier();
- console.log("setting wallet");
- wallet = new Wallet(db, http, badge!, notifier);
-
- // Handlers for messages coming directly from the content
- // script on the page
- let handlers = makeHandlers(db, wallet!);
- chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
- try {
- return dispatch(handlers, req, sender, sendResponse)
- } catch (e) {
- console.log(`exception during wallet handler (dispatch)`);
- console.log("request", req);
- console.error(e);
- sendResponse({
- error: "exception",
- hint: e.message,
- stack: e.stack.toString()
- });
- return false;
- }
- });
-
- // Handlers for catching HTTP requests
- chrome.webRequest.onHeadersReceived.addListener((details) => {
- if (details.statusCode != 402) {
- return;
- }
- console.log(`got 402 from ${details.url}`);
- return handleHttpPayment(details.responseHeaders || [],
- details.url,
- details.tabId);
- }, { urls: ["<all_urls>"] }, ["responseHeaders", "blocking"]);
- })
- .catch((e) => {
- console.error("could not initialize wallet messaging");
- console.error(e);
- });
-}