aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-09-28 23:41:34 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-09-28 23:41:34 +0200
commit274204c21e421ed13c66411ce56bb70dea03d410 (patch)
tree44f96996f5631526e96866fe583de2ca87ec2898 /lib
parent29909a27f592ac1bca98bfe7058b576167445518 (diff)
check contract hash, fix unicode bug
Diffstat (limited to 'lib')
-rw-r--r--lib/emscripten/emsc.d.ts2
-rw-r--r--lib/wallet/cryptoApi.ts4
-rw-r--r--lib/wallet/cryptoLib.ts5
-rw-r--r--lib/wallet/emscriptif.ts46
-rw-r--r--lib/wallet/wallet.ts78
-rw-r--r--lib/wallet/wxMessaging.ts14
6 files changed, 131 insertions, 18 deletions
diff --git a/lib/emscripten/emsc.d.ts b/lib/emscripten/emsc.d.ts
index 19a6990e5..b9690433f 100644
--- a/lib/emscripten/emsc.d.ts
+++ b/lib/emscripten/emsc.d.ts
@@ -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;
diff --git a/lib/wallet/cryptoApi.ts b/lib/wallet/cryptoApi.ts
index 585aa39e7..db29592fc 100644
--- a/lib/wallet/cryptoApi.ts
+++ b/lib/wallet/cryptoApi.ts
@@ -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);
}
diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts
index 58a3d5004..9a77b3d74 100644
--- a/lib/wallet/cryptoLib.ts
+++ b/lib/wallet/cryptoLib.ts
@@ -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)
diff --git a/lib/wallet/emscriptif.ts b/lib/wallet/emscriptif.ts
index 1e5fb0283..9a1d902c0 100644
--- a/lib/wallet/emscriptif.ts
+++ b/lib/wallet/emscriptif.ts
@@ -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),
@@ -396,6 +397,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");
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
index 35f86399a..0a2c07673 100644
--- a/lib/wallet/wallet.ts
+++ b/lib/wallet/wallet.ts
@@ -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.
*/
diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts
index be0e09de7..5c97248c4 100644
--- a/lib/wallet/wxMessaging.ts
+++ b/lib/wallet/wxMessaging.ts
@@ -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"});