Towards payment.

This commit is contained in:
Florian Dold 2015-12-17 22:56:24 +01:00
parent 5f907c13fc
commit 38c947d771
15 changed files with 1135 additions and 450 deletions

View File

@ -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;
}
};
});
}

View File

@ -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;
}
};
});
}

View File

@ -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 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 {
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;
}
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);

View File

@ -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);
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 EddsaPublicKey extends PackedArenaObject {
size() { return 32; }
}
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;
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,38 +690,104 @@ 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();
}
size() {
return 8;
}
}
// 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;
}
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 = 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();
}
@ -627,57 +797,49 @@ class RsaPublicKey extends ArenaObject {
this.nativePtr = 0;
}
encode(arena?: Arena): ByteArray {
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;
}
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;
}

File diff suppressed because one or more lines are too long

View File

@ -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;

View File

@ -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;
}
function grantCoins(db: IDBDatabase,
feeThreshold: AmountJson,
paymentAmount: AmountJson,
mintBaseUrl: string): Promise<any> {
throw "not implemented";
interface CoinPaySig {
coin_sig: string;
coin_pub: string;
ub_sig: string;
denom_pub: string;
f: AmountJson;
}
function confirmPay(db, detail: ConfirmPayRequest, sendResponse) {
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 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) {
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;
});
});

View File

@ -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; });
}

View File

@ -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;});
}

View File

@ -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>

View File

@ -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;
}
});
});

View File

@ -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;
}
});
});

View File

@ -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>

View File

@ -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
});
});
});

View File

@ -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
});
});
});