aboutsummaryrefslogtreecommitdiff
path: root/extension/background/wallet.ts
diff options
context:
space:
mode:
Diffstat (limited to 'extension/background/wallet.ts')
-rw-r--r--extension/background/wallet.ts947
1 files changed, 493 insertions, 454 deletions
diff --git a/extension/background/wallet.ts b/extension/background/wallet.ts
index d9187f14a..6479b961a 100644
--- a/extension/background/wallet.ts
+++ b/extension/background/wallet.ts
@@ -131,6 +131,54 @@ interface Reserve {
}
+interface PaymentResponse {
+ payUrl: string;
+ payReq: any;
+}
+
+
+interface ConfirmReserveRequest {
+ /**
+ * Name of the form field for the amount.
+ */
+ field_amount;
+
+ /**
+ * Name of the form field for the reserve public key.
+ */
+ field_reserve_pub;
+
+ /**
+ * Name of the form field for the reserve public key.
+ */
+ field_mint;
+
+ /**
+ * The actual amount in string form.
+ * TODO: where is this format specified?
+ */
+ amount_str;
+
+ /**
+ * Target URL for the reserve creation request.
+ */
+ post_url;
+
+ /**
+ * Mint URL where the bank should create the reserve.
+ */
+ mint;
+}
+
+
+interface ConfirmReserveResponse {
+ backlink: string;
+ success: boolean;
+ status: number;
+ text: string;
+}
+
+
type PayCoinInfo = Array<{ updatedCoin: Db.Coin, sig: CoinPaySig_interface }>;
@@ -148,521 +196,512 @@ function canonicalizeBaseUrl(url) {
return x.href()
}
+interface HttpRequestLibrary {
-function signDeposit(db: IDBDatabase,
- offer: Offer,
- cds: Db.CoinWithDenom[]): PayCoinInfo {
- let ret = [];
- let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
- let amountRemaining = new 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 Amount(cd.coin.currentAmount)) < 0) {
- coinSpend = new Amount(amountRemaining.toJson());
- } else {
- coinSpend = new Amount(cd.coin.currentAmount);
- }
+}
- amountSpent.add(coinSpend);
- amountRemaining.sub(coinSpend);
-
- let newAmount = new Amount(cd.coin.currentAmount);
- newAmount.sub(coinSpend);
- cd.coin.currentAmount = newAmount.toJson();
-
- let args: DepositRequestPS_Args = {
- h_contract: HashCode.fromCrock(offer.H_contract),
- h_wire: HashCode.fromCrock(offer.contract.H_wire),
- amount_with_fee: coinSpend.toNbo(),
- coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub),
- deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(),
- merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
- refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
- timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
- transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
- };
+interface Badge {
- let d = new DepositRequestPS(args);
+}
- let coinSig = eddsaSign(d.toPurpose(),
- EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
- .toCrock();
- let s: CoinPaySig_interface = {
- 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;
+function copy(o) {
+ return JSON.parse(JSON.stringify(o));
}
-/**
- * Get mints and associated coins that are still spendable,
- * but only if the sum the coins' remaining value exceeds the payment amount.
- * @param db
- * @param paymentAmount
- * @param depositFeeLimit
- * @param allowedMints
- */
-function getPossibleMintCoins(db: IDBDatabase,
- paymentAmount: AmountJson_interface,
- depositFeeLimit: AmountJson_interface,
- allowedMints: MintInfo[]): Promise<MintCoins> {
+function rankDenom(denom1: any, denom2: any) {
+ // Slow ... we should find a better way than to convert it evert time.
+ let v1 = new Amount(denom1.value);
+ let v2 = new Amount(denom2.value);
+ return (-1) * v1.cmp(v2);
+}
- let m: MintCoins = {};
+class Wallet {
+ private db: IDBDatabase;
+ private http: HttpRequestLibrary;
+ private badge: Badge;
- function storeMintCoin(mc) {
- let mint = mc[0];
- let coin = mc[1];
- let cd = {
- coin: coin,
- denom: mint.keys.denoms.find((e) => e.denom_pub === coin.denomPub)
- };
- if (!cd.denom) {
- throw Error("denom not found (database inconsistent)");
- }
- let x = m[mint.baseUrl];
- if (!x) {
- m[mint.baseUrl] = [cd];
- } else {
- x.push(cd);
- }
+ constructor(db: IDBDatabase, http: HttpRequestLibrary, badge: Badge) {
+ this.db = db;
+ this.http = http;
+ this.badge = badge;
}
- let ps = allowedMints.map((info) => {
- return Query(db)
- .iterIndex("mints", "pubKey", info.master_pub)
- .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
- .reduce(storeMintCoin);
- });
-
- return Promise.all(ps).then(() => {
- let ret: MintCoins = {};
-
- nextMint:
- for (let key in m) {
- let coins = m[key].map((x) => ({
- a: new Amount(x.denom.fee_deposit),
- c: x
- }));
- // Sort by ascending deposit fee
- coins.sort((o1, o2) => o1.a.cmp(o2.a));
- let maxFee = new Amount(depositFeeLimit);
- let minAmount = new Amount(paymentAmount);
- let accFee = new Amount(coins[0].c.denom.fee_deposit);
- let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
- let usableCoins: Db.CoinWithDenom[] = [];
- nextCoin:
- for (let i = 0; i < coins.length; i++) {
- let coinAmount = new Amount(coins[i].c.coin.currentAmount);
- let coinFee = coins[i].a;
- if (coinAmount.cmp(coinFee) <= 0) {
- continue nextCoin;
- }
- accFee.add(coinFee);
- accAmount.add(coinAmount);
- if (accFee.cmp(maxFee) >= 0) {
- console.log("too much fees");
- continue nextMint;
- }
- usableCoins.push(coins[i].c);
- if (accAmount.cmp(minAmount) >= 0) {
- ret[key] = usableCoins;
- continue nextMint;
- }
- }
+ static signDeposit(offer: Offer,
+ cds: Db.CoinWithDenom[]): PayCoinInfo {
+ let ret = [];
+ let amountSpent = Amount.getZero(cds[0].coin.currentAmount.currency);
+ let amountRemaining = new Amount(offer.contract.amount);
+ cds = copy(cds);
+ for (let cd of cds) {
+ let coinSpend;
+
+ if (amountRemaining.value == 0 && amountRemaining.fraction == 0) {
+ break;
}
- return ret;
- });
-}
-
-function executePay(db,
- offer: Offer,
- payCoinInfo: PayCoinInfo,
- merchantBaseUrl: string,
- chosenMint: string): Promise<void> {
- let payReq = {};
- payReq["H_wire"] = offer.contract.H_wire;
- payReq["H_contract"] = offer.H_contract;
- payReq["transaction_id"] = offer.contract.transaction_id;
- payReq["refund_deadline"] = offer.contract.refund_deadline;
- payReq["mint"] = URI(chosenMint).href();
- payReq["coins"] = payCoinInfo.map((x) => x.sig);
- payReq["timestamp"] = offer.contract.timestamp;
- let payUrl = URI(offer.pay_url).absoluteTo(merchantBaseUrl);
- let t: Transaction = {
- contractHash: offer.H_contract,
- contract: offer.contract,
- payUrl: payUrl.href(),
- payReq: payReq
- };
-
- return Query(db)
- .put("transactions", t)
- .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
- .finish();
-}
-
-
-function confirmPayHandler(db, detail: ConfirmPayRequest, sendResponse) {
- let offer: Offer = detail.offer;
- getPossibleMintCoins(db,
- offer.contract.amount,
- offer.contract.max_fee,
- offer.contract.mints)
- .then((mcs) => {
- if (Object.keys(mcs).length == 0) {
- sendResponse({error: "Not enough coins."});
- return;
+ if (amountRemaining.cmp(new Amount(cd.coin.currentAmount)) < 0) {
+ coinSpend = new Amount(amountRemaining.toJson());
+ } else {
+ coinSpend = new Amount(cd.coin.currentAmount);
}
- let mintUrl = Object.keys(mcs)[0];
- let ds = signDeposit(db, offer, mcs[mintUrl]);
- return executePay(db, offer, ds, detail.merchantPageUrl, mintUrl)
- .then(() => {
- sendResponse({
- success: true,
- });
- });
- });
- return true;
-}
+ amountSpent.add(coinSpend);
+ amountRemaining.sub(coinSpend);
+
+ let newAmount = new Amount(cd.coin.currentAmount);
+ newAmount.sub(coinSpend);
+ cd.coin.currentAmount = newAmount.toJson();
+
+ let args: DepositRequestPS_Args = {
+ h_contract: HashCode.fromCrock(offer.H_contract),
+ h_wire: HashCode.fromCrock(offer.contract.H_wire),
+ amount_with_fee: coinSpend.toNbo(),
+ coin_pub: EddsaPublicKey.fromCrock(cd.coin.coinPub),
+ deposit_fee: new Amount(cd.denom.fee_deposit).toNbo(),
+ merchant: EddsaPublicKey.fromCrock(offer.contract.merchant_pub),
+ refund_deadline: AbsoluteTimeNbo.fromTalerString(offer.contract.refund_deadline),
+ timestamp: AbsoluteTimeNbo.fromTalerString(offer.contract.timestamp),
+ transaction_id: UInt64.fromNumber(offer.contract.transaction_id),
+ };
-function doPaymentHandler(db, detail, sendResponse) {
- let H_contract = detail.H_contract;
- Query(db)
- .get("transactions", H_contract)
- .then((r) => {
- if (!r) {
- sendResponse({success: false, error: "contract not found"});
- return;
- }
- sendResponse({
- success: true,
- payUrl: r.payUrl,
- payReq: r.payReq
- });
- });
- // async sendResponse
- return true;
-}
+ let d = new DepositRequestPS(args);
+ let coinSig = eddsaSign(d.toPurpose(),
+ EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
+ .toCrock();
-function confirmReserveHandler(db, detail, sendResponse) {
- let reservePriv = EddsaPrivateKey.create();
- let reservePub = reservePriv.getPublicKey();
- let form = new FormData();
- let now = (new Date()).toString();
- form.append(detail.field_amount, detail.amount_str);
- form.append(detail.field_reserve_pub, reservePub.toCrock());
- form.append(detail.field_mint, detail.mint);
- // XXX: set bank-specified fields.
- let mintBaseUrl = canonicalizeBaseUrl(detail.mint);
- httpPostForm(detail.post_url, form)
- .then((hresp) => {
- // TODO: extract as interface
- let resp = {
- status: hresp.status,
- text: hresp.responseText,
- success: undefined,
- backlink: undefined
- };
- let reserveRecord = {
- reserve_pub: reservePub.toCrock(),
- reserve_priv: reservePriv.toCrock(),
- mint_base_url: mintBaseUrl,
- created: now,
- last_query: null,
- current_amount: null,
- // XXX: set to actual amount
- initial_amount: null
+ let s: CoinPaySig_interface = {
+ 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;
+ }
- if (hresp.status != 200) {
- resp.success = false;
- return resp;
- }
- resp.success = true;
- // We can't show the page directly, so
- // we show some generic page from the wallet.
- // TODO: this should not be webextensions-specific
- resp.backlink = chrome.extension.getURL("pages/reserve-success.html");
- return Query(db)
- .put("reserves", reserveRecord)
- .finish()
- .then(() => {
- // Do this in the background
- updateMintFromUrl(db, reserveRecord.mint_base_url)
- .then((mint) =>
- updateReserve(db, reservePub, mint)
- .then((reserve) => depleteReserve(db, reserve, mint))
- );
- return resp;
- });
- })
- .then((resp) => {
- sendResponse(resp);
- });
+ /**
+ * Get mints and associated coins that are still spendable,
+ * but only if the sum the coins' remaining value exceeds the payment amount.
+ * @param paymentAmount
+ * @param depositFeeLimit
+ * @param allowedMints
+ */
+ getPossibleMintCoins(paymentAmount: AmountJson_interface,
+ depositFeeLimit: AmountJson_interface,
+ allowedMints: MintInfo[]): Promise<MintCoins> {
- // Allow async response
- return true;
-}
+ let m: MintCoins = {};
-function copy(o) {
- return JSON.parse(JSON.stringify(o));
-}
+ function storeMintCoin(mc) {
+ let mint = mc[0];
+ let coin = mc[1];
+ let cd = {
+ coin: coin,
+ denom: mint.keys.denoms.find((e) => e.denom_pub === coin.denomPub)
+ };
+ if (!cd.denom) {
+ throw Error("denom not found (database inconsistent)");
+ }
+ let x = m[mint.baseUrl];
+ if (!x) {
+ m[mint.baseUrl] = [cd];
+ } else {
+ x.push(cd);
+ }
+ }
+ let ps = allowedMints.map((info) => {
+ return Query(this.db)
+ .iterIndex("mints", "pubKey", info.master_pub)
+ .indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
+ .reduce(storeMintCoin);
+ });
-function rankDenom(denom1: any, denom2: any) {
- // Slow ... we should find a better way than to convert it evert time.
- let v1 = new Amount(denom1.value);
- let v2 = new Amount(denom2.value);
- return (-1) * v1.cmp(v2);
-}
+ return Promise.all(ps).then(() => {
+ let ret: MintCoins = {};
+
+ nextMint:
+ for (let key in m) {
+ let coins = m[key].map((x) => ({
+ a: new Amount(x.denom.fee_deposit),
+ c: x
+ }));
+ // Sort by ascending deposit fee
+ coins.sort((o1, o2) => o1.a.cmp(o2.a));
+ let maxFee = new Amount(depositFeeLimit);
+ let minAmount = new Amount(paymentAmount);
+ let accFee = new Amount(coins[0].c.denom.fee_deposit);
+ let accAmount = Amount.getZero(coins[0].c.coin.currentAmount.currency);
+ let usableCoins: Db.CoinWithDenom[] = [];
+ nextCoin:
+ for (let i = 0; i < coins.length; i++) {
+ let coinAmount = new Amount(coins[i].c.coin.currentAmount);
+ let coinFee = coins[i].a;
+ if (coinAmount.cmp(coinFee) <= 0) {
+ continue nextCoin;
+ }
+ accFee.add(coinFee);
+ accAmount.add(coinAmount);
+ if (accFee.cmp(maxFee) >= 0) {
+ console.log("too much fees");
+ continue nextMint;
+ }
+ usableCoins.push(coins[i].c);
+ if (accAmount.cmp(minAmount) >= 0) {
+ ret[key] = usableCoins;
+ continue nextMint;
+ }
+ }
+ }
+ return ret;
+ });
+ }
-function withdrawPrepare(db: IDBDatabase,
- denom: Db.Denomination,
- reserve: Reserve): Promise<Db.PreCoin> {
- let reservePriv = new EddsaPrivateKey();
- reservePriv.loadCrock(reserve.reserve_priv);
- let reservePub = new EddsaPublicKey();
- reservePub.loadCrock(reserve.reserve_pub);
- let denomPub = RsaPublicKey.fromCrock(denom.denom_pub);
- let coinPriv = EddsaPrivateKey.create();
- let coinPub = coinPriv.getPublicKey();
- let blindingFactor = RsaBlindingKey.create(1024);
- let pubHash: HashCode = coinPub.hash();
- let ev: ByteArray = rsaBlind(pubHash, blindingFactor, denomPub);
-
- if (!denom.fee_withdraw) {
- throw Error("Field fee_withdraw missing");
- }
+ executePay(offer: Offer,
+ payCoinInfo: PayCoinInfo,
+ merchantBaseUrl: string,
+ chosenMint: string): Promise<void> {
+ let payReq = {};
+ payReq["H_wire"] = offer.contract.H_wire;
+ payReq["H_contract"] = offer.H_contract;
+ payReq["transaction_id"] = offer.contract.transaction_id;
+ payReq["refund_deadline"] = offer.contract.refund_deadline;
+ payReq["mint"] = URI(chosenMint).href();
+ payReq["coins"] = payCoinInfo.map((x) => x.sig);
+ payReq["timestamp"] = offer.contract.timestamp;
+ let payUrl = URI(offer.pay_url).absoluteTo(merchantBaseUrl);
+ let t: Transaction = {
+ contractHash: offer.H_contract,
+ contract: offer.contract,
+ payUrl: payUrl.href(),
+ payReq: payReq
+ };
- let amountWithFee = new Amount(denom.value);
- amountWithFee.add(new Amount(denom.fee_withdraw));
- let withdrawFee = new Amount(denom.fee_withdraw);
-
- // Signature
- let withdrawRequest = new WithdrawRequestPS({
- reserve_pub: reservePub,
- amount_with_fee: amountWithFee.toNbo(),
- withdraw_fee: withdrawFee.toNbo(),
- h_denomination_pub: denomPub.encode().hash(),
- h_coin_envelope: ev.hash()
- });
-
- var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
-
- let preCoin: Db.PreCoin = {
- reservePub: reservePub.toCrock(),
- blindingKey: blindingFactor.toCrock(),
- coinPub: coinPub.toCrock(),
- coinPriv: coinPriv.toCrock(),
- denomPub: denomPub.encode().toCrock(),
- mintBaseUrl: reserve.mint_base_url,
- withdrawSig: sig.toCrock(),
- coinEv: ev.toCrock(),
- coinValue: denom.value
- };
-
- return Query(db).put("precoins", preCoin).finish().then(() => preCoin);
-}
+ return Query(this.db)
+ .put("transactions", t)
+ .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))
+ .finish();
+ }
+ confirmPay(offer: Offer, merchantPageUrl: string): Promise<any> {
+ return Promise.resolve().then(() => {
+ return this.getPossibleMintCoins(offer.contract.amount,
+ offer.contract.max_fee,
+ offer.contract.mints)
+ }).then((mcs) => {
+ if (Object.keys(mcs).length == 0) {
+ throw Error("Not enough coins.");
+ }
+ let mintUrl = Object.keys(mcs)[0];
+ let ds = Wallet.signDeposit(offer, mcs[mintUrl]);
+ return this.executePay(offer, ds, merchantPageUrl, mintUrl);
+ });
+ }
-function withdrawExecute(db, pc: Db.PreCoin): Promise<Db.Coin> {
- return Query(db)
- .get("reserves", pc.reservePub)
- .then((r) => {
- let wd: any = {};
- wd.denom_pub = pc.denomPub;
- wd.reserve_pub = pc.reservePub;
- wd.reserve_sig = pc.withdrawSig;
- wd.coin_ev = pc.coinEv;
- let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
- return httpPostJson(reqUrl, wd);
- })
- .then(resp => {
- if (resp.status != 200) {
- throw new RequestException({
- hint: "Withdrawal failed",
- status: resp.status
+ doPayment(H_contract): Promise<PaymentResponse> {
+ return Promise.resolve().then(() => {
+ return Query(this.db)
+ .get("transactions", H_contract)
+ .then((t) => {
+ if (!t) {
+ throw Error("contract not found");
+ }
+ let resp: PaymentResponse = {
+ payUrl: t.payUrl,
+ payReq: t.payReq
+ };
+ return resp;
});
- }
- let r = JSON.parse(resp.responseText);
- let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig),
- RsaBlindingKey.fromCrock(pc.blindingKey),
- RsaPublicKey.fromCrock(pc.denomPub));
- let coin: Db.Coin = {
- coinPub: pc.coinPub,
- coinPriv: pc.coinPriv,
- denomPub: pc.denomPub,
- denomSig: denomSig.encode().toCrock(),
- currentAmount: pc.coinValue,
- mintBaseUrl: pc.mintBaseUrl,
- };
- return coin;
});
-}
+ }
+ confirmReserve(req: ConfirmReserveRequest): Promise<ConfirmReserveResponse> {
+ let reservePriv = EddsaPrivateKey.create();
+ let reservePub = reservePriv.getPublicKey();
+ let form = new FormData();
+ let now = (new Date()).toString();
+ form.append(req.field_amount, req.amount_str);
+ form.append(req.field_reserve_pub, reservePub.toCrock());
+ form.append(req.field_mint, req.mint);
+ // TODO: set bank-specified fields.
+ let mintBaseUrl = canonicalizeBaseUrl(req.mint);
+
+ return httpPostForm(req.post_url, form)
+ .then((hresp) => {
+ let resp: ConfirmReserveResponse = {
+ status: hresp.status,
+ text: hresp.responseText,
+ success: undefined,
+ backlink: undefined
+ };
+ let reserveRecord = {
+ reserve_pub: reservePub.toCrock(),
+ reserve_priv: reservePriv.toCrock(),
+ mint_base_url: mintBaseUrl,
+ created: now,
+ last_query: null,
+ current_amount: null,
+ // XXX: set to actual amount
+ initial_amount: null
+ };
+
+ if (hresp.status != 200) {
+ resp.success = false;
+ return resp;
+ }
-function updateBadge(db) {
- function countNonEmpty(c, n) {
- if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
- return n + 1;
- }
- return n;
+ resp.success = true;
+ // We can't show the page directly, so
+ // we show some generic page from the wallet.
+ resp.backlink = null;
+ return Query(this.db)
+ .put("reserves", reserveRecord)
+ .finish()
+ .then(() => {
+ // Do this in the background
+ this.updateMintFromUrl(reserveRecord.mint_base_url)
+ .then((mint) =>
+ this.updateReserve(reservePub, mint)
+ .then((reserve) => this.depleteReserve(reserve,
+ mint))
+ );
+ return resp;
+ });
+ });
}
- function doBadge(n) {
- chrome.browserAction.setBadgeText({text: "" + n});
- chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
- }
+ withdrawPrepare(denom: Db.Denomination,
+ reserve: Reserve): Promise<Db.PreCoin> {
+ let reservePriv = new EddsaPrivateKey();
+ reservePriv.loadCrock(reserve.reserve_priv);
+ let reservePub = new EddsaPublicKey();
+ reservePub.loadCrock(reserve.reserve_pub);
+ let denomPub = RsaPublicKey.fromCrock(denom.denom_pub);
+ let coinPriv = EddsaPrivateKey.create();
+ let coinPub = coinPriv.getPublicKey();
+ let blindingFactor = RsaBlindingKey.create(1024);
+ let pubHash: HashCode = coinPub.hash();
+ let ev: ByteArray = rsaBlind(pubHash, blindingFactor, denomPub);
+
+ if (!denom.fee_withdraw) {
+ throw Error("Field fee_withdraw missing");
+ }
- Query(db)
- .iter("coins")
- .reduce(countNonEmpty, 0)
- .then(doBadge);
-}
+ let amountWithFee = new Amount(denom.value);
+ amountWithFee.add(new Amount(denom.fee_withdraw));
+ let withdrawFee = new Amount(denom.fee_withdraw);
+
+ // Signature
+ let withdrawRequest = new WithdrawRequestPS({
+ reserve_pub: reservePub,
+ amount_with_fee: amountWithFee.toNbo(),
+ withdraw_fee: withdrawFee.toNbo(),
+ h_denomination_pub: denomPub.encode().hash(),
+ h_coin_envelope: ev.hash()
+ });
+ var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv);
+
+ let preCoin: Db.PreCoin = {
+ reservePub: reservePub.toCrock(),
+ blindingKey: blindingFactor.toCrock(),
+ coinPub: coinPub.toCrock(),
+ coinPriv: coinPriv.toCrock(),
+ denomPub: denomPub.encode().toCrock(),
+ mintBaseUrl: reserve.mint_base_url,
+ withdrawSig: sig.toCrock(),
+ coinEv: ev.toCrock(),
+ coinValue: denom.value
+ };
-function storeCoin(db, coin: Db.Coin) {
- Query(db)
- .delete("precoins", coin.coinPub)
- .add("coins", coin)
- .finish()
- .then(() => {
- updateBadge(db);
- });
-}
+ return Query(this.db).put("precoins", preCoin).finish().then(() => preCoin);
+ }
-function withdraw(db, denom, reserve): Promise<void> {
- return withdrawPrepare(db, denom, reserve)
- .then((pc) => withdrawExecute(db, pc))
- .then((c) => storeCoin(db, c));
-}
+ withdrawExecute(pc: Db.PreCoin): Promise<Db.Coin> {
+ return Query(this.db)
+ .get("reserves", pc.reservePub)
+ .then((r) => {
+ let wd: any = {};
+ wd.denom_pub = pc.denomPub;
+ wd.reserve_pub = pc.reservePub;
+ wd.reserve_sig = pc.withdrawSig;
+ wd.coin_ev = pc.coinEv;
+ let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
+ return httpPostJson(reqUrl, wd);
+ })
+ .then(resp => {
+ if (resp.status != 200) {
+ throw new RequestException({
+ hint: "Withdrawal failed",
+ status: resp.status
+ });
+ }
+ let r = JSON.parse(resp.responseText);
+ let denomSig = rsaUnblind(RsaSignature.fromCrock(r.ev_sig),
+ RsaBlindingKey.fromCrock(pc.blindingKey),
+ RsaPublicKey.fromCrock(pc.denomPub));
+ let coin: Db.Coin = {
+ coinPub: pc.coinPub,
+ coinPriv: pc.coinPriv,
+ denomPub: pc.denomPub,
+ denomSig: denomSig.encode().toCrock(),
+ currentAmount: pc.coinValue,
+ mintBaseUrl: pc.mintBaseUrl,
+ };
+ return coin;
+ });
+ }
-/**
- * Withdraw coins from a reserve until it is empty.
- */
-function depleteReserve(db, reserve, mint): void {
- let denoms = copy(mint.keys.denoms);
- let remaining = new Amount(reserve.current_amount);
- denoms.sort(rankDenom);
- let workList = [];
- for (let i = 0; i < 1000; i++) {
- let found = false;
- for (let d of denoms) {
- let cost = new Amount(d.value);
- cost.add(new Amount(d.fee_withdraw));
- if (remaining.cmp(cost) < 0) {
- continue;
+ updateBadge() {
+ function countNonEmpty(c, n) {
+ if (c.currentAmount.fraction != 0 || c.currentAmount.value != 0) {
+ return n + 1;
}
- found = true;
- remaining.sub(cost);
- workList.push(d);
+ return n;
}
- if (!found) {
- console.log("did not find coins for remaining ", remaining.toJson());
- break;
+
+ function doBadge(n) {
+ chrome.browserAction.setBadgeText({text: "" + n});
+ chrome.browserAction.setBadgeBackgroundColor({color: "#0F0"});
}
+
+ Query(this.db)
+ .iter("coins")
+ .reduce(countNonEmpty, 0)
+ .then(doBadge);
}
- // Do the request one by one.
- function next(): void {
- if (workList.length == 0) {
- return;
- }
- let d = workList.pop();
- withdraw(db, d, reserve)
- .then(() => next());
+ storeCoin(coin: Db.Coin) {
+ Query(this.db)
+ .delete("precoins", coin.coinPub)
+ .add("coins", coin)
+ .finish()
+ .then(() => {
+ this.updateBadge();
+ });
}
- next();
-}
+ withdraw(denom, reserve): Promise<void> {
+ return this.withdrawPrepare(denom, reserve)
+ .then((pc) => this.withdrawExecute(pc))
+ .then((c) => this.storeCoin(c));
+ }
-function updateReserve(db: IDBDatabase,
- reservePub: EddsaPublicKey,
- mint): Promise<Reserve> {
- let reservePubStr = reservePub.toCrock();
- return Query(db)
- .get("reserves", reservePubStr)
- .then((reserve) => {
- let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
- reqUrl.query({'reserve_pub': reservePubStr});
- return httpGet(reqUrl).then(resp => {
- if (resp.status != 200) {
- throw Error();
- }
- let reserveInfo = JSON.parse(resp.responseText);
- if (!reserveInfo) {
- throw Error();
+ /**
+ * Withdraw coins from a reserve until it is empty.
+ */
+ depleteReserve(reserve, mint): void {
+ let denoms = copy(mint.keys.denoms);
+ let remaining = new Amount(reserve.current_amount);
+ denoms.sort(rankDenom);
+ let workList = [];
+ for (let i = 0; i < 1000; i++) {
+ let found = false;
+ for (let d of denoms) {
+ let cost = new Amount(d.value);
+ cost.add(new Amount(d.fee_withdraw));
+ if (remaining.cmp(cost) < 0) {
+ continue;
}
- reserve.current_amount = reserveInfo.balance;
- return Query(db)
- .put("reserves", reserve)
- .finish()
- .then(() => reserve);
+ found = true;
+ remaining.sub(cost);
+ workList.push(d);
+ }
+ if (!found) {
+ console.log("did not find coins for remaining ", remaining.toJson());
+ break;
+ }
+ }
+
+ // Do the request one by one.
+ let next = () => {
+ if (workList.length == 0) {
+ return;
+ }
+ let d = workList.pop();
+ this.withdraw(d, reserve)
+ .then(() => next());
+ };
+
+ // Asynchronous recursion
+ next();
+ }
+
+ updateReserve(reservePub: EddsaPublicKey,
+ mint): Promise<Reserve> {
+ let reservePubStr = reservePub.toCrock();
+ return Query(this.db)
+ .get("reserves", reservePubStr)
+ .then((reserve) => {
+ let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
+ reqUrl.query({'reserve_pub': reservePubStr});
+ return httpGet(reqUrl).then(resp => {
+ if (resp.status != 200) {
+ throw Error();
+ }
+ let reserveInfo = JSON.parse(resp.responseText);
+ if (!reserveInfo) {
+ throw Error();
+ }
+ reserve.current_amount = reserveInfo.balance;
+ return Query(this.db)
+ .put("reserves", reserve)
+ .finish()
+ .then(() => reserve);
+ });
});
+ }
+
+ /**
+ * Update or add mint DB entry by fetching the /keys information.
+ * Optionally link the reserve entry to the new or existing
+ * mint entry in then DB.
+ */
+ updateMintFromUrl(baseUrl) {
+ let reqUrl = URI("keys").absoluteTo(baseUrl);
+ return httpGet(reqUrl).then((resp) => {
+ if (resp.status != 200) {
+ throw Error("/keys request failed");
+ }
+ let mintKeysJson = JSON.parse(resp.responseText);
+ if (!mintKeysJson) {
+ throw new RequestException({url: reqUrl, hint: "keys invalid"});
+ }
+ let mint: Db.Mint = {
+ baseUrl: baseUrl,
+ keys: mintKeysJson
+ };
+ return Query(this.db).put("mints", mint).finish().then(() => mint);
});
-}
+ }
-/**
- * Update or add mint DB entry by fetching the /keys information.
- * Optionally link the reserve entry to the new or existing
- * mint entry in then DB.
- */
-function updateMintFromUrl(db, baseUrl) {
- let reqUrl = URI("keys").absoluteTo(baseUrl);
- return httpGet(reqUrl).then((resp) => {
- if (resp.status != 200) {
- throw Error("/keys request failed");
- }
- let mintKeysJson = JSON.parse(resp.responseText);
- if (!mintKeysJson) {
- throw new RequestException({url: reqUrl, hint: "keys invalid"});
+ getBalances(): Promise<any> {
+ function collectBalances(c: Db.Coin, byCurrency) {
+ let acc: AmountJson_interface = byCurrency[c.currentAmount.currency];
+ if (!acc) {
+ acc = Amount.getZero(c.currentAmount.currency).toJson();
+ }
+ let am = new Amount(c.currentAmount);
+ am.add(new Amount(acc));
+ byCurrency[c.currentAmount.currency] = am.toJson();
+ return byCurrency;
}
- let mint: Db.Mint = {
- baseUrl: baseUrl,
- keys: mintKeysJson
- };
- return Query(db).put("mints", mint).finish().then(() => mint);
- });
-}
-
-function getBalances(db): Promise<any> {
- function collectBalances(c: Db.Coin, byCurrency) {
- let acc: AmountJson_interface = byCurrency[c.currentAmount.currency];
- if (!acc) {
- acc = Amount.getZero(c.currentAmount.currency).toJson();
- }
- let am = new Amount(c.currentAmount);
- am.add(new Amount(acc));
- byCurrency[c.currentAmount.currency] = am.toJson();
- return byCurrency;
+ return Query(this.db)
+ .iter("coins")
+ .reduce(collectBalances, {});
}
-
- return Query(db)
- .iter("coins")
- .reduce(collectBalances, {});
}