From 180e122ee639fa41d4cfbca0f57117f5a12fa33a Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 22 Feb 2016 19:21:06 +0100 Subject: separate crypto api --- extension/lib/wallet/cryptoApi.ts | 74 +++++++++++++++++++++++++++++++++++++ extension/lib/wallet/wallet.ts | 78 ++++++++++----------------------------- extension/tsconfig.json | 1 + 3 files changed, 94 insertions(+), 59 deletions(-) create mode 100644 extension/lib/wallet/cryptoApi.ts (limited to 'extension') diff --git a/extension/lib/wallet/cryptoApi.ts b/extension/lib/wallet/cryptoApi.ts new file mode 100644 index 000000000..c29e9a45e --- /dev/null +++ b/extension/lib/wallet/cryptoApi.ts @@ -0,0 +1,74 @@ +/* + This file is part of TALER + (C) 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see + */ + + +import {PreCoin} from "./types"; +import {Reserve} from "./types"; +import {Denomination} 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(methodName: string, ...args): Promise { + return new Promise((resolve, reject) => { + let msg = { + operation: methodName, + id: this.registerRpcId(resolve, reject), + args: args, + }; + this.cryptoWorker.postMessage(msg); + }); + } + + + createPreCoin(denom: Denomination, reserve: Reserve): Promise { + return this.doRpc("createPreCoin", denom, reserve); + } + + + isValidDenom(denom: Denomination, + masterPub: string): Promise { + return this.doRpc("isValidDenom", denom, masterPub); + } +} \ No newline at end of file diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index 8e7f63b12..2bd2beee5 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -30,6 +30,7 @@ import {canonicalizeBaseUrl} from "./helpers"; import {ReserveCreationInfo} from "./types"; import {PreCoin} from "./types"; import {Reserve} from "./types"; +import {CryptoApi} from "./cryptoApi"; "use strict"; @@ -107,7 +108,7 @@ class MintInfo implements IMintInfo { * mint info is updated with the new information up until * the first error. */ - mergeKeys(newKeys: KeysJson, wallet: Wallet): Promise { + mergeKeys(newKeys: KeysJson, cryptoApi: CryptoApi): Promise { if (!this.masterPublicKey) { this.masterPublicKey = newKeys.master_public_key; } @@ -140,20 +141,21 @@ class MintInfo implements IMintInfo { return Promise.resolve(); } - return wallet.isValidDenom(newDenom, this.masterPublicKey) - .then((valid) => { - if (!valid) { - throw Error("signature on denomination invalid"); - } + return cryptoApi + .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); + let d: Denomination = Object.assign({}, newDenom); + d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub) + .encode() + .hash() + .toCrock(); + this.denoms.push(d); - }); + }); }); return Promise.all(ps).then(() => void 0); @@ -410,9 +412,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,21 +423,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); - } + this.cryptoApi = new CryptoApi(); } @@ -859,7 +845,8 @@ export class Wallet { */ private withdraw(denom: Denomination, reserve: Reserve): Promise { 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) @@ -1026,31 +1013,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(methodName: string, ...args): Promise { - return new Promise((resolve, reject) => { - let msg = { - operation: methodName, - id: this.registerRpcId(resolve, reject), - args: args, - }; - this.cryptoWorker.postMessage(msg); - }); - } - - - createPreCoin(denom: Denomination, reserve: Reserve): Promise { - return this.doRpc("createPreCoin", denom, reserve); - } - - isValidDenom(denom: Denomination, - masterPub: string): Promise { - return this.doRpc("isValidDenom", denom, masterPub); - } } \ No newline at end of file diff --git a/extension/tsconfig.json b/extension/tsconfig.json index f11267a0c..0e6337d86 100644 --- a/extension/tsconfig.json +++ b/extension/tsconfig.json @@ -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", -- cgit v1.2.3 From 81428771b8fc526a692dd26cf3f1421b65e32d6d Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 22 Feb 2016 20:55:01 +0100 Subject: pure TS amount arithmetic --- extension/lib/wallet/types.ts | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) (limited to 'extension') diff --git a/extension/lib/wallet/types.ts b/extension/lib/wallet/types.ts index d18bd95f4..cfd30bbc3 100644 --- a/extension/lib/wallet/types.ts +++ b/extension/lib/wallet/types.ts @@ -126,6 +126,91 @@ export interface Reserve { reserve_pub: string; } +export namespace Amounts { + export interface Result { + amount: AmountJson; + // Was there an over-/underflow? + saturated: boolean; + } + + function getMaxAmount(currency: string) { + return { + currency, + value: Number.MAX_SAFE_INTEGER, + fraction: 2**32, + } + } + + export function add(first: AmountJson, ...rest: AmountJson[]): Result { + let currency = first.currency; + let value = first.value + Math.floor(first.fraction / 1e6); + if (value > Number.MAX_SAFE_INTEGER) { + return {amount: getMaxAmount(currency), saturated: true}; + } + let fraction = first.fraction; + for (let x of rest) { + if (x.currency !== currency) { + throw Error(`Mismatched currency: ${x.currency} and ${currency}`); + } + + fraction = (fraction + x.fraction) % 1e6; + value = value + x.value + (Math.floor(fraction + x.fraction) / 1e6); + if (value > Number.MAX_SAFE_INTEGER) { + return {amount: getMaxAmount(currency), saturated: true}; + } + } + return {amount: {currency, value, fraction}, saturated: false}; + } + + + export function sub(a: AmountJson, 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 { + if (a.currency !== b.currency) { + throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); + } + let av = a.value + Math.floor(a.fraction / 1e6); + let af = a.fraction % 1e6; + let bv = b.value + Math.floor(b.fraction / 1e6); + let bf = b.fraction % 1e6; + switch (true) { + case av < bv: + return -1; + case av > bv: + return 1; + case af < bf: + return -1; + case af > bf: + return 1; + case af == bf: + return 0; + default: + throw Error("assertion failed"); + } + } +} + export interface Notifier { notify(); } \ No newline at end of file -- cgit v1.2.3 From 2760591d4deb00b594010493a0bbd07b347204ab Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 22 Feb 2016 21:52:53 +0100 Subject: put all crypto into backend --- extension/lib/wallet/cryptoApi.ts | 19 ++++ extension/lib/wallet/cryptoLib.ts | 91 +++++++++++++++++- extension/lib/wallet/types.ts | 24 +++++ extension/lib/wallet/wallet.ts | 197 ++++++++++++-------------------------- 4 files changed, 192 insertions(+), 139 deletions(-) (limited to 'extension') diff --git a/extension/lib/wallet/cryptoApi.ts b/extension/lib/wallet/cryptoApi.ts index c29e9a45e..300b928db 100644 --- a/extension/lib/wallet/cryptoApi.ts +++ b/extension/lib/wallet/cryptoApi.ts @@ -18,6 +18,9 @@ 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 = {}; @@ -66,9 +69,25 @@ export class CryptoApi { return this.doRpc("createPreCoin", denom, reserve); } + hashRsaPub(rsaPub: string): Promise { + return this.doRpc("hashRsaPub", rsaPub); + } isValidDenom(denom: Denomination, masterPub: string): Promise { return this.doRpc("isValidDenom", denom, masterPub); } + + signDeposit(offer: Offer, + cds: CoinWithDenom[]): Promise { + return this.doRpc("signDeposit", offer, cds); + } + + createEddsaKeypair(): Promise<{priv: string, pub: string}> { + return this.doRpc("createEddsaKeypair"); + } + + rsaUnblind(sig: string, bk: string, pk: string): Promise { + return this.doRpc("rsaUnblind", sig, bk, pk); + } } \ No newline at end of file diff --git a/extension/lib/wallet/cryptoLib.ts b/extension/lib/wallet/cryptoLib.ts index 2c32b3a63..869ddbaff 100644 --- a/extension/lib/wallet/cryptoLib.ts +++ b/extension/lib/wallet/cryptoLib.ts @@ -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; + } } diff --git a/extension/lib/wallet/types.ts b/extension/lib/wallet/types.ts index cfd30bbc3..77603e083 100644 --- a/extension/lib/wallet/types.ts +++ b/extension/lib/wallet/types.ts @@ -120,12 +120,36 @@ 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; diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index 2bd2beee5..8446d7194 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -27,10 +27,12 @@ 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"; @@ -68,16 +70,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; @@ -147,14 +139,10 @@ class MintInfo implements IMintInfo { 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.hashRsaPub(newDenom.denom_pub); + }) + .then((h) => { + this.denoms.push(Object.assign({}, newDenom, {pub_hash: h})); }); }); @@ -302,8 +290,6 @@ export interface Badge { setColor(c: string): void; } -type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig }>; - function deepEquals(x, y) { if (x === y) { @@ -362,11 +348,8 @@ 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); +function rankDenom(denom1: Denomination, denom2: Denomination) { + return (-1) * Amounts.cmp(denom1.value, denom2.value); } @@ -427,65 +410,6 @@ export class Wallet { } - /** - * 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; - } - - /** * Get mints and associated coins that are still spendable, * but only if the sum the coins' remaining value exceeds the payment amount. @@ -647,9 +571,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(() => ({})); }); } @@ -711,44 +636,43 @@ export class Wallet { * Create a reserve, but do not flag it as confirmed yet. */ createReserve(req: CreateReserveRequest): Promise { - const reservePriv = native.EddsaPrivateKey.create(); - const reservePub = reservePriv.getPublicKey(); - - 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, - }; + return this.cryptoApi.createEddsaKeypair().then((keypair) => { + const now = (new Date).getTime(); + const canonMint = canonicalizeBaseUrl(req.mint); + + 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; + }); + }); } @@ -806,18 +730,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; + + }); }); } @@ -965,7 +890,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) -- cgit v1.2.3 From 268ca9924435ec65dcb15da0b425b608d4407008 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 22 Feb 2016 23:12:57 +0100 Subject: fix amount arithmetic --- extension/lib/wallet/types.ts | 108 ++++++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 36 deletions(-) (limited to 'extension') diff --git a/extension/lib/wallet/types.ts b/extension/lib/wallet/types.ts index 77603e083..8151bf41a 100644 --- a/extension/lib/wallet/types.ts +++ b/extension/lib/wallet/types.ts @@ -105,6 +105,7 @@ export interface ReserveCreationInfo { mintInfo: IMintInfo; selectedDenoms: Denomination[]; withdrawFee: AmountJson; + overhead: AmountJson; } @@ -157,7 +158,7 @@ export namespace Amounts { saturated: boolean; } - function getMaxAmount(currency: string) { + function getMaxAmount(currency: string): AmountJson { return { currency, value: Number.MAX_SAFE_INTEGER, @@ -165,25 +166,39 @@ export namespace Amounts { } } - export function add(first: AmountJson, ...rest: AmountJson[]): Result { - let currency = first.currency; - let value = first.value + Math.floor(first.fraction / 1e6); - if (value > Number.MAX_SAFE_INTEGER) { - return {amount: getMaxAmount(currency), saturated: true}; + export function getZero(currency: string): AmountJson { + return { + currency, + value: 0, + fraction: 0, } - let fraction = first.fraction; - for (let x of rest) { - if (x.currency !== currency) { - throw Error(`Mismatched currency: ${x.currency} and ${currency}`); - } + } - fraction = (fraction + x.fraction) % 1e6; - value = value + x.value + (Math.floor(fraction + x.fraction) / 1e6); + 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}; } - } - return {amount: {currency, value, fraction}, saturated: false}; + 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; } @@ -199,7 +214,7 @@ export namespace Amounts { return {amount: {currency, value: 0, fraction: 0}, saturated: true}; } value--; - fraction = +1e6; + fraction += 1e6; } console.assert(fraction >= b.fraction); fraction -= b.fraction; @@ -211,30 +226,51 @@ export namespace Amounts { } export function cmp(a: AmountJson, b: AmountJson): number { - if (a.currency !== b.currency) { - throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); - } - let av = a.value + Math.floor(a.fraction / 1e6); - let af = a.fraction % 1e6; - let bv = b.value + Math.floor(b.fraction / 1e6); - let bf = b.fraction % 1e6; - switch (true) { - case av < bv: - return -1; - case av > bv: - return 1; - case af < bf: - return -1; - case af > bf: - return 1; - case af == bf: - return 0; - default: - throw Error("assertion failed"); + 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(); } \ No newline at end of file -- cgit v1.2.3 From 82742861d29a9d75da4de90322a128bcad5da503 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 22 Feb 2016 23:13:28 +0100 Subject: don't use emsc directly in wallet; show more reserve creation detail --- extension/background/main.ts | 16 ------ extension/lib/wallet/wallet.ts | 80 +++++++++++++++--------------- extension/manifest.json | 1 - extension/pages/confirm-create-reserve.js | 42 +++++++++------- extension/pages/confirm-create-reserve.tsx | 45 ++++++++++------- 5 files changed, 91 insertions(+), 93 deletions(-) (limited to 'extension') diff --git a/extension/background/main.ts b/extension/background/main.ts index 4ec2c4d5d..746d81a60 100644 --- a/extension/background/main.ts +++ b/extension/background/main.ts @@ -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) => { diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index 8446d7194..76339fe5d 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -21,7 +21,6 @@ * @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"; @@ -344,15 +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: Denomination, denom2: Denomination) { - return (-1) * Amounts.cmp(denom1.value, denom2.value); -} - - /** * Get a list of denominations (with repetitions possible) * whose total value is as close as possible to the available @@ -360,11 +350,15 @@ function rankDenom(denom1: Denomination, denom2: Denomination) { */ 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 @@ -372,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; } } @@ -466,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; } @@ -848,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; }); @@ -911,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; } diff --git a/extension/manifest.json b/extension/manifest.json index c4c2de987..6ed8f0948 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -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" ] diff --git a/extension/pages/confirm-create-reserve.js b/extension/pages/confirm-create-reserve.js index 610697246..97b2314ff 100644 --- a/extension/pages/confirm-create-reserve.js +++ b/extension/pages/confirm-create-reserve.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; diff --git a/extension/pages/confirm-create-reserve.tsx b/extension/pages/confirm-create-reserve.tsx index 9ae2938f3..2c42813a1 100644 --- a/extension/pages/confirm-create-reserve.tsx +++ b/extension/pages/confirm-create-reserve.tsx @@ -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) + ]) + ]; } -- cgit v1.2.3