diff options
author | Florian Dold <florian.dold@gmail.com> | 2015-12-17 22:56:24 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2015-12-17 22:56:24 +0100 |
commit | 38c947d7712d77070ca521b4718032fb31c0f108 (patch) | |
tree | f460392f7f20fdf7ae01e1d3f57b524edd52932f | |
parent | 5f907c13fc76189ace1537af43903e7cd2c82c84 (diff) |
Towards payment.
-rw-r--r-- | extension/background/db.js | 35 | ||||
-rw-r--r-- | extension/background/db.ts | 89 | ||||
-rw-r--r-- | extension/background/emscriptif.js | 215 | ||||
-rw-r--r-- | extension/background/emscriptif.ts | 409 | ||||
-rw-r--r-- | extension/background/libwrapper.js | 141 | ||||
-rw-r--r-- | extension/background/wallet.js | 195 | ||||
-rw-r--r-- | extension/background/wallet.ts | 409 | ||||
-rw-r--r-- | extension/lib/util.js | 4 | ||||
-rw-r--r-- | extension/lib/util.ts | 5 | ||||
-rw-r--r-- | extension/pages/confirm-contract.html | 8 | ||||
-rw-r--r-- | extension/pages/confirm-contract.js | 18 | ||||
-rw-r--r-- | extension/pages/confirm-contract.tsx | 17 | ||||
-rw-r--r-- | extension/pages/debug.html | 1 | ||||
-rw-r--r-- | extension/popup/balance-overview.js | 15 | ||||
-rw-r--r-- | extension/popup/balance-overview.tsx | 16 |
15 files changed, 1131 insertions, 446 deletions
diff --git a/extension/background/db.js b/extension/background/db.js index b52ee457a..f6d81d9ac 100644 --- a/extension/background/db.js +++ b/extension/background/db.js @@ -1,5 +1,34 @@ +"use strict"; +const DB_NAME = "taler"; +const DB_VERSION = 1; /** - * Declarations and helpers for - * things that are stored in the wallet's - * database. + * Return a promise that resolves + * to the taler wallet db. */ +function openTalerDb() { + return new Promise((resolve, reject) => { + let req = indexedDB.open(DB_NAME, DB_VERSION); + req.onerror = (e) => { + reject(e); + }; + req.onsuccess = (e) => { + resolve(req.result); + }; + req.onupgradeneeded = (e) => { + let db = req.result; + console.log("DB: upgrade needed: oldVersion = " + e.oldVersion); + switch (e.oldVersion) { + case 0: + let mints = db.createObjectStore("mints", { keyPath: "baseUrl" }); + mints.createIndex("pubKey", "keys.master_public_key"); + db.createObjectStore("reserves", { keyPath: "reserve_pub" }); + db.createObjectStore("denoms", { keyPath: "denomPub" }); + let coins = db.createObjectStore("coins", { keyPath: "coinPub" }); + coins.createIndex("mintBaseUrl", "mintBaseUrl"); + db.createObjectStore("transactions", { keyPath: "contractHash" }); + db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true }); + break; + } + }; + }); +} diff --git a/extension/background/db.ts b/extension/background/db.ts new file mode 100644 index 000000000..12d944a83 --- /dev/null +++ b/extension/background/db.ts @@ -0,0 +1,89 @@ +"use strict"; +/** + * Declarations and helpers for + * things that are stored in the wallet's + * database. + */ + + + +namespace Db { + export interface Mint { + baseUrl: string; + keys: Keys + } + + export interface CoinWithDenom { + coin: Coin; + denom: Denomination; + } + + export interface Keys { + denoms: { [key: string]: Denomination }; + } + + export interface Denomination { + value: AmountJson; + denom_pub: string; + fee_withdraw: AmountJson; + fee_deposit: AmountJson; + } + + export interface PreCoin { + coinPub: string; + coinPriv: string; + reservePub: string; + denomPub: string; + blindingKey: string; + withdrawSig: string; + coinEv: string; + mintBaseUrl: string; + coinValue: AmountJson; + } + + export interface Coin { + coinPub: string; + coinPriv: string; + denomPub: string; + denomSig: string; + currentAmount: AmountJson; + } + + +} + + +const DB_NAME = "taler"; +const DB_VERSION = 1; + +/** + * Return a promise that resolves + * to the taler wallet db. + */ +function openTalerDb(): Promise<IDBDatabase> { + return new Promise((resolve, reject) => { + let req = indexedDB.open(DB_NAME, DB_VERSION); + req.onerror = (e) => { + reject(e); + }; + req.onsuccess = (e) => { + resolve(req.result); + }; + req.onupgradeneeded = (e) => { + let db = req.result; + console.log ("DB: upgrade needed: oldVersion = " + e.oldVersion); + switch (e.oldVersion) { + case 0: // DB does not exist yet + let mints = db.createObjectStore("mints", { keyPath: "baseUrl" }); + mints.createIndex("pubKey", "keys.master_public_key"); + db.createObjectStore("reserves", { keyPath: "reserve_pub"}); + db.createObjectStore("denoms", { keyPath: "denomPub" }); + let coins = db.createObjectStore("coins", { keyPath: "coinPub" }); + coins.createIndex("mintBaseUrl", "mintBaseUrl"); + db.createObjectStore("transactions", { keyPath: "contractHash" }); + db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true }); + break; + } + }; + }); +} diff --git a/extension/background/emscriptif.js b/extension/background/emscriptif.js index 81580fbed..15eef7a3a 100644 --- a/extension/background/emscriptif.js +++ b/extension/background/emscriptif.js @@ -1,19 +1,19 @@ /* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - 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 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. + 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/> -*/ + 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"; // Size of a native pointer. const PTR_SIZE = 4; @@ -30,6 +30,7 @@ var emsc = { 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']), @@ -62,6 +63,7 @@ var emscAlloc = { var SignaturePurpose; (function (SignaturePurpose) { SignaturePurpose[SignaturePurpose["RESERVE_WITHDRAW"] = 1200] = "RESERVE_WITHDRAW"; + SignaturePurpose[SignaturePurpose["WALLET_COIN_DEPOSIT"] = 1201] = "WALLET_COIN_DEPOSIT"; })(SignaturePurpose || (SignaturePurpose = {})); var RandomQuality; (function (RandomQuality) { @@ -138,7 +140,7 @@ class SyncArena extends DefaultArena { super(); let me = this; this.timer = new Worker('background/timerThread.js'); - this.timer.onmessage = (e) => { + this.timer.onmessage = () => { this.destroy(); }; //this.timer.postMessage({interval: 50}); @@ -164,6 +166,14 @@ class Amount extends ArenaObject { emsc.free(this.nativePtr); } } + static getZero(currency, a) { + let am = new Amount(null, a); + let r = emsc.amount_get_zero(currency, am.getNative()); + if (r != GNUNET_OK) { + throw Error("invalid currency"); + } + return am; + } toNbo(a) { let x = new AmountNbo(a); x.alloc(); @@ -260,7 +270,9 @@ class PackedArenaObject extends ArenaObject { } } class AmountNbo extends PackedArenaObject { - size() { return 24; } + size() { + return 24; + } } class EddsaPrivateKey extends PackedArenaObject { static create(a) { @@ -268,30 +280,60 @@ class EddsaPrivateKey extends PackedArenaObject { obj.nativePtr = emscAlloc.eddsa_key_create(); return obj; } - size() { return 32; } + size() { + return 32; + } getPublicKey(a) { let obj = new EddsaPublicKey(a); obj.nativePtr = emscAlloc.eddsa_public_key_from_private(this.nativePtr); return obj; } } -class EddsaPublicKey extends PackedArenaObject { - size() { return 32; } +mixinStatic(EddsaPrivateKey, fromCrock); +function fromCrock(s) { + let x = new this(); + x.alloc(); + x.loadCrock(s); + return x; } -class RsaBlindingKey extends ArenaObject { - static create(len, a) { - let o = new RsaBlindingKey(a); - o.nativePtr = emscAlloc.rsa_blinding_key_create(len); - return o; +function mixin(obj, method, name) { + if (!name) { + name = method.name; } - static fromCrock(s, a) { + if (!name) { + throw Error("Mixin needs a name."); + } + console.log("mixing in", name, "into", obj.name); + obj.prototype[method.name] = method; +} +function mixinStatic(obj, method, name) { + if (!name) { + name = method.name; + } + if (!name) { + throw Error("Mixin needs a name."); + } + console.log("mixing in", name, "into", obj.name); + obj[method.name] = method; +} +class EddsaPublicKey extends PackedArenaObject { + size() { + return 32; + } +} +mixinStatic(EddsaPublicKey, fromCrock); +function makeFromCrock(decodeFn) { + function fromCrock(s, a) { let obj = new this(a); let buf = ByteArray.fromCrock(s); - obj.setNative(emscAlloc.rsa_blinding_key_decode(buf.getNative(), buf.size())); + obj.setNative(decodeFn(buf.getNative(), buf.size())); buf.destroy(); return obj; } - toCrock() { + return fromCrock; +} +function makeToCrock(encodeFn) { + 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, '*')); @@ -300,12 +342,27 @@ class RsaBlindingKey extends ArenaObject { res.destroy(); return s; } + return toCrock; +} +class RsaBlindingKey extends ArenaObject { + constructor(...args) { + super(...args); + this.toCrock = makeToCrock(emscAlloc.rsa_blinding_key_encode); + } + static create(len, a) { + let o = new RsaBlindingKey(a); + o.nativePtr = emscAlloc.rsa_blinding_key_create(len); + return o; + } destroy() { // TODO } } +mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode)); class HashCode extends PackedArenaObject { - size() { return 64; } + size() { + return 64; + } random(qualStr) { let qual; switch (qualStr) { @@ -328,6 +385,7 @@ class HashCode extends PackedArenaObject { emsc.hash_create_random(qual, this.nativePtr); } } +mixinStatic(HashCode, fromCrock); class ByteArray extends PackedArenaObject { constructor(desiredSize, init, a) { super(a); @@ -339,7 +397,9 @@ class ByteArray extends PackedArenaObject { } this.allocatedSize = desiredSize; } - size() { return this.allocatedSize; } + size() { + return this.allocatedSize; + } static fromString(s, a) { let hstr = emscAlloc.malloc(s.length + 1); Module.writeStringToMemory(s, hstr); @@ -364,7 +424,9 @@ class EccSignaturePurpose extends PackedArenaObject { this.nativePtr = emscAlloc.purpose_create(purpose, payload.nativePtr, payload.size()); this.payloadSize = payload.size(); } - size() { return this.payloadSize + 8; } + size() { + return this.payloadSize + 8; + } } class SignatureStruct { constructor(x) { @@ -393,8 +455,7 @@ class SignatureStruct { ptr += size; } let ba = new ByteArray(totalSize, buf, a); - let x = new EccSignaturePurpose(this.purpose(), ba); - return x; + return new EccSignaturePurpose(this.purpose(), ba); } set(name, value) { let typemap = {}; @@ -414,32 +475,68 @@ class WithdrawRequestPS extends SignatureStruct { constructor(w) { super(w); } - purpose() { return SignaturePurpose.RESERVE_WITHDRAW; } + 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]]; + ["h_coin_envelope", HashCode] + ]; } } -function encodeWith(obj, fn, arena) { - let ptr = emscAlloc.malloc(PTR_SIZE); - let len = fn(obj.getNative(), ptr); - let res = new ByteArray(len, null, arena); - res.setNative(Module.getValue(ptr, '*')); - emsc.free(ptr); - return res; +class AbsoluteTimeNbo extends PackedArenaObject { + static fromTalerString(s) { + throw Error(); + } + size() { + return 8; + } } -class RsaPublicKey extends ArenaObject { - static fromCrock(s, a) { - let obj = new RsaPublicKey(a); - let buf = ByteArray.fromCrock(s); - obj.nativePtr = emscAlloc.rsa_public_key_decode(buf.nativePtr, buf.size()); - buf.destroy(); - return obj; +class UInt64 extends PackedArenaObject { + static fromNumber(n) { + throw Error(); + } + size() { + return 8; + } +} +class DepositRequestPS extends SignatureStruct { + constructor(w) { + 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], + ]; + } +} +function makeEncode(encodeFn) { + function encode(arena) { + let ptr = emscAlloc.malloc(PTR_SIZE); + let len = encodeFn(this.getNative(), ptr); + let res = new ByteArray(len, null, arena); + res.setNative(Module.getValue(ptr, '*')); + emsc.free(ptr); + return res; } + return encode; +} +class RsaPublicKey extends ArenaObject { toCrock() { return this.encode().toCrock(); } @@ -447,38 +544,26 @@ class RsaPublicKey extends ArenaObject { emsc.rsa_public_key_free(this.nativePtr); this.nativePtr = 0; } - encode(arena) { - let ptr = emscAlloc.malloc(PTR_SIZE); - let len = emscAlloc.rsa_public_key_encode(this.nativePtr, ptr); - let res = new ByteArray(len, Module.getValue(ptr, '*'), arena); - emsc.free(ptr); - return res; - } } +mixinStatic(RsaPublicKey, makeFromCrock(emscAlloc.rsa_public_key_decode)); +mixin(RsaPublicKey, makeEncode(emscAlloc.rsa_public_key_encode)); class EddsaSignature extends PackedArenaObject { - size() { return 64; } + size() { + return 64; + } } class RsaSignature extends ArenaObject { - static fromCrock(s, a) { - let obj = new this(a); - let buf = ByteArray.fromCrock(s); - obj.setNative(emscAlloc.rsa_signature_decode(buf.getNative(), buf.size())); - buf.destroy(); - return obj; - } - encode(arena) { - return encodeWith(this, emscAlloc.rsa_signature_encode); - } destroy() { emsc.rsa_signature_free(this.getNative()); this.setNative(0); } } +mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode)); +mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode)); function rsaBlind(hashCode, blindingKey, pkey, arena) { let ptr = emscAlloc.malloc(PTR_SIZE); let s = emscAlloc.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, pkey.nativePtr, ptr); - let res = new ByteArray(s, Module.getValue(ptr, '*'), arena); - return res; + return new ByteArray(s, Module.getValue(ptr, '*'), arena); } function eddsaSign(purpose, priv, a) { let sig = new EddsaSignature(a); diff --git a/extension/background/emscriptif.ts b/extension/background/emscriptif.ts index f21e60db4..ce6f351c0 100644 --- a/extension/background/emscriptif.ts +++ b/extension/background/emscriptif.ts @@ -1,23 +1,23 @@ /* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - 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 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. + 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/> -*/ + 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"; -declare var Module : any; +declare var Module: any; // Size of a native pointer. @@ -30,10 +30,18 @@ const GNUNET_SYSERR = -1; interface EmscFunGen { - (name: string, ret: string, args: string[]): ((...x: (number|string)[]) => any); - (name: string, ret: 'number', args: string[]): ((...x: (number|string)[]) => number); - (name: string, ret: 'void', args: string[]): ((...x: (number|string)[]) => void); - (name: string, ret: 'string', args: string[]): ((...x: (number|string)[]) => string); + (name: string, + ret: string, + args: string[]): ((...x: (number|string)[]) => any); + (name: string, + ret: 'number', + args: string[]): ((...x: (number|string)[]) => number); + (name: string, + ret: 'void', + args: string[]): ((...x: (number|string)[]) => void); + (name: string, + ret: 'string', + args: string[]): ((...x: (number|string)[]) => string); } let getEmsc: EmscFunGen = (...args) => Module.cwrap.apply(null, args); @@ -58,15 +66,18 @@ var emsc = { 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']), + 'void', + ['number', 'number']), amount_ntoh: getEmsc('TALER_amount_ntoh', - 'void', - ['number', 'number']), + 'void', + ['number', 'number']), hash: getEmsc('GNUNET_CRYPTO_hash', 'void', ['number', 'number', 'number']), @@ -77,8 +88,8 @@ var emsc = { 'void', ['number']), rsa_signature_free: getEmsc('GNUNET_CRYPTO_rsa_signature_free', - 'void', - ['number']), + 'void', + ['number']), string_to_data: getEmsc('GNUNET_STRINGS_string_to_data', 'number', ['number', 'number', 'number', 'number']), @@ -89,8 +100,8 @@ var emsc = { 'void', ['number', 'number']), rsa_blinding_key_destroy: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free', - 'void', - ['number']), + 'void', + ['number']), }; var emscAlloc = { @@ -99,9 +110,10 @@ var emscAlloc = { ['number', 'number', 'number', 'string']), eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create', 'number', []), - eddsa_public_key_from_private: getEmsc('TALER_WRALL_eddsa_public_key_from_private', - 'number', - ['number']), + eddsa_public_key_from_private: getEmsc( + 'TALER_WRALL_eddsa_public_key_from_private', + 'number', + ['number']), data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc', 'number', ['number', 'number']), @@ -112,35 +124,36 @@ var emscAlloc = { 'number', ['number', 'number', 'number', 'number']), rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create', - 'number', - ['number']), + 'number', + ['number']), rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode', - 'number', - ['number', 'number']), + 'number', + ['number', 'number']), rsa_signature_encode: getEmsc('GNUNET_CRYPTO_rsa_signature_encode', - 'number', - ['number', 'number']), + 'number', + ['number', 'number']), rsa_blinding_key_decode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_decode', - 'number', - ['number', 'number']), + 'number', + ['number', 'number']), rsa_public_key_decode: getEmsc('GNUNET_CRYPTO_rsa_public_key_decode', - 'number', - ['number', 'number']), + 'number', + ['number', 'number']), rsa_signature_decode: getEmsc('GNUNET_CRYPTO_rsa_signature_decode', - 'number', - ['number', 'number']), + 'number', + ['number', 'number']), rsa_public_key_encode: getEmsc('GNUNET_CRYPTO_rsa_public_key_encode', - 'number', - ['number', 'number']), + 'number', + ['number', 'number']), rsa_unblind: getEmsc('GNUNET_CRYPTO_rsa_unblind', - 'number', - ['number', 'number', 'number']), + 'number', + ['number', 'number', 'number']), malloc: (size: number) => Module._malloc(size), }; enum SignaturePurpose { - RESERVE_WITHDRAW = 1200 + RESERVE_WITHDRAW = 1200, + WALLET_COIN_DEPOSIT = 1201, } enum RandomQuality { @@ -153,6 +166,7 @@ enum RandomQuality { abstract class ArenaObject { private _nativePtr: number; arena: Arena; + abstract destroy(): void; constructor(arena?: Arena) { @@ -172,7 +186,7 @@ abstract class ArenaObject { // of native wrappers, but we never want to // pass 'undefined' to emscripten. if (this._nativePtr === undefined) { - throw Error("Native pointer not initialized"); + throw Error("Native pointer not initialized"); } return this._nativePtr; } @@ -198,11 +212,11 @@ abstract class ArenaObject { this._nativePtr = n; } - set nativePtr (v) { + set nativePtr(v) { this.setNative(v); } - get nativePtr () { + get nativePtr() { return this.getNative(); } @@ -215,6 +229,7 @@ interface Arena { class DefaultArena implements Arena { heap: Array<ArenaObject>; + constructor() { this.heap = []; } @@ -238,31 +253,35 @@ class DefaultArena implements Arena { */ class SyncArena extends DefaultArena { timer: Worker; + constructor() { super(); let me = this; this.timer = new Worker('background/timerThread.js'); - this.timer.onmessage = (e) => { + this.timer.onmessage = () => { this.destroy(); }; //this.timer.postMessage({interval: 50}); } + destroy() { super.destroy(); } } - let arenaStack: Arena[] = []; arenaStack.push(new SyncArena()); class Amount extends ArenaObject { - constructor(args?: any, arena?: Arena) { + constructor(args?: AmountJson, arena?: Arena) { super(arena); if (args) { - this.nativePtr = emscAlloc.get_amount(args.value, 0, args.fraction, args.currency); + this.nativePtr = emscAlloc.get_amount(args.value, + 0, + args.fraction, + args.currency); } else { this.nativePtr = emscAlloc.get_amount(0, 0, 0, ""); } @@ -274,6 +293,17 @@ class Amount extends ArenaObject { } } + + static getZero(currency: string, a?: Arena) { + let am = new Amount(null, a); + let r = emsc.amount_get_zero(currency, am.getNative()); + if (r != GNUNET_OK) { + throw Error("invalid currency"); + } + return am; + } + + toNbo(a?: Arena): AmountNbo { let x = new AmountNbo(a); x.alloc(); @@ -362,7 +392,10 @@ abstract class PackedArenaObject extends ArenaObject { // We need to get the javascript string // to the emscripten heap first. let buf = ByteArray.fromString(s); - let res = emsc.string_to_data(buf.nativePtr, s.length, this.nativePtr, this.size()); + let res = emsc.string_to_data(buf.nativePtr, + s.length, + this.nativePtr, + this.size()); buf.destroy(); if (res < 1) { throw {error: "wrong encoding"}; @@ -390,7 +423,9 @@ abstract class PackedArenaObject extends ArenaObject { class AmountNbo extends PackedArenaObject { - size() { return 24; } + size() { + return 24; + } } @@ -401,37 +436,76 @@ class EddsaPrivateKey extends PackedArenaObject { return obj; } - size() { return 32; } + 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: (string) => EddsaPrivateKey; } +mixinStatic(EddsaPrivateKey, fromCrock); -class EddsaPublicKey extends PackedArenaObject { - size() { return 32; } +function fromCrock(s: string) { + let x = new this(); + x.alloc(); + x.loadCrock(s); + return x; +} + + +function mixin(obj, method, name?: string) { + if (!name) { + name = method.name; + } + if (!name) { + throw Error("Mixin needs a name."); + } + console.log("mixing in", name, "into", obj.name); + obj.prototype[method.name] = method; } +function mixinStatic(obj, method, name?: string) { + if (!name) { + name = method.name; + } + if (!name) { + throw Error("Mixin needs a name."); + } + console.log("mixing in", name, "into", obj.name); + obj[method.name] = method; +} -class RsaBlindingKey extends ArenaObject { - static create(len: number, a?: Arena) { - let o = new RsaBlindingKey(a); - o.nativePtr = emscAlloc.rsa_blinding_key_create(len); - return o; + +class EddsaPublicKey extends PackedArenaObject { + size() { + return 32; } - static fromCrock(s: string, a?: Arena): RsaBlindingKey { + static fromCrock: (s: string) => EddsaPublicKey; +} +mixinStatic(EddsaPublicKey, 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(emscAlloc.rsa_blinding_key_decode(buf.getNative(), buf.size())); + obj.setNative(decodeFn(buf.getNative(), + buf.size())); buf.destroy(); return obj; } - toCrock(): string { + 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, '*')); @@ -440,15 +514,32 @@ class RsaBlindingKey extends ArenaObject { res.destroy(); return s; } + return toCrock; +} + +class RsaBlindingKey extends ArenaObject { + static create(len: number, a?: Arena) { + let o = new RsaBlindingKey(a); + o.nativePtr = emscAlloc.rsa_blinding_key_create(len); + return o; + } + + static fromCrock: (s: string, a?: Arena) => RsaBlindingKey; + toCrock = makeToCrock(emscAlloc.rsa_blinding_key_encode); destroy() { // TODO } } +mixinStatic(RsaBlindingKey, makeFromCrock(emscAlloc.rsa_blinding_key_decode)); class HashCode extends PackedArenaObject { - size() { return 64; } + size() { + return 64; + } + + static fromCrock: (s: string) => HashCode; random(qualStr: string) { let qual: RandomQuality; @@ -472,11 +563,15 @@ class HashCode extends PackedArenaObject { emsc.hash_create_random(qual, this.nativePtr); } } +mixinStatic(HashCode, fromCrock); class ByteArray extends PackedArenaObject { private allocatedSize: number; - size() { return this.allocatedSize; } + + size() { + return this.allocatedSize; + } constructor(desiredSize: number, init: number, a?: Arena) { super(a); @@ -510,11 +605,19 @@ class ByteArray extends PackedArenaObject { class EccSignaturePurpose extends PackedArenaObject { - size() { return this.payloadSize + 8; } + size() { + return this.payloadSize + 8; + } + payloadSize: number; - constructor(purpose: SignaturePurpose, payload: PackedArenaObject, a?: Arena) { + + constructor(purpose: SignaturePurpose, + payload: PackedArenaObject, + a?: Arena) { super(a); - this.nativePtr = emscAlloc.purpose_create(purpose, payload.nativePtr, payload.size()); + this.nativePtr = emscAlloc.purpose_create(purpose, + payload.nativePtr, + payload.size()); this.payloadSize = payload.size(); } } @@ -522,7 +625,9 @@ class EccSignaturePurpose extends PackedArenaObject { abstract class SignatureStruct { abstract fieldTypes(): Array<any>; + abstract purpose(): SignaturePurpose; + private members: any = {}; constructor(x: { [name: string]: any }) { @@ -552,12 +657,11 @@ abstract class SignatureStruct { ptr += size; } let ba = new ByteArray(totalSize, buf, a); - let x = new EccSignaturePurpose(this.purpose(), ba); - return x; + return new EccSignaturePurpose(this.purpose(), ba); } protected set(name: string, value: PackedArenaObject) { - let typemap: any = {} + let typemap: any = {}; for (let f of this.fieldTypes()) { typemap[f[0]] = f[1]; } @@ -586,98 +690,156 @@ class WithdrawRequestPS extends SignatureStruct { constructor(w: WithdrawRequestPS_Args) { super(w); } - purpose() { return SignaturePurpose.RESERVE_WITHDRAW; } + + 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]]; + ["reserve_pub", EddsaPublicKey], + ["amount_with_fee", AmountNbo], + ["withdraw_fee", AmountNbo], + ["h_denomination_pub", HashCode], + ["h_coin_envelope", HashCode] + ]; } } +class AbsoluteTimeNbo extends PackedArenaObject { + static fromTalerString(s: string): AbsoluteTimeNbo { + throw Error(); + } -function encodeWith(obj, fn, arena?: Arena) { - let ptr = emscAlloc.malloc(PTR_SIZE); - let len = fn(obj.getNative(), ptr); - let res = new ByteArray(len, null, arena); - res.setNative(Module.getValue(ptr, '*')); - emsc.free(ptr); - return res; + size() { + return 8; + } } -class RsaPublicKey extends ArenaObject { - static fromCrock(s: string, a?: Arena): RsaPublicKey { - let obj = new RsaPublicKey(a); - let buf = ByteArray.fromCrock(s); - obj.nativePtr = emscAlloc.rsa_public_key_decode(buf.nativePtr, buf.size()); - buf.destroy(); - return obj; +class UInt64 extends PackedArenaObject { + static fromNumber(n: number): UInt64 { + throw Error(); } - toCrock() { - return this.encode().toCrock(); + size() { + return 8; } +} - destroy() { - emsc.rsa_public_key_free(this.nativePtr); - this.nativePtr = 0; + +// It's redundant, but more type safe. +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; +} + + +class DepositRequestPS extends SignatureStruct { + constructor(w: DepositRequestPS_Args) { + super(w); + } + + purpose() { + return SignaturePurpose.WALLET_COIN_DEPOSIT; } - encode(arena?: Arena): ByteArray { + 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], + ]; + } +} + + +interface Encodeable { + encode(arena?: Arena): ByteArray; +} + +function makeEncode(encodeFn) { + function encode(arena?: Arena) { let ptr = emscAlloc.malloc(PTR_SIZE); - let len = emscAlloc.rsa_public_key_encode(this.nativePtr, ptr); - let res = new ByteArray(len, Module.getValue(ptr, '*'), arena); + let len = encodeFn(this.getNative(), ptr); + let res = new ByteArray(len, null, arena); + res.setNative(Module.getValue(ptr, '*')); emsc.free(ptr); return res; } + return encode; +} + + +class RsaPublicKey extends ArenaObject 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)); class EddsaSignature extends PackedArenaObject { - size() { return 64; } + size() { + return 64; + } } -class RsaSignature extends ArenaObject { - static fromCrock(s: string, a?: Arena): RsaSignature { - let obj = new this(a); - let buf = ByteArray.fromCrock(s); - obj.setNative(emscAlloc.rsa_signature_decode(buf.getNative(), buf.size())); - buf.destroy(); - return obj; - } +class RsaSignature extends ArenaObject implements Encodeable{ + static fromCrock: (s: string, a?: Arena) => RsaSignature; - encode(arena?: Arena): ByteArray { - return encodeWith(this, emscAlloc.rsa_signature_encode); - } + encode: (arena?: Arena) => ByteArray; destroy() { emsc.rsa_signature_free(this.getNative()); this.setNative(0); } } +mixinStatic(RsaSignature, makeFromCrock(emscAlloc.rsa_signature_decode)); +mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode)); + function rsaBlind(hashCode: HashCode, - blindingKey: RsaBlindingKey, + blindingKey: RsaBlindingKey, pkey: RsaPublicKey, - arena?: Arena): ByteArray -{ + arena?: Arena): ByteArray { let ptr = emscAlloc.malloc(PTR_SIZE); - let s = emscAlloc.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, pkey.nativePtr, ptr); - let res = new ByteArray(s, Module.getValue(ptr, '*'), arena); - return res; + let s = emscAlloc.rsa_blind(hashCode.nativePtr, + blindingKey.nativePtr, + pkey.nativePtr, + ptr); + return new ByteArray(s, Module.getValue(ptr, '*'), arena); } function eddsaSign(purpose: EccSignaturePurpose, priv: EddsaPrivateKey, - a?: Arena): EddsaSignature -{ + a?: Arena): EddsaSignature { let sig = new EddsaSignature(a); sig.alloc(); let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr); @@ -687,13 +849,14 @@ function eddsaSign(purpose: EccSignaturePurpose, return sig; } + function rsaUnblind(sig: RsaSignature, bk: RsaBlindingKey, pk: RsaPublicKey, a?: Arena): RsaSignature { let x = new RsaSignature(a); - x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr, bk.nativePtr, pk.nativePtr); + x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr, + bk.nativePtr, + pk.nativePtr); return x; } - - diff --git a/extension/background/libwrapper.js b/extension/background/libwrapper.js index 46d198372..791359a25 100644 --- a/extension/background/libwrapper.js +++ b/extension/background/libwrapper.js @@ -8890,85 +8890,73 @@ function _TALER_WR_multiply_amount($a,$factor) { function _TALER_WR_multiply_amounts($a,$b) { $a = $a|0; $b = $b|0; - var $0 = 0.0, $1 = 0, $10 = 0, $11 = 0, $12 = 0, $13 = 0.0, $14 = 0.0, $15 = 0, $16 = 0, $17 = 0, $18 = 0, $19 = 0, $2 = 0, $20 = 0, $21 = 0, $22 = 0.0, $23 = 0.0, $24 = 0.0, $25 = 0, $26 = 0; - var $27 = 0, $28 = 0, $29 = 0, $3 = 0, $30 = 0, $31 = 0, $32 = 0.0, $33 = 0.0, $34 = 0.0, $35 = 0.0, $36 = 0.0, $37 = 0.0, $38 = 0.0, $39 = 0, $4 = 0, $40 = 0.0, $41 = 0, $42 = 0.0, $43 = 0.0, $44 = 0.0; - var $45 = 0, $46 = 0.0, $47 = 0.0, $48 = 0.0, $49 = 0, $5 = 0, $50 = 0.0, $51 = 0.0, $6 = 0, $7 = 0, $8 = 0.0, $9 = 0.0, $ad = 0.0, $bd = 0.0, $or$cond = 0, $ret = 0.0, label = 0, sp = 0; + var $0 = 0.0, $1 = 0, $10 = 0, $11 = 0, $12 = 0.0, $13 = 0.0, $14 = 0, $15 = 0, $16 = 0, $17 = 0.0, $18 = 0.0, $19 = 0, $2 = 0, $20 = 0, $21 = 0, $22 = 0, $23 = 0, $24 = 0, $25 = 0, $26 = 0.0; + var $27 = 0.0, $28 = 0.0, $29 = 0, $3 = 0, $30 = 0, $31 = 0, $32 = 0, $33 = 0, $34 = 0, $35 = 0, $36 = 0.0, $37 = 0.0, $38 = 0.0, $39 = 0.0, $4 = 0, $40 = 0.0, $41 = 0.0, $42 = 0.0, $43 = 0.0, $5 = 0; + var $6 = 0, $7 = 0, $8 = 0, $9 = 0, $ad = 0.0, $bd = 0.0, $ret = 0.0, label = 0, sp = 0; sp = STACKTOP; STACKTOP = STACKTOP + 48|0; if ((STACKTOP|0) >= (STACK_MAX|0)) abort(); $1 = $a; $2 = $b; $3 = $1; - (_TALER_amount_normalize($3)|0); - $4 = $2; - (_TALER_amount_normalize($4)|0); - $5 = $1; - $6 = ((($5)) + 8|0); - $7 = HEAP32[$6>>2]|0; - $8 = (+($7>>>0)); - $9 = $8 / 1.0E+6; - $ad = $9; - $10 = $2; - $11 = ((($10)) + 8|0); - $12 = HEAP32[$11>>2]|0; - $13 = (+($12>>>0)); - $14 = $13 / 1.0E+6; - $bd = $14; - $15 = $1; - $16 = $15; - $17 = $16; - $18 = HEAP32[$17>>2]|0; - $19 = (($16) + 4)|0; - $20 = $19; - $21 = HEAP32[$20>>2]|0; - $22 = (+($18>>>0)) + (4294967296.0*(+($21>>>0))); - $23 = $ad; - $24 = $23 + $22; - $ad = $24; - $25 = $2; - $26 = $25; - $27 = $26; - $28 = HEAP32[$27>>2]|0; - $29 = (($26) + 4)|0; - $30 = $29; - $31 = HEAP32[$30>>2]|0; - $32 = (+($28>>>0)) + (4294967296.0*(+($31>>>0))); - $33 = $bd; - $34 = $33 + $32; - $bd = $34; - $35 = $ad; - $36 = $bd; - $37 = $35 * $36; - $ret = $37; - $38 = $ad; - $39 = $38 < 1.0; - $40 = $bd; - $41 = $40 < 1.0; - $or$cond = $39 | $41; - $42 = $ret; - $43 = $ad; - $44 = $bd; - $45 = $43 > $44; - $46 = $ad; - $47 = $bd; - $48 = $45 ? $46 : $47; - $49 = $42 < $48; - if ($or$cond) { - if (!($49)) { - $0 = -1.0; - $51 = $0; - STACKTOP = sp;return (+$51); - } + $4 = (_TALER_amount_normalize($3)|0); + $5 = (-1)==($4|0); + if ($5) { + $0 = -1.0; + $43 = $0; + STACKTOP = sp;return (+$43); + } + $6 = $2; + $7 = (_TALER_amount_normalize($6)|0); + $8 = (-1)==($7|0); + if ($8) { + $0 = -1.0; + $43 = $0; + STACKTOP = sp;return (+$43); } else { - if ($49) { - $0 = -1.0; - $51 = $0; - STACKTOP = sp;return (+$51); - } + $9 = $1; + $10 = ((($9)) + 8|0); + $11 = HEAP32[$10>>2]|0; + $12 = (+($11>>>0)); + $13 = $12 / 1.0E+6; + $ad = $13; + $14 = $2; + $15 = ((($14)) + 8|0); + $16 = HEAP32[$15>>2]|0; + $17 = (+($16>>>0)); + $18 = $17 / 1.0E+6; + $bd = $18; + $19 = $1; + $20 = $19; + $21 = $20; + $22 = HEAP32[$21>>2]|0; + $23 = (($20) + 4)|0; + $24 = $23; + $25 = HEAP32[$24>>2]|0; + $26 = (+($22>>>0)) + (4294967296.0*(+($25>>>0))); + $27 = $ad; + $28 = $27 + $26; + $ad = $28; + $29 = $2; + $30 = $29; + $31 = $30; + $32 = HEAP32[$31>>2]|0; + $33 = (($30) + 4)|0; + $34 = $33; + $35 = HEAP32[$34>>2]|0; + $36 = (+($32>>>0)) + (4294967296.0*(+($35>>>0))); + $37 = $bd; + $38 = $37 + $36; + $bd = $38; + $39 = $ad; + $40 = $bd; + $41 = $39 * $40; + $ret = $41; + $42 = $ret; + $0 = $42; + $43 = $0; + STACKTOP = sp;return (+$43); } - $50 = $ret; - $0 = $50; - $51 = $0; - STACKTOP = sp;return (+$51); + return +(0.0); } function _GNUNET_xmalloc_($size,$filename,$linenumber) { $size = $size|0; @@ -145293,7 +145281,7 @@ var FUNCTION_TABLE_viiii = [b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10, ,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,b10,_reporter,b10,b10 ,b10,b10,b10,b10,b10]; - return { _WRALL_make_purpose: _WRALL_make_purpose, _GNUNET_CRYPTO_rsa_blinding_key_encode: _GNUNET_CRYPTO_rsa_blinding_key_encode, _bitshift64Lshr: _bitshift64Lshr, _bitshift64Ashr: _bitshift64Ashr, _GNUNET_CRYPTO_eddsa_key_create: _GNUNET_CRYPTO_eddsa_key_create, _memcpy: _memcpy, _TALER_WR_GNUNET_free: _TALER_WR_GNUNET_free, _TALER_WRALL_amount_add: _TALER_WRALL_amount_add, _GNUNET_CRYPTO_rsa_private_key_decode: _GNUNET_CRYPTO_rsa_private_key_decode, _TALER_WR_multiply_amount: _TALER_WR_multiply_amount, _GNUNET_CRYPTO_rsa_blinding_key_decode: _GNUNET_CRYPTO_rsa_blinding_key_decode, _GNUNET_CRYPTO_ecc_ecdh: _GNUNET_CRYPTO_ecc_ecdh, _TALER_amount_hton: _TALER_amount_hton, _TALER_WR_verify_sign_key_enc: _TALER_WR_verify_sign_key_enc, _free: _free, _TALER_WRALL_sign_deposit_permission: _TALER_WRALL_sign_deposit_permission, _TALER_WR_verify_denoms: _TALER_WR_verify_denoms, _GNUNET_CRYPTO_rsa_blinding_key_create: _GNUNET_CRYPTO_rsa_blinding_key_create, _TALER_WR_verify_sign_key: _TALER_WR_verify_sign_key, _TALER_WRALL_rsa_public_key_hash: _TALER_WRALL_rsa_public_key_hash, _TALER_WRALL_gen_key_from_blob: _TALER_WRALL_gen_key_from_blob, _GNUNET_CRYPTO_rsa_private_key_get_public: _GNUNET_CRYPTO_rsa_private_key_get_public, _TALER_WRALL_rsa_public_key_decode_from_string: _TALER_WRALL_rsa_public_key_decode_from_string, _GNUNET_CRYPTO_symmetric_encrypt: _GNUNET_CRYPTO_symmetric_encrypt, _TALER_WR_get_fraction: _TALER_WR_get_fraction, _TALER_WR_verify_denoms_enc: _TALER_WR_verify_denoms_enc, _TALER_amount_cmp: _TALER_amount_cmp, _TALER_WRALL_ecdhe_public_key_from_private_key: _TALER_WRALL_ecdhe_public_key_from_private_key, _llvm_cttz_i32: _llvm_cttz_i32, _TALER_WRALL_eddsa_private_key_from_string: _TALER_WRALL_eddsa_private_key_from_string, _TALER_WRALL_gen_init_vector: _TALER_WRALL_gen_init_vector, _TALER_amount_ntoh: _TALER_amount_ntoh, _WR_verify_test: _WR_verify_test, _GNUNET_CRYPTO_rsa_public_key_free: _GNUNET_CRYPTO_rsa_public_key_free, _GNUNET_CRYPTO_hkdf: _GNUNET_CRYPTO_hkdf, _GNUNET_CRYPTO_eddsa_key_get_public: _GNUNET_CRYPTO_eddsa_key_get_public, _llvm_bswap_i32: _llvm_bswap_i32, _GNUNET_CRYPTO_rsa_private_key_create: _GNUNET_CRYPTO_rsa_private_key_create, _GNUNET_STRINGS_data_to_string_alloc: _GNUNET_STRINGS_data_to_string_alloc, _TALER_WRALL_sign_test: _TALER_WRALL_sign_test, _TALER_WR_verify_confirmation: _TALER_WR_verify_confirmation, _GNUNET_CRYPTO_rsa_public_key_decode: _GNUNET_CRYPTO_rsa_public_key_decode, _GNUNET_CRYPTO_rsa_blinding_key_free: _GNUNET_CRYPTO_rsa_blinding_key_free, _GNUNET_CRYPTO_rsa_signature_encode: _GNUNET_CRYPTO_rsa_signature_encode, _GNUNET_CRYPTO_rsa_verify: _GNUNET_CRYPTO_rsa_verify, _TALER_WR_eddsa_verify: _TALER_WR_eddsa_verify, _GNUNET_STRINGS_string_to_data: _GNUNET_STRINGS_string_to_data, _TALER_WRALL_gen_symmetric_key: _TALER_WRALL_gen_symmetric_key, _TALER_WRALL_ecc_ecdh: _TALER_WRALL_ecc_ecdh, _memset: _memset, _GNUNET_CRYPTO_rsa_unblind: _GNUNET_CRYPTO_rsa_unblind, _TALER_WR_get_currency: _TALER_WR_get_currency, _DEBUG_WR_get_purpose: _DEBUG_WR_get_purpose, _TALER_WRALL_get_amount: _TALER_WRALL_get_amount, _GNUNET_CRYPTO_rsa_private_key_encode: _GNUNET_CRYPTO_rsa_private_key_encode, _GNUNET_CRYPTO_symmetric_decrypt: _GNUNET_CRYPTO_symmetric_decrypt, _i64Subtract: _i64Subtract, _TALER_WR_get_value: _TALER_WR_get_value, _GNUNET_CRYPTO_rsa_signature_free: _GNUNET_CRYPTO_rsa_signature_free, _TALER_WRALL_eddsa_public_key_from_priv_string: _TALER_WRALL_eddsa_public_key_from_priv_string, _GNUNET_CRYPTO_rsa_blind: _GNUNET_CRYPTO_rsa_blind, _TALER_WR_verify_denom_enc: _TALER_WR_verify_denom_enc, _malloc: _malloc, _TALER_WR_hello_world: _TALER_WR_hello_world, _TALER_WRALL_make_withdraw_bundle: _TALER_WRALL_make_withdraw_bundle, _GNUNET_CRYPTO_rsa_signature_decode: _GNUNET_CRYPTO_rsa_signature_decode, _GNUNET_CRYPTO_rsa_sign: _GNUNET_CRYPTO_rsa_sign, _GNUNET_CRYPTO_ecdhe_key_create: _GNUNET_CRYPTO_ecdhe_key_create, _TALER_WRALL_get_encoding_from_rsa_signature: _TALER_WRALL_get_encoding_from_rsa_signature, _TALER_WRALL_hash: _TALER_WRALL_hash, _TALER_amount_normalize: _TALER_amount_normalize, _TALER_WRALL_sign_contract: _TALER_WRALL_sign_contract, _bitshift64Shl: _bitshift64Shl, _GNUNET_CRYPTO_rsa_private_key_free: _GNUNET_CRYPTO_rsa_private_key_free, _fflush: _fflush, _GNUNET_CRYPTO_hash_create_random: _GNUNET_CRYPTO_hash_create_random, _TALER_WRALL_eddsa_public_key_from_private: _TALER_WRALL_eddsa_public_key_from_private, _TALER_WR_multiply_amounts: _TALER_WR_multiply_amounts, _TALER_amount_add: _TALER_amount_add, _GNUNET_CRYPTO_ecdhe_key_get_public: _GNUNET_CRYPTO_ecdhe_key_get_public, _TALER_amount_subtract: _TALER_amount_subtract, _i64Add: _i64Add, _TALER_WRALL_purpose_create: _TALER_WRALL_purpose_create, _GNUNET_CRYPTO_eddsa_sign: _GNUNET_CRYPTO_eddsa_sign, _TALER_WR_verify_denom: _TALER_WR_verify_denom, _TALER_WRALL_get_current_time: _TALER_WRALL_get_current_time, ___errno_location: ___errno_location, _TALER_WR_get_fancy_time: _TALER_WR_get_fancy_time, _TALER_WRALL_make_eddsa_signature: _TALER_WRALL_make_eddsa_signature, _memmove: _memmove, _DEBUG_WR_dump_amount: _DEBUG_WR_dump_amount, _GNUNET_CRYPTO_rsa_public_key_encode: _GNUNET_CRYPTO_rsa_public_key_encode, _GNUNET_CRYPTO_hash: _GNUNET_CRYPTO_hash, _GNUNET_util_cl_init: _GNUNET_util_cl_init, _GNUNET_CRYPTO_random_init: _GNUNET_CRYPTO_random_init, _gpg_err_init: _gpg_err_init, runPostSets: runPostSets, stackAlloc: stackAlloc, stackSave: stackSave, stackRestore: stackRestore, establishStackSpace: establishStackSpace, setThrew: setThrew, setTempRet0: setTempRet0, getTempRet0: getTempRet0, dynCall_iiii: dynCall_iiii, dynCall_viiiii: dynCall_viiiii, dynCall_vi: dynCall_vi, dynCall_vii: dynCall_vii, dynCall_ii: dynCall_ii, dynCall_viii: dynCall_viii, dynCall_v: dynCall_v, dynCall_iiiii: dynCall_iiiii, dynCall_viiiiii: dynCall_viiiiii, dynCall_iii: dynCall_iii, dynCall_viiii: dynCall_viiii }; + return { _WRALL_make_purpose: _WRALL_make_purpose, _GNUNET_CRYPTO_rsa_blinding_key_encode: _GNUNET_CRYPTO_rsa_blinding_key_encode, _bitshift64Lshr: _bitshift64Lshr, _bitshift64Ashr: _bitshift64Ashr, _GNUNET_CRYPTO_eddsa_key_create: _GNUNET_CRYPTO_eddsa_key_create, _memcpy: _memcpy, _TALER_WR_GNUNET_free: _TALER_WR_GNUNET_free, _TALER_WRALL_amount_add: _TALER_WRALL_amount_add, _GNUNET_CRYPTO_rsa_private_key_decode: _GNUNET_CRYPTO_rsa_private_key_decode, _TALER_WR_multiply_amount: _TALER_WR_multiply_amount, _GNUNET_CRYPTO_rsa_blinding_key_decode: _GNUNET_CRYPTO_rsa_blinding_key_decode, _GNUNET_CRYPTO_ecc_ecdh: _GNUNET_CRYPTO_ecc_ecdh, _TALER_amount_hton: _TALER_amount_hton, _TALER_WR_verify_sign_key_enc: _TALER_WR_verify_sign_key_enc, _free: _free, _TALER_WRALL_sign_deposit_permission: _TALER_WRALL_sign_deposit_permission, _TALER_WR_verify_denoms: _TALER_WR_verify_denoms, _GNUNET_CRYPTO_rsa_blinding_key_create: _GNUNET_CRYPTO_rsa_blinding_key_create, _TALER_WR_verify_sign_key: _TALER_WR_verify_sign_key, _TALER_WRALL_rsa_public_key_hash: _TALER_WRALL_rsa_public_key_hash, _TALER_WRALL_gen_key_from_blob: _TALER_WRALL_gen_key_from_blob, _GNUNET_CRYPTO_rsa_private_key_get_public: _GNUNET_CRYPTO_rsa_private_key_get_public, _TALER_WRALL_rsa_public_key_decode_from_string: _TALER_WRALL_rsa_public_key_decode_from_string, _GNUNET_CRYPTO_symmetric_encrypt: _GNUNET_CRYPTO_symmetric_encrypt, _TALER_WR_get_fraction: _TALER_WR_get_fraction, _TALER_WR_verify_denoms_enc: _TALER_WR_verify_denoms_enc, _TALER_amount_cmp: _TALER_amount_cmp, _TALER_WRALL_ecdhe_public_key_from_private_key: _TALER_WRALL_ecdhe_public_key_from_private_key, _llvm_cttz_i32: _llvm_cttz_i32, _TALER_WRALL_eddsa_private_key_from_string: _TALER_WRALL_eddsa_private_key_from_string, _TALER_WRALL_gen_init_vector: _TALER_WRALL_gen_init_vector, _TALER_amount_ntoh: _TALER_amount_ntoh, _WR_verify_test: _WR_verify_test, _GNUNET_CRYPTO_rsa_public_key_free: _GNUNET_CRYPTO_rsa_public_key_free, _GNUNET_CRYPTO_hkdf: _GNUNET_CRYPTO_hkdf, _GNUNET_CRYPTO_eddsa_key_get_public: _GNUNET_CRYPTO_eddsa_key_get_public, _llvm_bswap_i32: _llvm_bswap_i32, _GNUNET_CRYPTO_rsa_private_key_create: _GNUNET_CRYPTO_rsa_private_key_create, _GNUNET_STRINGS_data_to_string_alloc: _GNUNET_STRINGS_data_to_string_alloc, _TALER_WRALL_sign_test: _TALER_WRALL_sign_test, _TALER_WR_verify_confirmation: _TALER_WR_verify_confirmation, _GNUNET_CRYPTO_rsa_public_key_decode: _GNUNET_CRYPTO_rsa_public_key_decode, _GNUNET_CRYPTO_rsa_blinding_key_free: _GNUNET_CRYPTO_rsa_blinding_key_free, _GNUNET_CRYPTO_rsa_signature_encode: _GNUNET_CRYPTO_rsa_signature_encode, _GNUNET_CRYPTO_rsa_verify: _GNUNET_CRYPTO_rsa_verify, _TALER_WR_eddsa_verify: _TALER_WR_eddsa_verify, _GNUNET_STRINGS_string_to_data: _GNUNET_STRINGS_string_to_data, _TALER_WRALL_gen_symmetric_key: _TALER_WRALL_gen_symmetric_key, _TALER_WRALL_ecc_ecdh: _TALER_WRALL_ecc_ecdh, _memset: _memset, _GNUNET_CRYPTO_rsa_unblind: _GNUNET_CRYPTO_rsa_unblind, _TALER_WR_get_currency: _TALER_WR_get_currency, _DEBUG_WR_get_purpose: _DEBUG_WR_get_purpose, _TALER_WRALL_get_amount: _TALER_WRALL_get_amount, _GNUNET_CRYPTO_rsa_private_key_encode: _GNUNET_CRYPTO_rsa_private_key_encode, _GNUNET_CRYPTO_symmetric_decrypt: _GNUNET_CRYPTO_symmetric_decrypt, _i64Subtract: _i64Subtract, _TALER_WR_get_value: _TALER_WR_get_value, _GNUNET_CRYPTO_rsa_signature_free: _GNUNET_CRYPTO_rsa_signature_free, _TALER_WRALL_eddsa_public_key_from_priv_string: _TALER_WRALL_eddsa_public_key_from_priv_string, _GNUNET_CRYPTO_rsa_blind: _GNUNET_CRYPTO_rsa_blind, _TALER_WR_verify_denom_enc: _TALER_WR_verify_denom_enc, _malloc: _malloc, _TALER_WR_hello_world: _TALER_WR_hello_world, _TALER_WRALL_make_withdraw_bundle: _TALER_WRALL_make_withdraw_bundle, _GNUNET_CRYPTO_rsa_signature_decode: _GNUNET_CRYPTO_rsa_signature_decode, _GNUNET_CRYPTO_rsa_sign: _GNUNET_CRYPTO_rsa_sign, _GNUNET_CRYPTO_ecdhe_key_create: _GNUNET_CRYPTO_ecdhe_key_create, _TALER_WRALL_get_encoding_from_rsa_signature: _TALER_WRALL_get_encoding_from_rsa_signature, _TALER_WRALL_hash: _TALER_WRALL_hash, _TALER_amount_normalize: _TALER_amount_normalize, _TALER_WRALL_sign_contract: _TALER_WRALL_sign_contract, _bitshift64Shl: _bitshift64Shl, _GNUNET_CRYPTO_rsa_private_key_free: _GNUNET_CRYPTO_rsa_private_key_free, _fflush: _fflush, _GNUNET_CRYPTO_hash_create_random: _GNUNET_CRYPTO_hash_create_random, _TALER_WRALL_eddsa_public_key_from_private: _TALER_WRALL_eddsa_public_key_from_private, _TALER_WR_multiply_amounts: _TALER_WR_multiply_amounts, _TALER_amount_add: _TALER_amount_add, _GNUNET_CRYPTO_ecdhe_key_get_public: _GNUNET_CRYPTO_ecdhe_key_get_public, _TALER_amount_subtract: _TALER_amount_subtract, _i64Add: _i64Add, _TALER_WRALL_purpose_create: _TALER_WRALL_purpose_create, _GNUNET_CRYPTO_eddsa_sign: _GNUNET_CRYPTO_eddsa_sign, _TALER_WR_verify_denom: _TALER_WR_verify_denom, _TALER_WRALL_get_current_time: _TALER_WRALL_get_current_time, ___errno_location: ___errno_location, _TALER_WR_get_fancy_time: _TALER_WR_get_fancy_time, _TALER_WRALL_make_eddsa_signature: _TALER_WRALL_make_eddsa_signature, _memmove: _memmove, _TALER_amount_get_zero: _TALER_amount_get_zero, _DEBUG_WR_dump_amount: _DEBUG_WR_dump_amount, _GNUNET_CRYPTO_rsa_public_key_encode: _GNUNET_CRYPTO_rsa_public_key_encode, _GNUNET_CRYPTO_hash: _GNUNET_CRYPTO_hash, _GNUNET_util_cl_init: _GNUNET_util_cl_init, _GNUNET_CRYPTO_random_init: _GNUNET_CRYPTO_random_init, _gpg_err_init: _gpg_err_init, runPostSets: runPostSets, stackAlloc: stackAlloc, stackSave: stackSave, stackRestore: stackRestore, establishStackSpace: establishStackSpace, setThrew: setThrew, setTempRet0: setTempRet0, getTempRet0: getTempRet0, dynCall_iiii: dynCall_iiii, dynCall_viiiii: dynCall_viiiii, dynCall_vi: dynCall_vi, dynCall_vii: dynCall_vii, dynCall_ii: dynCall_ii, dynCall_viii: dynCall_viii, dynCall_v: dynCall_v, dynCall_iiiii: dynCall_iiiii, dynCall_viiiiii: dynCall_viiiiii, dynCall_iii: dynCall_iii, dynCall_viiii: dynCall_viiii }; }) // EMSCRIPTEN_END_ASM (Module.asmGlobalArg, Module.asmLibraryArg, buffer); @@ -145843,6 +145831,12 @@ assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it a return real__GNUNET_CRYPTO_random_init.apply(null, arguments); }; +var real__TALER_amount_get_zero = asm["_TALER_amount_get_zero"]; asm["_TALER_amount_get_zero"] = function() { +assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)'); +assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)'); +return real__TALER_amount_get_zero.apply(null, arguments); +}; + var real__DEBUG_WR_dump_amount = asm["_DEBUG_WR_dump_amount"]; asm["_DEBUG_WR_dump_amount"] = function() { assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)'); assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)'); @@ -145954,6 +145948,7 @@ var _TALER_WR_get_fancy_time = Module["_TALER_WR_get_fancy_time"] = asm["_TALER_ var _TALER_WRALL_make_eddsa_signature = Module["_TALER_WRALL_make_eddsa_signature"] = asm["_TALER_WRALL_make_eddsa_signature"]; var _memmove = Module["_memmove"] = asm["_memmove"]; var _GNUNET_CRYPTO_random_init = Module["_GNUNET_CRYPTO_random_init"] = asm["_GNUNET_CRYPTO_random_init"]; +var _TALER_amount_get_zero = Module["_TALER_amount_get_zero"] = asm["_TALER_amount_get_zero"]; var _DEBUG_WR_dump_amount = Module["_DEBUG_WR_dump_amount"] = asm["_DEBUG_WR_dump_amount"]; var _GNUNET_CRYPTO_rsa_public_key_encode = Module["_GNUNET_CRYPTO_rsa_public_key_encode"] = asm["_GNUNET_CRYPTO_rsa_public_key_encode"]; var _TALER_WRALL_get_current_time = Module["_TALER_WRALL_get_current_time"] = asm["_TALER_WRALL_get_current_time"]; diff --git a/extension/background/wallet.js b/extension/background/wallet.js index 3bcbde91b..f915187ab 100644 --- a/extension/background/wallet.js +++ b/extension/background/wallet.js @@ -1,38 +1,6 @@ /// <reference path="../decl/urijs/URIjs.d.ts" /> /// <reference path="../decl/chrome/chrome.d.ts" /> 'use strict'; -const DB_NAME = "taler"; -const DB_VERSION = 1; -/** - * Return a promise that resolves - * to the taler wallet db. - */ -function openTalerDb() { - return new Promise((resolve, reject) => { - let req = indexedDB.open(DB_NAME, DB_VERSION); - req.onerror = (e) => { - reject(e); - }; - req.onsuccess = (e) => { - resolve(req.result); - }; - req.onupgradeneeded = (e) => { - let db = req.result; - console.log("DB: upgrade needed: oldVersion = " + e.oldVersion); - switch (e.oldVersion) { - case 0: - db.createObjectStore("mints", { keyPath: "baseUrl" }); - db.createObjectStore("reserves", { keyPath: "reserve_pub" }); - db.createObjectStore("denoms", { keyPath: "denomPub" }); - let coins = db.createObjectStore("coins", { keyPath: "coinPub" }); - coins.createIndex("mintBaseUrl", "mintBaseUrl"); - db.createObjectStore("transactions", { keyPath: "contractHash" }); - db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true }); - break; - } - }; - }); -} /** * See http://api.taler.net/wallet.html#general */ @@ -46,8 +14,141 @@ function canonicalizeBaseUrl(url) { x.query(); return x.href(); } -function grantCoins(db, feeThreshold, paymentAmount, mintBaseUrl) { - throw "not implemented"; +function signDeposit(db, offer, cds) { + let ret = []; + let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency); + let amountRemaining = new Amount(offer.contract.amount); + cds = copy(cds); + for (let cd of cds) { + let coinSpend; + if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) { + coinSpend = new Amount(amountRemaining.toJson()); + } + else { + coinSpend = new Amount(cd.coin.currentAmount); + } + let d = new DepositRequestPS({ + h_contract: HashCode.fromCrock(offer.H_contract), + h_wire: HashCode.fromCrock(offer.contract.H_wire), + amount_with_fee: new Amount(cd.coin.currentAmount).toNbo(), + coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub), + deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(), + merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub), + refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline), + timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp), + transaction_id: UInt64.fromNumber(offer.contract.transaction_id), + }); + amountSpent.add(coinSpend); + let newAmount = new Amount(cd.coin.currentAmount); + newAmount.sub(coinSpend); + cd.coin.currentAmount = newAmount.toJson(); + let coinSig = eddsaSign(d.toPurpose(), EddsaPrivateKey.fromCrock(cd.coin.coinPriv)) + .toCrock(); + let s = { + coin_sig: coinSig, + coin_pub: cd.coin.coinPub, + ub_sig: cd.coin.denomSig, + denom_pub: cd.coin.denomPub, + f: amountSpent.toJson(), + }; + ret.push({ sig: coinSig, updatedCoin: cd.coin }); + } + return ret; +} +/** + * Get mints and associated coins that are still spendable, + * but only if the sum the coins' remaining value exceeds the payment amount. + * @param db + * @param paymentAmount + * @param depositFeeLimit + * @param mintKeys + */ +function getPossibleMintCoins(db, paymentAmount, depositFeeLimit, allowedMints) { + return new Promise((resolve, reject) => { + let m = {}; + let found = false; + let tx = db.transaction(["mints", "coins"]); + // First pass: Get all coins from acceptable mints. + for (let info of allowedMints) { + let req_mints = tx.objectStore("mints") + .index("pubKey") + .get(info.master_pub); + req_mints.onsuccess = (e) => { + let mint = req_mints.result; + let req_coins = tx.objectStore("coins") + .index("mintBaseUrl") + .openCursor(IDBKeyRange.only(mint.baseUrl)); + req_coins.onsuccess = (e) => { + let cursor = req_coins.result; + if (!cursor) { + return; + } + let cd = { + coin: cursor.value, + denom: mint.keys.denoms[cursor.value.denomPub] + }; + let x = m[mint.baseUrl]; + if (!x) { + m[mint.baseUrl] = [cd]; + } + else { + x.push(cd); + } + }; + }; + } + tx.oncomplete = (e) => { + let ret = {}; + nextMint: for (let key in m) { + let coins = m[key].map((x) => ({ + a: new Amount(x.denom.fee_deposit), + c: x + })); + // Sort by ascending deposit fee + coins.sort((o1, o2) => o1.a.cmp(o2.a)); + let maxFee = new Amount(depositFeeLimit); + let minAmount = new Amount(paymentAmount); + let accFee = new Amount(coins[0].c.denom.fee_deposit); + let accAmount = new Amount(coins[0].c.coin.currentAmount); + for (let i = 0; i < coins.length; i++) { + if (accFee.cmp(maxFee) >= 0) { + continue nextMint; + } + if (accAmount.cmp(minAmount) >= 0) { + ret[key] = m[key]; + continue nextMint; + } + accFee.add(coins[i].a); + accFee.add(new Amount(coins[i].c.coin.currentAmount)); + } + } + resolve(ret); + }; + tx.onerror = (e) => { + reject(); + }; + }); +} +function executePay(db, offer, payCoinInfo, merchantBaseUrl, chosenMint) { + return new Promise((resolve, reject) => { + let reqData = {}; + reqData["H_wire"] = offer.contract.H_wire; + reqData["H_contract"] = offer.H_contract; + reqData["transaction_id"] = offer.contract.transaction_id; + reqData["refund_deadline"] = offer.contract.refund_deadline; + reqData["mint"] = chosenMint; + reqData["coins"] = payCoinInfo.map((x) => x.sig); + let payUrl = URI(merchantBaseUrl).absoluteTo(merchantBaseUrl); + console.log("Merchant URL", payUrl); + let req = new XMLHttpRequest(); + req.open('post', payUrl.href()); + req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + req.addEventListener('readystatechange', (e) => { + if (req.readyState == XMLHttpRequest.DONE) { + resolve(); + } + }); + }); } function confirmPay(db, detail, sendResponse) { console.log("confirmPay", JSON.stringify(detail)); @@ -57,10 +158,17 @@ function confirmPay(db, detail, sendResponse) { contract: detail.offer.contract, sig: detail.offer }; - let contract = detail.offer.contract; - //let chosenCoinPromise = chooseCoins(db, contract.max_fee, contract.amount) - // .then(x => generateDepositPermissions(db, x)) - // .then(executePayment); + let offer = detail.offer; + getPossibleMintCoins(db, offer.contract.amount, offer.contract.max_fee, offer.contract.mints) + .then((mcs) => { + if (Object.keys(mcs).length == 0) { + sendResponse({ error: "Not enough coins." }); + return; + } + let mintUrl = Object.keys(mcs)[0]; + let ds = signDeposit(db, offer, mcs[mintUrl]); + return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl); + }); return true; } function confirmReserve(db, detail, sendResponse) { @@ -110,7 +218,10 @@ function confirmReserve(db, detail, sendResponse) { sendResponse(resp); var mint; updateMintFromUrl(db, reserveRecord.mint_base_url) - .then((m) => { mint = m; return updateReserve(db, reservePub, mint); }) + .then((m) => { + mint = m; + return updateReserve(db, reservePub, mint); + }) .then((reserve) => depleteReserve(db, reserve, mint)); }); break; @@ -213,7 +324,7 @@ function withdrawExecute(db, pc) { console.log("Withdrawal successful"); console.log(myRequest.responseText); let resp = JSON.parse(myRequest.responseText); - let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.coin_ev), RsaBlindingKey.fromCrock(pc.blindingKey), RsaPublicKey.fromCrock(pc.denomPub)); + let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.ev_sig), RsaBlindingKey.fromCrock(pc.blindingKey), RsaPublicKey.fromCrock(pc.denomPub)); let coin = { coinPub: pc.coinPub, coinPriv: pc.coinPriv, @@ -397,7 +508,9 @@ function dumpDb(db, detail, sendResponse) { let name = db.objectStoreNames[i]; let storeDump = {}; dump.stores[name] = storeDump; - let store = tx.objectStore(name).openCursor().addEventListener('success', (e) => { + let store = tx.objectStore(name) + .openCursor() + .addEventListener('success', (e) => { let cursor = e.target.result; if (cursor) { storeDump[cursor.key] = cursor.value; diff --git a/extension/background/wallet.ts b/extension/background/wallet.ts index 07c8743e1..281a24c6e 100644 --- a/extension/background/wallet.ts +++ b/extension/background/wallet.ts @@ -2,39 +2,11 @@ /// <reference path="../decl/chrome/chrome.d.ts" /> 'use strict'; -const DB_NAME = "taler"; -const DB_VERSION = 1; - -/** - * Return a promise that resolves - * to the taler wallet db. - */ -function openTalerDb(): Promise<IDBDatabase> { - return new Promise((resolve, reject) => { - let req = indexedDB.open(DB_NAME, DB_VERSION); - req.onerror = (e) => { - reject(e); - }; - req.onsuccess = (e) => { - resolve(req.result); - }; - req.onupgradeneeded = (e) => { - let db = req.result; - console.log ("DB: upgrade needed: oldVersion = " + e.oldVersion); - switch (e.oldVersion) { - case 0: // DB does not exist yet - db.createObjectStore("mints", { keyPath: "baseUrl" }); - db.createObjectStore("reserves", { keyPath: "reserve_pub"}); - db.createObjectStore("denoms", { keyPath: "denomPub" }); - let coins = db.createObjectStore("coins", { keyPath: "coinPub" }); - coins.createIndex("mintBaseUrl", "mintBaseUrl"); - db.createObjectStore("transactions", { keyPath: "contractHash" }); - db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true }); - break; - } - }; - }); +interface AmountJson { + value: number; + fraction: number; + currency: string; } @@ -52,38 +24,244 @@ function canonicalizeBaseUrl(url) { return x.href() } + interface ConfirmPayRequest { - offer: any; - selectedMint: any; + merchantPageUrl: string; + offer: Offer; +} + +interface MintCoins { + [mintUrl: string]: Db.CoinWithDenom[]; +} + +interface Offer { + contract: Contract; + sig: string; + H_contract: string; +} + +interface Contract { + H_wire: string; + amount: AmountJson; + auditors: string[]; + expiry: string, + locations: string[]; + max_fee: AmountJson; + merchant: any; + merchant_pub: string; + mints: MintInfo[]; + pay_url: string; + products: string[]; + refund_deadline: string; + timestamp: string; + transaction_id: number; +} + + +interface CoinPaySig { + coin_sig: string; + coin_pub: string; + ub_sig: string; + denom_pub: string; + f: AmountJson; +} + + +type PayCoinInfo = Array<{ updatedCoin: Db.Coin, sig: CoinPaySig }>; + + +function signDeposit(db: IDBDatabase, + offer: Offer, + cds: Db.CoinWithDenom[]): PayCoinInfo { + let ret = []; + let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency); + let amountRemaining = new Amount(offer.contract.amount); + cds = copy(cds); + for (let cd of cds) { + let coinSpend; + if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) { + coinSpend = new Amount(amountRemaining.toJson()); + } else { + coinSpend = new Amount(cd.coin.currentAmount); + } + + let d = new DepositRequestPS({ + h_contract: HashCode.fromCrock(offer.H_contract), + h_wire: HashCode.fromCrock(offer.contract.H_wire), + amount_with_fee: new Amount(cd.coin.currentAmount).toNbo(), + coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub), + deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(), + merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub), + refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline), + timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp), + transaction_id: UInt64.fromNumber(offer.contract.transaction_id), + }); + + amountSpent.add(coinSpend); + + let newAmount = new Amount(cd.coin.currentAmount); + newAmount.sub(coinSpend); + cd.coin.currentAmount = newAmount.toJson(); + + let coinSig = eddsaSign(d.toPurpose(), + 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: amountSpent.toJson(), + }; + ret.push({sig: coinSig, updatedCoin: cd.coin}); + } + return ret; +} + +interface MintInfo { + master_pub: string; + url: string; +} + + +/** + * Get mints and associated coins that are still spendable, + * but only if the sum the coins' remaining value exceeds the payment amount. + * @param db + * @param paymentAmount + * @param depositFeeLimit + * @param mintKeys + */ +function getPossibleMintCoins(db: IDBDatabase, + paymentAmount: AmountJson, + depositFeeLimit: AmountJson, + allowedMints: MintInfo[]): Promise<MintCoins> { + return new Promise((resolve, reject) => { + let m: MintCoins = {}; + let found = false; + let tx = db.transaction(["mints", "coins"]); + // First pass: Get all coins from acceptable mints. + for (let info of allowedMints) { + let req_mints = tx.objectStore("mints") + .index("pubKey") + .get(info.master_pub); + req_mints.onsuccess = (e) => { + let mint: Db.Mint = req_mints.result; + let req_coins = tx.objectStore("coins") + .index("mintBaseUrl") + .openCursor(IDBKeyRange.only(mint.baseUrl)); + req_coins.onsuccess = (e) => { + let cursor: IDBCursorWithValue = req_coins.result; + if (!cursor) { + return; + } + let cd = { + coin: cursor.value, + denom: mint.keys.denoms[cursor.value.denomPub] + }; + let x = m[mint.baseUrl]; + if (!x) { + m[mint.baseUrl] = [cd]; + } else { + x.push(cd); + } + } + } + } + + tx.oncomplete = (e) => { + let ret: MintCoins = {}; + + nextMint: + for (let key in m) { + let coins = m[key].map((x) => ({ + a: new Amount(x.denom.fee_deposit), + c: x + })); + // Sort by ascending deposit fee + coins.sort((o1, o2) => o1.a.cmp(o2.a)); + let maxFee = new Amount(depositFeeLimit); + let minAmount = new Amount(paymentAmount); + let accFee = new Amount(coins[0].c.denom.fee_deposit); + let accAmount = new Amount(coins[0].c.coin.currentAmount); + for (let i = 0; i < coins.length; i++) { + if (accFee.cmp(maxFee) >= 0) { + continue nextMint; + } + if (accAmount.cmp(minAmount) >= 0) { + ret[key] = m[key]; + continue nextMint; + } + accFee.add(coins[i].a); + accFee.add(new Amount(coins[i].c.coin.currentAmount)); + } + } + resolve(ret); + }; + + tx.onerror = (e) => { + reject(); + } + }); } -function grantCoins(db: IDBDatabase, - feeThreshold: AmountJson, - paymentAmount: AmountJson, - mintBaseUrl: string): Promise<any> { - throw "not implemented"; +function executePay(db, + offer: Offer, + payCoinInfo: PayCoinInfo, + merchantBaseUrl: string, + chosenMint: string) { + return new Promise((resolve, reject) => { + let reqData = {}; + reqData["H_wire"] = offer.contract.H_wire; + reqData["H_contract"] = offer.H_contract; + reqData["transaction_id"] = offer.contract.transaction_id; + reqData["refund_deadline"] = offer.contract.refund_deadline; + reqData["mint"] = chosenMint; + reqData["coins"] = payCoinInfo.map((x) => x.sig); + let payUrl = URI(merchantBaseUrl).absoluteTo(merchantBaseUrl); + console.log("Merchant URL", payUrl); + let req = new XMLHttpRequest(); + req.open('post', payUrl.href()); + req.setRequestHeader("Content-Type", + "application/json;charset=UTF-8"); + req.addEventListener('readystatechange', (e) => { + if (req.readyState == XMLHttpRequest.DONE) { + resolve() + } + }); + }); } -function confirmPay(db, detail: ConfirmPayRequest, sendResponse) { +function confirmPay(db, detail: ConfirmPayRequest, sendResponse) { console.log("confirmPay", JSON.stringify(detail)); let tx = db.transaction(['transactions'], 'readwrite'); let trans = { contractHash: detail.offer.H_contract, contract: detail.offer.contract, sig: detail.offer - } - - let contract = detail.offer.contract; - - //let chosenCoinPromise = chooseCoins(db, contract.max_fee, contract.amount) - // .then(x => generateDepositPermissions(db, x)) - // .then(executePayment); + }; + let offer: Offer = detail.offer; + getPossibleMintCoins(db, + offer.contract.amount, + offer.contract.max_fee, + offer.contract.mints) + .then((mcs) => { + if (Object.keys(mcs).length == 0) { + sendResponse({error: "Not enough coins."}); + return; + } + let mintUrl = Object.keys(mcs)[0]; + let ds = signDeposit(db, offer, mcs[mintUrl]); + return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl); + }); return true; } + function confirmReserve(db, detail, sendResponse) { let reservePriv = EddsaPrivateKey.create(); let reservePub = reservePriv.getPublicKey(); @@ -131,8 +309,11 @@ function confirmReserve(db, detail, sendResponse) { sendResponse(resp); var mint; updateMintFromUrl(db, reserveRecord.mint_base_url) - .then((m) => { mint = m; return updateReserve(db, reservePub, mint); }) - .then((reserve) => depleteReserve(db, reserve, mint)); + .then((m) => { + mint = m; + return updateReserve(db, reservePub, mint); + }) + .then((reserve) => depleteReserve(db, reserve, mint)); }); break; default: @@ -160,8 +341,8 @@ function rankDenom(denom1: any, denom2: any) { function withdrawPrepare(db: IDBDatabase, - denom: Denomination, - reserve): Promise<PreCoin> { + denom: Db.Denomination, + reserve): Promise<Db.PreCoin> { let reservePriv = new EddsaPrivateKey(); reservePriv.loadCrock(reserve.reserve_priv); let reservePub = new EddsaPublicKey(); @@ -196,7 +377,7 @@ function withdrawPrepare(db: IDBDatabase, console.log("crypto done, doing request"); - let preCoin: PreCoin = { + let preCoin: Db.PreCoin = { reservePub: reservePub.toCrock(), blindingKey: blindingFactor.toCrock(), coinPub: coinPub.toCrock(), @@ -219,57 +400,59 @@ function withdrawPrepare(db: IDBDatabase, }); } + function dbGet(db, store: string, key: any): Promise<any> { let tx = db.transaction([store]); let req = tx.objectStore(store).get(key); return new Promise((resolve, reject) => { - req.onsuccess = (e) => resolve(req.result); + req.onsuccess = (e) => resolve(req.result); }); } -function withdrawExecute(db, pc: PreCoin): Promise<Coin> { +function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> { return dbGet(db, 'reserves', pc.reservePub) - .then((r) => new Promise((resolve, reject) => { - console.log("loading precoin", JSON.stringify(pc)); - 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(r.mint_base_url); - let myRequest = new XMLHttpRequest(); - console.log("making request to " + reqUrl.href()); - myRequest.open('post', reqUrl.href()); - myRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - myRequest.send(JSON.stringify(wd)); - myRequest.addEventListener('readystatechange', (e) => { - if (myRequest.readyState == XMLHttpRequest.DONE) { - if (myRequest.status != 200) { - console.log("Withdrawal failed, status ", myRequest.status); - reject(); - return; - } - console.log("Withdrawal successful"); - console.log(myRequest.responseText); - let resp = JSON.parse(myRequest.responseText); - let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.coin_ev), - RsaBlindingKey.fromCrock(pc.blindingKey), - RsaPublicKey.fromCrock(pc.denomPub)); - let coin: Coin = { - coinPub: pc.coinPub, - coinPriv: pc.coinPriv, - denomPub: pc.denomPub, - denomSig: denomSig.encode().toCrock(), - currentAmount: pc.coinValue - } - console.log("unblinded coin"); - resolve(coin); - } else { - console.log("ready state change to", myRequest.status); - } - }); - })); + .then((r) => new Promise((resolve, reject) => { + console.log("loading precoin", JSON.stringify(pc)); + 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(r.mint_base_url); + let myRequest = new XMLHttpRequest(); + console.log("making request to " + reqUrl.href()); + myRequest.open('post', reqUrl.href()); + myRequest.setRequestHeader("Content-Type", + "application/json;charset=UTF-8"); + myRequest.send(JSON.stringify(wd)); + myRequest.addEventListener('readystatechange', (e) => { + if (myRequest.readyState == XMLHttpRequest.DONE) { + if (myRequest.status != 200) { + console.log("Withdrawal failed, status ", myRequest.status); + reject(); + return; + } + console.log("Withdrawal successful"); + console.log(myRequest.responseText); + let resp = JSON.parse(myRequest.responseText); + let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.ev_sig), + RsaBlindingKey.fromCrock(pc.blindingKey), + RsaPublicKey.fromCrock(pc.denomPub)); + let coin: Db.Coin = { + coinPub: pc.coinPub, + coinPriv: pc.coinPriv, + denomPub: pc.denomPub, + denomSig: denomSig.encode().toCrock(), + currentAmount: pc.coinValue + }; + console.log("unblinded coin"); + resolve(coin); + } else { + console.log("ready state change to", myRequest.status); + } + }); + })); } @@ -284,14 +467,14 @@ function updateBadge(db) { cursor.continue(); } else { console.log("badge"); - chrome.browserAction.setBadgeText({text: ""+n}); + chrome.browserAction.setBadgeText({text: "" + n}); chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"}); } } } -function storeCoin(db, coin: Coin) { +function storeCoin(db, coin: Db.Coin) { let tx = db.transaction(['coins', 'precoins'], 'readwrite'); tx.objectStore('precoins').delete(coin.coinPub); tx.objectStore('coins').add(coin); @@ -306,8 +489,8 @@ function storeCoin(db, coin: Coin) { function withdraw(db, denom, reserve): Promise<void> { return withdrawPrepare(db, denom, reserve) - .then((pc) => withdrawExecute(db, pc)) - .then((c) => storeCoin(db, c)); + .then((pc) => withdrawExecute(db, pc)) + .then((c) => storeCoin(db, c)); } @@ -344,7 +527,7 @@ function depleteReserve(db, reserve, mint) { console.log("doing work"); let d = workList.pop(); withdraw(db, d, reserve) - .then(() => next()); + .then(() => next()); } next(); @@ -408,7 +591,7 @@ function updateMintFromUrl(db, baseUrl) { console.log("keys invalid"); reject(); } else { - let mint = { + let mint: Db.Mint = { baseUrl: baseUrl, keys: mintKeysJson }; @@ -419,7 +602,7 @@ function updateMintFromUrl(db, baseUrl) { let di = { denomPub: d.denom_pub, value: d.value - } + }; tx.objectStore('denoms').put(di); } tx.oncomplete = (e) => { @@ -446,19 +629,21 @@ function dumpDb(db, detail, sendResponse) { console.log("stores: " + JSON.stringify(db.objectStoreNames)); let tx = db.transaction(db.objectStoreNames); tx.addEventListener('complete', (e) => { - sendResponse(dump); + sendResponse(dump); }); for (let i = 0; i < db.objectStoreNames.length; i++) { let name = db.objectStoreNames[i]; let storeDump = {}; dump.stores[name] = storeDump; - let store = tx.objectStore(name).openCursor().addEventListener('success', (e) => { - let cursor = e.target.result; - if (cursor) { - storeDump[cursor.key] = cursor.value; - cursor.continue(); - } - }); + let store = tx.objectStore(name) + .openCursor() + .addEventListener('success', (e) => { + let cursor = e.target.result; + if (cursor) { + storeDump[cursor.key] = cursor.value; + cursor.continue(); + } + }); } return true; } @@ -513,7 +698,7 @@ chrome.browserAction.setBadgeText({text: ""}); openTalerDb().then((db) => { console.log("db loaded"); chrome.runtime.onMessage.addListener( - function (req, sender, onresponse) { + function(req, sender, onresponse) { let dispatch = { "confirm-reserve": confirmReserve, "confirm-pay": confirmPay, @@ -524,7 +709,9 @@ openTalerDb().then((db) => { if (req.type in dispatch) { return dispatch[req.type](db, req.detail, onresponse); } - console.error(format("Request type {1} unknown, req {0}", JSON.stringify(req), req.type)); + console.error(format("Request type {1} unknown, req {0}", + JSON.stringify(req), + req.type)); return false; }); }); diff --git a/extension/lib/util.js b/extension/lib/util.js index 801d7d189..71bfce4d3 100644 --- a/extension/lib/util.js +++ b/extension/lib/util.js @@ -28,3 +28,7 @@ function format(s, ...args) { s = s.replace(/{([0-9]+)}/g, r); return s; } +function promiseFinally(p, fn) { + return p.then((x) => { fn(); return x; }) + .catch((e) => { fn(); throw e; }); +} diff --git a/extension/lib/util.ts b/extension/lib/util.ts index 106e22970..b30d60432 100644 --- a/extension/lib/util.ts +++ b/extension/lib/util.ts @@ -32,3 +32,8 @@ function format(s: string, ...args: any[]) { return s; } + +function promiseFinally<T>(p: Promise<T>, fn): Promise<T> { + return p.then((x) => { fn(); return x; }) + .catch((e) => {fn(); throw e;}); +}
\ No newline at end of file diff --git a/extension/pages/confirm-contract.html b/extension/pages/confirm-contract.html index 8d68511dd..9959bb489 100644 --- a/extension/pages/confirm-contract.html +++ b/extension/pages/confirm-contract.html @@ -26,6 +26,10 @@ <p /> </script> + <script id="error-template" type="text/x-handlebars-template"> + Payment was not successful: {{error}} + </script> + </head> <body> @@ -33,7 +37,9 @@ <div id="render-contract"></div> - <button id="confirm-purchase">Confirm Purchase!</button> + <button id="confirm-pay">Confirm Pay!</button> + + <div id="status"></div> </body> </html> diff --git a/extension/pages/confirm-contract.js b/extension/pages/confirm-contract.js index c59ac90a9..0f6d83744 100644 --- a/extension/pages/confirm-contract.js +++ b/extension/pages/confirm-contract.js @@ -23,16 +23,16 @@ document.addEventListener("DOMContentLoaded", (e) => { let html = template(offer.contract); $_("render-contract").innerHTML = html; document.getElementById("confirm-pay").addEventListener("click", (e) => { - let d = clone(query); + let d = { + offer: JSON.parse(query.offer) + }; chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, (resp) => { - if (resp.success === true) { - document.location.href = resp.backlink; - } - else { - document.body.innerHTML = - `Oops, something went wrong. - Here is some more info: - <pre>${resp.text}</pre>`; + console.log("got response", resp); + if ("error" in resp) { + let source = $_("error-template").innerHTML; + let template = Handlebars.compile(source); + $_("status").innerHTML = template(resp); + return; } }); }); diff --git a/extension/pages/confirm-contract.tsx b/extension/pages/confirm-contract.tsx index 811e2e001..c9d41a2c8 100644 --- a/extension/pages/confirm-contract.tsx +++ b/extension/pages/confirm-contract.tsx @@ -34,15 +34,16 @@ document.addEventListener("DOMContentLoaded", (e) => { $_("render-contract").innerHTML = html; document.getElementById("confirm-pay").addEventListener("click", (e) => { - let d = clone(query); + let d = { + offer: JSON.parse(query.offer) + }; chrome.runtime.sendMessage({type:'confirm-pay', detail: d}, (resp) => { - if (resp.success === true) { - document.location.href = resp.backlink; - } else { - document.body.innerHTML = - `Oops, something went wrong. - Here is some more info: - <pre>${resp.text}</pre>`; + console.log("got response", resp); + if ("error" in resp) { + let source = $_("error-template").innerHTML; + let template = Handlebars.compile(source); + $_("status").innerHTML = template(resp); + return; } }); }); diff --git a/extension/pages/debug.html b/extension/pages/debug.html index 7dfa7a9cc..24682dd24 100644 --- a/extension/pages/debug.html +++ b/extension/pages/debug.html @@ -7,5 +7,6 @@ <h1>Debug Pages</h1> <a href="show-db.html">Show DB</a> <br> <a href="../popup/balance-overview.html">Show balance</a> + </body> </html> diff --git a/extension/popup/balance-overview.js b/extension/popup/balance-overview.js index 4fd991b23..b2c5e0837 100644 --- a/extension/popup/balance-overview.js +++ b/extension/popup/balance-overview.js @@ -20,6 +20,15 @@ document.addEventListener('DOMContentLoaded', (e) => { let context = document.getElementById("balance-template").innerHTML; let template = Handlebars.compile(context); document.getElementById("content").innerHTML = template(wallet); + let el = document.getElementById("link-kudos"); + if (el) { + el.onclick = (e) => { + let target = e.target; + chrome.tabs.create({ + "url": target.href + }); + }; + } }); document.getElementById("debug").addEventListener("click", (e) => { chrome.tabs.create({ @@ -29,10 +38,4 @@ document.addEventListener('DOMContentLoaded', (e) => { document.getElementById("reset").addEventListener("click", (e) => { chrome.runtime.sendMessage({ type: "reset" }); }); - document.getElementById("link-kudos").addEventListener("click", (e) => { - let target = e.target; - chrome.tabs.create({ - "url": target.href - }); - }); }); diff --git a/extension/popup/balance-overview.tsx b/extension/popup/balance-overview.tsx index 88ff1bccb..38be50375 100644 --- a/extension/popup/balance-overview.tsx +++ b/extension/popup/balance-overview.tsx @@ -16,12 +16,22 @@ let React = { } } + document.addEventListener('DOMContentLoaded', (e) => { console.log("content loaded"); chrome.runtime.sendMessage({type: "balances"}, function(wallet) { let context = document.getElementById("balance-template").innerHTML; let template = Handlebars.compile(context); document.getElementById("content").innerHTML = template(wallet); + let el = document.getElementById("link-kudos"); + if (el) { + el.onclick = (e) => { + let target: any = e.target; + chrome.tabs.create({ + "url": target.href + }); + }; + } }); document.getElementById("debug").addEventListener("click", (e) => { @@ -32,10 +42,4 @@ document.addEventListener('DOMContentLoaded', (e) => { document.getElementById("reset").addEventListener("click", (e) => { chrome.runtime.sendMessage({type: "reset"}); }); - document.getElementById("link-kudos").addEventListener("click", (e) => { - let target: any = e.target; - chrome.tabs.create({ - "url": target.href - }); - }); }); |