Merge branch 'master' of ssh://taler.net/var/git/wallet

This commit is contained in:
Marcello Stanisci 2015-12-17 15:30:08 +01:00
commit 34b039de45
13 changed files with 137 additions and 363 deletions

View File

@ -1,220 +1,5 @@
"use strict";
var DB = function () {
let DB = {}; // returned object with exported functions
let DB_NAME = "taler";
let DB_VERSION = 1;
let db = null;
let is_ready = null;
DB.open = function (onsuccess, onerror)
{
is_ready = false;
let req = indexedDB.open(DB_NAME, DB_VERSION);
req.onerror = onerror;
req.onsuccess = function (event)
{
db = event.target.result;
is_ready = true;
if (onsuccess)
onsuccess();
};
req.onupgradeneeded = function (event)
{
console.log ("DB: upgrade needed: oldVersion = "+ event.oldVersion);
db = event.target.result;
db.onerror = onerror;
switch (event.oldVersion)
{
case 0: // DB does not exist yet
{
let example = {};
let mints = db.createObjectStore("mints", { keyPath: "mint_pub" });
mints.createIndex("name", "name", { unique: true });
example.mint = {
mint_pub: "<mint's master pub key>", // length: 32
name: "Mint One",
url: "https://mint.one/",
};
let denoms = db.createObjectStore("denoms", { keyPath: "denom_pub" });
example.denom = {
denom_pub: "<denom pub key>", // length: 32
mint_pub: "<mint's master pub key>", // length: 32
mint_sig: "<mint's sig>", // length: 64
withdraw_expiry_time: 1234567890,
deposit_expiry_time: 1234567890,
start_time: 1234567890,
value: {
value: 1,
fraction: 230000, // 0..999999
currency: "EUR",
},
fee: {
withdraw: {
value: 0,
fraction: 100000,
currency: "EUR",
},
deposit: {
value: 0,
fraction: 100000,
currency: "EUR",
},
refresh: {
value: 0,
fraction: 100000,
currency: "EUR",
},
},
};
let reserves = db.createObjectStore("reserves", { keyPath: "reserve_pub"});
example.reserve = {
reserve_pub: "<pub key>",
reserve_priv: "<priv key>",
mint_pub: "<mint's master pub key>",
initial: {
value: 1,
fraction: 230000,
currency: "EUR",
},
current: {
value: 1,
fraction: 230000,
currency: "EUR",
blind_session_pub: "<pub key>",
status_sig: "<sig>",
},
};
let withdrawals = db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true });
example.withdrawal = {
id: 1, // generated
reserve_pub: "<pub key>",
reserve_sig: "<sig>",
denom_pub: "<pub key",
blind_session_pub: "<pub key>",
blind_priv: "<priv key>",
coin_pub: "<pub key>",
coin_priv: "<priv key>",
coin_ev: "",
};
let coins = db.createObjectStore("coins", { keyPath: "coin_pub" });
example.coin = {
// coin either has a withdraw_id or refresh_id
// or it is imported in which case both are null
withdraw_id: 1, // can be null
refresh_id: null, // can be null
is_refreshed: false,
denom_pub: "<pub key>",
coin_pub: "<pub key>",
coin_priv: "<priv key>",
denom_sig: "<sig>",
spent: {
value: 1,
fraction: 230000,
},
transactions: [ 123, 456 ], // list of transaction IDs where this coin was used
};
let transactions = db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true });
example.transaction = {
id: 1, // generated
wire_hash: "<hash>",
value: {
value: 1,
fraction: 230000,
currency: "EUR",
},
contract: "<JSON>",
is_checkout_done: true,
is_confirmed: true,
fulfillment_url: "https://some.shop/transaction/completed",
};
let refresh = db.createObjectStore("refresh");
example.refresh = {
// TODO
};
}
}
is_ready = true;
if (onsuccess)
onsuccess();
}
};
DB.close = function ()
{
db.close();
};
DB.wallet_get = function (onresult, onerror)
{
let wallet = { };
let tr = db.transaction([ "coins", "denoms" ], "readonly");
let coins = tr.objectStore("coins");
let denoms = tr.objectStore("denoms");
let coins_cur = coins.openCursor();
coins_cur.onerror = onerror;
coins_cur.onsuccess = function ()
{
let cur = event.target.result;
if (cur) {
let denom_get = denoms.get(cur.valcue.denom_pub);
denom_get.onerror = onerror;
denom_get.onsuccess = function (event)
{
let denom = event.target.result;
if (denom.currency in wallet)
{
let w = wallet[denom.currency];
w.value += denom.value;
w.fraction = (w.fraction + denom.fraction) % 1000000;
if (1000000 <= w.fraction + denom.fraction)
w.value++;
}
else
{
wallet[denom.currency] = denom;
}
cur.continue();
}
}
else // no more entries
{
onresult(wallet);
}
};
};
DB.transaction_list = function (onresult, onerror)
{
// TODO
};
DB.reserve_list = function (onresult, onerror)
{
// TODO
};
return DB;
}();
/**
* Declarations and helpers for
* things that are stored in the wallet's
* database.
*/

