aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/wallet/cryptoApi.ts19
-rw-r--r--lib/wallet/cryptoLib.ts92
-rw-r--r--lib/wallet/db.ts4
-rw-r--r--lib/wallet/emscriptif.ts41
-rw-r--r--lib/wallet/query.ts8
-rw-r--r--lib/wallet/types.ts53
-rw-r--r--lib/wallet/wallet.ts502
-rw-r--r--lib/wallet/wxApi.ts4
-rw-r--r--pages/tree.tsx8
9 files changed, 455 insertions, 276 deletions
diff --git a/lib/wallet/cryptoApi.ts b/lib/wallet/cryptoApi.ts
index 855afbb4e..ec20dd964 100644
--- a/lib/wallet/cryptoApi.ts
+++ b/lib/wallet/cryptoApi.ts
@@ -21,12 +21,12 @@
*/
-import {PreCoin} from "./types";
-import {Reserve} from "./types";
+import {PreCoin, Coin, ReserveRecord, AmountJson} from "./types";
import {Denomination} from "./types";
import {Offer} from "./wallet";
import {CoinWithDenom} from "./wallet";
import {PayCoinInfo} from "./types";
+import {RefreshSession} from "./types";
interface RegistryEntry {
resolve: any;
@@ -228,7 +228,7 @@ export class CryptoApi {
}
- createPreCoin(denom: Denomination, reserve: Reserve): Promise<PreCoin> {
+ createPreCoin(denom: Denomination, reserve: ReserveRecord): Promise<PreCoin> {
return this.doRpc("createPreCoin", 1, denom, reserve);
}
@@ -257,4 +257,17 @@ export class CryptoApi {
rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
return this.doRpc("rsaUnblind", 4, sig, bk, pk);
}
+
+ createWithdrawSession(kappa: number, meltCoin: Coin,
+ newCoinDenoms: Denomination[],
+ meltAmount: AmountJson,
+ meltFee: AmountJson): Promise<RefreshSession> {
+ return this.doRpc("createWithdrawSession",
+ 4,
+ kappa,
+ meltCoin,
+ newCoinDenoms,
+ meltAmount,
+ meltFee);
+ }
}
diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts
index 9a77b3d74..7969682b4 100644
--- a/lib/wallet/cryptoLib.ts
+++ b/lib/wallet/cryptoLib.ts
@@ -22,13 +22,21 @@
"use strict";
import * as native from "./emscriptif";
-import {PreCoin, Reserve, PayCoinInfo} from "./types";
+import {
+ PreCoin, PayCoinInfo, AmountJson,
+ RefreshSession, RefreshPreCoin, ReserveRecord
+} from "./types";
import create = chrome.alarms.create;
import {Offer} from "./wallet";
import {CoinWithDenom} from "./wallet";
import {CoinPaySig} from "./types";
import {Denomination} from "./types";
import {Amount} from "./emscriptif";
+import {Coin} from "../../background/lib/wallet/types";
+import {HashContext} from "./emscriptif";
+import {RefreshMeltCoinAffirmationPS} from "./emscriptif";
+import {EddsaPublicKey} from "./emscriptif";
+import {HashCode} from "./emscriptif";
export function main(worker: Worker) {
@@ -61,7 +69,7 @@ namespace RpcFunctions {
* reserve.
*/
export function createPreCoin(denom: Denomination,
- reserve: Reserve): PreCoin {
+ reserve: ReserveRecord): PreCoin {
let reservePriv = new native.EddsaPrivateKey();
reservePriv.loadCrock(reserve.reserve_priv);
let reservePub = new native.EddsaPublicKey();
@@ -224,4 +232,82 @@ namespace RpcFunctions {
}
return ret;
}
-}
+
+
+ function createWithdrawSession(kappa: number, meltCoin: Coin,
+ newCoinDenoms: Denomination[],
+ meltAmount: AmountJson,
+ meltFee: AmountJson): RefreshSession {
+
+ let sessionHc = new HashContext();
+
+ let transferPubs: string[] = [];
+
+ let preCoinsForGammas: RefreshPreCoin[][] = [];
+
+ for (let i = 0; i < newCoinDenoms.length; i++) {
+ let t = native.EcdsaPrivateKey.create();
+ sessionHc.read(t);
+ transferPubs.push(t.toCrock());
+ }
+
+ for (let i = 0; i < newCoinDenoms.length; i++) {
+ let r = native.RsaPublicKey.fromCrock(newCoinDenoms[i].denom_pub);
+ sessionHc.read(r.encode());
+ }
+
+ sessionHc.read(native.RsaPublicKey.fromCrock(meltCoin.coinPub).encode());
+ sessionHc.read((new native.Amount(meltAmount)).toNbo());
+
+ for (let j = 0; j < kappa; j++) {
+ let preCoins: RefreshPreCoin[] = [];
+ for (let i = 0; i < newCoinDenoms.length; i++) {
+
+ let coinPriv = native.EddsaPrivateKey.create();
+ let coinPub = coinPriv.getPublicKey();
+ let blindingFactor = native.RsaBlindingKeySecret.create();
+ let pubHash: native.HashCode = coinPub.hash();
+ let denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[i].denom_pub);
+ let ev: native.ByteArray = native.rsaBlind(pubHash,
+ blindingFactor,
+ denomPub);
+ let preCoin: RefreshPreCoin = {
+ blindingKey: blindingFactor.toCrock(),
+ coinEv: ev.toCrock(),
+ publicKey: coinPub.toCrock(),
+ privateKey: coinPriv.toCrock(),
+ };
+ preCoins.push(preCoin);
+ sessionHc.read(ev);
+ }
+ preCoinsForGammas.push(preCoins);
+ }
+
+ let sessionHash = new HashCode();
+ sessionHash.alloc();
+ sessionHc.finish(sessionHash);
+
+ let confirmData = new RefreshMeltCoinAffirmationPS({
+ coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub),
+ amount_with_fee: (new Amount(meltAmount)).toNbo(),
+ session_hash: sessionHash,
+ melt_fee: (new Amount(meltFee)).toNbo()
+ });
+
+ let confirmSig: string = native.eddsaSign(confirmData.toPurpose(),
+ native.EddsaPrivateKey.fromCrock(
+ meltCoin.coinPriv)).toCrock();
+
+ let refreshSession: RefreshSession = {
+ meltCoinPub: meltCoin.coinPub,
+ newDenoms: newCoinDenoms.map((d) => d.denom_pub),
+ confirmSig,
+ valueWithFee: meltAmount,
+ transferPubs,
+ preCoinsForGammas,
+ };
+
+ return refreshSession;
+ }
+
+} \ No newline at end of file
diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts
index 23cc9eb07..55e943393 100644
--- a/lib/wallet/db.ts
+++ b/lib/wallet/db.ts
@@ -25,7 +25,7 @@
*/
const DB_NAME = "taler";
-const DB_VERSION = 7;
+const DB_VERSION = 8;
/**
* Return a promise that resolves
@@ -72,7 +72,7 @@ export function openTalerDb(): Promise<IDBDatabase> {
if (e.oldVersion != DB_VERSION) {
window.alert("Incompatible wallet dababase version, please reset" +
" db.");
- chrome.browserAction.setBadgeText({text: "R!"});
+ chrome.browserAction.setBadgeText({text: "err"});
chrome.browserAction.setBadgeBackgroundColor({color: "#F00"});
throw Error("incompatible DB");
}
diff --git a/lib/wallet/emscriptif.ts b/lib/wallet/emscriptif.ts
index 23014114a..7c08fdc45 100644
--- a/lib/wallet/emscriptif.ts
+++ b/lib/wallet/emscriptif.ts
@@ -119,10 +119,16 @@ var emscAlloc = {
['number', 'number', 'number', 'string']),
eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create',
'number', []),
+ ecdsa_key_create: getEmsc('GNUNET_CRYPTO_ecdsa_key_create',
+ 'number', []),
eddsa_public_key_from_private: getEmsc(
'TALER_WRALL_eddsa_public_key_from_private',
'number',
['number']),
+ ecdsa_public_key_from_private: getEmsc(
+ 'TALER_WRALL_ecdsa_public_key_from_private',
+ 'number',
+ ['number']),
data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc',
'number',
['number', 'number']),
@@ -181,7 +187,7 @@ interface ArenaObject {
}
-class HashContext implements ArenaObject {
+export class HashContext implements ArenaObject {
private hashContextPtr: number | undefined;
constructor() {
@@ -590,6 +596,29 @@ export class EddsaPrivateKey extends PackedArenaObject {
mixinStatic(EddsaPrivateKey, fromCrock);
+export class EcdsaPrivateKey extends PackedArenaObject {
+ static create(a?: Arena): EcdsaPrivateKey {
+ let obj = new EcdsaPrivateKey(a);
+ obj.nativePtr = emscAlloc.ecdsa_key_create();
+ return obj;
+ }
+
+ size() {
+ return 32;
+ }
+
+ getPublicKey(a?: Arena): EcdsaPublicKey {
+ let obj = new EcdsaPublicKey(a);
+ obj.nativePtr = emscAlloc.ecdsa_public_key_from_private(this.nativePtr);
+ return obj;
+ }
+
+ static fromCrock: (s: string) => EcdsaPrivateKey;
+}
+mixinStatic(EcdsaPrivateKey, fromCrock);
+
+
+
function fromCrock(s: string) {
let x = new this();
x.alloc();
@@ -629,6 +658,16 @@ export class EddsaPublicKey extends PackedArenaObject {
}
mixinStatic(EddsaPublicKey, fromCrock);
+export class EcdsaPublicKey extends PackedArenaObject {
+ size() {
+ return 32;
+ }
+
+ static fromCrock: (s: string) => EcdsaPublicKey;
+}
+mixinStatic(EddsaPublicKey, fromCrock);
+
+
function makeFromCrock(decodeFn: (p: number, s: number) => number) {
function fromCrock(s: string, a?: Arena) {
let obj = new this(a);
diff --git a/lib/wallet/query.ts b/lib/wallet/query.ts
index 77a4f8e35..fa78fe640 100644
--- a/lib/wallet/query.ts
+++ b/lib/wallet/query.ts
@@ -24,10 +24,6 @@
"use strict";
-export function Query(db: IDBDatabase) {
- return new QueryRoot(db);
-}
-
/**
* Stream that can be filtered, reduced or joined
* with indices.
@@ -265,7 +261,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
}
-class QueryRoot {
+export class QueryRoot {
private work: ((t: IDBTransaction) => void)[] = [];
private db: IDBDatabase;
private stores = new Set();
@@ -332,7 +328,7 @@ class QueryRoot {
/**
* Get one object from a store by its key.
*/
- get(storeName: any, key: any): Promise<any> {
+ get<T>(storeName: any, key: any): Promise<T|undefined> {
if (key === void 0) {
throw Error("key must not be undefined");
}
diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts
index 91b329842..9ff8680ca 100644
--- a/lib/wallet/types.ts
+++ b/lib/wallet/types.ts
@@ -42,6 +42,30 @@ export class AmountJson {
}
+export interface ReserveRecord {
+ reserve_pub: string;
+ reserve_priv: string,
+ exchange_base_url: string,
+ created: number,
+ last_query: number | null,
+ /**
+ * Current amount left in the reserve
+ */
+ current_amount: AmountJson | null,
+ /**
+ * Amount requested when the reserve was created.
+ * When a reserve is re-used (rare!) the current_amount can
+ * be higher than the requested_amount
+ */
+ requested_amount: AmountJson,
+ /**
+ * Amount we've already withdrawn from the reserve.
+ */
+ withdrawn_amount: AmountJson;
+ confirmed: boolean,
+}
+
+
@Checkable.Class
export class CreateReserveResponse {
/**
@@ -147,6 +171,13 @@ export interface PreCoin {
coinValue: AmountJson;
}
+export interface RefreshPreCoin {
+ publicKey: string;
+ privateKey: string;
+ coinEv: string;
+ blindingKey: string
+}
+
/**
* Ongoing refresh
@@ -173,20 +204,9 @@ export interface RefreshSession {
*/
newDenoms: string[];
- /**
- * Blinded public keys for the requested coins.
- */
- newCoinBlanks: string[][];
- /**
- * Blinding factors for the new coins.
- */
- newCoinBlindingFactors: string[][];
+ preCoinsForGammas: RefreshPreCoin[][];
- /**
- * Private keys for the requested coins.
- */
- newCoinPrivs: string[][];
/**
* The transfer keys, kappa of them.
@@ -195,15 +215,6 @@ export interface RefreshSession {
}
-export interface Reserve {
- exchange_base_url: string
- reserve_priv: string;
- reserve_pub: string;
- created: number;
- current_amount: AmountJson;
-}
-
-
export interface CoinPaySig {
coin_sig: string;
coin_pub: string;
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
index 337ed8255..49e4e0a8d 100644
--- a/lib/wallet/wallet.ts
+++ b/lib/wallet/wallet.ts
@@ -27,21 +27,20 @@ import {
IExchangeInfo,
Denomination,
Notifier,
- WireInfo
+ WireInfo, RefreshSession, ReserveRecord
} from "./types";
-import { HttpResponse, RequestException } from "./http";
-import { Query } from "./query";
-import { Checkable } from "./checkable";
-import { canonicalizeBaseUrl } from "./helpers";
-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";
-import { CheckRepurchaseResult } from "./types";
-import { Contract } from "./types";
-import { ExchangeHandle } from "./types";
+import {HttpResponse, RequestException} from "./http";
+import {QueryRoot} from "./query";
+import {Checkable} from "./checkable";
+import {canonicalizeBaseUrl} from "./helpers";
+import {ReserveCreationInfo, Amounts} from "./types";
+import {PreCoin} from "./types";
+import {CryptoApi} from "./cryptoApi";
+import {Coin} from "./types";
+import {PayCoinInfo} from "./types";
+import {CheckRepurchaseResult} from "./types";
+import {Contract} from "./types";
+import {ExchangeHandle} from "./types";
"use strict";
@@ -50,29 +49,6 @@ export interface CoinWithDenom {
denom: Denomination;
}
-interface ReserveRecord {
- reserve_pub: string;
- reserve_priv: string,
- exchange_base_url: string,
- created: number,
- last_query: number | null,
- /**
- * Current amount left in the reserve
- */
- current_amount: AmountJson | null,
- /**
- * Amount requested when the reserve was created.
- * When a reserve is re-used (rare!) the current_amount can
- * be higher than the requested_amount
- */
- requested_amount: AmountJson,
- /**
- * Amount we've already withdrawn from the reserve.
- */
- withdrawn_amount: AmountJson;
- confirmed: boolean,
-}
-
@Checkable.Class
export class KeysJson {
@@ -256,8 +232,8 @@ function isWithdrawableDenom(d: Denomination) {
interface HttpRequestLibrary {
req(method: string,
- url: string | uri.URI,
- options?: any): Promise<HttpResponse>;
+ url: string | uri.URI,
+ options?: any): Promise<HttpResponse>;
get(url: string | uri.URI): Promise<HttpResponse>;
@@ -288,7 +264,7 @@ interface KeyUpdateInfo {
* amount, but never larger.
*/
function getWithdrawDenomList(amountAvailable: AmountJson,
- denoms: Denomination[]): Denomination[] {
+ denoms: Denomination[]): Denomination[] {
let remaining = Amounts.copy(amountAvailable);
const ds: Denomination[] = [];
@@ -330,10 +306,14 @@ export class Wallet {
*/
private runningOperations: Set<string> = new Set();
+ q(): QueryRoot {
+ return new QueryRoot(this.db);
+ }
+
constructor(db: IDBDatabase,
- http: HttpRequestLibrary,
- badge: Badge,
- notifier: Notifier) {
+ http: HttpRequestLibrary,
+ badge: Badge,
+ notifier: Notifier) {
this.db = db;
this.http = http;
this.badge = badge;
@@ -359,14 +339,14 @@ export class Wallet {
updateExchanges(): void {
console.log("updating exchanges");
- Query(this.db)
- .iter("exchanges")
- .reduce((exchange: IExchangeInfo) => {
- this.updateExchangeFromUrl(exchange.baseUrl)
- .catch((e) => {
- console.error("updating exchange failed", e);
- });
- });
+ this.q()
+ .iter("exchanges")
+ .reduce((exchange: IExchangeInfo) => {
+ this.updateExchangeFromUrl(exchange.baseUrl)
+ .catch((e) => {
+ console.error("updating exchange failed", e);
+ });
+ });
}
/**
@@ -376,19 +356,19 @@ export class Wallet {
private resumePendingFromDb(): void {
console.log("resuming pending operations from db");
- Query(this.db)
- .iter("reserves")
- .reduce((reserve: any) => {
- console.log("resuming reserve", reserve.reserve_pub);
- this.processReserve(reserve);
- });
-
- Query(this.db)
- .iter("precoins")
- .reduce((preCoin: any) => {
- console.log("resuming precoin");
- this.processPreCoin(preCoin);
- });
+ this.q()
+ .iter("reserves")
+ .reduce((reserve: any) => {
+ console.log("resuming reserve", reserve.reserve_pub);
+ this.processReserve(reserve);
+ });
+
+ this.q()
+ .iter("precoins")
+ .reduce((preCoin: any) => {
+ console.log("resuming precoin");
+ this.processPreCoin(preCoin);
+ });
}
@@ -397,8 +377,8 @@ export class Wallet {
* but only if the sum the coins' remaining value exceeds the payment amount.
*/
private async getPossibleExchangeCoins(paymentAmount: AmountJson,
- depositFeeLimit: AmountJson,
- allowedExchanges: ExchangeHandle[]): Promise<ExchangeCoins> {
+ depositFeeLimit: AmountJson,
+ allowedExchanges: ExchangeHandle[]): Promise<ExchangeCoins> {
// Mapping from exchange base URL to list of coins together with their
// denomination
let m: ExchangeCoins = {};
@@ -411,9 +391,9 @@ export class Wallet {
let coin: Coin = mc[1];
if (coin.suspended) {
console.log("skipping suspended coin",
- coin.denomPub,
- "from exchange",
- exchange.baseUrl);
+ coin.denomPub,
+ "from exchange",
+ exchange.baseUrl);
return;
}
let denom = exchange.active_denoms.find((e) => e.denom_pub === coin.denomPub);
@@ -425,7 +405,7 @@ export class Wallet {
console.warn("same pubkey for different currencies");
return;
}
- let cd = { coin, denom };
+ let cd = {coin, denom};
let x = m[url];
if (!x) {
m[url] = [cd];
@@ -445,10 +425,12 @@ export class Wallet {
handledExchanges.add(info.url);
console.log("Checking for merchant's exchange", JSON.stringify(info));
return [
- Query(this.db)
- .iter("exchanges", { indexName: "pubKey", only: info.master_pub })
- .indexJoin("coins", "exchangeBaseUrl", (exchange) => exchange.baseUrl)
- .reduce((x) => storeExchangeCoin(x, info.url))
+ this.q()
+ .iter("exchanges", {indexName: "pubKey", only: info.master_pub})
+ .indexJoin("coins",
+ "exchangeBaseUrl",
+ (exchange) => exchange.baseUrl)
+ .reduce((x) => storeExchangeCoin(x, info.url))
];
});
@@ -467,38 +449,38 @@ export class Wallet {
// under depositFeeLimit
nextExchange:
- for (let key in m) {
- let coins = m[key];
- // Sort by ascending deposit fee
- 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 = Amounts.copy(coins[i].coin.currentAmount);
- let coinFee = coins[i].denom.fee_deposit;
- if (Amounts.cmp(coinAmount, coinFee) <= 0) {
- continue nextCoin;
- }
- 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 nextExchange;
- }
- usableCoins.push(coins[i]);
- if (Amounts.cmp(accAmount, minAmount) >= 0) {
- ret[key] = usableCoins;
- continue nextExchange;
- }
+ for (let key in m) {
+ let coins = m[key];
+ // Sort by ascending deposit fee
+ 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 = Amounts.copy(coins[i].coin.currentAmount);
+ let coinFee = coins[i].denom.fee_deposit;
+ if (Amounts.cmp(coinAmount, coinFee) <= 0) {
+ continue nextCoin;
+ }
+ 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 nextExchange;
+ }
+ usableCoins.push(coins[i]);
+ if (Amounts.cmp(accAmount, minAmount) >= 0) {
+ ret[key] = usableCoins;
+ continue nextExchange;
+ }
+ }
}
- }
return ret;
}
@@ -508,8 +490,8 @@ export class Wallet {
* pay for a contract in the wallet's database.
*/
private async recordConfirmPay(offer: Offer,
- payCoinInfo: PayCoinInfo,
- chosenExchange: string): Promise<void> {
+ payCoinInfo: PayCoinInfo,
+ chosenExchange: string): Promise<void> {
let payReq: any = {};
payReq["amount"] = offer.contract.amount;
payReq["coins"] = payCoinInfo.map((x) => x.sig);
@@ -539,18 +521,18 @@ export class Wallet {
}
};
- await Query(this.db)
- .put("transactions", t)
- .put("history", historyEntry)
- .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
- .finish();
+ await this.q()
+ .put("transactions", t)
+ .put("history", historyEntry)
+ .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
+ .finish();
this.notifier.notify();
}
async putHistory(historyEntry: HistoryRecord): Promise<void> {
- await Query(this.db).put("history", historyEntry).finish();
+ await this.q().put("history", historyEntry).finish();
this.notifier.notify();
}
@@ -562,8 +544,7 @@ export class Wallet {
async confirmPay(offer: Offer): Promise<any> {
console.log("executing confirmPay");
- let transaction = await Query(this.db)
- .get("transactions", offer.H_contract);
+ let transaction = await this.q().get("transactions", offer.H_contract);
if (transaction) {
// Already payed ...
@@ -571,8 +552,8 @@ export class Wallet {
}
let mcs = await this.getPossibleExchangeCoins(offer.contract.amount,
- offer.contract.max_fee,
- offer.contract.exchanges);
+ offer.contract.max_fee,
+ offer.contract.exchanges);
if (Object.keys(mcs).length == 0) {
console.log("not confirming payment, insufficient coins");
@@ -584,8 +565,8 @@ export class Wallet {
let ds = await this.cryptoApi.signDeposit(offer, mcs[exchangeUrl]);
await this.recordConfirmPay(offer,
- ds,
- exchangeUrl);
+ ds,
+ exchangeUrl);
return {};
}
@@ -596,17 +577,15 @@ export class Wallet {
*/
async checkPay(offer: Offer): Promise<any> {
// First check if we already payed for it.
- let transaction = await
- Query(this.db)
- .get("transactions", offer.H_contract);
+ let transaction = await this.q().get("transactions", offer.H_contract);
if (transaction) {
- return { isPayed: true };
+ return {isPayed: true};
}
// If not already payed, check if we could pay for it.
let mcs = await this.getPossibleExchangeCoins(offer.contract.amount,
- offer.contract.max_fee,
- offer.contract.exchanges);
+ offer.contract.max_fee,
+ offer.contract.exchanges);
if (Object.keys(mcs).length == 0) {
console.log("not confirming payment, insufficient coins");
@@ -614,7 +593,7 @@ export class Wallet {
error: "coins-insufficient",
};
}
- return { isPayed: false };
+ return {isPayed: false};
}
@@ -623,8 +602,7 @@ export class Wallet {
* with the given hash.
*/
async executePayment(H_contract: string): Promise<any> {
- let t = await Query(this.db)
- .get("transactions", H_contract);
+ let t = await this.q().get<Transaction>("transactions", H_contract);
if (!t) {
return {
success: false,
@@ -645,14 +623,14 @@ export class Wallet {
* then deplete the reserve, withdrawing coins until it is empty.
*/
private async processReserve(reserveRecord: ReserveRecord,
- retryDelayMs: number = 250): Promise<void> {
+ retryDelayMs: number = 250): Promise<void> {
const opId = "reserve-" + reserveRecord.reserve_pub;
this.startOperation(opId);
try {
let exchange = await this.updateExchangeFromUrl(reserveRecord.exchange_base_url);
let reserve = await this.updateReserve(reserveRecord.reserve_pub,
- exchange);
+ exchange);
let n = await this.depleteReserve(reserve, exchange);
if (n != 0) {
@@ -667,15 +645,15 @@ export class Wallet {
currentAmount: reserveRecord.current_amount,
}
};
- await Query(this.db).put("history", depleted).finish();
+ await this.q().put("history", depleted).finish();
}
} catch (e) {
// random, exponential backoff truncated at 3 minutes
let nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(),
- 3000 * 60);
+ 3000 * 60);
console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`);
setTimeout(() => this.processReserve(reserveRecord, nextDelay),
- retryDelayMs);
+ retryDelayMs);
} finally {
this.stopOperation(opId);
}
@@ -683,18 +661,18 @@ export class Wallet {
private async processPreCoin(preCoin: PreCoin,
- retryDelayMs = 100): Promise<void> {
+ retryDelayMs = 100): Promise<void> {
try {
const coin = await this.withdrawExecute(preCoin);
this.storeCoin(coin);
} catch (e) {
console.error("Failed to withdraw coin from precoin, retrying in",
- retryDelayMs,
- "ms", e);
+ retryDelayMs,
+ "ms", e);
// exponential backoff truncated at one minute
let nextRetryDelayMs = Math.min(retryDelayMs * 2, 1000 * 60);
setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs),
- retryDelayMs);
+ retryDelayMs);
}
}
@@ -730,10 +708,10 @@ export class Wallet {
}
};
- await Query(this.db)
- .put("reserves", reserveRecord)
- .put("history", historyEntry)
- .finish();
+ await this.q()
+ .put("reserves", reserveRecord)
+ .put("history", historyEntry)
+ .finish();
let r: CreateReserveResponse = {
exchange: canonExchange,
@@ -754,8 +732,13 @@ export class Wallet {
*/
async confirmReserve(req: ConfirmReserveRequest): Promise<void> {
const now = (new Date).getTime();
- let reserve: ReserveRecord = await Query(this.db)
- .get("reserves", req.reservePub);
+ let reserve: ReserveRecord|undefined = await (
+ this.q().get<ReserveRecord>("reserves",
+ req.reservePub));
+ if (!reserve) {
+ console.error("Unable to confirm reserve, not found in DB");
+ return;
+ }
const historyEntry = {
type: "confirm-reserve",
timestamp: now,
@@ -766,23 +749,22 @@ export class Wallet {
requestedAmount: reserve.requested_amount,
}
};
- if (!reserve) {
- console.error("Unable to confirm reserve, not found in DB");
- return;
- }
reserve.confirmed = true;
- await Query(this.db)
- .put("reserves", reserve)
- .put("history", historyEntry)
- .finish();
+ await this.q()
+ .put("reserves", reserve)
+ .put("history", historyEntry)
+ .finish();
this.processReserve(reserve);
}
private async withdrawExecute(pc: PreCoin): Promise<Coin> {
- let reserve = await Query(this.db)
- .get("reserves", pc.reservePub);
+ let reserve = await this.q().get<ReserveRecord>("reserves", pc.reservePub);
+
+ if (!reserve) {
+ throw Error("db inconsistent");
+ }
let wd: any = {};
wd.denom_pub = pc.denomPub;
@@ -801,8 +783,8 @@ export class Wallet {
}
let r = JSON.parse(resp.responseText);
let denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig,
- pc.blindingKey,
- pc.denomPub);
+ pc.blindingKey,
+ pc.denomPub);
let coin: Coin = {
coinPub: pc.coinPub,
coinPriv: pc.coinPriv,
@@ -825,11 +807,11 @@ export class Wallet {
coinPub: coin.coinPub,
}
};
- await Query(this.db)
- .delete("precoins", coin.coinPub)
- .add("coins", coin)
- .add("history", historyEntry)
- .finish();
+ await this.q()
+ .delete("precoins", coin.coinPub)
+ .add("coins", coin)
+ .add("history", historyEntry)
+ .finish();
this.notifier.notify();
}
@@ -837,13 +819,14 @@ export class Wallet {
/**
* Withdraw one coin of the given denomination from the given reserve.
*/
- private async withdraw(denom: Denomination, reserve: Reserve): Promise<void> {
+ private async withdraw(denom: Denomination,
+ reserve: ReserveRecord): Promise<void> {
console.log("creating pre coin at", new Date());
let preCoin = await this.cryptoApi
- .createPreCoin(denom, reserve);
- await Query(this.db)
- .put("precoins", preCoin)
- .finish();
+ .createPreCoin(denom, reserve);
+ await this.q()
+ .put("precoins", preCoin)
+ .finish();
await this.processPreCoin(preCoin);
}
@@ -852,10 +835,10 @@ export class Wallet {
* Withdraw coins from a reserve until it is empty.
*/
private async depleteReserve(reserve: any,
- exchange: IExchangeInfo): Promise<number> {
+ exchange: IExchangeInfo): Promise<number> {
let denomsAvailable: Denomination[] = copy(exchange.active_denoms);
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
- denomsAvailable);
+ denomsAvailable);
let ps = denomsForWithdraw.map((denom) => this.withdraw(denom, reserve));
await Promise.all(ps);
@@ -868,11 +851,14 @@ export class Wallet {
* by quering the reserve's exchange.
*/
private async updateReserve(reservePub: string,
- exchange: IExchangeInfo): Promise<Reserve> {
- let reserve = await Query(this.db)
- .get("reserves", reservePub);
+ exchange: IExchangeInfo): Promise<ReserveRecord> {
+ let reserve = await this.q()
+ .get<ReserveRecord>("reserves", reservePub);
+ if (!reserve) {
+ throw Error("reserve not in db");
+ }
let reqUrl = URI("reserve/status").absoluteTo(exchange.baseUrl);
- reqUrl.query({ 'reserve_pub': reservePub });
+ reqUrl.query({'reserve_pub': reservePub});
let resp = await this.http.get(reqUrl);
if (resp.status != 200) {
throw Error();
@@ -895,9 +881,9 @@ export class Wallet {
newAmount
}
};
- await Query(this.db)
- .put("reserves", reserve)
- .finish();
+ await this.q()
+ .put("reserves", reserve)
+ .finish();
return reserve;
}
@@ -922,18 +908,18 @@ export class Wallet {
}
async getReserveCreationInfo(baseUrl: string,
- amount: AmountJson): Promise<ReserveCreationInfo> {
+ amount: AmountJson): Promise<ReserveCreationInfo> {
let exchangeInfo = await this.updateExchangeFromUrl(baseUrl);
let selectedDenoms = getWithdrawDenomList(amount,
- exchangeInfo.active_denoms);
+ exchangeInfo.active_denoms);
let acc = Amounts.getZero(amount.currency);
for (let d of selectedDenoms) {
acc = Amounts.add(acc, d.fee_withdraw).amount;
}
let actualCoinCost = selectedDenoms
.map((d: Denomination) => Amounts.add(d.value,
- d.fee_withdraw).amount)
+ d.fee_withdraw).amount)
.reduce((a, b) => Amounts.add(a, b).amount);
let wireInfo = await this.getWireInfo(baseUrl);
@@ -966,17 +952,18 @@ export class Wallet {
}
private async suspendCoins(exchangeInfo: IExchangeInfo): Promise<void> {
- let suspendedCoins = await Query(this.db)
- .iter("coins",
- { indexName: "exchangeBaseUrl", only: exchangeInfo.baseUrl })
- .reduce((coin: Coin, suspendedCoins: Coin[]) => {
- if (!exchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) {
- return Array.prototype.concat(suspendedCoins, [coin]);
- }
- return Array.prototype.concat(suspendedCoins);
- }, []);
-
- let q = Query(this.db);
+ let suspendedCoins = await (
+ this.q()
+ .iter("coins",
+ {indexName: "exchangeBaseUrl", only: exchangeInfo.baseUrl})
+ .reduce((coin: Coin, suspendedCoins: Coin[]) => {
+ if (!exchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) {
+ return Array.prototype.concat(suspendedCoins, [coin]);
+ }
+ return Array.prototype.concat(suspendedCoins);
+ }, []));
+
+ let q = this.q();
suspendedCoins.map((c) => {
console.log("suspending coin", c);
c.suspended = true;
@@ -987,13 +974,13 @@ export class Wallet {
private async updateExchangeFromJson(baseUrl: string,
- exchangeKeysJson: KeysJson): Promise<IExchangeInfo> {
+ exchangeKeysJson: KeysJson): Promise<IExchangeInfo> {
const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
if (updateTimeSec === null) {
throw Error("invalid update time");
}
- let r = await Query(this.db).get("exchanges", baseUrl);
+ let r = await this.q().get<IExchangeInfo>("exchanges", baseUrl);
let exchangeInfo: IExchangeInfo;
@@ -1016,19 +1003,19 @@ export class Wallet {
}
let updatedExchangeInfo = await this.updateExchangeInfo(exchangeInfo,
- exchangeKeysJson);
+ exchangeKeysJson);
await this.suspendCoins(updatedExchangeInfo);
- await Query(this.db)
- .put("exchanges", updatedExchangeInfo)
- .finish();
+ await this.q()
+ .put("exchanges", updatedExchangeInfo)
+ .finish();
return updatedExchangeInfo;
}
private async updateExchangeInfo(exchangeInfo: IExchangeInfo,
- newKeys: KeysJson): Promise<IExchangeInfo> {
+ newKeys: KeysJson): Promise<IExchangeInfo> {
if (exchangeInfo.masterPublicKey != newKeys.master_public_key) {
throw Error("public keys do not match");
}
@@ -1064,15 +1051,15 @@ export class Wallet {
return true;
});
- let ps = denomsToCheck.map(async (denom) => {
+ let ps = denomsToCheck.map(async(denom) => {
let valid = await this.cryptoApi
- .isValidDenom(denom,
- exchangeInfo.masterPublicKey);
+ .isValidDenom(denom,
+ exchangeInfo.masterPublicKey);
if (!valid) {
console.error("invalid denomination",
- denom,
- "with key",
- exchangeInfo.masterPublicKey);
+ denom,
+ "with key",
+ exchangeInfo.masterPublicKey);
// FIXME: report to auditors
}
exchangeInfo.active_denoms.push(denom);
@@ -1099,15 +1086,58 @@ export class Wallet {
acc = Amounts.getZero(c.currentAmount.currency);
}
byCurrency[c.currentAmount.currency] = Amounts.add(c.currentAmount,
- acc).amount;
+ acc).amount;
return byCurrency;
}
- let byCurrency = await Query(this.db)
- .iter("coins")
- .reduce(collectBalances, {});
+ let byCurrency = await (
+ this.q()
+ .iter("coins")
+ .reduce(collectBalances, {}));
+
+ return {balances: byCurrency};
+ }
+
+
+ async refresh(oldCoinPub: string): Promise<void> {
+ // FIXME: this is not running in a transaction.
+
+ let coin = await this.q().get<Coin>("coins", oldCoinPub);
+
+ if (!coin) {
+ console.error("coin not found");
+ return;
+ }
+
+ let exchange = await this.q().get<IExchangeInfo>("exchanges",
+ coin.exchangeBaseUrl);
+ if (!exchange) {
+ throw Error("db inconsistent");
+ }
+
+ let oldDenom = exchange.all_denoms.find((d) => d.denom_pub == coin!.denomPub);
+
+ if (!oldDenom) {
+ throw Error("db inconsistent");
+ }
+
+ let availableDenoms: Denomination[] = exchange.active_denoms;
+
+ let newCoinDenoms = getWithdrawDenomList(coin.currentAmount,
+ availableDenoms);
+
+ console.log("refreshing into", newCoinDenoms);
+
+
+ let refreshSession: RefreshSession = await (
+ this.cryptoApi.createWithdrawSession(3,
+ coin,
+ newCoinDenoms,
+ coin.currentAmount,
+ oldDenom.fee_refresh));
+
+ // FIXME: implement rest
- return { balances: byCurrency };
}
@@ -1120,40 +1150,40 @@ export class Wallet {
return acc;
}
- let history = await
- Query(this.db)
- .iter("history", { indexName: "timestamp" })
- .reduce(collect, []);
+ let history = await (
+ this.q()
+ .iter("history", {indexName: "timestamp"})
+ .reduce(collect, []));
- return { history };
+ return {history};
}
async getExchanges(): Promise<IExchangeInfo[]> {
- return Query(this.db)
- .iter<IExchangeInfo>("exchanges")
- .flatMap((e) => [e])
- .toArray();
+ return this.q()
+ .iter<IExchangeInfo>("exchanges")
+ .flatMap((e) => [e])
+ .toArray();
}
- async getReserves(exchangeBaseUrl: string): Promise<Reserve[]> {
- return Query(this.db)
- .iter<Reserve>("reserves")
- .filter((r: Reserve) => r.exchange_base_url === exchangeBaseUrl)
- .toArray();
+ async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
+ return this.q()
+ .iter<ReserveRecord>("reserves")
+ .filter((r: ReserveRecord) => r.exchange_base_url === exchangeBaseUrl)
+ .toArray();
}
async getCoins(exchangeBaseUrl: string): Promise<Coin[]> {
- return Query(this.db)
- .iter<Coin>("coins")
- .filter((c: Coin) => c.exchangeBaseUrl === exchangeBaseUrl)
- .toArray();
+ return this.q()
+ .iter<Coin>("coins")
+ .filter((c: Coin) => c.exchangeBaseUrl === exchangeBaseUrl)
+ .toArray();
}
async getPreCoins(exchangeBaseUrl: string): Promise<PreCoin[]> {
- return Query(this.db)
- .iter<PreCoin>("precoins")
- .filter((c: PreCoin) => c.exchangeBaseUrl === exchangeBaseUrl)
- .toArray();
+ return this.q()
+ .iter<PreCoin>("precoins")
+ .filter((c: PreCoin) => c.exchangeBaseUrl === exchangeBaseUrl)
+ .toArray();
}
@@ -1167,12 +1197,16 @@ export class Wallet {
async checkRepurchase(contract: Contract): Promise<CheckRepurchaseResult> {
if (!contract.repurchase_correlation_id) {
console.log("no repurchase: no correlation id");
- return { isRepurchase: false };
+ return {isRepurchase: false};
}
- let result: Transaction = await Query(this.db)
- .getIndexed("transactions",
- "repurchase",
- [contract.merchant_pub, contract.repurchase_correlation_id]);
+ let result: Transaction = await (
+ this.q()
+ .getIndexed("transactions",
+ "repurchase",
+ [
+ contract.merchant_pub,
+ contract.repurchase_correlation_id
+ ]));
if (result) {
console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id);
@@ -1182,7 +1216,7 @@ export class Wallet {
existingFulfillmentUrl: result.contract.fulfillment_url,
};
} else {
- return { isRepurchase: false };
+ return {isRepurchase: false};
}
}
}
diff --git a/lib/wallet/wxApi.ts b/lib/wallet/wxApi.ts
index 549ce0a5a..12d11a387 100644
--- a/lib/wallet/wxApi.ts
+++ b/lib/wallet/wxApi.ts
@@ -20,7 +20,7 @@ import {
PreCoin,
ReserveCreationInfo,
IExchangeInfo,
- Reserve
+ ReserveRecord
} from "./types";
/**
@@ -58,7 +58,7 @@ export async function getExchanges(): Promise<IExchangeInfo[]> {
return await callBackend("get-exchanges");
}
-export async function getReserves(exchangeBaseUrl: string): Promise<Reserve[]> {
+export async function getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
return await callBackend("get-reserves", { exchangeBaseUrl });
}
diff --git a/pages/tree.tsx b/pages/tree.tsx
index b1c22b9f8..ba5f787b4 100644
--- a/pages/tree.tsx
+++ b/pages/tree.tsx
@@ -23,18 +23,18 @@
/// <reference path="../lib/decl/preact.d.ts" />
import { IExchangeInfo } from "../lib/wallet/types";
-import { Reserve, Coin, PreCoin, Denomination } from "../lib/wallet/types";
+import { ReserveRecord, Coin, PreCoin, Denomination } from "../lib/wallet/types";
import { ImplicitStateComponent, StateHolder } from "../lib/components";
import { getReserves, getExchanges, getCoins, getPreCoins } from "../lib/wallet/wxApi";
import { prettyAmount, abbrev } from "../lib/wallet/renderHtml";
interface ReserveViewProps {
- reserve: Reserve;
+ reserve: ReserveRecord;
}
class ReserveView extends preact.Component<ReserveViewProps, void> {
render(): JSX.Element {
- let r: Reserve = this.props.reserve;
+ let r: ReserveRecord = this.props.reserve;
return (
<div className="tree-item">
<ul>
@@ -248,7 +248,7 @@ class DenominationList extends ImplicitStateComponent<DenominationListProps> {
}
class ReserveList extends ImplicitStateComponent<ReserveListProps> {
- reserves = this.makeState<Reserve[] | null>(null);
+ reserves = this.makeState<ReserveRecord[] | null>(null);
expanded = this.makeState<boolean>(false);
constructor(props: ReserveListProps) {