From 274204c21e421ed13c66411ce56bb70dea03d410 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 28 Sep 2016 23:41:34 +0200 Subject: check contract hash, fix unicode bug --- lib/wallet/cryptoApi.ts | 4 +++ lib/wallet/cryptoLib.ts | 5 +++ lib/wallet/emscriptif.ts | 46 ++++++++++++++++++++++------ lib/wallet/wallet.ts | 78 +++++++++++++++++++++++++++++++++++++++++------ lib/wallet/wxMessaging.ts | 14 +++++++++ 5 files changed, 129 insertions(+), 18 deletions(-) (limited to 'lib/wallet') 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 { + return this.doRpc("hashString", 1, str); + } + hashRsaPub(rsaPub: string): Promise { 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), @@ -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"); 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 { + 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 { 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 { 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 { + 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"}); -- cgit v1.2.3