gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-wallet-webex] 02/02: download, store and check signa


From: gnunet
Subject: [GNUnet-SVN] [taler-wallet-webex] 02/02: download, store and check signatures for wire fees
Date: Thu, 27 Apr 2017 03:09:35 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-webex.

commit 82b5754e157a1a3b22afe48c8366c76525eb91e3
Author: Florian Dold <address@hidden>
AuthorDate: Thu Apr 27 03:09:29 2017 +0200

    download, store and check signatures for wire fees
---
 src/checkable.ts    | 119 ++++++++++++++++++++++++++++++++++++++------------
 src/cryptoApi.ts    |   6 ++-
 src/cryptoWorker.ts |  21 ++++++++-
 src/emscriptif.ts   |  39 +++++++++++++++++
 src/types.ts        |  17 ++++++++
 src/wallet.ts       | 122 +++++++++++++++++++++++++++++++++++++++++++++++++---
 6 files changed, 289 insertions(+), 35 deletions(-)

diff --git a/src/checkable.ts b/src/checkable.ts
index c8cc27b..8af70f5 100644
--- a/src/checkable.ts
+++ b/src/checkable.ts
@@ -40,9 +40,17 @@ export namespace Checkable {
   interface Prop {
     propertyKey: any;
     checker: any;
-    type: any;
+    type?: any;
     elementChecker?: any;
     elementProp?: any;
+    keyProp?: any;
+    valueProp?: any;
+    optional?: boolean;
+    extraAllowed?: boolean;
+  }
+
+  interface CheckableInfo {
+    props: Prop[];
   }
 
   export let SchemaError = (function SchemaError(message: string) {
@@ -54,7 +62,24 @@ export namespace Checkable {
 
   SchemaError.prototype = new Error;
 
-  let chkSym = Symbol("checkable");
+  /**
+   * Classes that are checkable are annotated with this
+   * checkable info symbol, which contains the information necessary
+   * to check if they're valid.
+   */
+  let checkableInfoSym = Symbol("checkableInfo");
+
+  /**
+   * Get the current property list for a checkable type.
+   */
+  function getCheckableInfo(target: any): CheckableInfo {
+    let chk = target[checkableInfoSym] as CheckableInfo|undefined;
+    if (!chk) {
+      chk = { props: [] };
+      target[checkableInfoSym] = chk;
+    }
+    return chk;
+  }
 
 
   function checkNumber(target: any, prop: Prop, path: Path): any {
@@ -104,6 +129,17 @@ export namespace Checkable {
     return target;
   }
 
+  function checkMap(target: any, prop: Prop, path: Path): any {
+    if (typeof target !== "object") {
+      throw new SchemaError(`expected  object for ${path}, got ${typeof 
target} instead`);
+    }
+    for (let key in target) {
+      prop.keyProp.checker(key, prop.keyProp, path.concat([key]));
+      let value = target[key];
+      prop.valueProp.checker(value, prop.valueProp, path.concat([key]));
+    }
+  }
+
 
   function checkOptional(target: any, prop: Prop, path: Path): any {
     console.assert(prop.propertyKey);
@@ -124,7 +160,7 @@ export namespace Checkable {
       throw new SchemaError(
         `expected object for ${path.join(".")}, got ${typeof v} instead`);
     }
-    let props = type.prototype[chkSym].props;
+    let props = type.prototype[checkableInfoSym].props;
     let remainingPropNames = new Set(Object.getOwnPropertyNames(v));
     let obj = new type();
     for (let prop of props) {
@@ -132,7 +168,7 @@ export namespace Checkable {
         if (prop.optional) {
           continue;
         }
-        throw new SchemaError("Property missing: " + prop.propertyKey);
+        throw new SchemaError(`Property ${prop.propertyKey} missing on 
${path}`);
       }
       if (!remainingPropNames.delete(prop.propertyKey)) {
         throw new SchemaError("assertion failed");
@@ -143,7 +179,7 @@ export namespace Checkable {
         path.concat([prop.propertyKey]));
     }
 
-    if (remainingPropNames.size != 0) {
+    if (!prop.extraAllowed && remainingPropNames.size != 0) {
       throw new SchemaError("superfluous properties " + 
JSON.stringify(Array.from(
         remainingPropNames.values())));
     }
@@ -162,6 +198,18 @@ export namespace Checkable {
     return target;
   }
 
+  export function ClassWithExtra(target: any) {
+    target.checked = (v: any) => {
+      return checkValue(v, {
+        propertyKey: "(root)",
+        type: target,
+        extraAllowed: true,
+        checker: checkValue
+      }, ["(root)"]);
+    };
+    return target;
+  }
+
 
   export function ClassWithValidator(target: any) {
     target.checked = (v: any) => {
@@ -187,7 +235,7 @@ export namespace Checkable {
       throw Error("Type does not exist yet (wrong order of definitions?)");
     }
     function deco(target: Object, propertyKey: string | symbol): void {
-      let chk = mkChk(target);
+      let chk = getCheckableInfo(target);
       chk.props.push({
         propertyKey: propertyKey,
         checker: checkValue,
@@ -202,13 +250,13 @@ export namespace Checkable {
   export function List(type: any) {
     let stub = {};
     type(stub, "(list-element)");
-    let elementProp = mkChk(stub).props[0];
+    let elementProp = getCheckableInfo(stub).props[0];
     let elementChecker = elementProp.checker;
     if (!elementChecker) {
       throw Error("assertion failed");
     }
     function deco(target: Object, propertyKey: string | symbol): void {
-      let chk = mkChk(target);
+      let chk = getCheckableInfo(target);
       chk.props.push({
         elementChecker,
         elementProp,
@@ -221,16 +269,43 @@ export namespace Checkable {
   }
 
 
+  export function Map(keyType: any, valueType: any) {
+    let keyStub = {};
+    keyType(keyStub, "(map-key)");
+    let keyProp = getCheckableInfo(keyStub).props[0];
+    if (!keyProp) {
+      throw Error("assertion failed");
+    }
+    let valueStub = {};
+    valueType(valueStub, "(map-value)");
+    let valueProp = getCheckableInfo(valueStub).props[0];
+    if (!valueProp) {
+      throw Error("assertion failed");
+    }
+    function deco(target: Object, propertyKey: string | symbol): void {
+      let chk = getCheckableInfo(target);
+      chk.props.push({
+        keyProp,
+        valueProp,
+        propertyKey: propertyKey,
+        checker: checkMap,
+      });
+    }
+
+    return deco;
+  }
+
+
   export function Optional(type: any) {
     let stub = {};
     type(stub, "(optional-element)");
-    let elementProp = mkChk(stub).props[0];
+    let elementProp = getCheckableInfo(stub).props[0];
     let elementChecker = elementProp.checker;
     if (!elementChecker) {
       throw Error("assertion failed");
     }
     function deco(target: Object, propertyKey: string | symbol): void {
-      let chk = mkChk(target);
+      let chk = getCheckableInfo(target);
       chk.props.push({
         elementChecker,
         elementProp,
@@ -245,14 +320,13 @@ export namespace Checkable {
 
 
   export function Number(target: Object, propertyKey: string | symbol): void {
-    let chk = mkChk(target);
+    let chk = getCheckableInfo(target);
     chk.props.push({ propertyKey: propertyKey, checker: checkNumber });
   }
 
 
-  export function AnyObject(target: Object,
-    propertyKey: string | symbol): void {
-    let chk = mkChk(target);
+  export function AnyObject(target: Object, propertyKey: string | symbol): 
void {
+    let chk = getCheckableInfo(target);
     chk.props.push({
       propertyKey: propertyKey,
       checker: checkAnyObject
@@ -260,9 +334,8 @@ export namespace Checkable {
   }
 
 
-  export function Any(target: Object,
-    propertyKey: string | symbol): void {
-    let chk = mkChk(target);
+  export function Any(target: Object, propertyKey: string | symbol): void {
+    let chk = getCheckableInfo(target);
     chk.props.push({
       propertyKey: propertyKey,
       checker: checkAny,
@@ -272,22 +345,14 @@ export namespace Checkable {
 
 
   export function String(target: Object, propertyKey: string | symbol): void {
-    let chk = mkChk(target);
+    let chk = getCheckableInfo(target);
     chk.props.push({ propertyKey: propertyKey, checker: checkString });
   }
 
   export function Boolean(target: Object, propertyKey: string | symbol): void {
-    let chk = mkChk(target);
+    let chk = getCheckableInfo(target);
     chk.props.push({ propertyKey: propertyKey, checker: checkBoolean });
   }
 
 
-  function mkChk(target: any) {
-    let chk = target[chkSym];
-    if (!chk) {
-      chk = { props: [] };
-      target[chkSym] = chk;
-    }
-    return chk;
-  }
 }
diff --git a/src/cryptoApi.ts b/src/cryptoApi.ts
index 98fc2c6..5657d74 100644
--- a/src/cryptoApi.ts
+++ b/src/cryptoApi.ts
@@ -28,7 +28,7 @@ import {
 import {OfferRecord} from "./wallet";
 import {CoinWithDenom} from "./wallet";
 import {PayCoinInfo} from "./types";
-import {RefreshSessionRecord} from "./types";
+import {RefreshSessionRecord, WireFee} from "./types";
 
 
 interface WorkerState {
@@ -235,6 +235,10 @@ export class CryptoApi {
     return this.doRpc<boolean>("isValidDenom", 2, denom, masterPub);
   }
 
+  isValidWireFee(type: string, wf: WireFee, masterPub: string): 
Promise<boolean> {
+    return this.doRpc<boolean>("isValidWireFee", 2, type, wf, masterPub);
+  }
+
   isValidPaymentSignature(sig: string, contractHash: string, merchantPub: 
string) {
     return this.doRpc<PayCoinInfo>("isValidPaymentSignature", 1, sig, 
contractHash, merchantPub);
   }
diff --git a/src/cryptoWorker.ts b/src/cryptoWorker.ts
index cb7bee4..4275d65 100644
--- a/src/cryptoWorker.ts
+++ b/src/cryptoWorker.ts
@@ -30,7 +30,7 @@ import create = chrome.alarms.create;
 import {OfferRecord} from "./wallet";
 import {CoinWithDenom} from "./wallet";
 import {CoinPaySig, CoinRecord} from "./types";
-import {DenominationRecord, Amounts} from "./types";
+import {DenominationRecord, Amounts, WireFee} from "./types";
 import {Amount} from "./emscriptif";
 import {HashContext} from "./emscriptif";
 import {RefreshMeltCoinAffirmationPS} from "./emscriptif";
@@ -110,6 +110,25 @@ namespace RpcFunctions {
                               nativePub);
   }
 
+  export function isValidWireFee(type: string, wf: WireFee, masterPub: 
string): boolean {
+    let p = new native.MasterWireFeePS({
+      h_wire_method: native.ByteArray.fromStringWithNull(type).hash(),
+      start_date: native.AbsoluteTimeNbo.fromStamp(wf.startStamp),
+      end_date: native.AbsoluteTimeNbo.fromStamp(wf.endStamp),
+      wire_fee: (new native.Amount(wf.wireFee)).toNbo(),
+      closing_fee: (new native.Amount(wf.closingFee)).toNbo(),
+    });
+
+    let nativeSig = new native.EddsaSignature();
+    nativeSig.loadCrock(wf.sig);
+    let nativePub = native.EddsaPublicKey.fromCrock(masterPub);
+
+    return native.eddsaVerify(native.SignaturePurpose.MASTER_WIRE_FEES,
+                              p.toPurpose(),
+                              nativeSig,
+                              nativePub);
+  }
+
 
   export function isValidDenom(denom: DenominationRecord,
                                masterPub: string): boolean {
diff --git a/src/emscriptif.ts b/src/emscriptif.ts
index 3a34f64..3f23476 100644
--- a/src/emscriptif.ts
+++ b/src/emscriptif.ts
@@ -207,6 +207,7 @@ export enum SignaturePurpose {
   WALLET_COIN_MELT = 1202,
   TEST = 4242,
   MERCHANT_PAYMENT_OK = 1104,
+  MASTER_WIRE_FEES = 1028,
 }
 
 
@@ -993,6 +994,35 @@ export class RefreshMeltCoinAffirmationPS extends 
SignatureStruct {
 }
 
 
+interface MasterWireFeePS_Args {
+  h_wire_method: HashCode;
+  start_date: AbsoluteTimeNbo;
+  end_date: AbsoluteTimeNbo;
+  wire_fee: AmountNbo;
+  closing_fee: AmountNbo;
+}
+
+export class MasterWireFeePS extends SignatureStruct {
+  constructor(w: MasterWireFeePS_Args) {
+    super(w);
+  }
+
+  purpose() {
+    return SignaturePurpose.MASTER_WIRE_FEES;
+  }
+
+  fieldTypes() {
+    return [
+      ["h_wire_method", HashCode],
+      ["start_date", AbsoluteTimeNbo],
+      ["end_date", AbsoluteTimeNbo],
+      ["wire_fee", AmountNbo],
+      ["closing_fee", AmountNbo],
+    ];
+  }
+}
+
+
 export class AbsoluteTimeNbo extends PackedArenaObject {
   static fromTalerString(s: string): AbsoluteTimeNbo {
     let x = new AbsoluteTimeNbo();
@@ -1008,6 +1038,15 @@ export class AbsoluteTimeNbo extends PackedArenaObject {
     return x;
   }
 
+  static fromStamp(stamp: number): AbsoluteTimeNbo {
+    let x = new AbsoluteTimeNbo();
+    x.alloc();
+    // XXX: This only works up to 54 bit numbers.
+    set64(x.nativePtr, stamp);
+    return x;
+  }
+
+
   size() {
     return 8;
   }
diff --git a/src/types.ts b/src/types.ts
index e0baa16..c6111bd 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -474,6 +474,9 @@ export class Contract {
   @Checkable.String
   H_wire: string;
 
+  @Checkable.String
+  wire_method: string;
+
   @Checkable.Optional(Checkable.String)
   summary?: string;
 
@@ -535,6 +538,20 @@ export class Contract {
 }
 
 
+export interface WireFee {
+  wireFee: AmountJson;
+  closingFee: AmountJson;
+  startStamp: number;
+  endStamp: number;
+  sig: string;
+}
+
+export interface ExchangeWireFeesRecord {
+  exchangeBaseUrl: string;
+  feesForType: { [type: string]: WireFee[] };
+}
+
+
 export type PayCoinInfo = Array<{ updatedCoin: CoinRecord, sig: CoinPaySig }>;
 
 
diff --git a/src/wallet.ts b/src/wallet.ts
index e8b655f..8b220ec 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -42,6 +42,8 @@ import {
   AuditorRecord,
   WalletBalance,
   WalletBalanceEntry,
+  WireFee,
+  ExchangeWireFeesRecord,
   WireInfo, DenominationRecord, DenominationStatus, denominationRecordFromKeys,
   CoinStatus,
 } from "./types";
@@ -113,6 +115,41 @@ export class KeysJson {
 }
 
 
+
+
address@hidden
+class WireFeesJson {
+  @Checkable.Value(AmountJson)
+  wire_fee: AmountJson;
+
+  @Checkable.Value(AmountJson)
+  closing_fee: AmountJson;
+
+  @Checkable.String
+  sig: string;
+
+  @Checkable.String
+  start_date: string;
+
+  @Checkable.String
+  end_date: string;
+
+  static checked: (obj: any) => WireFeesJson;
+}
+
+
address@hidden
+class WireDetailJson {
+  @Checkable.String
+  type: string;
+
+  @Checkable.List(Checkable.Value(WireFeesJson))
+  fees: WireFeesJson[];
+
+  static checked: (obj: any) => WireDetailJson;
+}
+
+
 @Checkable.Class
 export class CreateReserveRequest {
   /**
@@ -223,6 +260,7 @@ export interface ConfigRecord {
 }
 
 
+
 const builtinCurrencies: CurrencyRecord[] = [
   {
     name: "KUDOS",
@@ -417,7 +455,13 @@ export namespace Stores {
     }
   }
 
+  class ExchangeWireFeesStore extends Store<ExchangeWireFeesRecord> {
+    constructor() {
+      super("exchangeWireFees", {keyPath: "exchangeBaseUrl"});
+    }
+  }
   export const exchanges: ExchangeStore = new ExchangeStore();
+  export const exchangeWireFees: ExchangeWireFeesStore = new 
ExchangeWireFeesStore();
   export const nonces: NonceStore = new NonceStore();
   export const transactions: TransactionsStore = new TransactionsStore();
   export const reserves: Store<ReserveRecord> = new 
Store<ReserveRecord>("reserves", {keyPath: "reserve_pub"});
@@ -1254,13 +1298,27 @@ export class Wallet {
    */
   async updateExchangeFromUrl(baseUrl: string): Promise<ExchangeRecord> {
     baseUrl = canonicalizeBaseUrl(baseUrl);
-    let reqUrl = new URI("keys").absoluteTo(baseUrl);
-    let resp = await this.http.get(reqUrl.href());
-    if (resp.status != 200) {
+    let keysUrl = new URI("keys").absoluteTo(baseUrl);
+    let wireUrl = new URI("wire").absoluteTo(baseUrl);
+    let keysResp = await this.http.get(keysUrl.href());
+    if (keysResp.status != 200) {
       throw Error("/keys request failed");
     }
-    let exchangeKeysJson = KeysJson.checked(JSON.parse(resp.responseText));
-    return this.updateExchangeFromJson(baseUrl, exchangeKeysJson);
+    let wireResp = await this.http.get(wireUrl.href());
+    if (wireResp.status != 200) {
+      throw Error("/wire request failed");
+    }
+    let exchangeKeysJson = KeysJson.checked(JSON.parse(keysResp.responseText));
+    let wireRespJson = JSON.parse(wireResp.responseText);
+    if (typeof wireRespJson !== "object") {
+      throw Error("/wire response is not an object");
+    }
+    console.log("exchange wire", wireRespJson);
+    let wireMethodDetails: WireDetailJson[] = [];
+    for (let methodName in wireRespJson) {
+      wireMethodDetails.push(WireDetailJson.checked(wireRespJson[methodName]));
+    }
+    return this.updateExchangeFromJson(baseUrl, exchangeKeysJson, 
wireMethodDetails);
   }
 
 
@@ -1289,7 +1347,10 @@ export class Wallet {
 
 
   private async updateExchangeFromJson(baseUrl: string,
-                                       exchangeKeysJson: KeysJson): 
Promise<ExchangeRecord> {
+                                       exchangeKeysJson: KeysJson,
+                                       wireMethodDetails: WireDetailJson[]): 
Promise<ExchangeRecord> {
+
+    // FIXME: all this should probably be commited atomically
     const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
     if (updateTimeSec === null) {
       throw Error("invalid update time");
@@ -1325,6 +1386,55 @@ export class Wallet {
               .put(Stores.exchanges, updatedExchangeInfo)
               .finish();
 
+    let oldWireFees = await this.q().get(Stores.exchangeWireFees, baseUrl);
+    if (!oldWireFees) {
+      oldWireFees = {
+        exchangeBaseUrl: baseUrl,
+        feesForType: {},
+      };
+    }
+
+    for (let detail of wireMethodDetails) {
+      let latestFeeStamp = 0;
+      let fees = oldWireFees.feesForType[detail.type] || [];
+      oldWireFees.feesForType[detail.type] = fees;
+      for (let oldFee of fees) {
+        if (oldFee.endStamp > latestFeeStamp) {
+          latestFeeStamp = oldFee.endStamp;
+        }
+      }
+      for (let fee of detail.fees) {
+        let start = getTalerStampSec(fee.start_date);
+        if (start == null) {
+          console.error("invalid start stamp in fee", fee);
+          continue;
+        }
+        if (start < latestFeeStamp) {
+          continue;
+        }
+        let end = getTalerStampSec(fee.end_date);
+        if (end == null) {
+          console.error("invalid end stamp in fee", fee);
+          continue;
+        }
+        let wf: WireFee = {
+          wireFee: fee.wire_fee,
+          closingFee: fee.closing_fee,
+          sig: fee.sig,
+          startStamp: start,
+          endStamp: end,
+        }
+        let valid: boolean = await this.cryptoApi.isValidWireFee(detail.type, 
wf, exchangeInfo.masterPublicKey);
+        if (!valid) {
+          console.error("fee signature invalid", fee);
+          continue;
+        }
+        fees.push(wf);
+      }
+    }
+
+    await this.q().put(Stores.exchangeWireFees, oldWireFees);
+
     return updatedExchangeInfo;
   }
 

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]