View File

@ -284,6 +284,13 @@ class RsaBlindingKey extends ArenaObject {
o.nativePtr = emscAlloc.rsa_blinding_key_create(len);
return o;
}
static fromCrock(s, a) {
let obj = new this(a);
let buf = ByteArray.fromCrock(s);
obj.setNative(emscAlloc.rsa_blinding_key_decode(buf.getNative(), buf.size()));
buf.destroy();
return obj;
}
toCrock() {
let ptr = emscAlloc.malloc(PTR_SIZE);
let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr);

View File

@ -416,12 +416,20 @@ class EddsaPublicKey extends PackedArenaObject {
}
class RsaBlindingKey extends ArenaObject {
static create(len: number, a?: Arena) {
let o = new RsaBlindingKey(a);
o.nativePtr = emscAlloc.rsa_blinding_key_create(len);
return o;
}
static fromCrock(s: string, a?: Arena): RsaBlindingKey {
let obj = new this(a);
let buf = ByteArray.fromCrock(s);
obj.setNative(emscAlloc.rsa_blinding_key_decode(buf.getNative(), buf.size()));
buf.destroy();
return obj;
}
toCrock(): string {
let ptr = emscAlloc.malloc(PTR_SIZE);

View File

@ -1,3 +1,10 @@
/**
* This file should be used as a WebWorker.
* Background pages in the WebExtensions model do
* not allow to schedule callbacks that should be called
* after a timeout. We can emulate this with WebWorkers.
*/
onmessage = function(e) {
self.setInterval(() => postMessage(true), e.data.interval);
}

View File

@ -24,15 +24,18 @@ function openTalerDb() {
db.createObjectStore("mints", { keyPath: "baseUrl" });
db.createObjectStore("reserves", { keyPath: "reserve_pub" });
db.createObjectStore("denoms", { keyPath: "denomPub" });
db.createObjectStore("coins", { keyPath: "coinPub" });
db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true });
db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true });
let coins = db.createObjectStore("coins", { keyPath: "coinPub" });
coins.createIndex("mintBaseUrl", "mintBaseUrl");
db.createObjectStore("transactions", { keyPath: "contractHash" });
db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true });
break;
}
};
});
}
/**
* See http://api.taler.net/wallet.html#general
*/
function canonicalizeBaseUrl(url) {
let x = new URI(url);
if (!x.protocol()) {
@ -43,8 +46,24 @@ function canonicalizeBaseUrl(url) {
x.query();
return x.href();
}
function grantCoins(db, feeThreshold, paymentAmount, mintBaseUrl) {
throw "not implemented";
}
function confirmPay(db, detail, sendResponse) {
console.log("confirmPay", JSON.stringify(detail));
let tx = db.transaction(['transactions'], 'readwrite');
let trans = {
contractHash: detail.offer.H_contract,
contract: detail.offer.contract,
sig: detail.offer
};
let contract = detail.offer.contract;
//let chosenCoinPromise = chooseCoins(db, contract.max_fee, contract.amount)
// .then(x => generateDepositPermissions(db, x))
// .then(executePayment);
return true;
}
function confirmReserve(db, detail, sendResponse) {
console.log('detail: ' + JSON.stringify(detail));
let reservePriv = EddsaPrivateKey.create();
let reservePub = reservePriv.getPublicKey();
let form = new FormData();
@ -114,11 +133,8 @@ function rankDenom(denom1, denom2) {
return (-1) * v1.cmp(v2);
}
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);
@ -126,9 +142,7 @@ function withdrawPrepare(db, denom, reserve) {
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");
}
@ -153,8 +167,10 @@ function withdrawPrepare(db, denom, reserve) {
coinPub: coinPub.toCrock(),
coinPriv: coinPriv.toCrock(),
denomPub: denomPub.encode().toCrock(),
mintBaseUrl: reserve.mintBaseUrl,
withdrawSig: sig.toCrock(),
coinEv: ev.toCrock()
coinEv: ev.toCrock(),
coinValue: denom.value
};
console.log("storing precoin", JSON.stringify(preCoin));
let tx = db.transaction(['precoins'], 'readwrite');
@ -197,15 +213,13 @@ function withdrawExecute(db, pc) {
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 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()
denomSig: denomSig.encode().toCrock(),
currentAmount: pc.coinValue
};
console.log("unblinded coin");
resolve(coin);
@ -442,6 +456,7 @@ openTalerDb().then((db) => {
chrome.runtime.onMessage.addListener(function (req, sender, onresponse) {
let dispatch = {
"confirm-reserve": confirmReserve,
"confirm-pay": confirmPay,
"dump-db": dumpDb,
"balances": balances,
"reset": reset
@ -449,7 +464,7 @@ openTalerDb().then((db) => {
if (req.type in dispatch) {
return dispatch[req.type](db, req.detail, onresponse);
}
console.error(format("Request type unknown, req {0}", JSON.stringify(req)));
console.error(format("Request type {1} unknown, req {0}", JSON.stringify(req), req.type));
return false;
});
});

View File

@ -27,9 +27,9 @@ function openTalerDb(): Promise<IDBDatabase> {
db.createObjectStore("mints", { keyPath: "baseUrl" });
db.createObjectStore("reserves", { keyPath: "reserve_pub"});
db.createObjectStore("denoms", { keyPath: "denomPub" });
db.createObjectStore("coins", { keyPath: "coinPub" });
db.createObjectStore("withdrawals", { keyPath: "id", autoIncrement: true });
db.createObjectStore("transactions", { keyPath: "id", autoIncrement: true });
let coins = db.createObjectStore("coins", { keyPath: "coinPub" });
coins.createIndex("mintBaseUrl", "mintBaseUrl");
db.createObjectStore("transactions", { keyPath: "contractHash" });
db.createObjectStore("precoins", { keyPath: "coinPub", autoIncrement: true });
break;
}
@ -38,6 +38,9 @@ function openTalerDb(): Promise<IDBDatabase> {
}
/**
* See http://api.taler.net/wallet.html#general
*/
function canonicalizeBaseUrl(url) {
let x = new URI(url);
if (!x.protocol()) {
@ -49,9 +52,39 @@ function canonicalizeBaseUrl(url) {
return x.href()
}
interface ConfirmPayRequest {
offer: any;
selectedMint: any;
}
function grantCoins(db: IDBDatabase,
feeThreshold: AmountJson,
paymentAmount: AmountJson,
mintBaseUrl: string): Promise<any> {
throw "not implemented";
}
function confirmPay(db, detail: ConfirmPayRequest, sendResponse) {
console.log("confirmPay", JSON.stringify(detail));
let tx = db.transaction(['transactions'], 'readwrite');
let trans = {
contractHash: detail.offer.H_contract,
contract: detail.offer.contract,
sig: detail.offer
}
let contract = detail.offer.contract;
//let chosenCoinPromise = chooseCoins(db, contract.max_fee, contract.amount)
// .then(x => generateDepositPermissions(db, x))
// .then(executePayment);
return true;
}
function confirmReserve(db, detail, sendResponse) {
console.log('detail: ' + JSON.stringify(detail));
let reservePriv = EddsaPrivateKey.create();
let reservePub = reservePriv.getPublicKey();
let form = new FormData();
@ -126,45 +159,11 @@ function rankDenom(denom1: any, denom2: any) {
}
interface AmountJson {
value: number;
fraction: number;
currency: string;
}
interface Denomination {
value: AmountJson;
denomPub: string;
}
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<PreCoin> {
console.log("in withdraw prepare");
function withdrawPrepare(db: IDBDatabase,
denom: Denomination,
reserve): Promise<PreCoin> {
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);
@ -172,9 +171,7 @@ function withdrawPrepare(db: IDBDatabase, denom, reserve): Promise<PreCoin> {
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");
@ -205,8 +202,10 @@ function withdrawPrepare(db: IDBDatabase, denom, reserve): Promise<PreCoin> {
coinPub: coinPub.toCrock(),
coinPriv: coinPriv.toCrock(),
denomPub: denomPub.encode().toCrock(),
mintBaseUrl: reserve.mintBaseUrl,
withdrawSig: sig.toCrock(),
coinEv: ev.toCrock()
coinEv: ev.toCrock(),
coinValue: denom.value
};
console.log("storing precoin", JSON.stringify(preCoin));
@ -254,15 +253,15 @@ function withdrawExecute(db, pc: PreCoin): Promise<Coin> {
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 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()
denomSig: denomSig.encode().toCrock(),
currentAmount: pc.coinValue
}
console.log("unblinded coin");
resolve(coin);
@ -505,7 +504,7 @@ function balances(db, detail, sendResponse) {
sendResponse(byCurrency);
console.log("response", JSON.stringify(byCurrency));
}
}
};
return true;
}
@ -517,15 +516,15 @@ openTalerDb().then((db) => {
function (req, sender, onresponse) {
let dispatch = {
"confirm-reserve": confirmReserve,
"confirm-pay": confirmPay,
"dump-db": dumpDb,
"balances": balances,
"reset": reset
}
};
if (req.type in dispatch) {
return dispatch[req.type](db, req.detail, onresponse);
}
console.error(format("Request type unknown, req {0}", JSON.stringify(req)));
console.error(format("Request type {1} unknown, req {0}", JSON.stringify(req), req.type));
return false;
});
});

View File

@ -44,10 +44,10 @@ document.addEventListener("DOMContentLoaded", function(e) {
});
document.body.addEventListener('taler-contract', function(e) {
// XXX: the merchant should just give us the parsed data ...
let contract = JSON.parse(e.detail);
let offer = JSON.parse(e.detail);
let uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
let params = {
contract: JSON.stringify(contract)
offer: JSON.stringify(offer)
}
document.location.href = uri.query(params).href();
});

View File

@ -16,15 +16,15 @@ Handlebars.registerHelper('prettyAmount', function (amount) {
return v.toFixed(2) + " " + amount.currency;
});
document.addEventListener("DOMContentLoaded", (e) => {
let contract = JSON.parse(query.contract);
console.dir(contract);
let offer = JSON.parse(query.offer);
console.dir(offer);
let source = $_("contract-template").innerHTML;
let template = Handlebars.compile(source);
let html = template(contract.contract);
let html = template(offer.contract);
$_("render-contract").innerHTML = html;
document.getElementById("confirm-purchase").addEventListener("click", (e) => {
document.getElementById("confirm-pay").addEventListener("click", (e) => {
let d = clone(query);
chrome.runtime.sendMessage({ type: 'confirm-purchase', detail: d }, (resp) => {
chrome.runtime.sendMessage({ type: 'confirm-pay', detail: d }, (resp) => {
if (resp.success === true) {
document.location.href = resp.backlink;
}

View File

@ -24,19 +24,18 @@ Handlebars.registerHelper('prettyAmount', function(amount) {
document.addEventListener("DOMContentLoaded", (e) => {
let contract = JSON.parse(query.contract);
console.dir(contract);
let offer = JSON.parse(query.offer);
console.dir(offer);
let source = $_("contract-template").innerHTML;
let template = Handlebars.compile(source);
let html = template(contract.contract);
let html = template(offer.contract);
$_("render-contract").innerHTML = html;
document.getElementById("confirm-purchase").addEventListener("click", (e) => {
document.getElementById("confirm-pay").addEventListener("click", (e) => {
let d = clone(query);
chrome.runtime.sendMessage({type:'confirm-purchase', detail: d}, (resp) => {
chrome.runtime.sendMessage({type:'confirm-pay', detail: d}, (resp) => {
if (resp.success === true) {
document.location.href = resp.backlink;
} else {
@ -46,7 +45,6 @@ document.addEventListener("DOMContentLoaded", (e) => {
<pre>${resp.text}</pre>`;
}
});
});
});

View File

@ -10,9 +10,14 @@
<script src="balance-overview.js" type="text/javascript"></script>
<script id="balance-template" type="text/x-handlebars-template">
{{#if this.length}}
{{#each this}}
<p>{{prettyAmountNoCurrency this}} <a>{{@key}}</a></p>
{{/each}}
{{else}}
<p>Looks like your wallet is empty. Want to get some
<a id="link-kudos" href="http://bank.demo.taler.net">KUDOS?</a>
{{/if}}
</script>
</head>
@ -26,11 +31,6 @@
</div>
<div id="content">
<div id="balance">
<p>Looks like your wallet is empty. Want to get some
<a id="link-kudos" href="http://bank.demo.taler.net">KUDOS?</a>
</p>
</div>
</div>
</body>

View File

@ -17,31 +17,9 @@ let React = {
document.addEventListener('DOMContentLoaded', (e) => {
console.log("content loaded");
chrome.runtime.sendMessage({ type: "balances" }, function (wallet) {
console.log("got balance");
let n = 0;
let table = React.createElement("div", null);
let source = document.getElementById("balance-template").innerHTML;
console.log("size", Object.keys(wallet).length);
if (Object.keys(wallet).length > 0) {
let template = Handlebars.compile(source);
console.log("wallet ", JSON.stringify(wallet));
let html = template(wallet);
console.log("Hb generated html", html);
table.innerHTML = html;
let p = document.getElementById("content");
p.replaceChild(table, p.firstElementChild);
}
/*
for (let curr in wallet) {
n++;
let x = wallet[curr];
let num = x.value + x.fraction / 10e6;
table.appendChild(<p>{num} <a>{x.currency}</a></p>);
}
if (n != 0) {
let p = document.getElementById("content");
p.replaceChild(table, p.firstElementChild);
} */
let context = document.getElementById("balance-template").innerHTML;
let template = Handlebars.compile(context);
document.getElementById("content").innerHTML = template(wallet);
});
document.getElementById("debug").addEventListener("click", (e) => {
chrome.tabs.create({

View File

@ -19,33 +19,9 @@ let React = {
document.addEventListener('DOMContentLoaded', (e) => {
console.log("content loaded");
chrome.runtime.sendMessage({type: "balances"}, function(wallet) {
console.log("got balance");
let n = 0;
let table = <div />;
let source = document.getElementById("balance-template").innerHTML;
console.log("size", Object.keys(wallet).length);
if (Object.keys(wallet).length > 0){
let template = Handlebars.compile(source);
console.log("wallet ", JSON.stringify(wallet));
let html = template(wallet);
console.log("Hb generated html", html);
table.innerHTML = html;
let p = document.getElementById("content");
p.replaceChild(table, p.firstElementChild);
}
/*
for (let curr in wallet) {
n++;
let x = wallet[curr];
let num = x.value + x.fraction / 10e6;
table.appendChild(<p>{num} <a>{x.currency}</a></p>);
}
if (n != 0) {
let p = document.getElementById("content");
p.replaceChild(table, p.firstElementChild);
} */
let context = document.getElementById("balance-template").innerHTML;
let template = Handlebars.compile(context);
document.getElementById("content").innerHTML = template(wallet);
});
document.getElementById("debug").addEventListener("click", (e) => {

View File

@ -6,6 +6,7 @@
"files": [
"background/wallet.ts",
"background/emscriptif.ts",
"background/db.ts",
"lib/util.ts",
"popup/balance-overview.tsx",
"pages/confirm-contract.tsx"