diff options
Diffstat (limited to 'extension/lib/wallet/wallet.ts')
-rw-r--r-- | extension/lib/wallet/wallet.ts | 339 |
1 files changed, 111 insertions, 228 deletions
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 |