diff --git a/extension/background/emscriptif.js b/extension/background/emscriptif.js index 0e4f2a209..11260c74d 100644 --- a/extension/background/emscriptif.js +++ b/extension/background/emscriptif.js @@ -21,14 +21,14 @@ const GNUNET_OK = 1; const GNUNET_YES = 1; const GNUNET_NO = 0; const GNUNET_SYSERR = -1; -let getEmsc = Module.cwrap; +let getEmsc = (...args) => Module.cwrap.apply(null, args); var emsc = { free: (ptr) => Module._free(ptr), get_value: getEmsc('TALER_WR_get_value', 'number', ['number']), get_fraction: getEmsc('TALER_WR_get_fraction', 'number', ['number']), get_currency: getEmsc('TALER_WR_get_currency', 'string', ['number']), - amount_add: getEmsc('TALER_amount_add', 'void', ['number', 'number', 'number']), - amount_subtract: getEmsc('TALER_amount_subtract', 'void', ['number', 'number', 'number']), + amount_add: getEmsc('TALER_amount_add', 'number', ['number', 'number', 'number']), + amount_subtract: getEmsc('TALER_amount_subtract', 'number', ['number', 'number', 'number']), amount_normalize: getEmsc('TALER_amount_normalize', 'void', ['number']), amount_cmp: getEmsc('TALER_amount_cmp', 'number', ['number', 'number']), amount_hton: getEmsc('TALER_amount_hton', 'void', ['number', 'number']), @@ -36,21 +36,25 @@ var emsc = { hash: getEmsc('GNUNET_CRYPTO_hash', 'void', ['number', 'number', 'number']), memmove: getEmsc('memmove', 'number', ['number', 'number', 'number']), rsa_public_key_free: getEmsc('GNUNET_CRYPTO_rsa_public_key_free', 'void', ['number']), - string_to_data: getEmsc('GNUNET_STRINGS_string_to_data', 'void', ['number', 'number', 'number', 'number']), + rsa_signature_free: getEmsc('GNUNET_CRYPTO_rsa_signature_free', 'void', ['number']), + string_to_data: getEmsc('GNUNET_STRINGS_string_to_data', 'number', ['number', 'number', 'number', 'number']), eddsa_sign: getEmsc('GNUNET_CRYPTO_eddsa_sign', 'number', ['number', 'number', 'number']), hash_create_random: getEmsc('GNUNET_CRYPTO_hash_create_random', 'void', ['number', 'number']), + rsa_blinding_key_destroy: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free', 'void', ['number']), }; var emscAlloc = { get_amount: getEmsc('TALER_WRALL_get_amount', 'number', ['number', 'number', 'number', 'string']), - eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create', 'number'), + eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create', 'number', []), eddsa_public_key_from_private: getEmsc('TALER_WRALL_eddsa_public_key_from_private', 'number', ['number']), data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc', 'number', ['number', 'number']), purpose_create: getEmsc('TALER_WRALL_purpose_create', 'number', ['number', 'number', 'number']), rsa_blind: getEmsc('GNUNET_CRYPTO_rsa_blind', 'number', ['number', 'number', 'number', 'number']), - rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create', 'void', ['number']), - rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode', 'void', ['number', 'number']), + rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create', 'number', ['number']), + rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode', 'number', ['number', 'number']), + rsa_signature_encode: getEmsc('GNUNET_CRYPTO_rsa_signature_encode', 'number', ['number', 'number']), rsa_blinding_key_decode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_decode', 'number', ['number', 'number']), rsa_public_key_decode: getEmsc('GNUNET_CRYPTO_rsa_public_key_decode', 'number', ['number', 'number']), + rsa_signature_decode: getEmsc('GNUNET_CRYPTO_rsa_signature_decode', 'number', ['number', 'number']), rsa_public_key_encode: getEmsc('GNUNET_CRYPTO_rsa_public_key_encode', 'number', ['number', 'number']), malloc: (size) => Module._malloc(size), }; @@ -72,6 +76,39 @@ class ArenaObject { arena.put(this); this.arena = arena; } + getNative() { + // We want to allow latent allocation + // of native wrappers, but we never want to + // pass 'undefined' to emscripten. + if (this._nativePtr === undefined) { + throw Error("Native pointer not initialized"); + } + return this._nativePtr; + } + free() { + if (this.nativePtr !== undefined) { + emsc.free(this.nativePtr); + this.nativePtr = undefined; + } + } + alloc(size) { + if (this.nativePtr !== undefined) { + throw Error("Double allocation"); + } + this.nativePtr = emscAlloc.malloc(size); + } + setNative(n) { + if (n === undefined) { + throw Error("Native pointer must be a number or null"); + } + this._nativePtr = n; + } + set nativePtr(v) { + this.setNative(v); + } + get nativePtr() { + return this.getNative(); + } } class Arena { constructor() { @@ -348,6 +385,14 @@ class WithdrawRequestPS extends SignatureStruct { ["h_coin_envelope", HashCode]]; } } +function encodeWith(obj, fn, arena) { + let ptr = emscAlloc.malloc(PTR_SIZE); + let len = fn(obj.getNative(), ptr); + let res = new ByteArray(len, null, arena); + res.setNative(Module.getValue(ptr, '*')); + emsc.free(ptr); + return res; +} class RsaPublicKey extends ArenaObject { static fromCrock(s, a) { let obj = new RsaPublicKey(a); @@ -374,6 +419,22 @@ class RsaPublicKey extends ArenaObject { class EddsaSignature extends PackedArenaObject { size() { return 64; } } +class RsaSignature extends ArenaObject { + static fromCrock(s, a) { + let obj = new this(a); + let buf = ByteArray.fromCrock(s); + obj.setNative(emscAlloc.rsa_signature_decode(buf.getNative(), buf.size())); + buf.destroy(); + return obj; + } + encode(arena) { + return encodeWith(this, emscAlloc.rsa_signature_encode); + } + destroy() { + emsc.rsa_signature_free(this.getNative()); + this.setNative(0); + } +} function rsaBlind(hashCode, blindingKey, pkey, arena) { let ptr = emscAlloc.malloc(PTR_SIZE); let s = emscAlloc.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, pkey.nativePtr, ptr); @@ -389,3 +450,6 @@ function eddsaSign(purpose, priv, a) { } return sig; } +function rsaUnblind(sig, bk, pk) { + throw Error("Not implemented"); +} diff --git a/extension/background/emscriptif.ts b/extension/background/emscriptif.ts index 251c57b32..4fcb3668c 100644 --- a/extension/background/emscriptif.ts +++ b/extension/background/emscriptif.ts @@ -28,7 +28,15 @@ const GNUNET_YES = 1; const GNUNET_NO = 0; const GNUNET_SYSERR = -1; -let getEmsc = Module.cwrap; + +interface EmscFunGen { + (name: string, ret: string, args: string[]): ((...x: (number|string)[]) => any); + (name: string, ret: 'number', args: string[]): ((...x: (number|string)[]) => number); + (name: string, ret: 'void', args: string[]): ((...x: (number|string)[]) => void); + (name: string, ret: 'string', args: string[]): ((...x: (number|string)[]) => string); +} + +let getEmsc: EmscFunGen = (...args) => Module.cwrap.apply(null, args); var emsc = { free: (ptr) => Module._free(ptr), @@ -42,10 +50,10 @@ var emsc = { 'string', ['number']), amount_add: getEmsc('TALER_amount_add', - 'void', + 'number', ['number', 'number', 'number']), amount_subtract: getEmsc('TALER_amount_subtract', - 'void', + 'number', ['number', 'number', 'number']), amount_normalize: getEmsc('TALER_amount_normalize', 'void', @@ -68,15 +76,21 @@ var emsc = { rsa_public_key_free: getEmsc('GNUNET_CRYPTO_rsa_public_key_free', 'void', ['number']), + rsa_signature_free: getEmsc('GNUNET_CRYPTO_rsa_signature_free', + 'void', + ['number']), string_to_data: getEmsc('GNUNET_STRINGS_string_to_data', - 'void', + 'number', ['number', 'number', 'number', 'number']), eddsa_sign: getEmsc('GNUNET_CRYPTO_eddsa_sign', - 'number', - ['number', 'number', 'number']), + 'number', + ['number', 'number', 'number']), hash_create_random: getEmsc('GNUNET_CRYPTO_hash_create_random', 'void', ['number', 'number']), + rsa_blinding_key_destroy: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free', + 'void', + ['number']), }; var emscAlloc = { @@ -84,7 +98,7 @@ var emscAlloc = { 'number', ['number', 'number', 'number', 'string']), eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create', - 'number'), + 'number', []), eddsa_public_key_from_private: getEmsc('TALER_WRALL_eddsa_public_key_from_private', 'number', ['number']), @@ -95,13 +109,16 @@ var emscAlloc = { 'number', ['number', 'number', 'number']), rsa_blind: getEmsc('GNUNET_CRYPTO_rsa_blind', - 'number', - ['number', 'number', 'number', 'number']), + 'number', + ['number', 'number', 'number', 'number']), rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create', - 'void', + 'number', ['number']), rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode', - 'void', + 'number', + ['number', 'number']), + rsa_signature_encode: getEmsc('GNUNET_CRYPTO_rsa_signature_encode', + 'number', ['number', 'number']), rsa_blinding_key_decode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_decode', 'number', @@ -109,6 +126,9 @@ var emscAlloc = { rsa_public_key_decode: getEmsc('GNUNET_CRYPTO_rsa_public_key_decode', 'number', ['number', 'number']), + rsa_signature_decode: getEmsc('GNUNET_CRYPTO_rsa_signature_decode', + 'number', + ['number', 'number']), rsa_public_key_encode: getEmsc('GNUNET_CRYPTO_rsa_public_key_encode', 'number', ['number', 'number']), @@ -128,7 +148,7 @@ enum RandomQuality { abstract class ArenaObject { - nativePtr: number; + private _nativePtr: number; arena: Arena; abstract destroy(): void; @@ -139,6 +159,46 @@ abstract class ArenaObject { arena.put(this); this.arena = arena; } + + getNative(): number { + // We want to allow latent allocation + // of native wrappers, but we never want to + // pass 'undefined' to emscripten. + if (this._nativePtr === undefined) { + throw Error("Native pointer not initialized"); + } + return this._nativePtr; + } + + free() { + if (this.nativePtr !== undefined) { + emsc.free(this.nativePtr); + this.nativePtr = undefined; + } + } + + alloc(size: number) { + if (this.nativePtr !== undefined) { + throw Error("Double allocation"); + } + this.nativePtr = emscAlloc.malloc(size); + } + + setNative(n: number) { + if (n === undefined) { + throw Error("Native pointer must be a number or null"); + } + this._nativePtr = n; + } + + set nativePtr (v) { + this.setNative(v); + } + + get nativePtr () { + return this.getNative(); + } + } class Arena { @@ -473,6 +533,17 @@ class WithdrawRequestPS extends SignatureStruct { } + +function encodeWith(obj, fn, arena?: Arena) { + let ptr = emscAlloc.malloc(PTR_SIZE); + let len = fn(obj.getNative(), ptr); + let res = new ByteArray(len, null, arena); + res.setNative(Module.getValue(ptr, '*')); + emsc.free(ptr); + return res; +} + + class RsaPublicKey extends ArenaObject { static fromCrock(s: string, a?: Arena): RsaPublicKey { let obj = new RsaPublicKey(a); @@ -507,6 +578,25 @@ class EddsaSignature extends PackedArenaObject { } +class RsaSignature extends ArenaObject { + static fromCrock(s: string, a?: Arena): RsaSignature { + let obj = new this(a); + let buf = ByteArray.fromCrock(s); + obj.setNative(emscAlloc.rsa_signature_decode(buf.getNative(), buf.size())); + buf.destroy(); + return obj; + } + + encode(arena?: Arena): ByteArray { + return encodeWith(this, emscAlloc.rsa_signature_encode); + } + + destroy() { + emsc.rsa_signature_free(this.getNative()); + this.setNative(0); + } +} + function rsaBlind(hashCode: HashCode, blindingKey: RsaBlindingKey, pkey: RsaPublicKey, @@ -532,3 +622,10 @@ function eddsaSign(purpose: EccSignaturePurpose, return sig; } +function rsaUnblind(sig: RsaSignature, + bk: RsaBlindingKey, + pk: RsaPublicKey): RsaSignature { + throw Error("Not implemented"); +} + + diff --git a/extension/background/wallet.js b/extension/background/wallet.js index dd1bccaea..5483059a5 100644 --- a/extension/background/wallet.js +++ b/extension/background/wallet.js @@ -17,9 +17,8 @@ function openTalerDb() { resolve(req.result); }; req.onupgradeneeded = (e) => { - let db = e.target.result; + let db = req.result; console.log("DB: upgrade needed: oldVersion = " + e.oldVersion); - db = e.target.result; switch (e.oldVersion) { case 0: db.createObjectStore("mints", { keyPath: "baseUrl" }); @@ -28,6 +27,7 @@ function openTalerDb() { db.createObjectStore("coins", { keyPath: "coin_pub" }); db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true }); db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true }); + db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true }); break; } }; @@ -113,52 +113,74 @@ function rankDenom(denom1, denom2) { let v2 = new Amount(denom2.value); return (-1) * v1.cmp(v2); } -function withdraw(denom, reserve, mint) { - console.log("in withdraw"); - let wd = {}; - wd.denom_pub = denom.denom_pub; - wd.reserve_pub = reserve.reserve_pub; - let a = new Arena(); - try { - 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(512); - let pubHash = coinPub.hash(); - console.log("about to blind"); - let ev = rsaBlind(pubHash, blindingFactor, denomPub); - console.log("blinded"); - if (!denom.fee_withdraw) { - throw Error("Field fee_withdraw missing"); - } - 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(); - withdrawRequest.set("reserve_pub", reservePub); - withdrawRequest.set("amount_with_fee", amountWithFee.toNbo()); - withdrawRequest.set("withdraw_fee", withdrawFee.toNbo()); - withdrawRequest.set("h_denomination_pub", denomPub.encode().hash()); - withdrawRequest.set("h_coin_envelope", ev.hash()); - console.log("about to sign"); - var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv); - console.log("signed"); - wd.reserve_sig = sig.toCrock(); - wd.coin_ev = ev.toCrock(); - } - finally { - a.destroy(); +function withdrawPrepare(db, denom, reserve) { + console.log("in withdraw prepare"); + let reservePriv = new EddsaPrivateKey(); + console.log("loading reserve priv", reserve.reserve_priv); + reservePriv.loadCrock(reserve.reserve_priv); + console.log("reserve priv is", reservePriv.toCrock()); + 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 = coinPub.hash(); + console.log("about to blind"); + let ev = rsaBlind(pubHash, blindingFactor, denomPub); + console.log("blinded"); + if (!denom.fee_withdraw) { + throw Error("Field fee_withdraw missing"); } + 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(); + withdrawRequest.set("reserve_pub", reservePub); + withdrawRequest.set("amount_with_fee", amountWithFee.toNbo()); + withdrawRequest.set("withdraw_fee", withdrawFee.toNbo()); + withdrawRequest.set("h_denomination_pub", denomPub.encode().hash()); + withdrawRequest.set("h_coin_envelope", ev.hash()); + console.log("about to sign"); + var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv); + console.log("signed"); console.log("crypto done, doing request"); - console.log("request:"); - console.log(JSON.stringify(wd)); + let preCoin = { + reservePub: reservePub.toCrock(), + blindingKey: blindingFactor.toCrock(), + coinPub: coinPub.toCrock(), + coinPriv: coinPriv.toCrock(), + denomPub: denomPub.encode().toCrock(), + withdrawSig: sig.toCrock(), + coinEv: ev.toCrock() + }; + console.log("storing precoin", JSON.stringify(preCoin)); + let tx = db.transaction(['precoins'], 'readwrite'); + tx.objectStore('precoins').add(preCoin); return new Promise((resolve, reject) => { - let reqUrl = URI("reserve/withdraw").absoluteTo(mint.baseUrl); + tx.oncomplete = (e) => { + resolve(preCoin); + }; + }); +} +function dbGet(db, store, key) { + let tx = db.transaction([store]); + let req = tx.objectStore(store).get(key); + return new Promise((resolve, reject) => { + req.onsuccess = (e) => resolve(req.result); + }); +} +function withdrawExecute(db, pc) { + return dbGet(db, 'reserves', pc.reservePub) + .then((r) => new Promise((resolve, reject) => { + console.log("loading precoin", JSON.stringify(pc)); + let wd = {}; + 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); let myRequest = new XMLHttpRequest(); console.log("making request to " + reqUrl.href()); myRequest.open('post', reqUrl.href()); @@ -173,13 +195,32 @@ function withdraw(denom, reserve, mint) { } console.log("Withdrawal successful"); console.log(myRequest.responseText); - resolve(); + let resp = JSON.parse(myRequest.responseText); + //let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.coin_ev), + // RsaBlindingKey.fromCrock(pc.blindingKey), + // RsaPublicKey.fromCrock(pc.denomPub)); + let coin = { + coinPub: pc.coinPub, + coinPriv: pc.coinPriv, + denomPub: pc.denomPub, + reservePub: pc.reservePub, + denomSig: "foo" //denomSig.encode().toCrock() + }; + console.log("unblinded coin"); + resolve(coin); } else { console.log("ready state change to", myRequest.status); } }); - }); + })); +} +function storeCoin(db, coin) { +} +function withdraw(db, denom, reserve) { + return withdrawPrepare(db, denom, reserve) + .then((pc) => withdrawExecute(db, pc)) + .then((c) => storeCoin(db, c)); } /** * Withdraw coins from a reserve until it is empty. @@ -199,7 +240,7 @@ function depleteReserve(db, reserve, mint) { } found = true; remaining.sub(cost); - workList.push([d, reserve, mint]); + workList.push(d); } if (!found) { break; @@ -211,8 +252,8 @@ function depleteReserve(db, reserve, mint) { return; } console.log("doing work"); - let w = workList.pop(); - withdraw(w[0], w[1], w[2]) + let d = workList.pop(); + withdraw(db, d, reserve) .then(() => next()); } next(); @@ -324,6 +365,10 @@ openTalerDb().then((db) => { "confirm-reserve": confirmReserve, "dump-db": dumpDb }; - return dispatch[req.type](db, req.detail, onresponse); + if (req.type in dispatch) { + return dispatch[req.type](db, req.detail, onresponse); + } + console.error(format("Request type unknown, req {0}", JSON.stringify(req))); + return false; }); }); diff --git a/extension/background/wallet.ts b/extension/background/wallet.ts index 236f607e2..a000855b6 100644 --- a/extension/background/wallet.ts +++ b/extension/background/wallet.ts @@ -10,20 +10,19 @@ const DB_VERSION = 1; * Return a promise that resolves * to the taler wallet db. */ -function openTalerDb() { +function openTalerDb(): Promise { return new Promise((resolve, reject) => { let req = indexedDB.open(DB_NAME, DB_VERSION); req.onerror = (e) => { reject(e); }; - req.onsuccess = (e : any) => { - resolve(e.target.result); + req.onsuccess = (e) => { + resolve(req.result); }; - req.onupgradeneeded = (event : any) => { - let db = event.target.result; - console.log ("DB: upgrade needed: oldVersion = " + event.oldVersion); - db = event.target.result; - switch (event.oldVersion) { + req.onupgradeneeded = (e) => { + let db = req.result; + console.log ("DB: upgrade needed: oldVersion = " + e.oldVersion); + switch (e.oldVersion) { case 0: // DB does not exist yet db.createObjectStore("mints", { keyPath: "baseUrl" }); db.createObjectStore("reserves", { keyPath: "reserve_pub"}); @@ -31,6 +30,7 @@ function openTalerDb() { db.createObjectStore("coins", { keyPath: "coin_pub" }); db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true }); db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true }); + db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true }); break; } }; @@ -126,78 +126,156 @@ function rankDenom(denom1: any, denom2: any) { } -function withdraw(denom, reserve, mint): Promise { - console.log("in withdraw"); - let wd: any = {}; - wd.denom_pub = denom.denom_pub; - wd.reserve_pub = reserve.reserve_pub; - let a = new Arena(); - try { - 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(); - console.log("about to blind"); - let ev: ByteArray = rsaBlind(pubHash, blindingFactor, denomPub); - console.log("blinded"); +interface ReservePub { + reservePub: string; +} - if (!denom.fee_withdraw) { - throw Error("Field fee_withdraw missing"); - } +interface CoinPub { + coinPub: string; +} - 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(); - withdrawRequest.set("reserve_pub", reservePub); - withdrawRequest.set("amount_with_fee", amountWithFee.toNbo()); - withdrawRequest.set("withdraw_fee", withdrawFee.toNbo()); - withdrawRequest.set("h_denomination_pub", denomPub.encode().hash()); - withdrawRequest.set("h_coin_envelope", ev.hash()); - console.log("about to sign"); - var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv); - console.log("signed"); - wd.reserve_sig = sig.toCrock(); - wd.coin_ev = ev.toCrock(); - } finally { - a.destroy(); +interface PreCoin { + coinPub: string; + coinPriv: string; + reservePub: string; + denomPub: string; + blindingKey: string; + withdrawSig: string; + coinEv: string; +} + + +interface Coin { + coinPub: string; + coinPriv: string; + reservePub: string; + denomPub: string; + denomSig: string; +} + + +function withdrawPrepare(db: IDBDatabase, denom, reserve): Promise { + console.log("in withdraw prepare"); + let reservePriv = new EddsaPrivateKey(); + console.log("loading reserve priv", reserve.reserve_priv); + reservePriv.loadCrock(reserve.reserve_priv); + console.log("reserve priv is", reservePriv.toCrock()); + 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(); + console.log("about to blind"); + let ev: ByteArray = rsaBlind(pubHash, blindingFactor, denomPub); + console.log("blinded"); + + if (!denom.fee_withdraw) { + throw Error("Field fee_withdraw missing"); } + 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(); + withdrawRequest.set("reserve_pub", reservePub); + withdrawRequest.set("amount_with_fee", amountWithFee.toNbo()); + withdrawRequest.set("withdraw_fee", withdrawFee.toNbo()); + withdrawRequest.set("h_denomination_pub", denomPub.encode().hash()); + withdrawRequest.set("h_coin_envelope", ev.hash()); + console.log("about to sign"); + var sig = eddsaSign(withdrawRequest.toPurpose(), reservePriv); + console.log("signed"); + console.log("crypto done, doing request"); - console.log("request:"); - console.log(JSON.stringify(wd)); + let preCoin: PreCoin = { + reservePub: reservePub.toCrock(), + blindingKey: blindingFactor.toCrock(), + coinPub: coinPub.toCrock(), + coinPriv: coinPriv.toCrock(), + denomPub: denomPub.encode().toCrock(), + withdrawSig: sig.toCrock(), + coinEv: ev.toCrock() + }; - return new Promise((resolve, reject) => { - let reqUrl = URI("reserve/withdraw").absoluteTo(mint.baseUrl); - let myRequest = new XMLHttpRequest(); - console.log("making request to " + reqUrl.href()); - myRequest.open('post', reqUrl.href()); - myRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - myRequest.send(JSON.stringify(wd)); - myRequest.addEventListener('readystatechange', (e) => { - if (myRequest.readyState == XMLHttpRequest.DONE) { - if (myRequest.status != 200) { - console.log("Withdrawal failed, status ", myRequest.status); - reject(); - return; - } - console.log("Withdrawal successful"); - console.log(myRequest.responseText); - resolve(); - } else { - console.log("ready state change to", myRequest.status); - } - }); + console.log("storing precoin", JSON.stringify(preCoin)); + + let tx = db.transaction(['precoins'], 'readwrite'); + tx.objectStore('precoins').add(preCoin); + return new Promise((resolve, reject) => { + tx.oncomplete = (e) => { + resolve(preCoin); + } }); +} +function dbGet(db, store: string, key: any): Promise { + let tx = db.transaction([store]); + let req = tx.objectStore(store).get(key); + return new Promise((resolve, reject) => { + req.onsuccess = (e) => resolve(req.result); + }); +} + + +function withdrawExecute(db, pc: PreCoin): Promise { + return dbGet(db, 'reserves', pc.reservePub) + .then((r) => new Promise((resolve, reject) => { + console.log("loading precoin", JSON.stringify(pc)); + 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); + let myRequest = new XMLHttpRequest(); + console.log("making request to " + reqUrl.href()); + myRequest.open('post', reqUrl.href()); + myRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + myRequest.send(JSON.stringify(wd)); + myRequest.addEventListener('readystatechange', (e) => { + if (myRequest.readyState == XMLHttpRequest.DONE) { + if (myRequest.status != 200) { + console.log("Withdrawal failed, status ", myRequest.status); + reject(); + return; + } + console.log("Withdrawal successful"); + console.log(myRequest.responseText); + let resp = JSON.parse(myRequest.responseText); + //let denomSig = rsaUnblind(RsaSignature.fromCrock(resp.coin_ev), + // RsaBlindingKey.fromCrock(pc.blindingKey), + // RsaPublicKey.fromCrock(pc.denomPub)); + let coin: Coin = { + coinPub: pc.coinPub, + coinPriv: pc.coinPriv, + denomPub: pc.denomPub, + reservePub: pc.reservePub, + denomSig: "foo" //denomSig.encode().toCrock() + } + console.log("unblinded coin"); + resolve(coin); + } else { + console.log("ready state change to", myRequest.status); + } + }); + })); +} + + +function storeCoin(db, coin) { +} + + +function withdraw(db, denom, reserve): Promise { + return withdrawPrepare(db, denom, reserve) + .then((pc) => withdrawExecute(db, pc)) + .then((c) => storeCoin(db, c)); } @@ -208,7 +286,7 @@ function depleteReserve(db, reserve, mint) { let denoms = copy(mint.keys.denoms); let remaining = new Amount(reserve.current_amount); denoms.sort(rankDenom); - let workList = [] + let workList = []; for (let i = 0; i < 1000; i++) { let found = false; for (let d of denoms) { @@ -219,7 +297,7 @@ function depleteReserve(db, reserve, mint) { } found = true; remaining.sub(cost); - workList.push([d, reserve, mint]); + workList.push(d); } if (!found) { break; @@ -227,17 +305,17 @@ function depleteReserve(db, reserve, mint) { } // Do the request one by one. - function work(): void { + function next(): void { if (workList.length == 0) { return; } console.log("doing work"); - let w = workList.pop(); - withdraw(w[0], w[1], w[2]) - .then(() => work()); + let d = workList.pop(); + withdraw(db, d, reserve) + .then(() => next()); } - work(); + next(); } @@ -354,7 +432,11 @@ openTalerDb().then((db) => { "confirm-reserve": confirmReserve, "dump-db": dumpDb } - return dispatch[req.type](db, req.detail, onresponse); + if (req.type in dispatch) { + return dispatch[req.type](db, req.detail, onresponse); + } + console.error(format("Request type unknown, req {0}", JSON.stringify(req))); + return false; }); });