diff options
-rw-r--r-- | extension/background/main.ts | 16 | ||||
-rw-r--r-- | extension/lib/wallet/cryptoApi.ts | 93 | ||||
-rw-r--r-- | extension/lib/wallet/cryptoLib.ts | 91 | ||||
-rw-r--r-- | extension/lib/wallet/types.ts | 145 | ||||
-rw-r--r-- | extension/lib/wallet/wallet.ts | 339 | ||||
-rw-r--r-- | extension/manifest.json | 1 | ||||
-rw-r--r-- | extension/pages/confirm-create-reserve.js | 42 | ||||
-rw-r--r-- | extension/pages/confirm-create-reserve.tsx | 45 | ||||
-rw-r--r-- | extension/tsconfig.json | 1 |
9 files changed, 490 insertions, 283 deletions
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/cryptoApi.ts b/extension/lib/wallet/cryptoApi.ts new file mode 100644 index 000000000..300b928db --- /dev/null +++ b/extension/lib/wallet/cryptoApi.ts @@ -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); + } +}
\ 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 d18bd95f4..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; } @@ -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(); }
\ No newline at end of file diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index 8e7f63b12..76339fe5d 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -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) { @@ -357,29 +344,21 @@ 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 * amount, but never larger. */ 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(); - - 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; + }); + }); } @@ -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); - } }
\ No newline at end of file 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) + ]) + ]; } 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", |