aboutsummaryrefslogtreecommitdiff
path: root/extension/lib/wallet/wallet.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-02-18 22:50:17 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-02-18 22:50:27 +0100
commit079e764ae6f827b1fa7c12b26f370199fc5e4d48 (patch)
treee938a0868edfaa7bfc7d38b92ed26e978446f142 /extension/lib/wallet/wallet.ts
parent0f607edbb2146ff82f9dcd71a1fe2905630c0d58 (diff)
fix #4121 and #4115
Diffstat (limited to 'extension/lib/wallet/wallet.ts')
-rw-r--r--extension/lib/wallet/wallet.ts224
1 files changed, 191 insertions, 33 deletions
diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts
index 608876abf..eca937ff1 100644
--- a/extension/lib/wallet/wallet.ts
+++ b/extension/lib/wallet/wallet.ts
@@ -22,37 +22,49 @@
*/
import * as native from "./emscriptif";
-import {AmountJson, CreateReserveResponse} from "./types";
+import {AmountJson, CreateReserveResponse, IMintInfo, Denomination} from "./types";
import {HttpResponse, RequestException} from "./http";
import {Query} from "./query";
import {Checkable} from "./checkable";
import {canonicalizeBaseUrl} from "./helpers";
+import {ReserveCreationInfo} from "./types";
"use strict";
-export interface Mint {
- baseUrl: string;
- keys: Keys
-}
-
export interface CoinWithDenom {
coin: Coin;
denom: Denomination;
}
-export interface Keys {
+
+@Checkable.Class
+export class KeysJson {
+ @Checkable.List(Checkable.Value(Denomination))
denoms: Denomination[];
-}
-export interface Denomination {
- value: AmountJson;
- denom_pub: string;
- fee_withdraw: AmountJson;
- fee_deposit: AmountJson;
- stamp_expire_withdraw: string;
+ @Checkable.String
+ master_public_key: string;
+
+ @Checkable.Any
+ auditors: any[];
+
+ @Checkable.String
+ list_issue_date: string;
+
+ @Checkable.Any
+ signkeys: any;
+
+ @Checkable.String
+ eddsa_pub: string;
+
+ @Checkable.String
+ eddsa_sig: string;
+
+ static checked: (obj: any) => KeysJson;
}
+
export interface PreCoin {
coinPub: string;
coinPriv: string;
@@ -75,6 +87,115 @@ export interface Coin {
}
+function isValidDenom(denom: Denomination,
+ masterPub: string): boolean {
+ let p = new native.DenominationKeyValidityPS({
+ master: native.EddsaPublicKey.fromCrock(masterPub),
+ denom_hash: native.RsaPublicKey.fromCrock(denom.denom_pub).encode().hash(),
+ expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_legal),
+ expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_deposit),
+ expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_withdraw),
+ start: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_start),
+ value: (new native.Amount(denom.value)).toNbo(),
+ fee_deposit: (new native.Amount(denom.fee_deposit)).toNbo(),
+ fee_refresh: (new native.Amount(denom.fee_refresh)).toNbo(),
+ fee_withdraw: (new native.Amount(denom.fee_withdraw)).toNbo(),
+ });
+
+ let nativeSig = new native.EddsaSignature();
+ nativeSig.loadCrock(denom.master_sig);
+
+ let nativePub = native.EddsaPublicKey.fromCrock(masterPub);
+
+ return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
+ p.toPurpose(),
+ nativeSig,
+ nativePub);
+
+}
+
+
+class MintInfo implements IMintInfo {
+ baseUrl: string;
+ masterPublicKey: string;
+ denoms: Denomination[];
+
+ constructor(obj: {baseUrl: string} & any) {
+ this.baseUrl = obj.baseUrl;
+
+ if (obj.denoms) {
+ this.denoms = Array.from(<Denomination[]>obj.denoms);
+ } else {
+ this.denoms = [];
+ }
+
+ if (typeof obj.masterPublicKey === "string") {
+ this.masterPublicKey = obj.masterPublicKey;
+ }
+ }
+
+ static fresh(baseUrl: string): MintInfo {
+ return new MintInfo({baseUrl});
+ }
+
+ /**
+ * Merge new key information into the mint info.
+ * If the new key information is invalid (missing fields,
+ * invalid signatures), an exception is thrown, but the
+ * mint info is updated with the new information up until
+ * the first error.
+ */
+ mergeKeys(newKeys: KeysJson) {
+ if (!this.masterPublicKey) {
+ this.masterPublicKey = newKeys.master_public_key;
+ }
+
+ if (this.masterPublicKey != newKeys.master_public_key) {
+ throw Error("public keys do not match");
+ }
+
+ for (let newDenom of newKeys.denoms) {
+ let found = false;
+ for (let oldDenom of this.denoms) {
+ if (oldDenom.denom_pub === newDenom.denom_pub) {
+ let a = Object.assign({}, oldDenom);
+ let b = Object.assign({}, newDenom);
+ // pub hash is only there for convenience in the wallet
+ delete a["pub_hash"];
+ delete b["pub_hash"];
+ if (!_.isEqual(a, b)) {
+ console.log("old/new:");
+ console.dir(a);
+ console.dir(b);
+ throw Error("denomination modified");
+ }
+ // TODO: check if info still matches
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ continue;
+ }
+
+ console.log("validating denomination");
+
+ if (!isValidDenom(newDenom, this.masterPublicKey)) {
+ 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);
+ }
+ }
+}
+
+
@Checkable.Class
export class CreateReserveRequest {
/**
@@ -107,14 +228,14 @@ export class ConfirmReserveRequest {
@Checkable.Class
-export class MintInfo {
+export class MintHandle {
@Checkable.String
master_pub: string;
@Checkable.String
url: string;
- static checked: (obj: any) => MintInfo;
+ static checked: (obj: any) => MintHandle;
}
@@ -144,8 +265,8 @@ export class Contract {
@Checkable.String
merchant_pub: string;
- @Checkable.List(Checkable.Value(MintInfo))
- mints: MintInfo[];
+ @Checkable.List(Checkable.Value(MintHandle))
+ mints: MintHandle[];
@Checkable.List(Checkable.AnyObject)
products: any[];
@@ -274,6 +395,10 @@ function rankDenom(denom1: any, denom2: any) {
}
+function mergeMintKeys(oldKeys: KeysJson, newKeys: KeysJson) {
+}
+
+
/**
* Create a pre-coin of the given denomination to be withdrawn from then given
* reserve.
@@ -441,7 +566,7 @@ export class Wallet {
*/
private getPossibleMintCoins(paymentAmount: AmountJson,
depositFeeLimit: AmountJson,
- allowedMints: MintInfo[]): Promise<MintCoins> {
+ allowedMints: MintHandle[]): Promise<MintCoins> {
// Mapping from mint base URL to list of coins together with their
// denomination
let m: MintCoins = {};
@@ -639,7 +764,8 @@ export class Wallet {
return Query(this.db).put("history", depleted).finish();
})
.catch((e) => {
- console.error("Failed to deplete reserve", e.stack);
+ console.error("Failed to deplete reserve");
+ console.error(e);
});
}
@@ -791,8 +917,8 @@ export class Wallet {
/**
* Withdraw coins from a reserve until it is empty.
*/
- private depleteReserve(reserve, mint: Mint): Promise<void> {
- let denomsAvailable: Denomination[] = copy(mint.keys.denoms);
+ private depleteReserve(reserve, mint: MintInfo): Promise<void> {
+ let denomsAvailable: Denomination[] = copy(mint.denoms);
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
denomsAvailable);
@@ -811,7 +937,7 @@ export class Wallet {
* Update the information about a reserve that is stored in the wallet
* by quering the reserve's mint.
*/
- private updateReserve(reservePub: string, mint: Mint): Promise<Reserve> {
+ private updateReserve(reservePub: string, mint: MintInfo): Promise<Reserve> {
return Query(this.db)
.get("reserves", reservePub)
.then((reserve) => {
@@ -846,26 +972,58 @@ export class Wallet {
}
+ getReserveCreationInfo(baseUrl: string,
+ amount: AmountJson): Promise<ReserveCreationInfo> {
+ return this.updateMintFromUrl(baseUrl)
+ .then((mintInfo: IMintInfo) => {
+ let selectedDenoms = getWithdrawDenomList(amount,
+ mintInfo.denoms);
+
+ let acc = native.Amount.getZero(amount.currency);
+ for (let d of selectedDenoms) {
+ acc.add(new native.Amount(d.fee_withdraw));
+ }
+ let ret: ReserveCreationInfo = {
+ mintInfo,
+ selectedDenoms,
+ withdrawFee: acc.toJson(),
+ };
+ return ret;
+ });
+ }
+
+
/**
* 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.
*/
- private updateMintFromUrl(baseUrl): Promise<Mint> {
+ updateMintFromUrl(baseUrl): Promise<MintInfo> {
+ baseUrl = canonicalizeBaseUrl(baseUrl);
let reqUrl = URI("keys").absoluteTo(baseUrl);
return this.http.get(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: Mint = {
- baseUrl: baseUrl,
- keys: mintKeysJson
- };
- return Query(this.db).put("mints", mint).finish().then(() => mint);
+ let mintKeysJson = KeysJson.checked(JSON.parse(resp.responseText));
+
+ return Query(this.db).get("mints", baseUrl).then((r) => {
+ let mint;
+
+ console.log("got mints result");
+ console.dir(r);
+
+ if (!r) {
+ mint = MintInfo.fresh(baseUrl);
+ console.log("making fresh mint");
+ } else {
+ mint = new MintInfo(r);
+ console.log("using old mint");
+ }
+
+ mint.mergeKeys(mintKeysJson);
+ return Query(this.db).put("mints", mint).finish().then(() => mint);
+ });
});
}