summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-10-12 23:30:10 +0200
committerFlorian Dold <florian.dold@gmail.com>2016-10-12 23:30:10 +0200
commit122e069d914622343fa1a21c3990a2f416ea9dfe (patch)
treea824a5e12509a791cb3c47822fa3a308a0ded360
parentd4be3906e32ac7d9933c6030d6493f2f2152bdd9 (diff)
downloadwallet-core-122e069d914622343fa1a21c3990a2f416ea9dfe.tar.gz
wallet-core-122e069d914622343fa1a21c3990a2f416ea9dfe.tar.bz2
wallet-core-122e069d914622343fa1a21c3990a2f416ea9dfe.zip
crypto for refreshing
-rw-r--r--.vscode/settings.json5
-rw-r--r--lib/emscripten/emsc.d.ts2
-rw-r--r--lib/wallet/emscriptif.ts371
-rw-r--r--lib/wallet/helpers.ts6
-rw-r--r--lib/wallet/types.ts66
-rw-r--r--pages/tree.tsx25
6 files changed, 351 insertions, 124 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d6f4a1a1d..e17e44c92 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,6 @@
// Place your settings in this file to overwrite default and user settings.
{
- // Use latest language services
+ // Use latest language servicesu
"typescript.tsdk": "node_modules/typescript/lib",
// Defines space handling after a comma delimiter
"typescript.format.insertSpaceAfterCommaDelimiter": true,
@@ -33,5 +33,6 @@
"when": "$(basename).tsx"
},
"**/*.js.map": true
- }
+ },
+ "editor.wrappingIndent": "same"
} \ No newline at end of file
diff --git a/lib/emscripten/emsc.d.ts b/lib/emscripten/emsc.d.ts
index b9690433f..0b180781a 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 ccall(name: string, ret:"number"|"string", argTypes: any[], args: any[]): any
+
function stringToUTF8(s: string, addr: number, maxLength: number): void
function _free(ptr: number): void;
diff --git a/lib/wallet/emscriptif.ts b/lib/wallet/emscriptif.ts
index 5879300e7..23014114a 100644
--- a/lib/wallet/emscriptif.ts
+++ b/lib/wallet/emscriptif.ts
@@ -14,13 +14,13 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import {AmountJson} from "./types";
+import { AmountJson } from "./types";
import * as EmscWrapper from "../emscripten/emsc";
/**
* High-level interface to emscripten-compiled modules used
* by the wallet.
- * @module EmscriptIf
+ *
* @author Florian Dold
*/
@@ -43,110 +43,122 @@ let getEmsc: EmscWrapper.EmscFunGen = (...args: any[]) => Module.cwrap.apply(
var emsc = {
free: (ptr: number) => Module._free(ptr),
get_value: getEmsc('TALER_WR_get_value',
- 'number',
- ['number']),
+ 'number',
+ ['number']),
get_fraction: getEmsc('TALER_WR_get_fraction',
- 'number',
- ['number']),
+ 'number',
+ ['number']),
get_currency: getEmsc('TALER_WR_get_currency',
- 'string',
- ['number']),
+ 'string',
+ ['number']),
amount_add: getEmsc('TALER_amount_add',
- 'number',
- ['number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number']),
amount_subtract: getEmsc('TALER_amount_subtract',
- 'number',
- ['number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number']),
amount_normalize: getEmsc('TALER_amount_normalize',
- 'void',
- ['number']),
+ 'void',
+ ['number']),
amount_get_zero: getEmsc('TALER_amount_get_zero',
- 'number',
- ['string', 'number']),
+ 'number',
+ ['string', 'number']),
amount_cmp: getEmsc('TALER_amount_cmp',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
amount_hton: getEmsc('TALER_amount_hton',
- 'void',
- ['number', 'number']),
+ 'void',
+ ['number', 'number']),
amount_ntoh: getEmsc('TALER_amount_ntoh',
- 'void',
- ['number', 'number']),
+ 'void',
+ ['number', 'number']),
hash: getEmsc('GNUNET_CRYPTO_hash',
- 'void',
- ['number', 'number', 'number']),
+ 'void',
+ ['number', 'number', 'number']),
memmove: getEmsc('memmove',
- 'number',
- ['number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number']),
rsa_public_key_free: getEmsc('GNUNET_CRYPTO_rsa_public_key_free',
- 'void',
- ['number']),
+ 'void',
+ ['number']),
rsa_signature_free: getEmsc('GNUNET_CRYPTO_rsa_signature_free',
- 'void',
- ['number']),
+ 'void',
+ ['number']),
string_to_data: getEmsc('GNUNET_STRINGS_string_to_data',
- 'number',
- ['number', 'number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number', 'number']),
eddsa_sign: getEmsc('GNUNET_CRYPTO_eddsa_sign',
- 'number',
- ['number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number']),
eddsa_verify: getEmsc('GNUNET_CRYPTO_eddsa_verify',
- 'number',
- ['number', 'number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number', 'number']),
hash_create_random: getEmsc('GNUNET_CRYPTO_hash_create_random',
- 'void',
- ['number', 'number']),
+ 'void',
+ ['number', 'number']),
rsa_blinding_key_destroy: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_free',
- 'void',
- ['number']),
+ 'void',
+ ['number']),
random_block: getEmsc('GNUNET_CRYPTO_random_block',
- 'void',
- ['number', 'number', 'number']),
+ 'void',
+ ['number', 'number', 'number']),
+ hash_context_abort: getEmsc('GNUNET_CRYPTO_hash_context_abort',
+ 'void',
+ ['number']),
+ hash_context_read: getEmsc('GNUNET_CRYPTO_hash_context_read',
+ 'void',
+ ['number', 'number', 'number']),
+ hash_context_finish: getEmsc('GNUNET_CRYPTO_hash_context_finish',
+ 'void',
+ ['number', 'number']),
};
var emscAlloc = {
get_amount: getEmsc('TALER_WRALL_get_amount',
- 'number',
- ['number', 'number', 'number', 'string']),
+ 'number',
+ ['number', 'number', 'number', 'string']),
eddsa_key_create: getEmsc('GNUNET_CRYPTO_eddsa_key_create',
- 'number', []),
+ 'number', []),
eddsa_public_key_from_private: getEmsc(
'TALER_WRALL_eddsa_public_key_from_private',
'number',
['number']),
data_to_string_alloc: getEmsc('GNUNET_STRINGS_data_to_string_alloc',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
purpose_create: getEmsc('TALER_WRALL_purpose_create',
- 'number',
- ['number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number']),
rsa_blind: getEmsc('GNUNET_CRYPTO_rsa_blind',
- 'number',
- ['number', 'number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number', 'number']),
rsa_blinding_key_create: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_create',
- 'number',
- ['number']),
+ 'number',
+ ['number']),
rsa_blinding_key_encode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_encode',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
rsa_signature_encode: getEmsc('GNUNET_CRYPTO_rsa_signature_encode',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
rsa_blinding_key_decode: getEmsc('GNUNET_CRYPTO_rsa_blinding_key_decode',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
rsa_public_key_decode: getEmsc('GNUNET_CRYPTO_rsa_public_key_decode',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
rsa_signature_decode: getEmsc('GNUNET_CRYPTO_rsa_signature_decode',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
rsa_public_key_encode: getEmsc('GNUNET_CRYPTO_rsa_public_key_encode',
- 'number',
- ['number', 'number']),
+ 'number',
+ ['number', 'number']),
rsa_unblind: getEmsc('GNUNET_CRYPTO_rsa_unblind',
- 'number',
- ['number', 'number', 'number']),
+ 'number',
+ ['number', 'number', 'number']),
+ hash_context_start: getEmsc('GNUNET_CRYPTO_hash_context_start',
+ 'number',
+ []),
malloc: (size: number) => Module._malloc(size),
};
@@ -155,6 +167,7 @@ export enum SignaturePurpose {
RESERVE_WITHDRAW = 1200,
WALLET_COIN_DEPOSIT = 1201,
MASTER_DENOMINATION_KEY_VALIDITY = 1025,
+ WALLET_COIN_MELT = 1202,
}
enum RandomQuality {
@@ -163,9 +176,49 @@ enum RandomQuality {
NONCE = 2
}
+interface ArenaObject {
+ destroy(): void;
+}
+
+
+class HashContext implements ArenaObject {
+ private hashContextPtr: number | undefined;
+
+ constructor() {
+ this.hashContextPtr = emscAlloc.hash_context_start();
+ }
+
+ read(obj: PackedArenaObject): void {
+ if (!this.hashContextPtr) {
+ throw Error("assertion failed");
+ }
+ emsc.hash_context_read(this.hashContextPtr, obj.getNative(), obj.size());
+ }
+
+ finish(h: HashCode) {
+ if (!this.hashContextPtr) {
+ throw Error("assertion failed");
+ }
+ h.alloc();
+ emsc.hash_context_finish(this.hashContextPtr, h.getNative());
+ }
+
+ destroy(): void {
+ if (this.hashContextPtr) {
+ emsc.hash_context_abort(this.hashContextPtr);
+ }
+ this.hashContextPtr = undefined;
+ }
+}
+
-abstract class ArenaObject {
+abstract class MallocArenaObject implements ArenaObject {
protected _nativePtr: number | undefined = undefined;
+
+ /**
+ * Is this a weak reference to the underlying memory?
+ */
+ isWeak = false;
arena: Arena;
abstract destroy(): void;
@@ -192,7 +245,7 @@ abstract class ArenaObject {
}
free() {
- if (this.nativePtr) {
+ if (this.nativePtr && !this.isWeak) {
emsc.free(this.nativePtr);
this._nativePtr = undefined;
}
@@ -270,7 +323,7 @@ class SyncArena extends DefaultArena {
super();
}
- pub(obj: ArenaObject) {
+ pub(obj: MallocArenaObject) {
super.put(obj);
if (!this.isScheduled) {
this.schedule();
@@ -295,14 +348,14 @@ let arenaStack: Arena[] = [];
arenaStack.push(new SyncArena());
-export class Amount extends ArenaObject {
+export class Amount extends MallocArenaObject {
constructor(args?: AmountJson, arena?: Arena) {
super(arena);
if (args) {
this.nativePtr = emscAlloc.get_amount(args.value,
- 0,
- args.fraction,
- args.currency);
+ 0,
+ args.fraction,
+ args.currency);
} else {
this.nativePtr = emscAlloc.get_amount(0, 0, 0, "");
}
@@ -399,7 +452,7 @@ export class Amount extends ArenaObject {
/**
* Count the UTF-8 characters in a JavaScript string.
*/
-function countBytes(str: string): number {
+function countUtf8Bytes(str: string): number {
var s = str.length;
// JavaScript strings are UTF-16 arrays
for (let i = str.length - 1; i >= 0; i--) {
@@ -424,7 +477,7 @@ function countBytes(str: string): number {
* Managed reference to a contiguous block of memory in the Emscripten heap.
* Should contain only data, not pointers.
*/
-abstract class PackedArenaObject extends ArenaObject {
+abstract class PackedArenaObject extends MallocArenaObject {
abstract size(): number;
constructor(a?: Arena) {
@@ -455,12 +508,12 @@ abstract class PackedArenaObject extends ArenaObject {
// to the emscripten heap first.
let buf = ByteArray.fromString(s);
let res = emsc.string_to_data(buf.nativePtr,
- s.length,
- this.nativePtr,
- this.size());
+ s.length,
+ this.nativePtr,
+ this.size());
buf.destroy();
if (res < 1) {
- throw {error: "wrong encoding"};
+ throw { error: "wrong encoding" };
}
}
@@ -581,7 +634,7 @@ function makeFromCrock(decodeFn: (p: number, s: number) => number) {
let obj = new this(a);
let buf = ByteArray.fromCrock(s);
obj.setNative(decodeFn(buf.getNative(),
- buf.size()));
+ buf.size()));
buf.destroy();
return obj;
}
@@ -590,7 +643,7 @@ function makeFromCrock(decodeFn: (p: number, s: number) => number) {
}
function makeToCrock(encodeFn: (po: number,
- ps: number) => number): () => string {
+ ps: number) => number): () => string {
function toCrock() {
let ptr = emscAlloc.malloc(PTR_SIZE);
let size = emscAlloc.rsa_blinding_key_encode(this.nativePtr, ptr);
@@ -658,14 +711,14 @@ export class ByteArray extends PackedArenaObject {
static fromString(s: string, a?: Arena): ByteArray {
// UTF-8 bytes, including 0-terminator
- let terminatedByteLength = countBytes(s) + 1;
+ let terminatedByteLength = countUtf8Bytes(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 byteLength = countBytes(s);
+ let byteLength = countUtf8Bytes(s);
let hstr = emscAlloc.malloc(byteLength + 1);
Module.stringToUTF8(s, hstr, byteLength + 1);
let decodedLen = Math.floor((byteLength * 5) / 8);
@@ -688,12 +741,12 @@ export class EccSignaturePurpose extends PackedArenaObject {
payloadSize: number;
constructor(purpose: SignaturePurpose,
- payload: PackedArenaObject,
- a?: Arena) {
+ payload: PackedArenaObject,
+ a?: Arena) {
super(a);
this.nativePtr = emscAlloc.purpose_create(purpose,
- payload.nativePtr,
- payload.size());
+ payload.nativePtr,
+ payload.size());
this.payloadSize = payload.size();
}
}
@@ -798,6 +851,34 @@ export class WithdrawRequestPS extends SignatureStruct {
}
+interface RefreshMeltCoinAffirmationPS_Args {
+ session_hash: HashCode;
+ amount_with_fee: AmountNbo;
+ melt_fee: AmountNbo;
+ coin_pub: EddsaPublicKey;
+}
+
+export class RefreshMeltCoinAffirmationPS extends SignatureStruct {
+
+ constructor(w: RefreshMeltCoinAffirmationPS_Args) {
+ super(w);
+ }
+
+ purpose() {
+ return SignaturePurpose.WALLET_COIN_MELT;
+ }
+
+ fieldTypes() {
+ return [
+ ["session_hash", HashCode],
+ ["amount_with_fee", AmountNbo],
+ ["melt_fee", AmountNbo],
+ ["coin_pub", EddsaPublicKey]
+ ];
+ }
+}
+
+
export class AbsoluteTimeNbo extends PackedArenaObject {
static fromTalerString(s: string): AbsoluteTimeNbo {
let x = new AbsoluteTimeNbo();
@@ -825,7 +906,14 @@ function set64(p: number, n: number) {
Module.setValue(p + (7 - i), n & 0xFF, "i8");
n = Math.floor(n / 256);
}
+}
+// XXX: This only works up to 54 bit numbers.
+function set32(p: number, n: number) {
+ for (let i = 0; i < 4; ++i) {
+ Module.setValue(p + (3 - i), n & 0xFF, "i8");
+ n = Math.floor(n / 256);
+ }
}
@@ -843,6 +931,20 @@ export class UInt64 extends PackedArenaObject {
}
+export class UInt32 extends PackedArenaObject {
+ static fromNumber(n: number): UInt64 {
+ let x = new UInt32();
+ x.alloc();
+ set32(x.getNative(), n);
+ return x;
+ }
+
+ size() {
+ return 8;
+ }
+}
+
+
// It's redundant, but more type safe.
export interface DepositRequestPS_Args {
h_contract: HashCode;
@@ -940,7 +1042,7 @@ function makeEncode(encodeFn: any) {
}
-export class RsaPublicKey extends ArenaObject implements Encodeable {
+export class RsaPublicKey extends MallocArenaObject implements Encodeable {
static fromCrock: (s: string, a?: Arena) => RsaPublicKey;
toCrock() {
@@ -965,7 +1067,7 @@ export class EddsaSignature extends PackedArenaObject {
}
-export class RsaSignature extends ArenaObject implements Encodeable {
+export class RsaSignature extends MallocArenaObject implements Encodeable {
static fromCrock: (s: string, a?: Arena) => RsaSignature;
encode: (arena?: Arena) => ByteArray;
@@ -980,21 +1082,21 @@ mixin(RsaSignature, makeEncode(emscAlloc.rsa_signature_encode));
export function rsaBlind(hashCode: HashCode,
- blindingKey: RsaBlindingKeySecret,
- pkey: RsaPublicKey,
- arena?: Arena): ByteArray {
+ blindingKey: RsaBlindingKeySecret,
+ pkey: RsaPublicKey,
+ arena?: Arena): ByteArray {
let ptr = emscAlloc.malloc(PTR_SIZE);
let s = emscAlloc.rsa_blind(hashCode.nativePtr,
- blindingKey.nativePtr,
- pkey.nativePtr,
- ptr);
+ blindingKey.nativePtr,
+ pkey.nativePtr,
+ ptr);
return new ByteArray(s, Module.getValue(ptr, '*'), arena);
}
export function eddsaSign(purpose: EccSignaturePurpose,
- priv: EddsaPrivateKey,
- a?: Arena): EddsaSignature {
+ priv: EddsaPrivateKey,
+ a?: Arena): EddsaSignature {
let sig = new EddsaSignature(a);
sig.alloc();
let res = emsc.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr);
@@ -1006,14 +1108,14 @@ export function eddsaSign(purpose: EccSignaturePurpose,
export function eddsaVerify(purposeNum: number,
- verify: EccSignaturePurpose,
- sig: EddsaSignature,
- pub: EddsaPublicKey,
- a?: Arena): boolean {
+ verify: EccSignaturePurpose,
+ sig: EddsaSignature,
+ pub: EddsaPublicKey,
+ a?: Arena): boolean {
let r = emsc.eddsa_verify(purposeNum,
- verify.nativePtr,
- sig.nativePtr,
- pub.nativePtr);
+ verify.nativePtr,
+ sig.nativePtr,
+ pub.nativePtr);
if (r === GNUNET_OK) {
return true;
}
@@ -1022,12 +1124,61 @@ export function eddsaVerify(purposeNum: number,
export function rsaUnblind(sig: RsaSignature,
- bk: RsaBlindingKeySecret,
- pk: RsaPublicKey,
- a?: Arena): RsaSignature {
+ bk: RsaBlindingKeySecret,
+ pk: RsaPublicKey,
+ a?: Arena): RsaSignature {
let x = new RsaSignature(a);
x.nativePtr = emscAlloc.rsa_unblind(sig.nativePtr,
- bk.nativePtr,
- pk.nativePtr);
+ bk.nativePtr,
+ pk.nativePtr);
return x;
}
+
+
+type TransferSecretP = HashCode;
+
+export function kdf(outLength: number,
+ salt: PackedArenaObject,
+ skm: PackedArenaObject,
+ ...contextChunks: PackedArenaObject[]): ByteArray {
+ const args: number[] = [];
+ let out = new ByteArray(outLength);
+ args.push(out.nativePtr, outLength);
+ args.push(salt.nativePtr, salt.size());
+ args.push(skm.nativePtr, skm.size());
+ for (let chunk of contextChunks) {
+ args.push(chunk.nativePtr, chunk.size());
+ }
+ // end terminator (it's varargs)
+ args.push(0);
+ args.push(0);
+
+ let argTypes = args.map(() => "number");
+
+ const res = Module.ccall("GNUNET_CRYPTO_kdf", "number", argTypes, args);
+ if (res != GNUNET_OK) {
+ throw Error("fatal: kdf failed");
+ }
+
+ return out;
+}
+
+
+export interface FreshCoin {
+ priv: EddsaPrivateKey;
+ blindingKey: RsaBlindingKeySecret;
+}
+
+export function setupFreshCoin(secretSeed: TransferSecretP, coinIndex: number): FreshCoin {
+ let priv = new EddsaPrivateKey();
+ priv.isWeak = true;
+ let blindingKey = new RsaBlindingKeySecret();
+ blindingKey.isWeak = true;
+
+ let buf = kdf(priv.size() + blindingKey.size(), UInt32.fromNumber(coinIndex), ByteArray.fromString("taler-coin-derivation"));
+
+ priv.nativePtr = buf.nativePtr;
+ blindingKey.nativePtr = buf.nativePtr + priv.size();
+
+ return { priv, blindingKey };
+} \ No newline at end of file
diff --git a/lib/wallet/helpers.ts b/lib/wallet/helpers.ts
index 5d231fe64..8f65517f7 100644
--- a/lib/wallet/helpers.ts
+++ b/lib/wallet/helpers.ts
@@ -14,7 +14,6 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-
/**
* Smaller helper functions that do not depend
* on the emscripten machinery.
@@ -22,7 +21,10 @@
* @author Florian Dold
*/
+/// <reference path="../decl/urijs/URIjs.d.ts" />
+
import {AmountJson} from "./types";
+import URI = uri.URI;
export function substituteFulfillmentUrl(url: string, vars: any) {
url = url.replace("${H_contract}", vars.H_contract);
@@ -43,7 +45,7 @@ export function amountToPretty(amount: AmountJson): string {
* See http://api.taler.net/wallet.html#general
*/
export function canonicalizeBaseUrl(url: string) {
- let x = new URI(url);
+ let x: URI = new URI(url);
if (!x.protocol()) {
x.protocol("https");
}
diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts
index 5e139a9bc..91b329842 100644
--- a/lib/wallet/types.ts
+++ b/lib/wallet/types.ts
@@ -25,7 +25,7 @@
* @author Florian Dold
*/
-import {Checkable} from "./checkable";
+import { Checkable } from "./checkable";
@Checkable.Class
export class AmountJson {
@@ -120,7 +120,7 @@ export interface IExchangeInfo {
}
export interface WireInfo {
- [type: string]: any;
+ [type: string]: any;
}
export interface ReserveCreationInfo {
@@ -148,6 +148,53 @@ export interface PreCoin {
}
+/**
+ * Ongoing refresh
+ */
+export interface RefreshSession {
+ /**
+ * Public key that's being melted in this session.
+ */
+ meltCoinPub: string;
+
+ /**
+ * How much of the coin's value is melted away
+ * with this refresh session?
+ */
+ valueWithFee: AmountJson
+
+ /**
+ * Signature to confirm the melting.
+ */
+ confirmSig: string;
+
+ /**
+ * Denominations of the newly requested coins
+ */
+ newDenoms: string[];
+
+ /**
+ * Blinded public keys for the requested coins.
+ */
+ newCoinBlanks: string[][];
+
+ /**
+ * Blinding factors for the new coins.
+ */
+ newCoinBlindingFactors: string[][];
+
+ /**
+ * Private keys for the requested coins.
+ */
+ newCoinPrivs: string[][];
+
+ /**
+ * The transfer keys, kappa of them.
+ */
+ transferPubs: string[];
+}
+
+
export interface Reserve {
exchange_base_url: string
reserve_priv: string;
@@ -165,7 +212,6 @@ export interface CoinPaySig {
f: AmountJson;
}
-
/**
* Coin as stored in the "coins" data store
* of the wallet database.
@@ -291,7 +337,7 @@ export namespace Amounts {
return {
currency,
value: Number.MAX_SAFE_INTEGER,
- fraction: 2**32,
+ fraction: 2 ** 32,
}
}
@@ -307,7 +353,7 @@ export namespace Amounts {
let currency = first.currency;
let value = first.value + Math.floor(first.fraction / 1e6);
if (value > Number.MAX_SAFE_INTEGER) {
- return {amount: getMaxAmount(currency), saturated: true};
+ return { amount: getMaxAmount(currency), saturated: true };
}
let fraction = first.fraction % 1e6;
for (let x of rest) {
@@ -318,10 +364,10 @@ export namespace Amounts {
value = value + x.value + Math.floor((fraction + x.fraction) / 1e6);
fraction = (fraction + x.fraction) % 1e6;
if (value > Number.MAX_SAFE_INTEGER) {
- return {amount: getMaxAmount(currency), saturated: true};
+ return { amount: getMaxAmount(currency), saturated: true };
}
}
- return {amount: {currency, value, fraction}, saturated: false};
+ return { amount: { currency, value, fraction }, saturated: false };
}
@@ -334,7 +380,7 @@ export namespace Amounts {
let fraction = a.fraction;
if (fraction < b.fraction) {
if (value < 1) {
- return {amount: {currency, value: 0, fraction: 0}, saturated: true};
+ return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
}
value--;
fraction += 1e6;
@@ -342,10 +388,10 @@ export namespace Amounts {
console.assert(fraction >= b.fraction);
fraction -= b.fraction;
if (value < b.value) {
- return {amount: {currency, value: 0, fraction: 0}, saturated: true};
+ return { amount: { currency, value: 0, fraction: 0 }, saturated: true };
}
value -= b.value;
- return {amount: {currency, value, fraction}, saturated: false};
+ return { amount: { currency, value, fraction }, saturated: false };
}
export function cmp(a: AmountJson, b: AmountJson): number {
diff --git a/pages/tree.tsx b/pages/tree.tsx
index acc470216..b1c22b9f8 100644
--- a/pages/tree.tsx
+++ b/pages/tree.tsx
@@ -84,6 +84,30 @@ interface CoinViewProps {
coin: Coin;
}
+interface RefreshDialogProps {
+ coin: Coin;
+}
+
+class RefreshDialog extends ImplicitStateComponent<RefreshDialogProps> {
+ refreshRequested = this.makeState<boolean>(false);
+ render(): JSX.Element {
+ if (!this.refreshRequested()) {
+ return (
+ <div style="display:inline;">
+ <button onClick={() => this.refreshRequested(true)}>refresh</button>
+ </div>
+ );
+ }
+ return (
+ <div>
+ Refresh amount: <input type="text" size={10} />
+ <button>ok</button>
+ <button onClick={() => this.refreshRequested(false)}>cancel</button>
+ </div>
+ );
+ }
+}
+
class CoinView extends preact.Component<CoinViewProps, void> {
render() {
let c = this.props.coin;
@@ -94,6 +118,7 @@ class CoinView extends preact.Component<CoinViewProps, void> {
<li>Current amount: {prettyAmount(c.currentAmount)}</li>
<li>Denomination: {abbrev(c.denomPub, 20)}</li>
<li>Suspended: {(c.suspended || false).toString()}</li>
+ <li><RefreshDialog coin={c} /></li>
</ul>
</div>
);