diff options
| -rw-r--r-- | extension/lib/wallet/cryptoLib.ts | 137 | ||||
| -rw-r--r-- | extension/lib/wallet/cryptoWorker.ts | 65 | ||||
| -rw-r--r-- | extension/lib/wallet/types.ts | 19 | ||||
| -rw-r--r-- | extension/lib/wallet/wallet.ts | 272 | ||||
| -rw-r--r-- | extension/tsconfig.json | 4 | 
5 files changed, 344 insertions, 153 deletions
diff --git a/extension/lib/wallet/cryptoLib.ts b/extension/lib/wallet/cryptoLib.ts new file mode 100644 index 000000000..2c32b3a63 --- /dev/null +++ b/extension/lib/wallet/cryptoLib.ts @@ -0,0 +1,137 @@ +/* + This file is part of TALER + (C) 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> + */ + +import {Denomination} from "./types"; +/** + * Web worker for crypto operations. + * @author Florian Dold + */ + +"use strict"; + +import * as native from "./emscriptif"; +import {PreCoin, Reserve} from "./types"; +import create = chrome.alarms.create; + + +export function main(worker: Worker) { +  worker.onmessage = (msg: MessageEvent) => { +    console.log("got data", msg.data); +    if (!Array.isArray(msg.data.args)) { +      console.error("args must be array"); +      return; +    } +    if (typeof msg.data.id != "number") { +      console.error("RPC id must be number"); +    } +    if (typeof msg.data.operation != "string") { +      console.error("RPC operation must be string"); +    } +    let f = RpcFunctions[msg.data.operation]; +    if (!f) { +      console.error(`unknown operation: '${msg.data.operation}'`); +      return; +    } +    let res = f(...msg.data.args); +    worker.postMessage({result: res, id: msg.data.id}); +  } +} + +console.log("hello, this is the crypto lib"); + +namespace RpcFunctions { + +  /** +   * Create a pre-coin of the given denomination to be withdrawn from then given +   * reserve. +   */ +  export function createPreCoin(denom: Denomination, reserve: Reserve): PreCoin { +    let reservePriv = new native.EddsaPrivateKey(); +    reservePriv.loadCrock(reserve.reserve_priv); +    let reservePub = new native.EddsaPublicKey(); +    reservePub.loadCrock(reserve.reserve_pub); +    let denomPub = native.RsaPublicKey.fromCrock(denom.denom_pub); +    let coinPriv = native.EddsaPrivateKey.create(); +    let coinPub = coinPriv.getPublicKey(); +    let blindingFactor = native.RsaBlindingKey.create(1024); +    let pubHash: native.HashCode = coinPub.hash(); +    let ev: native.ByteArray = native.rsaBlind(pubHash, +                                               blindingFactor, +                                               denomPub); + +    if (!denom.fee_withdraw) { +      throw Error("Field fee_withdraw missing"); +    } + +    let amountWithFee = new native.Amount(denom.value); +    amountWithFee.add(new native.Amount(denom.fee_withdraw)); +    let withdrawFee = new native.Amount(denom.fee_withdraw); + +    // Signature +    let withdrawRequest = new native.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 = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv); + +    let preCoin: 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 preCoin; +  } + + +  export 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); + +  } +} diff --git a/extension/lib/wallet/cryptoWorker.ts b/extension/lib/wallet/cryptoWorker.ts new file mode 100644 index 000000000..958c2de74 --- /dev/null +++ b/extension/lib/wallet/cryptoWorker.ts @@ -0,0 +1,65 @@ +/* + This file is part of TALER + (C) 2016 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Web worker for crypto operations. + * @author Florian Dold + */ + +"use strict"; + + +importScripts("../emscripten/libwrapper.js", +              "../vendor/system-csp-production.src.js"); + + +// TypeScript does not allow ".js" extensions in the +// module name, so SystemJS must add it. +System.config({ +                defaultJSExtensions: true, +              }); + +// We expect that in the manifest, the emscripten js is loaded +// becore the background page. +// Currently it is not possible to use SystemJS to load the emscripten js. +declare var Module: any; +if ("object" !== typeof Module) { +  throw Error("emscripten not loaded, no 'Module' defined"); +} + + +// Manually register the emscripten js as a SystemJS, so that +// we can use it from TypeScript by importing it. + +{ +  let mod = System.newModule({Module: Module}); +  let modName = System.normalizeSync("../emscripten/emsc"); +  console.log("registering", modName); +  System.set(modName, mod); +} + +System.import("./cryptoLib") +      .then((m) => { +        m.main(self); +        console.log("loaded"); +      }) +      .catch((e) => { +        console.log("crypto worker failed"); +        console.error(e.stack); +      }); + +console.log("in worker thread"); + diff --git a/extension/lib/wallet/types.ts b/extension/lib/wallet/types.ts index 3b0cb2638..d18bd95f4 100644 --- a/extension/lib/wallet/types.ts +++ b/extension/lib/wallet/types.ts @@ -107,6 +107,25 @@ export interface ReserveCreationInfo {    withdrawFee: AmountJson;  } + +export interface PreCoin { +  coinPub: string; +  coinPriv: string; +  reservePub: string; +  denomPub: string; +  blindingKey: string; +  withdrawSig: string; +  coinEv: string; +  mintBaseUrl: string; +  coinValue: AmountJson; +} + +export interface Reserve { +  mint_base_url: string +  reserve_priv: string; +  reserve_pub: string; +} +  export interface Notifier {    notify();  }
\ No newline at end of file diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts index 2d4a29c8d..475d316e3 100644 --- a/extension/lib/wallet/wallet.ts +++ b/extension/lib/wallet/wallet.ts @@ -28,6 +28,8 @@ import {Query} from "./query";  import {Checkable} from "./checkable";  import {canonicalizeBaseUrl} from "./helpers";  import {ReserveCreationInfo} from "./types"; +import {PreCoin} from "./types"; +import {Reserve} from "./types";  "use strict"; @@ -65,18 +67,6 @@ export class KeysJson {  } -export interface PreCoin { -  coinPub: string; -  coinPriv: string; -  reservePub: string; -  denomPub: string; -  blindingKey: string; -  withdrawSig: string; -  coinEv: string; -  mintBaseUrl: string; -  coinValue: AmountJson; -} -  export interface Coin {    coinPub: string;    coinPriv: string; @@ -87,34 +77,6 @@ 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; @@ -145,53 +107,61 @@ class MintInfo implements IMintInfo {     * 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; -    } +  mergeKeys(newKeys: KeysJson, wallet: Wallet): Promise<void> { +    return Promise.resolve().then(() => { +      if (!this.masterPublicKey) { +        this.masterPublicKey = newKeys.master_public_key; +      } -    if (this.masterPublicKey != newKeys.master_public_key) { -      throw Error("public keys do not match"); -    } +      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"); +      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;            } -          // TODO: check if info still matches -          found = true; -          break;          } -      } -      if (found) { -        continue; -      } +        if (found) { +          continue; +        } -      console.log("validating denomination"); +        console.log("validating denomination"); -      if (!isValidDenom(newDenom, this.masterPublicKey)) { -        throw Error("signature on denomination invalid"); -      } +        return wallet.isValidDenom(newDenom, this.masterPublicKey) +                     .then((valid) => { +                       if (!valid) { +                         throw Error("signature on denomination invalid"); +                       } -      let d: Denomination = Object.assign({}, newDenom); -      d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub) -                         .encode() -                         .hash() -                         .toCrock(); -      this.denoms.push(d); -    } +                       let d: Denomination = Object.assign({}, newDenom); +                       d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub) +                                          .encode() +                                          .hash() +                                          .toCrock(); +                       this.denoms.push(d); + +                     }); + +      } +      return; +    });    }  } @@ -330,13 +300,6 @@ interface Transaction {  } -interface Reserve { -  mint_base_url: string -  reserve_priv: string; -  reserve_pub: string; -} - -  export interface Badge {    setText(s: string): void;    setColor(c: string): void; @@ -395,62 +358,6 @@ 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. - */ -function createPreCoin(denom: Denomination, reserve: Reserve): PreCoin { -  let reservePriv = new native.EddsaPrivateKey(); -  reservePriv.loadCrock(reserve.reserve_priv); -  let reservePub = new native.EddsaPublicKey(); -  reservePub.loadCrock(reserve.reserve_pub); -  let denomPub = native.RsaPublicKey.fromCrock(denom.denom_pub); -  let coinPriv = native.EddsaPrivateKey.create(); -  let coinPub = coinPriv.getPublicKey(); -  let blindingFactor = native.RsaBlindingKey.create(1024); -  let pubHash: native.HashCode = coinPub.hash(); -  let ev: native.ByteArray = native.rsaBlind(pubHash, -                                             blindingFactor, -                                             denomPub); - -  if (!denom.fee_withdraw) { -    throw Error("Field fee_withdraw missing"); -  } - -  let amountWithFee = new native.Amount(denom.value); -  amountWithFee.add(new native.Amount(denom.fee_withdraw)); -  let withdrawFee = new native.Amount(denom.fee_withdraw); - -  // Signature -  let withdrawRequest = new native.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 = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv); - -  let preCoin: 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 preCoin; -} - -  /**   * Get a list of denominations (with repetitions possible)   * whose total value is as close as possible to the available @@ -493,6 +400,9 @@ export class Wallet {    private http: HttpRequestLibrary;    private badge: Badge;    private notifier: Notifier; +  private cryptoWorker: Worker; +  private nextRpcId: number = 1; +  private rpcRegistry = {};    constructor(db: IDBDatabase, @@ -503,6 +413,21 @@ export class Wallet {      this.http = http;      this.badge = badge;      this.notifier = notifier; +    this.cryptoWorker = new Worker("/lib/wallet/cryptoWorker.js"); + +    this.cryptoWorker.onmessage = (msg: MessageEvent) => { +      let id = msg.data.id; +      if (typeof id !== "number") { +        console.error("rpc id must be number"); +        return; +      } +      if (!this.rpcRegistry[id]) { +        console.error(`RPC with id ${id} has no registry entry`); +        return; +      } +      let {resolve, reject} = this.rpcRegistry[id]; +      resolve(msg.data.result); +    }    } @@ -693,7 +618,9 @@ export class Wallet {        .put("history", historyEntry)        .putAll("coins", payCoinInfo.map((pci) => pci.updatedCoin))        .finish() -      .then(() => { this.notifier.notify(); }); +      .then(() => { +        this.notifier.notify(); +      });    } @@ -903,7 +830,9 @@ export class Wallet {        .add("coins", coin)        .add("history", historyEntry)        .finish() -      .then(() => { this.notifier.notify(); }); +      .then(() => { +        this.notifier.notify(); +      });    } @@ -912,12 +841,15 @@ export class Wallet {     */    private withdraw(denom: Denomination, reserve: Reserve): Promise<void> {      console.log("creating pre coin at", new Date()); -    let preCoin = createPreCoin(denom, reserve); -    return Query(this.db) -      .put("precoins", preCoin) -      .finish() -      .then(() => this.withdrawExecute(preCoin)) -      .then((c) => this.storeCoin(c)); +    return this.createPreCoin(denom, reserve) +               .then((preCoin) => { +                 return Query(this.db) +                   .put("precoins", preCoin) +                   .finish() +                   .then(() => this.withdrawExecute(preCoin)) +                   .then((c) => this.storeCoin(c)); +               }); +    } @@ -1028,8 +960,14 @@ export class Wallet {            console.log("using old mint");          } -        mint.mergeKeys(mintKeysJson); -        return Query(this.db).put("mints", mint).finish().then(() => mint); +        return mint.mergeKeys(mintKeysJson) +                   .then(() => { +                     return Query(this.db) +                       .put("mints", mint) +                       .finish() +                       .then(() => mint); +                   }); +        });      });    } @@ -1070,4 +1008,34 @@ export class Wallet {        .iter("history", {indexName: "timestamp"})        .reduce(collect, [])    } + +  registerRpcId(resolve, reject): number { +    let id = this.nextRpcId++; +    this.rpcRegistry[id] = {resolve, reject}; +    return id; +  } + + +  createPreCoin(denom: Denomination, reserve: Reserve): Promise<PreCoin> { +    return new Promise((resolve, reject) => { +      let msg = { +        operation: "createPreCoin", +        id: this.registerRpcId(resolve, reject), +        args: [denom, reserve] +      }; +      this.cryptoWorker.postMessage(msg); +    }); +  } + +  isValidDenom(denom: Denomination, +               masterPub: string): Promise<boolean> { +    return new Promise((resolve, reject) => { +      let msg = { +        operation: "isValidDenom", +        id: this.registerRpcId(resolve, reject), +        args: [denom, masterPub] +      }; +      this.cryptoWorker.postMessage(msg); +    }); +  }  }
\ No newline at end of file diff --git a/extension/tsconfig.json b/extension/tsconfig.json index a3eea3a70..96f360370 100644 --- a/extension/tsconfig.json +++ b/extension/tsconfig.json @@ -13,6 +13,8 @@      "lib/i18n.ts",      "lib/refs.ts",      "lib/wallet/checkable.ts", +    "lib/wallet/cryptoLib.ts", +    "lib/wallet/cryptoWorker.ts",      "lib/wallet/db.ts",      "lib/wallet/emscriptif.ts",      "lib/wallet/helpers.ts", @@ -29,4 +31,4 @@      "pages/confirm-create-reserve.tsx",      "test/tests/taler.ts"    ] -} +}
\ No newline at end of file  | 
