Merge branch 'master' of taler.net:/var/git/wallet
This commit is contained in:
commit
4e0362878e
@ -26,22 +26,6 @@ System.config({
|
||||
defaultJSExtensions: true,
|
||||
});
|
||||
|
||||
// We expect that in the manifest, the emscripten js is loaded
|
||||
// becore the background page.
|
||||
// Currently it is not possible to use SystemJS to load the emscripten js.
|
||||
declare var Module: any;
|
||||
if ("object" !== typeof Module) {
|
||||
throw Error("emscripten not loaded, no 'Module' defined");
|
||||
}
|
||||
|
||||
// Manually register the emscripten js as a SystemJS, so that
|
||||
// we can use it from TypeScript by importing it.
|
||||
{
|
||||
let mod = System.newModule({Module: Module});
|
||||
let modName = System.normalizeSync("../lib/emscripten/emsc");
|
||||
console.log("registering", modName);
|
||||
System.set(modName, mod);
|
||||
}
|
||||
|
||||
System.import("../lib/wallet/wxMessaging")
|
||||
.then((wxMessaging) => {
|
||||
|
93
extension/lib/wallet/cryptoApi.ts
Normal file
93
extension/lib/wallet/cryptoApi.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2016 GNUnet e.V.
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify it under the
|
||||
terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 3, or (at your option) any later version.
|
||||
|
||||
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
|
||||
import {PreCoin} from "./types";
|
||||
import {Reserve} from "./types";
|
||||
import {Denomination} from "./types";
|
||||
import {Offer} from "./wallet";
|
||||
import {CoinWithDenom} from "./wallet";
|
||||
import {PayCoinInfo} from "./types";
|
||||
export class CryptoApi {
|
||||
private nextRpcId: number = 1;
|
||||
private rpcRegistry = {};
|
||||
private cryptoWorker: Worker;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.cryptoWorker = new Worker("/lib/wallet/cryptoWorker.js");
|
||||
|
||||
this.cryptoWorker.onmessage = (msg: MessageEvent) => {
|
||||
let id = msg.data.id;
|
||||
if (typeof id !== "number") {
|
||||
console.error("rpc id must be number");
|
||||
return;
|
||||
}
|
||||
if (!this.rpcRegistry[id]) {
|
||||
console.error(`RPC with id ${id} has no registry entry`);
|
||||
return;
|
||||
}
|
||||
let {resolve, reject} = this.rpcRegistry[id];
|
||||
resolve(msg.data.result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private registerRpcId(resolve, reject): number {
|
||||
let id = this.nextRpcId++;
|
||||
this.rpcRegistry[id] = {resolve, reject};
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
private doRpc<T>(methodName: string, ...args): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
let msg = {
|
||||
operation: methodName,
|
||||
id: this.registerRpcId(resolve, reject),
|
||||
args: args,
|
||||
};
|
||||
this.cryptoWorker.postMessage(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
createPreCoin(denom: Denomination, reserve: Reserve): Promise<PreCoin> {
|
||||
return this.doRpc("createPreCoin", denom, reserve);
|
||||
}
|
||||
|
||||
hashRsaPub(rsaPub: string): Promise<string> {
|
||||
return this.doRpc("hashRsaPub", rsaPub);
|
||||
}
|
||||
|
||||
isValidDenom(denom: Denomination,
|
||||
masterPub: string): Promise<boolean> {
|
||||
return this.doRpc("isValidDenom", denom, masterPub);
|
||||
}
|
||||
|
||||
signDeposit(offer: Offer,
|
||||
cds: CoinWithDenom[]): Promise<PayCoinInfo> {
|
||||
return this.doRpc("signDeposit", offer, cds);
|
||||
}
|
||||
|
||||
createEddsaKeypair(): Promise<{priv: string, pub: string}> {
|
||||
return this.doRpc("createEddsaKeypair");
|
||||
}
|
||||
|
||||
rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
|
||||
return this.doRpc("rsaUnblind", sig, bk, pk);
|
||||
}
|
||||
}
|
@ -23,8 +23,11 @@ import {Denomination} from "./types";
|
||||
"use strict";
|
||||
|
||||
import * as native from "./emscriptif";
|
||||
import {PreCoin, Reserve} from "./types";
|
||||
import {PreCoin, Reserve, PayCoinInfo} from "./types";
|
||||
import create = chrome.alarms.create;
|
||||
import {Offer} from "./wallet";
|
||||
import {CoinWithDenom} from "./wallet";
|
||||
import {CoinPaySig} from "./types";
|
||||
|
||||
|
||||
export function main(worker: Worker) {
|
||||
@ -58,7 +61,8 @@ namespace RpcFunctions {
|
||||
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||
* reserve.
|
||||
*/
|
||||
export function createPreCoin(denom: Denomination, reserve: Reserve): PreCoin {
|
||||
export function createPreCoin(denom: Denomination,
|
||||
reserve: Reserve): PreCoin {
|
||||
let reservePriv = new native.EddsaPrivateKey();
|
||||
reservePriv.loadCrock(reserve.reserve_priv);
|
||||
let reservePub = new native.EddsaPublicKey();
|
||||
@ -107,7 +111,7 @@ namespace RpcFunctions {
|
||||
|
||||
|
||||
export function isValidDenom(denom: Denomination,
|
||||
masterPub: string): boolean {
|
||||
masterPub: string): boolean {
|
||||
let p = new native.DenominationKeyValidityPS({
|
||||
master: native.EddsaPublicKey.fromCrock(masterPub),
|
||||
denom_hash: native.RsaPublicKey.fromCrock(denom.denom_pub)
|
||||
@ -134,4 +138,85 @@ namespace RpcFunctions {
|
||||
nativePub);
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function hashRsaPub(rsaPub: string): string {
|
||||
return native.RsaPublicKey.fromCrock(rsaPub)
|
||||
.encode()
|
||||
.hash()
|
||||
.toCrock();
|
||||
}
|
||||
|
||||
|
||||
export function createEddsaKeypair(): {priv: string, pub: string} {
|
||||
const priv = native.EddsaPrivateKey.create();
|
||||
const pub = priv.getPublicKey();
|
||||
return {priv: priv.toCrock(), pub: pub.toCrock()};
|
||||
}
|
||||
|
||||
|
||||
export function rsaUnblind(sig, bk, pk): string {
|
||||
let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig),
|
||||
native.RsaBlindingKey.fromCrock(bk),
|
||||
native.RsaPublicKey.fromCrock(pk));
|
||||
return denomSig.encode().toCrock()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate updated coins (to store in the database)
|
||||
* and deposit permissions for each given coin.
|
||||
*/
|
||||
export function signDeposit(offer: Offer,
|
||||
cds: CoinWithDenom[]): PayCoinInfo {
|
||||
let ret = [];
|
||||
let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
|
||||
let amountRemaining = new native.Amount(offer.contract.amount);
|
||||
for (let cd of cds) {
|
||||
let coinSpend;
|
||||
|
||||
if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (amountRemaining.cmp(new native.Amount(cd.coin.currentAmount)) < 0) {
|
||||
coinSpend = new native.Amount(amountRemaining.toJson());
|
||||
} else {
|
||||
coinSpend = new native.Amount(cd.coin.currentAmount);
|
||||
}
|
||||
|
||||
amountSpent.add(coinSpend);
|
||||
amountRemaining.sub(coinSpend);
|
||||
|
||||
let newAmount = new native.Amount(cd.coin.currentAmount);
|
||||
newAmount.sub(coinSpend);
|
||||
cd.coin.currentAmount = newAmount.toJson();
|
||||
|
||||
let d = new native.DepositRequestPS({
|
||||
h_contract: native.HashCode.fromCrock(offer.H_contract),
|
||||
h_wire: native.HashCode.fromCrock(offer.contract.H_wire),
|
||||
amount_with_fee: coinSpend.toNbo(),
|
||||
coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub),
|
||||
deposit_fee: new native.Amount(cd.denom.fee_deposit).toNbo(),
|
||||
merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
|
||||
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
|
||||
timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
|
||||
transaction_id: native.UInt64.fromNumber(offer.contract.transaction_id),
|
||||
});
|
||||
|
||||
let coinSig = native.eddsaSign(d.toPurpose(),
|
||||
native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
|
||||
.toCrock();
|
||||
|
||||
let s: CoinPaySig = {
|
||||
coin_sig: coinSig,
|
||||
coin_pub: cd.coin.coinPub,
|
||||
ub_sig: cd.coin.denomSig,
|
||||
denom_pub: cd.coin.denomPub,
|
||||
f: coinSpend.toJson(),
|
||||
};
|
||||
ret.push({sig: s, updatedCoin: cd.coin});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ export interface ReserveCreationInfo {
|
||||
mintInfo: IMintInfo;
|
||||
selectedDenoms: Denomination[];
|
||||
withdrawFee: AmountJson;
|
||||
overhead: AmountJson;
|
||||
}
|
||||
|
||||
|
||||
@ -120,12 +121,156 @@ export interface PreCoin {
|
||||
coinValue: AmountJson;
|
||||
}
|
||||
|
||||
|
||||
export interface Reserve {
|
||||
mint_base_url: string
|
||||
reserve_priv: string;
|
||||
reserve_pub: string;
|
||||
}
|
||||
|
||||
|
||||
export interface CoinPaySig {
|
||||
coin_sig: string;
|
||||
coin_pub: string;
|
||||
ub_sig: string;
|
||||
denom_pub: string;
|
||||
f: AmountJson;
|
||||
}
|
||||
|
||||
|
||||
export interface Coin {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
denomPub: string;
|
||||
denomSig: string;
|
||||
currentAmount: AmountJson;
|
||||
mintBaseUrl: string;
|
||||
}
|
||||
|
||||
|
||||
export type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig }>;
|
||||
|
||||
|
||||
export namespace Amounts {
|
||||
export interface Result {
|
||||
amount: AmountJson;
|
||||
// Was there an over-/underflow?
|
||||
saturated: boolean;
|
||||
}
|
||||
|
||||
function getMaxAmount(currency: string): AmountJson {
|
||||
return {
|
||||
currency,
|
||||
value: Number.MAX_SAFE_INTEGER,
|
||||
fraction: 2**32,
|
||||
}
|
||||
}
|
||||
|
||||
export function getZero(currency: string): AmountJson {
|
||||
return {
|
||||
currency,
|
||||
value: 0,
|
||||
fraction: 0,
|
||||
}
|
||||
}
|
||||
|
||||
export function add(first: AmountJson, ...rest: AmountJson[]): Result {
|
||||
const doit = () => {
|
||||
let currency = first.currency;
|
||||
let value = first.value + Math.floor(first.fraction / 1e6);
|
||||
if (value > Number.MAX_SAFE_INTEGER) {
|
||||
return {amount: getMaxAmount(currency), saturated: true};
|
||||
}
|
||||
let fraction = first.fraction % 1e6;
|
||||
for (let x of rest) {
|
||||
if (x.currency !== currency) {
|
||||
throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
|
||||
}
|
||||
|
||||
value = value + x.value + Math.floor((fraction + x.fraction) / 1e6);
|
||||
fraction = (fraction + x.fraction) % 1e6;
|
||||
if (value > Number.MAX_SAFE_INTEGER) {
|
||||
return {amount: getMaxAmount(currency), saturated: true};
|
||||
}
|
||||
}
|
||||
return {amount: {currency, value, fraction}, saturated: false};
|
||||
};
|
||||
console.log("adding", first, "and", rest);
|
||||
let ret = doit();
|
||||
console.log("result is", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
export function sub(a: AmountJson, b: AmountJson): Result {
|
||||
if (a.currency !== b.currency) {
|
||||
throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
|
||||
}
|
||||
let currency = a.currency;
|
||||
let value = a.value;
|
||||
let fraction = a.fraction;
|
||||
if (fraction < b.fraction) {
|
||||
if (value < 1) {
|
||||
return {amount: {currency, value: 0, fraction: 0}, saturated: true};
|
||||
}
|
||||
value--;
|
||||
fraction += 1e6;
|
||||
}
|
||||
console.assert(fraction >= b.fraction);
|
||||
fraction -= b.fraction;
|
||||
if (value < b.value) {
|
||||
return {amount: {currency, value: 0, fraction: 0}, saturated: true};
|
||||
}
|
||||
value -= b.value;
|
||||
return {amount: {currency, value, fraction}, saturated: false};
|
||||
}
|
||||
|
||||
export function cmp(a: AmountJson, b: AmountJson): number {
|
||||
const doit = () => {
|
||||
if (a.currency !== b.currency) {
|
||||
throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
|
||||
}
|
||||
let av = a.value + Math.floor(a.fraction / 1e6);
|
||||
let af = a.fraction % 1e6;
|
||||
let bv = b.value + Math.floor(b.fraction / 1e6);
|
||||
let bf = b.fraction % 1e6;
|
||||
switch (true) {
|
||||
case av < bv:
|
||||
return -1;
|
||||
case av > bv:
|
||||
return 1;
|
||||
case af < bf:
|
||||
return -1;
|
||||
case af > bf:
|
||||
return 1;
|
||||
case af == bf:
|
||||
return 0;
|
||||
default:
|
||||
throw Error("assertion failed");
|
||||
}
|
||||
};
|
||||
|
||||
console.log("comparing", a, "and", b);
|
||||
let res = doit();
|
||||
console.log("result:", res);
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
export function copy(a: AmountJson): AmountJson {
|
||||
return {
|
||||
value: a.value,
|
||||
fraction: a.fraction,
|
||||
currency: a.currency,
|
||||
}
|
||||
}
|
||||
|
||||
export function isNonZero(a: AmountJson) {
|
||||
return a.value > 0 || a.fraction > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export interface Notifier {
|
||||
notify();
|
||||
}
|
@ -21,15 +21,17 @@
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
import * as native from "./emscriptif";
|
||||
import {AmountJson, CreateReserveResponse, IMintInfo, Denomination, Notifier} from "./types";
|
||||
import {HttpResponse, RequestException} from "./http";
|
||||
import {Query} from "./query";
|
||||
import {Checkable} from "./checkable";
|
||||
import {canonicalizeBaseUrl} from "./helpers";
|
||||
import {ReserveCreationInfo} from "./types";
|
||||
import {ReserveCreationInfo, Amounts} from "./types";
|
||||
import {PreCoin} from "./types";
|
||||
import {Reserve} from "./types";
|
||||
import {CryptoApi} from "./cryptoApi";
|
||||
import {Coin} from "./types";
|
||||
import {PayCoinInfo} from "./types";
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -67,16 +69,6 @@ export class KeysJson {
|
||||
}
|
||||
|
||||
|
||||
export interface Coin {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
denomPub: string;
|
||||
denomSig: string;
|
||||
currentAmount: AmountJson;
|
||||
mintBaseUrl: string;
|
||||
}
|
||||
|
||||
|
||||
class MintInfo implements IMintInfo {
|
||||
baseUrl: string;
|
||||
masterPublicKey: string;
|
||||
@ -107,7 +99,7 @@ class MintInfo implements IMintInfo {
|
||||
* mint info is updated with the new information up until
|
||||
* the first error.
|
||||
*/
|
||||
mergeKeys(newKeys: KeysJson, wallet: Wallet): Promise<void> {
|
||||
mergeKeys(newKeys: KeysJson, cryptoApi: CryptoApi): Promise<void> {
|
||||
if (!this.masterPublicKey) {
|
||||
this.masterPublicKey = newKeys.master_public_key;
|
||||
}
|
||||
@ -140,20 +132,17 @@ class MintInfo implements IMintInfo {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return wallet.isValidDenom(newDenom, this.masterPublicKey)
|
||||
.then((valid) => {
|
||||
if (!valid) {
|
||||
throw Error("signature on denomination invalid");
|
||||
}
|
||||
|
||||
let d: Denomination = Object.assign({}, newDenom);
|
||||
d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub)
|
||||
.encode()
|
||||
.hash()
|
||||
.toCrock();
|
||||
this.denoms.push(d);
|
||||
|
||||
});
|
||||
return cryptoApi
|
||||
.isValidDenom(newDenom, this.masterPublicKey)
|
||||
.then((valid) => {
|
||||
if (!valid) {
|
||||
throw Error("signature on denomination invalid");
|
||||
}
|
||||
return cryptoApi.hashRsaPub(newDenom.denom_pub);
|
||||
})
|
||||
.then((h) => {
|
||||
this.denoms.push(Object.assign({}, newDenom, {pub_hash: h}));
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(ps).then(() => void 0);
|
||||
@ -300,8 +289,6 @@ export interface Badge {
|
||||
setColor(c: string): void;
|
||||
}
|
||||
|
||||
type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig }>;
|
||||
|
||||
|
||||
function deepEquals(x, y) {
|
||||
if (x === y) {
|
||||
@ -356,18 +343,6 @@ function copy(o) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rank two denomination by how desireable it is to withdraw them,
|
||||
* based on their fees and value.
|
||||
*/
|
||||
function rankDenom(denom1: any, denom2: any) {
|
||||
// Slow ... we should find a better way than to convert it evert time.
|
||||
let v1 = new native.Amount(denom1.value);
|
||||
let v2 = new native.Amount(denom2.value);
|
||||
return (-1) * v1.cmp(v2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of denominations (with repetitions possible)
|
||||
* whose total value is as close as possible to the available
|
||||
@ -375,11 +350,15 @@ function rankDenom(denom1: any, denom2: any) {
|
||||
*/
|
||||
function getWithdrawDenomList(amountAvailable: AmountJson,
|
||||
denoms: Denomination[]): Denomination[] {
|
||||
let remaining = new native.Amount(amountAvailable);
|
||||
let remaining = Amounts.copy(amountAvailable);
|
||||
let ds: Denomination[] = [];
|
||||
|
||||
denoms = denoms.filter(isWithdrawableDenom);
|
||||
denoms.sort(rankDenom);
|
||||
denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
|
||||
|
||||
console.log("ranked denoms");
|
||||
console.dir(denoms);
|
||||
|
||||
|
||||
// This is an arbitrary number of coins
|
||||
// we can withdraw in one go. It's not clear if this limit
|
||||
@ -387,17 +366,17 @@ function getWithdrawDenomList(amountAvailable: AmountJson,
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
let found = false;
|
||||
for (let d of denoms) {
|
||||
let cost = new native.Amount(d.value);
|
||||
cost.add(new native.Amount(d.fee_withdraw));
|
||||
if (remaining.cmp(cost) < 0) {
|
||||
let cost = Amounts.add(d.value, d.fee_withdraw).amount;
|
||||
if (Amounts.cmp(remaining, cost) < 0) {
|
||||
continue;
|
||||
}
|
||||
found = true;
|
||||
remaining.sub(cost);
|
||||
remaining = Amounts.sub(remaining, cost).amount;
|
||||
ds.push(d);
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
console.log("did not find coins for remaining ", remaining.toJson());
|
||||
console.log("did not find coins for remaining ", remaining);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -410,9 +389,7 @@ export class Wallet {
|
||||
private http: HttpRequestLibrary;
|
||||
private badge: Badge;
|
||||
private notifier: Notifier;
|
||||
private cryptoWorker: Worker;
|
||||
private nextRpcId: number = 1;
|
||||
private rpcRegistry = {};
|
||||
public cryptoApi: CryptoApi;
|
||||
|
||||
|
||||
constructor(db: IDBDatabase,
|
||||
@ -423,80 +400,7 @@ export class Wallet {
|
||||
this.http = http;
|
||||
this.badge = badge;
|
||||
this.notifier = notifier;
|
||||
this.cryptoWorker = new Worker("/lib/wallet/cryptoWorker.js");
|
||||
|
||||
this.cryptoWorker.onmessage = (msg: MessageEvent) => {
|
||||
let id = msg.data.id;
|
||||
if (typeof id !== "number") {
|
||||
console.error("rpc id must be number");
|
||||
return;
|
||||
}
|
||||
if (!this.rpcRegistry[id]) {
|
||||
console.error(`RPC with id ${id} has no registry entry`);
|
||||
return;
|
||||
}
|
||||
let {resolve, reject} = this.rpcRegistry[id];
|
||||
resolve(msg.data.result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate updated coins (to store in the database)
|
||||
* and deposit permissions for each given coin.
|
||||
*/
|
||||
private static signDeposit(offer: Offer,
|
||||
cds: CoinWithDenom[]): PayCoinInfo {
|
||||
let ret = [];
|
||||
let amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
|
||||
let amountRemaining = new native.Amount(offer.contract.amount);
|
||||
cds = copy(cds);
|
||||
for (let cd of cds) {
|
||||
let coinSpend;
|
||||
|
||||
if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (amountRemaining.cmp(new native.Amount(cd.coin.currentAmount)) < 0) {
|
||||
coinSpend = new native.Amount(amountRemaining.toJson());
|
||||
} else {
|
||||
coinSpend = new native.Amount(cd.coin.currentAmount);
|
||||
}
|
||||
|
||||
amountSpent.add(coinSpend);
|
||||
amountRemaining.sub(coinSpend);
|
||||
|
||||
let newAmount = new native.Amount(cd.coin.currentAmount);
|
||||
newAmount.sub(coinSpend);
|
||||
cd.coin.currentAmount = newAmount.toJson();
|
||||
|
||||
let d = new native.DepositRequestPS({
|
||||
h_contract: native.HashCode.fromCrock(offer.H_contract),
|
||||
h_wire: native.HashCode.fromCrock(offer.contract.H_wire),
|
||||
amount_with_fee: coinSpend.toNbo(),
|
||||
coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub),
|
||||
deposit_fee: new native.Amount(cd.denom.fee_deposit).toNbo(),
|
||||
merchant: native.EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
|
||||
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
|
||||
timestamp: native.AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
|
||||
transaction_id: native.UInt64.fromNumber(offer.contract.transaction_id),
|
||||
});
|
||||
|
||||
let coinSig = native.eddsaSign(d.toPurpose(),
|
||||
native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
|
||||
.toCrock();
|
||||
|
||||
let s: CoinPaySig = {
|
||||
coin_sig: coinSig,
|
||||
coin_pub: cd.coin.coinPub,
|
||||
ub_sig: cd.coin.denomSig,
|
||||
denom_pub: cd.coin.denomPub,
|
||||
f: coinSpend.toJson(),
|
||||
};
|
||||
ret.push({sig: s, updatedCoin: cd.coin});
|
||||
}
|
||||
return ret;
|
||||
this.cryptoApi = new CryptoApi();
|
||||
}
|
||||
|
||||
|
||||
@ -556,34 +460,32 @@ export class Wallet {
|
||||
|
||||
nextMint:
|
||||
for (let key in m) {
|
||||
let coins = m[key].map((x) => ({
|
||||
a: new native.Amount(x.denom.fee_deposit),
|
||||
c: x
|
||||
}));
|
||||
let coins = m[key];
|
||||
// Sort by ascending deposit fee
|
||||
coins.sort((o1, o2) => o1.a.cmp(o2.a));
|
||||
let maxFee = new native.Amount(depositFeeLimit);
|
||||
let minAmount = new native.Amount(paymentAmount);
|
||||
let accFee = new native.Amount(coins[0].c.denom.fee_deposit);
|
||||
let accAmount = native.Amount.getZero(coins[0].c.coin.currentAmount.currency);
|
||||
coins.sort((o1, o2) => Amounts.cmp(o1.denom.fee_deposit,
|
||||
o2.denom.fee_deposit));
|
||||
let maxFee = Amounts.copy(depositFeeLimit);
|
||||
let minAmount = Amounts.copy(paymentAmount);
|
||||
let accFee = Amounts.copy(coins[0].denom.fee_deposit);
|
||||
let accAmount = Amounts.getZero(coins[0].coin.currentAmount.currency);
|
||||
let usableCoins: CoinWithDenom[] = [];
|
||||
nextCoin:
|
||||
for (let i = 0; i < coins.length; i++) {
|
||||
let coinAmount = new native.Amount(coins[i].c.coin.currentAmount);
|
||||
let coinFee = coins[i].a;
|
||||
if (coinAmount.cmp(coinFee) <= 0) {
|
||||
let coinAmount = Amounts.copy(coins[i].coin.currentAmount);
|
||||
let coinFee = coins[i].denom.fee_deposit;
|
||||
if (Amounts.cmp(coinAmount, coinFee) <= 0) {
|
||||
continue nextCoin;
|
||||
}
|
||||
accFee.add(coinFee);
|
||||
accAmount.add(coinAmount);
|
||||
if (accFee.cmp(maxFee) >= 0) {
|
||||
accFee = Amounts.add(accFee, coinFee).amount;
|
||||
accAmount = Amounts.add(accAmount, coinAmount).amount;
|
||||
if (Amounts.cmp(accFee, maxFee) >= 0) {
|
||||
// FIXME: if the fees are too high, we have
|
||||
// to cover them ourselves ....
|
||||
console.log("too much fees");
|
||||
continue nextMint;
|
||||
}
|
||||
usableCoins.push(coins[i].c);
|
||||
if (accAmount.cmp(minAmount) >= 0) {
|
||||
usableCoins.push(coins[i]);
|
||||
if (Amounts.cmp(accAmount, minAmount) >= 0) {
|
||||
ret[key] = usableCoins;
|
||||
continue nextMint;
|
||||
}
|
||||
@ -661,9 +563,10 @@ export class Wallet {
|
||||
}
|
||||
console.log("about to record ...");
|
||||
let mintUrl = Object.keys(mcs)[0];
|
||||
let ds = Wallet.signDeposit(offer, mcs[mintUrl]);
|
||||
return this.recordConfirmPay(offer, ds, mintUrl)
|
||||
.then((() => ({})));
|
||||
|
||||
return this.cryptoApi.signDeposit(offer, mcs[mintUrl])
|
||||
.then((ds) => this.recordConfirmPay(offer, ds, mintUrl))
|
||||
.then(() => ({}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -725,44 +628,43 @@ export class Wallet {
|
||||
* Create a reserve, but do not flag it as confirmed yet.
|
||||
*/
|
||||
createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
|
||||
const reservePriv = native.EddsaPrivateKey.create();
|
||||
const reservePub = reservePriv.getPublicKey();
|
||||
return this.cryptoApi.createEddsaKeypair().then((keypair) => {
|
||||
const now = (new Date).getTime();
|
||||
const canonMint = canonicalizeBaseUrl(req.mint);
|
||||
|
||||
const now = (new Date).getTime();
|
||||
const canonMint = canonicalizeBaseUrl(req.mint);
|
||||
|
||||
const reserveRecord = {
|
||||
reserve_pub: reservePub.toCrock(),
|
||||
reserve_priv: reservePriv.toCrock(),
|
||||
mint_base_url: canonMint,
|
||||
created: now,
|
||||
last_query: null,
|
||||
current_amount: null,
|
||||
requested_amount: req.amount,
|
||||
confirmed: false,
|
||||
};
|
||||
const reserveRecord = {
|
||||
reserve_pub: keypair.pub,
|
||||
reserve_priv: keypair.priv,
|
||||
mint_base_url: canonMint,
|
||||
created: now,
|
||||
last_query: null,
|
||||
current_amount: null,
|
||||
requested_amount: req.amount,
|
||||
confirmed: false,
|
||||
};
|
||||
|
||||
|
||||
const historyEntry = {
|
||||
type: "create-reserve",
|
||||
timestamp: now,
|
||||
detail: {
|
||||
requestedAmount: req.amount,
|
||||
reservePub: reserveRecord.reserve_pub,
|
||||
}
|
||||
};
|
||||
const historyEntry = {
|
||||
type: "create-reserve",
|
||||
timestamp: now,
|
||||
detail: {
|
||||
requestedAmount: req.amount,
|
||||
reservePub: reserveRecord.reserve_pub,
|
||||
}
|
||||
};
|
||||
|
||||
return Query(this.db)
|
||||
.put("reserves", reserveRecord)
|
||||
.put("history", historyEntry)
|
||||
.finish()
|
||||
.then(() => {
|
||||
let r: CreateReserveResponse = {
|
||||
mint: canonMint,
|
||||
reservePub: reservePub.toCrock(),
|
||||
};
|
||||
return r;
|
||||
});
|
||||
return Query(this.db)
|
||||
.put("reserves", reserveRecord)
|
||||
.put("history", historyEntry)
|
||||
.finish()
|
||||
.then(() => {
|
||||
let r: CreateReserveResponse = {
|
||||
mint: canonMint,
|
||||
reservePub: keypair.pub,
|
||||
};
|
||||
return r;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -820,18 +722,19 @@ export class Wallet {
|
||||
});
|
||||
}
|
||||
let r = JSON.parse(resp.responseText);
|
||||
let denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(r.ev_sig),
|
||||
native.RsaBlindingKey.fromCrock(pc.blindingKey),
|
||||
native.RsaPublicKey.fromCrock(pc.denomPub));
|
||||
let coin: Coin = {
|
||||
coinPub: pc.coinPub,
|
||||
coinPriv: pc.coinPriv,
|
||||
denomPub: pc.denomPub,
|
||||
denomSig: denomSig.encode().toCrock(),
|
||||
currentAmount: pc.coinValue,
|
||||
mintBaseUrl: pc.mintBaseUrl,
|
||||
};
|
||||
return coin;
|
||||
return this.cryptoApi.rsaUnblind(r.ev_sig, pc.blindingKey, pc.denomPub)
|
||||
.then((denomSig) => {
|
||||
let coin: Coin = {
|
||||
coinPub: pc.coinPub,
|
||||
coinPriv: pc.coinPriv,
|
||||
denomPub: pc.denomPub,
|
||||
denomSig: denomSig,
|
||||
currentAmount: pc.coinValue,
|
||||
mintBaseUrl: pc.mintBaseUrl,
|
||||
};
|
||||
return coin;
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -859,7 +762,8 @@ export class Wallet {
|
||||
*/
|
||||
private withdraw(denom: Denomination, reserve: Reserve): Promise<void> {
|
||||
console.log("creating pre coin at", new Date());
|
||||
return this.createPreCoin(denom, reserve)
|
||||
return this.cryptoApi
|
||||
.createPreCoin(denom, reserve)
|
||||
.then((preCoin) => {
|
||||
return Query(this.db)
|
||||
.put("precoins", preCoin)
|
||||
@ -936,14 +840,21 @@ export class Wallet {
|
||||
let selectedDenoms = getWithdrawDenomList(amount,
|
||||
mintInfo.denoms);
|
||||
|
||||
let acc = native.Amount.getZero(amount.currency);
|
||||
let acc = Amounts.getZero(amount.currency);
|
||||
for (let d of selectedDenoms) {
|
||||
acc.add(new native.Amount(d.fee_withdraw));
|
||||
acc = Amounts.add(acc, d.fee_withdraw).amount;
|
||||
}
|
||||
let actualCoinCost = selectedDenoms
|
||||
.map((d: Denomination) => Amounts.add(d.value,
|
||||
d.fee_withdraw).amount)
|
||||
.reduce((a, b) => Amounts.add(a, b).amount);
|
||||
console.log("actual coin cost", actualCoinCost);
|
||||
console.log("amount", amount);
|
||||
let ret: ReserveCreationInfo = {
|
||||
mintInfo,
|
||||
selectedDenoms,
|
||||
withdrawFee: acc.toJson(),
|
||||
withdrawFee: acc,
|
||||
overhead: Amounts.sub(amount, actualCoinCost).amount,
|
||||
};
|
||||
return ret;
|
||||
});
|
||||
@ -978,7 +889,7 @@ export class Wallet {
|
||||
console.log("using old mint");
|
||||
}
|
||||
|
||||
return mintInfo.mergeKeys(mintKeysJson, this)
|
||||
return mintInfo.mergeKeys(mintKeysJson, this.cryptoApi)
|
||||
.then(() => {
|
||||
return Query(this.db)
|
||||
.put("mints", mintInfo)
|
||||
@ -999,11 +910,10 @@ export class Wallet {
|
||||
function collectBalances(c: Coin, byCurrency) {
|
||||
let acc: AmountJson = byCurrency[c.currentAmount.currency];
|
||||
if (!acc) {
|
||||
acc = native.Amount.getZero(c.currentAmount.currency).toJson();
|
||||
acc = Amounts.getZero(c.currentAmount.currency);
|
||||
}
|
||||
let am = new native.Amount(c.currentAmount);
|
||||
am.add(new native.Amount(acc));
|
||||
byCurrency[c.currentAmount.currency] = am.toJson();
|
||||
byCurrency[c.currentAmount.currency] = Amounts.add(c.currentAmount,
|
||||
acc).amount;
|
||||
return byCurrency;
|
||||
}
|
||||
|
||||
@ -1026,31 +936,4 @@ export class Wallet {
|
||||
.iter("history", {indexName: "timestamp"})
|
||||
.reduce(collect, [])
|
||||
}
|
||||
|
||||
registerRpcId(resolve, reject): number {
|
||||
let id = this.nextRpcId++;
|
||||
this.rpcRegistry[id] = {resolve, reject};
|
||||
return id;
|
||||
}
|
||||
|
||||
private doRpc<T>(methodName: string, ...args): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
let msg = {
|
||||
operation: methodName,
|
||||
id: this.registerRpcId(resolve, reject),
|
||||
args: args,
|
||||
};
|
||||
this.cryptoWorker.postMessage(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
createPreCoin(denom: Denomination, reserve: Reserve): Promise<PreCoin> {
|
||||
return this.doRpc("createPreCoin", denom, reserve);
|
||||
}
|
||||
|
||||
isValidDenom(denom: Denomination,
|
||||
masterPub: string): Promise<boolean> {
|
||||
return this.doRpc("isValidDenom", denom, masterPub);
|
||||
}
|
||||
}
|
@ -43,7 +43,6 @@
|
||||
"scripts": [
|
||||
"lib/vendor/URI.js",
|
||||
"lib/vendor/lodash.core.min.js",
|
||||
"lib/emscripten/libwrapper.js",
|
||||
"lib/vendor/system-csp-production.src.js",
|
||||
"background/main.js"
|
||||
]
|
||||
|
@ -16,7 +16,7 @@
|
||||
System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril", "../lib/wallet/wxApi"], function(exports_1, context_1) {
|
||||
"use strict";
|
||||
var __moduleName = context_1 && context_1.id;
|
||||
var helpers_1, types_1, mithril_1, wxApi_1;
|
||||
var helpers_1, types_1, mithril_1, types_2, wxApi_1;
|
||||
var DelayTimer, Controller;
|
||||
function view(ctrl) {
|
||||
var controls = [];
|
||||
@ -46,28 +46,29 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril", "../
|
||||
mx("p", "Checking URL, please wait ...");
|
||||
}
|
||||
if (ctrl.reserveCreationInfo) {
|
||||
var withdrawFeeStr = helpers_1.amountToPretty(ctrl.reserveCreationInfo.withdrawFee);
|
||||
mx("p", "Fee for withdrawal: " + withdrawFeeStr);
|
||||
var totalCost = types_2.Amounts.add(ctrl.reserveCreationInfo.overhead, ctrl.reserveCreationInfo.withdrawFee).amount;
|
||||
mx("p", "Withdraw cost: " + helpers_1.amountToPretty(totalCost));
|
||||
if (ctrl.detailCollapsed()) {
|
||||
mx("button.linky", {
|
||||
onclick: function () {
|
||||
ctrl.detailCollapsed(false);
|
||||
}
|
||||
}, "show more");
|
||||
}, "show more details");
|
||||
}
|
||||
else {
|
||||
mx("button.linky", {
|
||||
onclick: function () {
|
||||
ctrl.detailCollapsed(true);
|
||||
}
|
||||
}, "show less");
|
||||
mx("div", {}, renderCoinTable(ctrl.reserveCreationInfo.selectedDenoms));
|
||||
}, "hide details");
|
||||
mx("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo));
|
||||
}
|
||||
}
|
||||
return mithril_1.default("div", controls);
|
||||
var _a;
|
||||
}
|
||||
function renderCoinTable(denoms) {
|
||||
function renderReserveCreationDetails(rci) {
|
||||
var denoms = rci.selectedDenoms;
|
||||
function row(denom) {
|
||||
return mithril_1.default("tr", [
|
||||
mithril_1.default("td", denom.pub_hash.substr(0, 5) + "..."),
|
||||
@ -77,16 +78,22 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril", "../
|
||||
mithril_1.default("td", helpers_1.amountToPretty(denom.fee_deposit)),
|
||||
]);
|
||||
}
|
||||
return mithril_1.default("table", [
|
||||
mithril_1.default("tr", [
|
||||
mithril_1.default("th", "Key Hash"),
|
||||
mithril_1.default("th", "Value"),
|
||||
mithril_1.default("th", "Withdraw Fee"),
|
||||
mithril_1.default("th", "Refresh Fee"),
|
||||
mithril_1.default("th", "Deposit Fee"),
|
||||
]),
|
||||
denoms.map(row)
|
||||
]);
|
||||
var withdrawFeeStr = helpers_1.amountToPretty(rci.withdrawFee);
|
||||
var overheadStr = helpers_1.amountToPretty(rci.overhead);
|
||||
return [
|
||||
mithril_1.default("p", "Fee for withdrawal: " + withdrawFeeStr),
|
||||
mithril_1.default("p", "Overhead: " + overheadStr),
|
||||
mithril_1.default("table", [
|
||||
mithril_1.default("tr", [
|
||||
mithril_1.default("th", "Key Hash"),
|
||||
mithril_1.default("th", "Value"),
|
||||
mithril_1.default("th", "Withdraw Fee"),
|
||||
mithril_1.default("th", "Refresh Fee"),
|
||||
mithril_1.default("th", "Deposit Fee"),
|
||||
]),
|
||||
denoms.map(row)
|
||||
])
|
||||
];
|
||||
}
|
||||
function probeMint(mintBaseUrl) {
|
||||
throw Error("not implemented");
|
||||
@ -131,6 +138,7 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril", "../
|
||||
},
|
||||
function (types_1_1) {
|
||||
types_1 = types_1_1;
|
||||
types_2 = types_1_1;
|
||||
},
|
||||
function (mithril_1_1) {
|
||||
mithril_1 = mithril_1_1;
|
||||
|
@ -20,7 +20,7 @@ import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
|
||||
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
|
||||
import m from "mithril";
|
||||
import {IMintInfo} from "../lib/wallet/types";
|
||||
import {ReserveCreationInfo} from "../lib/wallet/types";
|
||||
import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
|
||||
import MithrilComponent = _mithril.MithrilComponent;
|
||||
import {Denomination} from "../lib/wallet/types";
|
||||
import {getReserveCreationInfo} from "../lib/wallet/wxApi";
|
||||
@ -201,22 +201,22 @@ function view(ctrl: Controller) {
|
||||
}
|
||||
|
||||
if (ctrl.reserveCreationInfo) {
|
||||
let withdrawFeeStr = amountToPretty(ctrl.reserveCreationInfo.withdrawFee);
|
||||
mx("p", `Fee for withdrawal: ${withdrawFeeStr}`);
|
||||
|
||||
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
|
||||
ctrl.reserveCreationInfo.withdrawFee).amount;
|
||||
mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
|
||||
if (ctrl.detailCollapsed()) {
|
||||
mx("button.linky", {
|
||||
onclick: () => {
|
||||
ctrl.detailCollapsed(false);
|
||||
}
|
||||
}, "show more");
|
||||
}, "show more details");
|
||||
} else {
|
||||
mx("button.linky", {
|
||||
onclick: () => {
|
||||
ctrl.detailCollapsed(true);
|
||||
}
|
||||
}, "show less");
|
||||
mx("div", {}, renderCoinTable(ctrl.reserveCreationInfo.selectedDenoms))
|
||||
}, "hide details");
|
||||
mx("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo))
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +224,9 @@ function view(ctrl: Controller) {
|
||||
}
|
||||
|
||||
|
||||
function renderCoinTable(denoms: Denomination[]) {
|
||||
function renderReserveCreationDetails(rci: ReserveCreationInfo) {
|
||||
let denoms = rci.selectedDenoms;
|
||||
|
||||
function row(denom: Denomination) {
|
||||
return m("tr", [
|
||||
m("td", denom.pub_hash.substr(0, 5) + "..."),
|
||||
@ -234,16 +236,23 @@ function renderCoinTable(denoms: Denomination[]) {
|
||||
m("td", amountToPretty(denom.fee_deposit)),
|
||||
]);
|
||||
}
|
||||
return m("table", [
|
||||
m("tr", [
|
||||
m("th", "Key Hash"),
|
||||
m("th", "Value"),
|
||||
m("th", "Withdraw Fee"),
|
||||
m("th", "Refresh Fee"),
|
||||
m("th", "Deposit Fee"),
|
||||
]),
|
||||
denoms.map(row)
|
||||
]);
|
||||
|
||||
let withdrawFeeStr = amountToPretty(rci.withdrawFee);
|
||||
let overheadStr = amountToPretty(rci.overhead);
|
||||
return [
|
||||
m("p", `Fee for withdrawal: ${withdrawFeeStr}`),
|
||||
m("p", `Overhead: ${overheadStr}`),
|
||||
m("table", [
|
||||
m("tr", [
|
||||
m("th", "Key Hash"),
|
||||
m("th", "Value"),
|
||||
m("th", "Withdraw Fee"),
|
||||
m("th", "Refresh Fee"),
|
||||
m("th", "Deposit Fee"),
|
||||
]),
|
||||
denoms.map(row)
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
"lib/i18n.ts",
|
||||
"lib/refs.ts",
|
||||
"lib/wallet/checkable.ts",
|
||||
"lib/wallet/cryptoApi.ts",
|
||||
"lib/wallet/cryptoLib.ts",
|
||||
"lib/wallet/cryptoWorker.ts",
|
||||
"lib/wallet/db.ts",
|
||||
|
Loading…
Reference in New Issue
Block a user