diff options
Diffstat (limited to 'extension/background/wallet.js')
-rw-r--r-- | extension/background/wallet.js | 268 |
1 files changed, 222 insertions, 46 deletions
diff --git a/extension/background/wallet.js b/extension/background/wallet.js index 18b0a511c..91b6de0d4 100644 --- a/extension/background/wallet.js +++ b/extension/background/wallet.js @@ -1,47 +1,58 @@ 'use strict'; -const DONE = 4; const DB_NAME = "taler"; const DB_VERSION = 1; -// Shown in the UI. -let backendFailed = false; /** - * Run a function with the opened taler DB. + * Return a promise that resolves + * to the taler wallet db. */ -function withTalerDb(f) { - let req = indexedDB.open(DB_NAME, DB_VERSION); - req.addEventListener("error", (e) => { - // XXX: more details - backendFailed = true; - }); - req.addEventListener("success", (e) => { - var db = e.target.result; - f(db); - }); - req.addEventListener("upgradeneeded", (e) => { - console.log ("DB: upgrade needed: oldVersion = "+ event.oldVersion); - db = event.target.result; - switch (event.oldVersion) { - case 0: // DB does not exist yet - db.createObjectStore("mints", { keyPath: "mint_pub" }); - db.createObjectStore("reserves", { keyPath: "reserve_pub"}); - db.createObjectStore("denoms", { keyPath: "denom_pub" }); - db.createObjectStore("coins", { keyPath: "coin_pub" }); - db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true }); - db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true }); - break; - } +function openTalerDb(cont) { + return new Promise((resolve, reject) => { + let req = indexedDB.open(DB_NAME, DB_VERSION); + req.addEventListener("error", (e) => { + reject(e); + }); + req.addEventListener("success", (e) => { + resolve(e.target.result); + }); + req.addEventListener("upgradeneeded", (e) => { + let db = e.target.result; + console.log ("DB: upgrade needed: oldVersion = " + event.oldVersion); + db = event.target.result; + switch (event.oldVersion) { + case 0: // DB does not exist yet + db.createObjectStore("mints", { keyPath: "baseUrl" }); + db.createObjectStore("reserves", { keyPath: "reserve_pub"}); + db.createObjectStore("denoms", { keyPath: "denom_pub" }); + db.createObjectStore("coins", { keyPath: "coin_pub" }); + db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true }); + db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true }); + break; + } + }); }); } +function canonicalizeBaseUrl(url) { + let x = new URI(url); + if (!x.protocol()) { + x.protocol("https"); + } + x.path(x.path() + "/").normalizePath(); + x.fragment(); + x.query(); + return x.href() +} + + function confirmReserve(db, detail, sendResponse) { console.log('detail: ' + JSON.stringify(detail)); let keypair = createEddsaKeyPair(); let form = new FormData(); - let now = new Date(); + let now = (new Date()).toString(); form.append(detail.field_amount, detail.amount_str); form.append(detail.field_reserve_pub, keypair.pub); form.append(detail.field_mint, detail.mint); @@ -50,15 +61,16 @@ function confirmReserve(db, detail, sendResponse) { console.log("making request to " + detail.post_url); myRequest.open('post', detail.post_url); myRequest.send(form); + let mintBaseUrl = canonicalizeBaseUrl(detail.mint); myRequest.addEventListener('readystatechange', (e) => { - if (myRequest.readyState == DONE) { + if (myRequest.readyState == XMLHttpRequest.DONE) { let resp = {}; resp.status = myRequest.status; resp.text = myRequest.responseText; let reserveRecord = { reserve_pub: keypair.pub, reserve_priv: keypair.priv, - mint_base_url: detail.mint, + mint_base_url: mintBaseUrl, created: now, last_query: null, current_amount: null, @@ -77,8 +89,10 @@ function confirmReserve(db, detail, sendResponse) { tx.addEventListener('complete', (e) => { console.log('tx complete, pk was ' + reserveRecord.reserve_pub); sendResponse(resp); - updateReserveMints(db); - // We have to update the mints now ... + var mint; + updateMintFromUrl(db, reserveRecord.mint_base_url) + .then((m) => { mint = m; return updateReserve(db, keypair.pub, mint); }) + .then((reserve) => depleteReserve(db, reserve, mint)); }); break; default: @@ -92,13 +106,179 @@ function confirmReserve(db, detail, sendResponse) { } +function sumAmounts() { + let v = 0; + let f = 0; + let c; + let xs = arguments; + if (xs.length == 0) { + throw "no arguments given"; + } + for (let x of xs) { + v = (v + x.value) + Math.floor((f + x.value) / 10e6); + f = (f + x.fraction) % 10e6; + c = x.currency; + } + return {value: v, fraction:f, currency: c}; +} + +function subtractAmount(a1, a2) { + if (compareAmount(a1, a2) < 0) { + throw "negative result"; + } + let r = { + currency: a1.currency, + value: a1.value, + fraction: a1.fraction + }; + if (a2.fraction > a1.fraction) { + r.value--; + r.fraction += 1000000; + } + r.value -= a2.value; + r.fraction -= a2.fraction; + return r; +} + + +function compareAmount(a1, a2) { + if (a1.currency !== a2.currency) { + throw "can't compare different currencies"; + } + let v1 = [a1.value + Math.floor(a1.fraction / 10e6), a1.fraction % 10e6]; + let v2 = [a2.value + Math.floor(a2.fraction / 10e6), a2.fraction % 10e6]; + for (let i in v1) { + if (v1[i] < v2[i]) { + return -1; + } + if (v1[i] > v2[i]) { + return 1; + } + } + return 0; +} + +function copy(o) { + return JSON.parse(JSON.stringify(o)); +} + + +function rankDenom(o1, o2) { + return (-1) * compareAmount(o1.value, o2.value); +} + + +function withdraw(denom, reserve, mint) { + let wd = {}; + wd.denom_pub = denom.denom_pub; + wd.reserve_pub = reserve.reserve_pub; + let coin_key = createEddsaKeyPair(); + // create RSA blinding key + // blind coin + // generate signature +} + + /** - * Fetch information for mints that - * are referenced in a reserve and that were not - * updated recently. + * Withdraw coins from a reserve until it is empty. */ -function updateReserveMints(db) { +function depleteReserve(db, reserve, mint) { + let denoms = copy(mint.keys.denoms); + let remaining = copy(reserve.current_amount); + denoms.sort(rankDenom); + for (let i = 0; i < 1000; i++) { + let found = false; + for (let d of denoms) { + let cost = sumAmounts(d.value, d.fee_withdraw); + if (compareAmount (remaining, cost) < 0) { + continue; + } + found = true; + remaining = subtractAmount(remaining, cost); + withdraw(d, reserve, mint); + } + if (!found) { + break; + } + } + +} + +function updateReserve(db, reservePub, mint) { + let reserve; + return new Promise((resolve, reject) => { + let tx = db.transaction(['reserves']); + tx.objectStore('reserves').get(reservePub).onsuccess = (e) => { + let reserve = e.target.result; + let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl); + reqUrl.query({'reserve_pub': reservePub}); + let myRequest = new XMLHttpRequest(); + console.log("making request to " + reqUrl.href()); + myRequest.open('get', reqUrl.href()); + myRequest.send(); + myRequest.addEventListener('readystatechange', (e) => { + if (myRequest.readyState == XMLHttpRequest.DONE) { + if (myRequest.status != 200) { + reject(); + return; + } + let reserveInfo = JSON.parse(myRequest.responseText); + console.log("got response " + JSON.stringify(reserveInfo)); + reserve.current_amount = reserveInfo.balance; + let tx = db.transaction(['reserves'], 'readwrite'); + console.log("putting updated reserve " + JSON.stringify(reserve)); + tx.objectStore('reserves').put(reserve); + tx.oncomplete = (e) => { + resolve(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. + */ +function updateMintFromUrl(db, baseUrl) { + console.log("base url is " + baseUrl); + let reqUrl = URI("keys").absoluteTo(baseUrl); + let myRequest = new XMLHttpRequest(); + myRequest.open('get', reqUrl.href()); + myRequest.send(); + return new Promise((resolve, reject) => { + myRequest.addEventListener('readystatechange', (e) => { + console.log("state change to " + myRequest.readyState); + if (myRequest.readyState == XMLHttpRequest.DONE) { + if (myRequest.status == 200) { + console.log("got /keys"); + let mintKeysJson = JSON.parse(myRequest.responseText); + if (!mintKeysJson) { + console.log("keys invalid"); + reject(); + } else { + let mint = {}; + mint.baseUrl = baseUrl; + mint.keys = mintKeysJson; + let tx = db.transaction(['mints'], 'readwrite'); + tx.objectStore('mints').put(mint); + tx.oncomplete = (e) => { + resolve(mint); + }; + } + } else { + console.log("/keys request failed with status " + myRequest.status); + // XXX: also write last error to DB to show in the UI + reject(); + } + } + }); + }); } @@ -127,20 +307,16 @@ function dumpDb(db, detail, sendResponse) { return true; } -withTalerDb((db) => { + +openTalerDb().then((db) => { console.log("db loaded"); chrome.runtime.onMessage.addListener( function (req, sender, onresponse) { - // XXX: use assoc. instead of switch? - switch (req.type) - { - case "confirm-reserve": - return confirmReserve(db, req.detail, onresponse) - break; - case "dump-db": - console.log('dumping db'); - return dumpDb(db, req.detail, onresponse); + let dispatch = { + "confirm-reserve": confirmReserve, + "dump-db": dumpDb } + return dispatch[req.type](db, req.detail, onresponse); }); }); |