check contract hash, fix unicode bug
This commit is contained in:
parent
29909a27f5
commit
274204c21e
@ -76,8 +76,8 @@ namespace TalerNotify {
|
||||
console.log("it's execute");
|
||||
document.documentElement.style.visibility = "hidden";
|
||||
taler.internalExecutePayment(resp.contractHash,
|
||||
resp.payUrl,
|
||||
resp.offerUrl);
|
||||
resp.payUrl,
|
||||
resp.offerUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -163,38 +163,62 @@ namespace TalerNotify {
|
||||
return;
|
||||
}
|
||||
|
||||
const walletMsg = {
|
||||
type: "check-repurchase",
|
||||
detail: {
|
||||
contract: offer.contract
|
||||
},
|
||||
if (!offer.H_contract) {
|
||||
console.error("H_contract field missing");
|
||||
return;
|
||||
}
|
||||
|
||||
let walletHashContractMsg = {
|
||||
type: "hash-contract",
|
||||
detail: {contract: offer.contract}
|
||||
};
|
||||
|
||||
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
||||
if (resp.error) {
|
||||
console.error("wallet backend error", resp);
|
||||
chrome.runtime.sendMessage(walletHashContractMsg, (resp: any) => {
|
||||
|
||||
if (!resp.hash) {
|
||||
console.log("error", resp);
|
||||
throw Error("hashing failed");
|
||||
}
|
||||
|
||||
if (resp.hash != offer.H_contract) {
|
||||
console.error("merchant-supplied contract hash is wrong");
|
||||
return;
|
||||
}
|
||||
if (resp.isRepurchase) {
|
||||
console.log("doing repurchase");
|
||||
console.assert(resp.existingFulfillmentUrl);
|
||||
console.assert(resp.existingContractHash);
|
||||
window.location.href = subst(resp.existingFulfillmentUrl,
|
||||
resp.existingContractHash);
|
||||
|
||||
} else {
|
||||
const uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
|
||||
const params = {
|
||||
offer: JSON.stringify(offer),
|
||||
merchantPageUrl: document.location.href,
|
||||
};
|
||||
const target = uri.query(params).href();
|
||||
if (msg.replace_navigation === true) {
|
||||
document.location.replace(target);
|
||||
} else {
|
||||
document.location.href = target;
|
||||
const walletMsg = {
|
||||
type: "check-repurchase",
|
||||
detail: {
|
||||
contract: offer.contract
|
||||
},
|
||||
};
|
||||
|
||||
chrome.runtime.sendMessage(walletMsg, (resp: any) => {
|
||||
if (resp.error) {
|
||||
console.error("wallet backend error", resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (resp.isRepurchase) {
|
||||
console.log("doing repurchase");
|
||||
console.assert(resp.existingFulfillmentUrl);
|
||||
console.assert(resp.existingContractHash);
|
||||
window.location.href = subst(resp.existingFulfillmentUrl,
|
||||
resp.existingContractHash);
|
||||
|
||||
} else {
|
||||
const uri = URI(chrome.extension.getURL(
|
||||
"pages/confirm-contract.html"));
|
||||
const params = {
|
||||
offer: JSON.stringify(offer),
|
||||
merchantPageUrl: document.location.href,
|
||||
};
|
||||
const target = uri.query(params).href();
|
||||
if (msg.replace_navigation === true) {
|
||||
document.location.replace(target);
|
||||
} else {
|
||||
document.location.href = target;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
2
lib/emscripten/emsc.d.ts
vendored
2
lib/emscripten/emsc.d.ts
vendored
@ -33,6 +33,8 @@ export interface EmscFunGen {
|
||||
export declare namespace Module {
|
||||
var cwrap: EmscFunGen;
|
||||
|
||||
function stringToUTF8(s: string, addr: number, maxLength: number): void
|
||||
|
||||
function _free(ptr: number): void;
|
||||
|
||||
function _malloc(n: number): number;
|
||||
|
@ -176,6 +176,10 @@ export class CryptoApi {
|
||||
return this.doRpc("createPreCoin", 1, denom, reserve);
|
||||
}
|
||||
|
||||
hashString(str: string): Promise<string> {
|
||||
return this.doRpc("hashString", 1, str);
|
||||
}
|
||||
|
||||
hashRsaPub(rsaPub: string): Promise<string> {
|
||||
return this.doRpc("hashRsaPub", 2, rsaPub);
|
||||
}
|
||||
|
@ -139,6 +139,11 @@ namespace RpcFunctions {
|
||||
|
||||
}
|
||||
|
||||
export function hashString(str: string): string {
|
||||
const b = native.ByteArray.fromString(str);
|
||||
return b.hash().toCrock();
|
||||
}
|
||||
|
||||
|
||||
export function hashRsaPub(rsaPub: string): string {
|
||||
return native.RsaPublicKey.fromCrock(rsaPub)
|
||||
|
@ -36,8 +36,9 @@ const GNUNET_SYSERR = -1;
|
||||
|
||||
let Module = EmscWrapper.Module;
|
||||
|
||||
let getEmsc: EmscWrapper.EmscFunGen = (...args: any[]) => Module.cwrap.apply(null,
|
||||
args);
|
||||
let getEmsc: EmscWrapper.EmscFunGen = (...args: any[]) => Module.cwrap.apply(
|
||||
null,
|
||||
args);
|
||||
|
||||
var emsc = {
|
||||
free: (ptr: number) => Module._free(ptr),
|
||||
@ -395,6 +396,30 @@ export class Amount extends ArenaObject {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Count the UTF-8 characters in a JavaScript string.
|
||||
*/
|
||||
function countBytes(str: string): number {
|
||||
var s = str.length;
|
||||
// JavaScript strings are UTF-16 arrays
|
||||
for (let i = str.length - 1; i >= 0; i--) {
|
||||
var code = str.charCodeAt(i);
|
||||
if (code > 0x7f && code <= 0x7ff) {
|
||||
// We need an extra byte in utf-8 here
|
||||
s++;
|
||||
} else if (code > 0x7ff && code <= 0xffff) {
|
||||
// We need two extra bytes in utf-8 here
|
||||
s += 2;
|
||||
}
|
||||
// Skip over the other surrogate
|
||||
if (code >= 0xDC00 && code <= 0xDFFF) {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Managed reference to a contiguous block of memory in the Emscripten heap.
|
||||
* Should contain only data, not pointers.
|
||||
@ -632,17 +657,20 @@ export class ByteArray extends PackedArenaObject {
|
||||
}
|
||||
|
||||
static fromString(s: string, a?: Arena): ByteArray {
|
||||
let hstr = emscAlloc.malloc(s.length + 1);
|
||||
Module.writeStringToMemory(s, hstr);
|
||||
return new ByteArray(s.length, hstr, a);
|
||||
// UTF-8 bytes, including 0-terminator
|
||||
let terminatedByteLength = countBytes(s) + 1;
|
||||
let hstr = emscAlloc.malloc(terminatedByteLength);
|
||||
Module.stringToUTF8(s, hstr, terminatedByteLength);
|
||||
return new ByteArray(terminatedByteLength, hstr, a);
|
||||
}
|
||||
|
||||
static fromCrock(s: string, a?: Arena): ByteArray {
|
||||
let hstr = emscAlloc.malloc(s.length + 1);
|
||||
Module.writeStringToMemory(s, hstr);
|
||||
let decodedLen = Math.floor((s.length * 5) / 8);
|
||||
let byteLength = countBytes(s) + 1;
|
||||
let hstr = emscAlloc.malloc(byteLength);
|
||||
Module.stringToUTF8(s, hstr, byteLength);
|
||||
let decodedLen = Math.floor((byteLength * 5) / 8);
|
||||
let ba = new ByteArray(decodedLen, undefined, a);
|
||||
let res = emsc.string_to_data(hstr, s.length, ba.nativePtr, decodedLen);
|
||||
let res = emsc.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen);
|
||||
emsc.free(hstr);
|
||||
if (res != GNUNET_OK) {
|
||||
throw Error("decoding failed");
|
||||
|
@ -45,12 +45,22 @@ import {ExchangeHandle} from "./types";
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
export interface CoinWithDenom {
|
||||
coin: Coin;
|
||||
denom: Denomination;
|
||||
}
|
||||
|
||||
interface ReserveRecord {
|
||||
reserve_pub: string;
|
||||
reserve_priv: string,
|
||||
exchange_base_url: string,
|
||||
created: number,
|
||||
last_query: number|null,
|
||||
current_amount: null,
|
||||
requested_amount: AmountJson,
|
||||
confirmed: boolean,
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class
|
||||
export class KeysJson {
|
||||
@ -124,6 +134,13 @@ export class Offer {
|
||||
static checked: (obj: any) => Offer;
|
||||
}
|
||||
|
||||
export interface HistoryRecord {
|
||||
type: string;
|
||||
timestamp: number;
|
||||
subjectId?: string;
|
||||
detail: any;
|
||||
}
|
||||
|
||||
|
||||
interface ExchangeCoins {
|
||||
[exchangeUrl: string]: CoinWithDenom[];
|
||||
@ -145,6 +162,32 @@ export interface Badge {
|
||||
stopBusy(): void;
|
||||
}
|
||||
|
||||
export function canonicalJson(obj: any): string {
|
||||
// Check for cycles, etc.
|
||||
JSON.stringify(obj);
|
||||
if (typeof obj == "string" || typeof obj == "number" || obj === null) {
|
||||
return JSON.stringify(obj)
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
let objs: string[] = obj.map((e) => canonicalJson(e));
|
||||
return `[${objs.join(',')}]`;
|
||||
}
|
||||
let keys: string[] = [];
|
||||
for (let key in obj) {
|
||||
keys.push(key);
|
||||
}
|
||||
keys.sort();
|
||||
let s = "{";
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
|
||||
if (i != keys.length - 1) {
|
||||
s += ",";
|
||||
}
|
||||
}
|
||||
return s + "}";
|
||||
}
|
||||
|
||||
|
||||
function deepEquals(x: any, y: any): boolean {
|
||||
if (x === y) {
|
||||
@ -467,6 +510,7 @@ export class Wallet {
|
||||
let historyEntry = {
|
||||
type: "pay",
|
||||
timestamp: (new Date).getTime(),
|
||||
subjectId: `contract-${offer.H_contract}`,
|
||||
detail: {
|
||||
merchantName: offer.contract.merchant.name,
|
||||
amount: offer.contract.amount,
|
||||
@ -485,6 +529,11 @@ export class Wallet {
|
||||
}
|
||||
|
||||
|
||||
async putHistory(historyEntry: HistoryRecord): Promise<void> {
|
||||
await Query(this.db).put("history", historyEntry).finish();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a contract to the wallet and sign coins,
|
||||
* but do not send them yet.
|
||||
@ -574,7 +623,7 @@ export class Wallet {
|
||||
* First fetch information requred to withdraw from the reserve,
|
||||
* then deplete the reserve, withdrawing coins until it is empty.
|
||||
*/
|
||||
private async processReserve(reserveRecord: any,
|
||||
private async processReserve(reserveRecord: ReserveRecord,
|
||||
retryDelayMs: number = 250): Promise<void> {
|
||||
const opId = "reserve-" + reserveRecord.reserve_pub;
|
||||
this.startOperation(opId);
|
||||
@ -586,9 +635,11 @@ export class Wallet {
|
||||
await this.depleteReserve(reserve, exchange);
|
||||
let depleted = {
|
||||
type: "depleted-reserve",
|
||||
subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
|
||||
timestamp: (new Date).getTime(),
|
||||
detail: {
|
||||
reservePub: reserveRecord.reserve_pub,
|
||||
currentAmount: reserveRecord.current_amount,
|
||||
}
|
||||
};
|
||||
await Query(this.db).put("history", depleted).finish();
|
||||
@ -630,7 +681,7 @@ export class Wallet {
|
||||
const now = (new Date).getTime();
|
||||
const canonExchange = canonicalizeBaseUrl(req.exchange);
|
||||
|
||||
const reserveRecord = {
|
||||
const reserveRecord: ReserveRecord = {
|
||||
reserve_pub: keypair.pub,
|
||||
reserve_priv: keypair.priv,
|
||||
exchange_base_url: canonExchange,
|
||||
@ -644,6 +695,7 @@ export class Wallet {
|
||||
const historyEntry = {
|
||||
type: "create-reserve",
|
||||
timestamp: now,
|
||||
subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
|
||||
detail: {
|
||||
requestedAmount: req.amount,
|
||||
reservePub: reserveRecord.reserve_pub,
|
||||
@ -674,26 +726,28 @@ export class Wallet {
|
||||
*/
|
||||
async confirmReserve(req: ConfirmReserveRequest): Promise<void> {
|
||||
const now = (new Date).getTime();
|
||||
let reserve: ReserveRecord = await Query(this.db)
|
||||
.get("reserves", req.reservePub);
|
||||
const historyEntry = {
|
||||
type: "confirm-reserve",
|
||||
timestamp: now,
|
||||
subjectId: `reserve-progress-${reserve.reserve_pub}`,
|
||||
detail: {
|
||||
reservePub: req.reservePub,
|
||||
requestedAmount: reserve.requested_amount,
|
||||
}
|
||||
};
|
||||
let r = await Query(this.db)
|
||||
.get("reserves", req.reservePub);
|
||||
if (!r) {
|
||||
if (!reserve) {
|
||||
console.error("Unable to confirm reserve, not found in DB");
|
||||
return;
|
||||
}
|
||||
r.confirmed = true;
|
||||
reserve.confirmed = true;
|
||||
await Query(this.db)
|
||||
.put("reserves", r)
|
||||
.put("reserves", reserve)
|
||||
.put("history", historyEntry)
|
||||
.finish();
|
||||
|
||||
this.processReserve(r);
|
||||
this.processReserve(reserve);
|
||||
}
|
||||
|
||||
|
||||
@ -801,8 +855,10 @@ export class Wallet {
|
||||
let historyEntry = {
|
||||
type: "reserve-update",
|
||||
timestamp: (new Date).getTime(),
|
||||
subjectId: `reserve-progress-${reserve.reserve_pub}`,
|
||||
detail: {
|
||||
reservePub,
|
||||
requestedAmount: reserve.requested_amount,
|
||||
oldAmount,
|
||||
newAmount
|
||||
}
|
||||
@ -1040,6 +1096,10 @@ export class Wallet {
|
||||
return {history};
|
||||
}
|
||||
|
||||
async hashContract(contract: any): Promise<string> {
|
||||
return this.cryptoApi.hashString(canonicalJson(contract));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's an equivalent contract we've already purchased.
|
||||
*/
|
||||
|
@ -151,6 +151,20 @@ function makeHandlers(db: IDBDatabase,
|
||||
}
|
||||
return wallet.updateExchangeFromUrl(detail.baseUrl);
|
||||
},
|
||||
["hash-contract"]: function(detail) {
|
||||
if (!detail.contract) {
|
||||
return Promise.resolve({error: "contract missing"});
|
||||
}
|
||||
return wallet.hashContract(detail.contract).then((hash) => {
|
||||
return {hash};
|
||||
});
|
||||
},
|
||||
["put-history-entry"]: function(detail: any) {
|
||||
if (!detail.historyEntry) {
|
||||
return Promise.resolve({error: "historyEntry missing"});
|
||||
}
|
||||
return wallet.putHistory(detail.historyEntry);
|
||||
},
|
||||
["reserve-creation-info"]: function(detail, sender) {
|
||||
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
||||
return Promise.resolve({error: "bad url"});
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
|
||||
import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
|
||||
import {Wallet} from "../lib/wallet/wallet";
|
||||
import {HistoryRecord} from "../lib/wallet/wallet";
|
||||
import {AmountJson} from "../lib/wallet/types";
|
||||
|
||||
declare var m: any;
|
||||
@ -173,7 +173,7 @@ function retryPayment(url: string, contractHash: string) {
|
||||
}
|
||||
|
||||
|
||||
function formatHistoryItem(historyItem: any) {
|
||||
function formatHistoryItem(historyItem: HistoryRecord) {
|
||||
const d = historyItem.detail;
|
||||
const t = historyItem.timestamp;
|
||||
console.log("hist item", historyItem);
|
||||
@ -215,7 +215,7 @@ namespace WalletHistory {
|
||||
}
|
||||
|
||||
class Controller {
|
||||
myHistory: any;
|
||||
myHistory: any[];
|
||||
gotError = false;
|
||||
|
||||
constructor() {
|
||||
@ -241,14 +241,24 @@ namespace WalletHistory {
|
||||
}
|
||||
|
||||
export function view(ctrl: Controller) {
|
||||
let history = ctrl.myHistory;
|
||||
let history: HistoryRecord[] = ctrl.myHistory;
|
||||
if (ctrl.gotError) {
|
||||
return i18n`Error: could not retrieve event history`;
|
||||
}
|
||||
if (!history) {
|
||||
throw Error("Could not retrieve history");
|
||||
}
|
||||
let listing = _.map(history, formatHistoryItem);
|
||||
|
||||
let subjectMemo: {[s: string]: boolean} = {};
|
||||
let listing: any[] = [];
|
||||
for (let record of history.reverse()) {
|
||||
//if (record.subjectId && subjectMemo[record.subjectId]) {
|
||||
// return;
|
||||
//}
|
||||
subjectMemo[record.subjectId as string] = true;
|
||||
listing.push(formatHistoryItem(record));
|
||||
}
|
||||
|
||||
if (listing.length > 0) {
|
||||
return m("div.container", listing);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user