implement JS-only Taler, remove emscripten
This commit is contained in:
parent
c3ca556aff
commit
706c07fa1d
@ -1,4 +0,0 @@
|
|||||||
The taler-emscripten-lib.js is compiled from C using emscripten.
|
|
||||||
|
|
||||||
See https://git.taler.net/libtalerutil-emscripten.git for automated build
|
|
||||||
instructions and the functions exported from this module.
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -23,61 +23,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CryptoImplementation } from "./cryptoImplementation";
|
import { CryptoImplementation } from "./cryptoImplementation";
|
||||||
import { EmscEnvironment } from "./emscInterface";
|
|
||||||
|
|
||||||
const worker: Worker = (self as any) as Worker;
|
const worker: Worker = (self as any) as Worker;
|
||||||
|
|
||||||
class BrowserEmscriptenLoader {
|
|
||||||
private cachedEmscEnvironment: EmscEnvironment | undefined = undefined;
|
|
||||||
private cachedEmscEnvironmentPromise:
|
|
||||||
| Promise<EmscEnvironment>
|
|
||||||
| undefined = undefined;
|
|
||||||
|
|
||||||
async getEmscriptenEnvironment(): Promise<EmscEnvironment> {
|
|
||||||
|
|
||||||
if (this.cachedEmscEnvironment) {
|
|
||||||
return this.cachedEmscEnvironment;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.cachedEmscEnvironmentPromise) {
|
|
||||||
return this.cachedEmscEnvironmentPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("loading emscripten lib with 'importScripts'");
|
|
||||||
// @ts-ignore
|
|
||||||
self.TalerEmscriptenLib = {};
|
|
||||||
// @ts-ignore
|
|
||||||
importScripts('/emscripten/taler-emscripten-lib.js')
|
|
||||||
// @ts-ignore
|
|
||||||
if (!self.TalerEmscriptenLib) {
|
|
||||||
throw Error("can't import taler emscripten lib");
|
|
||||||
}
|
|
||||||
const locateFile = (path: string, scriptDir: string) => {
|
|
||||||
console.log("locating file", "path", path, "scriptDir", scriptDir);
|
|
||||||
// This is quite hacky and assumes that our scriptDir is dist/
|
|
||||||
return scriptDir + "../emscripten/" + path;
|
|
||||||
};
|
|
||||||
console.log("instantiating TalerEmscriptenLib");
|
|
||||||
// @ts-ignore
|
|
||||||
const lib = self.TalerEmscriptenLib({ locateFile });
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
lib.then((mod: any) => {
|
|
||||||
this.cachedEmscEnvironmentPromise = undefined;
|
|
||||||
const emsc = new EmscEnvironment(mod);
|
|
||||||
this.cachedEmscEnvironment = new EmscEnvironment(mod);
|
|
||||||
console.log("emscripten module fully loaded");
|
|
||||||
resolve(emsc);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let loader = new BrowserEmscriptenLoader();
|
|
||||||
|
|
||||||
async function handleRequest(operation: string, id: number, args: string[]) {
|
async function handleRequest(operation: string, id: number, args: string[]) {
|
||||||
let emsc = await loader.getEmscriptenEnvironment();
|
const impl = new CryptoImplementation();
|
||||||
|
|
||||||
const impl = new CryptoImplementation(emsc);
|
|
||||||
|
|
||||||
if (!(operation in impl)) {
|
if (!(operation in impl)) {
|
||||||
console.error(`crypto operation '${operation}' not found`);
|
console.error(`crypto operation '${operation}' not found`);
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2017 Inria and GNUnet e.V.
|
|
||||||
|
|
||||||
TALER is free software; you can redistribute it and/or modify it under the
|
|
||||||
terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation; either version 3, or (at your option) any later version.
|
|
||||||
|
|
||||||
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
// tslint:disable:max-line-length
|
|
||||||
|
|
||||||
import test from "ava";
|
|
||||||
|
|
||||||
import {
|
|
||||||
DenominationRecord,
|
|
||||||
DenominationStatus,
|
|
||||||
ReserveRecord,
|
|
||||||
ReserveRecordStatus,
|
|
||||||
} from "../dbTypes";
|
|
||||||
|
|
||||||
import { CryptoApi } from "./cryptoApi";
|
|
||||||
import { NodeCryptoWorkerFactory } from "./nodeProcessWorker";
|
|
||||||
|
|
||||||
const masterPub1: string =
|
|
||||||
"CQQZ9DY3MZ1ARMN5K1VKDETS04Y2QCKMMCFHZSWJWWVN82BTTH00";
|
|
||||||
|
|
||||||
const denomValid1: DenominationRecord = {
|
|
||||||
denomPub:
|
|
||||||
"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GHS84R3JHHP6GSM2D9Q6514CGT568R32C9J6CWM4DSH64TM4DSM851K0CA48CVKAC1P6H144C2160T46DHK8CVM4HJ274S38C1M6S338D9N6GWM8DT684T3JCT36S13EC9G88R3EGHQ8S0KJGSQ60SKGD216N33AGJ2651K2E9S60TMCD1N75244HHQ6X33EDJ570R3GGJ2651MACA38D130DA560VK4HHJ68WK2CA26GW3ECSH6D13EC9S88VK2GT66WVK8D9G750K0D9R8RRK4DHQ71332GHK8D23GE26710M2H9K6WVK8HJ38MVKEGA66N23AC9H88VKACT58MV3CCSJ6H1K4DT38GRK0C9M8N33CE1R60V4AHA38H1KECSH6S33JH9N8GRKGH1K68S36GH354520818CMG26C1H60R30C935452081918G2J2G0",
|
|
||||||
denomPubHash: "dummy",
|
|
||||||
exchangeBaseUrl: "https://exchange.example.com/",
|
|
||||||
feeDeposit: {
|
|
||||||
currency: "PUDOS",
|
|
||||||
fraction: 10000,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
feeRefresh: {
|
|
||||||
currency: "PUDOS",
|
|
||||||
fraction: 10000,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
feeRefund: {
|
|
||||||
currency: "PUDOS",
|
|
||||||
fraction: 10000,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
feeWithdraw: {
|
|
||||||
currency: "PUDOS",
|
|
||||||
fraction: 10000,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
isOffered: true,
|
|
||||||
masterSig:
|
|
||||||
"CJFJCQ48Q45PSGJ5KY94N6M2TPARESM2E15BSPBD95YVVPEARAEQ6V6G4Z2XBMS0QM0F3Y9EYVP276FCS90EQ1578ZC8JHFBZ3NGP3G",
|
|
||||||
stampExpireDeposit: "/Date(1851580381)/",
|
|
||||||
stampExpireLegal: "/Date(1567756381)/",
|
|
||||||
stampExpireWithdraw: "/Date(2482300381)/",
|
|
||||||
stampStart: "/Date(1473148381)/",
|
|
||||||
status: DenominationStatus.Unverified,
|
|
||||||
value: {
|
|
||||||
currency: "PUDOS",
|
|
||||||
fraction: 100000,
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const denomInvalid1 = JSON.parse(JSON.stringify(denomValid1));
|
|
||||||
denomInvalid1.value.value += 1;
|
|
||||||
|
|
||||||
test("string hashing", async t => {
|
|
||||||
const crypto = new CryptoApi(new NodeCryptoWorkerFactory());
|
|
||||||
const s = await crypto.hashString("hello taler");
|
|
||||||
const sh =
|
|
||||||
"8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
|
|
||||||
t.true(s === sh);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("precoin creation", async t => {
|
|
||||||
const crypto = new CryptoApi(new NodeCryptoWorkerFactory());
|
|
||||||
const { priv, pub } = await crypto.createEddsaKeypair();
|
|
||||||
const r: ReserveRecord = {
|
|
||||||
created: { t_ms: 0 },
|
|
||||||
currentAmount: null,
|
|
||||||
exchangeBaseUrl: "https://example.com/exchange",
|
|
||||||
hasPayback: false,
|
|
||||||
precoinAmount: { currency: "PUDOS", value: 0, fraction: 0 },
|
|
||||||
requestedAmount: { currency: "PUDOS", value: 0, fraction: 0 },
|
|
||||||
reservePriv: priv,
|
|
||||||
reservePub: pub,
|
|
||||||
timestampConfirmed: undefined,
|
|
||||||
timestampReserveInfoPosted: undefined,
|
|
||||||
exchangeWire: "payto://foo",
|
|
||||||
reserveStatus: ReserveRecordStatus.UNCONFIRMED,
|
|
||||||
};
|
|
||||||
|
|
||||||
const precoin = await crypto.createPreCoin(denomValid1, r);
|
|
||||||
t.truthy(precoin);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("denom validation", async t => {
|
|
||||||
const crypto = new CryptoApi(new NodeCryptoWorkerFactory());
|
|
||||||
let v: boolean;
|
|
||||||
v = await crypto.isValidDenom(denomValid1, masterPub1);
|
|
||||||
t.true(v);
|
|
||||||
v = await crypto.isValidDenom(denomInvalid1, masterPub1);
|
|
||||||
t.true(!v);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
@ -14,7 +14,6 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronous implementation of crypto-related functions for the wallet.
|
* Synchronous implementation of crypto-related functions for the wallet.
|
||||||
*
|
*
|
||||||
@ -38,19 +37,118 @@ import {
|
|||||||
} from "../dbTypes";
|
} from "../dbTypes";
|
||||||
|
|
||||||
import { CoinPaySig, ContractTerms, PaybackRequest } from "../talerTypes";
|
import { CoinPaySig, ContractTerms, PaybackRequest } from "../talerTypes";
|
||||||
import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes";
|
import {
|
||||||
import { canonicalJson } from "../helpers";
|
BenchmarkResult,
|
||||||
import { EmscEnvironment } from "./emscInterface";
|
CoinWithDenom,
|
||||||
import * as native from "./emscInterface";
|
PayCoinInfo,
|
||||||
|
Timestamp,
|
||||||
|
} from "../walletTypes";
|
||||||
|
import { canonicalJson, getTalerStampSec } from "../helpers";
|
||||||
import { AmountJson } from "../amounts";
|
import { AmountJson } from "../amounts";
|
||||||
import * as Amounts from "../amounts";
|
import * as Amounts from "../amounts";
|
||||||
import * as timer from "../timer";
|
import * as timer from "../timer";
|
||||||
import { getRandomBytes, encodeCrock } from "./talerCrypto";
|
import {
|
||||||
|
getRandomBytes,
|
||||||
|
encodeCrock,
|
||||||
|
decodeCrock,
|
||||||
|
createEddsaKeyPair,
|
||||||
|
createBlindingKeySecret,
|
||||||
|
hash,
|
||||||
|
rsaBlind,
|
||||||
|
eddsaVerify,
|
||||||
|
eddsaSign,
|
||||||
|
rsaUnblind,
|
||||||
|
stringToBytes,
|
||||||
|
createHashContext,
|
||||||
|
createEcdheKeyPair,
|
||||||
|
keyExchangeEcdheEddsa,
|
||||||
|
setupRefreshPlanchet,
|
||||||
|
} from "./talerCrypto";
|
||||||
|
import { randomBytes } from "./primitives/nacl-fast";
|
||||||
|
|
||||||
|
enum SignaturePurpose {
|
||||||
|
RESERVE_WITHDRAW = 1200,
|
||||||
|
WALLET_COIN_DEPOSIT = 1201,
|
||||||
|
MASTER_DENOMINATION_KEY_VALIDITY = 1025,
|
||||||
|
WALLET_COIN_MELT = 1202,
|
||||||
|
TEST = 4242,
|
||||||
|
MERCHANT_PAYMENT_OK = 1104,
|
||||||
|
MASTER_WIRE_FEES = 1028,
|
||||||
|
WALLET_COIN_PAYBACK = 1203,
|
||||||
|
WALLET_COIN_LINK = 1204,
|
||||||
|
}
|
||||||
|
|
||||||
|
function amountToBuffer(amount: AmountJson): Uint8Array {
|
||||||
|
const buffer = new ArrayBuffer(8 + 4 + 12);
|
||||||
|
const dvbuf = new DataView(buffer);
|
||||||
|
const u8buf = new Uint8Array(buffer);
|
||||||
|
const te = new TextEncoder();
|
||||||
|
const curr = te.encode(amount.currency);
|
||||||
|
dvbuf.setBigUint64(0, BigInt(amount.value));
|
||||||
|
dvbuf.setUint32(8, amount.fraction);
|
||||||
|
u8buf.set(curr, 8 + 4);
|
||||||
|
|
||||||
|
return u8buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function timestampToBuffer(ts: Timestamp): Uint8Array {
|
||||||
|
const b = new ArrayBuffer(8);
|
||||||
|
const v = new DataView(b);
|
||||||
|
const s = BigInt(ts.t_ms) * BigInt(1000);
|
||||||
|
v.setBigUint64(0, s);
|
||||||
|
return new Uint8Array(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function talerTimestampStringToBuffer(ts: string): Uint8Array {
|
||||||
|
const t_sec = getTalerStampSec(ts);
|
||||||
|
if (t_sec === null || t_sec === undefined) {
|
||||||
|
// Should have been validated before!
|
||||||
|
throw Error("invalid timestamp");
|
||||||
|
}
|
||||||
|
const buffer = new ArrayBuffer(8);
|
||||||
|
const dvbuf = new DataView(buffer);
|
||||||
|
const s = BigInt(t_sec) * BigInt(1000 * 1000);
|
||||||
|
dvbuf.setBigUint64(0, s);
|
||||||
|
return new Uint8Array(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignaturePurposeBuilder {
|
||||||
|
private chunks: Uint8Array[] = [];
|
||||||
|
|
||||||
|
constructor(private purposeNum: number) {}
|
||||||
|
|
||||||
|
put(bytes: Uint8Array): SignaturePurposeBuilder {
|
||||||
|
this.chunks.push(Uint8Array.from(bytes));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): Uint8Array {
|
||||||
|
let payloadLen = 0;
|
||||||
|
for (let c of this.chunks) {
|
||||||
|
payloadLen += c.byteLength;
|
||||||
|
}
|
||||||
|
const buf = new ArrayBuffer(4 + 4 + payloadLen);
|
||||||
|
const u8buf = new Uint8Array(buf);
|
||||||
|
let p = 8;
|
||||||
|
for (let c of this.chunks) {
|
||||||
|
u8buf.set(c, p);
|
||||||
|
p += c.byteLength;
|
||||||
|
}
|
||||||
|
const dvbuf = new DataView(buf);
|
||||||
|
dvbuf.setUint32(0, payloadLen + 4 + 4);
|
||||||
|
dvbuf.setUint32(4, this.purposeNum);
|
||||||
|
return u8buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSigPS(purposeNum: number): SignaturePurposeBuilder {
|
||||||
|
return new SignaturePurposeBuilder(purposeNum);
|
||||||
|
}
|
||||||
|
|
||||||
export class CryptoImplementation {
|
export class CryptoImplementation {
|
||||||
static enableTracing: boolean = false;
|
static enableTracing: boolean = false;
|
||||||
|
|
||||||
constructor(private emsc: EmscEnvironment) {}
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a pre-coin of the given denomination to be withdrawn from then given
|
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||||
@ -60,54 +158,39 @@ export class CryptoImplementation {
|
|||||||
denom: DenominationRecord,
|
denom: DenominationRecord,
|
||||||
reserve: ReserveRecord,
|
reserve: ReserveRecord,
|
||||||
): PreCoinRecord {
|
): PreCoinRecord {
|
||||||
const reservePriv = new native.EddsaPrivateKey(this.emsc);
|
const reservePub = decodeCrock(reserve.reservePub);
|
||||||
reservePriv.loadCrock(reserve.reservePriv);
|
const reservePriv = decodeCrock(reserve.reservePriv);
|
||||||
const reservePub = new native.EddsaPublicKey(this.emsc);
|
const denomPub = decodeCrock(denom.denomPub);
|
||||||
reservePub.loadCrock(reserve.reservePub);
|
const coinKeyPair = createEddsaKeyPair();
|
||||||
const denomPub = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
|
const blindingFactor = createBlindingKeySecret();
|
||||||
const coinPriv = native.EddsaPrivateKey.create(this.emsc);
|
const coinPubHash = hash(coinKeyPair.eddsaPub);
|
||||||
const coinPub = coinPriv.getPublicKey();
|
const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
|
||||||
const blindingFactor = native.RsaBlindingKeySecret.create(this.emsc);
|
const amountWithFee = Amounts.add(denom.value, denom.feeWithdraw).amount;
|
||||||
const pubHash: native.HashCode = coinPub.hash();
|
const denomPubHash = hash(denomPub);
|
||||||
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
|
const evHash = hash(ev);
|
||||||
|
|
||||||
if (!ev) {
|
const withdrawRequest = buildSigPS(SignaturePurpose.RESERVE_WITHDRAW)
|
||||||
throw Error("couldn't blind (malicious exchange key?)");
|
.put(reservePub)
|
||||||
}
|
.put(amountToBuffer(amountWithFee))
|
||||||
|
.put(amountToBuffer(denom.feeWithdraw))
|
||||||
|
.put(denomPubHash)
|
||||||
|
.put(evHash)
|
||||||
|
.build();
|
||||||
|
|
||||||
if (!denom.feeWithdraw) {
|
const sig = eddsaSign(withdrawRequest, reservePriv);
|
||||||
throw Error("Field fee_withdraw missing");
|
|
||||||
}
|
|
||||||
|
|
||||||
const amountWithFee = new native.Amount(this.emsc, denom.value);
|
|
||||||
amountWithFee.add(new native.Amount(this.emsc, denom.feeWithdraw));
|
|
||||||
const withdrawFee = new native.Amount(this.emsc, denom.feeWithdraw);
|
|
||||||
|
|
||||||
const denomPubHash = denomPub.encode().hash();
|
|
||||||
|
|
||||||
// Signature
|
|
||||||
const withdrawRequest = new native.WithdrawRequestPS(this.emsc, {
|
|
||||||
amount_with_fee: amountWithFee.toNbo(),
|
|
||||||
h_coin_envelope: ev.hash(),
|
|
||||||
h_denomination_pub: denomPubHash,
|
|
||||||
reserve_pub: reservePub,
|
|
||||||
withdraw_fee: withdrawFee.toNbo(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
|
|
||||||
|
|
||||||
const preCoin: PreCoinRecord = {
|
const preCoin: PreCoinRecord = {
|
||||||
blindingKey: blindingFactor.toCrock(),
|
blindingKey: encodeCrock(blindingFactor),
|
||||||
coinEv: ev.toCrock(),
|
coinEv: encodeCrock(ev),
|
||||||
coinPriv: coinPriv.toCrock(),
|
coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
|
||||||
coinPub: coinPub.toCrock(),
|
coinPub: encodeCrock(coinKeyPair.eddsaPub),
|
||||||
coinValue: denom.value,
|
coinValue: denom.value,
|
||||||
denomPub: denomPub.toCrock(),
|
denomPub: encodeCrock(denomPub),
|
||||||
denomPubHash: denomPubHash.toCrock(),
|
denomPubHash: encodeCrock(denomPubHash),
|
||||||
exchangeBaseUrl: reserve.exchangeBaseUrl,
|
exchangeBaseUrl: reserve.exchangeBaseUrl,
|
||||||
isFromTip: false,
|
isFromTip: false,
|
||||||
reservePub: reservePub.toCrock(),
|
reservePub: encodeCrock(reservePub),
|
||||||
withdrawSig: sig.toCrock(),
|
withdrawSig: encodeCrock(sig),
|
||||||
};
|
};
|
||||||
return preCoin;
|
return preCoin;
|
||||||
}
|
}
|
||||||
@ -116,32 +199,20 @@ export class CryptoImplementation {
|
|||||||
* Create a planchet used for tipping, including the private keys.
|
* Create a planchet used for tipping, including the private keys.
|
||||||
*/
|
*/
|
||||||
createTipPlanchet(denom: DenominationRecord): TipPlanchet {
|
createTipPlanchet(denom: DenominationRecord): TipPlanchet {
|
||||||
const denomPub = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
|
const denomPub = decodeCrock(denom.denomPub);
|
||||||
const coinPriv = native.EddsaPrivateKey.create(this.emsc);
|
const coinKeyPair = createEddsaKeyPair();
|
||||||
const coinPub = coinPriv.getPublicKey();
|
const blindingFactor = createBlindingKeySecret();
|
||||||
const blindingFactor = native.RsaBlindingKeySecret.create(this.emsc);
|
const coinPubHash = hash(coinKeyPair.eddsaPub);
|
||||||
const pubHash: native.HashCode = coinPub.hash();
|
const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
|
||||||
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
|
|
||||||
|
|
||||||
if (!ev) {
|
|
||||||
throw Error("couldn't blind (malicious exchange key?)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!denom.feeWithdraw) {
|
|
||||||
throw Error("Field fee_withdraw missing");
|
|
||||||
}
|
|
||||||
|
|
||||||
const tipPlanchet: TipPlanchet = {
|
const tipPlanchet: TipPlanchet = {
|
||||||
blindingKey: blindingFactor.toCrock(),
|
blindingKey: encodeCrock(blindingFactor),
|
||||||
coinEv: ev.toCrock(),
|
coinEv: encodeCrock(ev),
|
||||||
coinPriv: coinPriv.toCrock(),
|
coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
|
||||||
coinPub: coinPub.toCrock(),
|
coinPub: encodeCrock(coinKeyPair.eddsaPub),
|
||||||
coinValue: denom.value,
|
coinValue: denom.value,
|
||||||
denomPub: denomPub.encode().toCrock(),
|
denomPub: encodeCrock(denomPub),
|
||||||
denomPubHash: denomPub
|
denomPubHash: encodeCrock(hash(denomPub)),
|
||||||
.encode()
|
|
||||||
.hash()
|
|
||||||
.toCrock(),
|
|
||||||
};
|
};
|
||||||
return tipPlanchet;
|
return tipPlanchet;
|
||||||
}
|
}
|
||||||
@ -150,22 +221,18 @@ export class CryptoImplementation {
|
|||||||
* Create and sign a message to request payback for a coin.
|
* Create and sign a message to request payback for a coin.
|
||||||
*/
|
*/
|
||||||
createPaybackRequest(coin: CoinRecord): PaybackRequest {
|
createPaybackRequest(coin: CoinRecord): PaybackRequest {
|
||||||
const p = new native.PaybackRequestPS(this.emsc, {
|
const p = buildSigPS(SignaturePurpose.WALLET_COIN_PAYBACK)
|
||||||
coin_blind: native.RsaBlindingKeySecret.fromCrock(
|
.put(decodeCrock(coin.coinPub))
|
||||||
this.emsc,
|
.put(decodeCrock(coin.denomPubHash))
|
||||||
coin.blindingKey,
|
.put(decodeCrock(coin.blindingKey))
|
||||||
),
|
.build();
|
||||||
coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, coin.coinPub),
|
|
||||||
h_denom_pub: native.RsaPublicKey.fromCrock(this.emsc, coin.denomPub)
|
const coinPriv = decodeCrock(coin.coinPriv);
|
||||||
.encode()
|
const coinSig = eddsaSign(p, coinPriv);
|
||||||
.hash(),
|
|
||||||
});
|
|
||||||
const coinPriv = native.EddsaPrivateKey.fromCrock(this.emsc, coin.coinPriv);
|
|
||||||
const coinSig = native.eddsaSign(p.toPurpose(), coinPriv);
|
|
||||||
const paybackRequest: PaybackRequest = {
|
const paybackRequest: PaybackRequest = {
|
||||||
coin_blind_key_secret: coin.blindingKey,
|
coin_blind_key_secret: coin.blindingKey,
|
||||||
coin_pub: coin.coinPub,
|
coin_pub: coin.coinPub,
|
||||||
coin_sig: coinSig.toCrock(),
|
coin_sig: encodeCrock(coinSig),
|
||||||
denom_pub: coin.denomPub,
|
denom_pub: coin.denomPub,
|
||||||
denom_sig: coin.denomSig,
|
denom_sig: coin.denomSig,
|
||||||
};
|
};
|
||||||
@ -180,114 +247,72 @@ export class CryptoImplementation {
|
|||||||
contractHash: string,
|
contractHash: string,
|
||||||
merchantPub: string,
|
merchantPub: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
const p = new native.PaymentSignaturePS(this.emsc, {
|
const p = buildSigPS(SignaturePurpose.MERCHANT_PAYMENT_OK)
|
||||||
contract_hash: native.HashCode.fromCrock(this.emsc, contractHash),
|
.put(decodeCrock(contractHash))
|
||||||
});
|
.build();
|
||||||
const nativeSig = new native.EddsaSignature(this.emsc);
|
const sigBytes = decodeCrock(sig);
|
||||||
nativeSig.loadCrock(sig);
|
const pubBytes = decodeCrock(merchantPub);
|
||||||
const nativePub = native.EddsaPublicKey.fromCrock(this.emsc, merchantPub);
|
return eddsaVerify(p, sigBytes, pubBytes);
|
||||||
return native.eddsaVerify(
|
|
||||||
native.SignaturePurpose.MERCHANT_PAYMENT_OK,
|
|
||||||
p.toPurpose(),
|
|
||||||
nativeSig,
|
|
||||||
nativePub,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a wire fee is correctly signed.
|
* Check if a wire fee is correctly signed.
|
||||||
*/
|
*/
|
||||||
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
||||||
const p = new native.MasterWireFeePS(this.emsc, {
|
const p = buildSigPS(SignaturePurpose.MASTER_WIRE_FEES)
|
||||||
closing_fee: new native.Amount(this.emsc, wf.closingFee).toNbo(),
|
.put(hash(stringToBytes(type + "\0")))
|
||||||
end_date: native.AbsoluteTimeNbo.fromStampSeconds(this.emsc, (wf.endStamp.t_ms / 1000)),
|
.put(timestampToBuffer(wf.startStamp))
|
||||||
h_wire_method: native.ByteArray.fromStringWithNull(
|
.put(timestampToBuffer(wf.endStamp))
|
||||||
this.emsc,
|
.put(amountToBuffer(wf.wireFee))
|
||||||
type,
|
.build();
|
||||||
).hash(),
|
const sig = decodeCrock(wf.sig);
|
||||||
start_date: native.AbsoluteTimeNbo.fromStampSeconds(
|
const pub = decodeCrock(masterPub);
|
||||||
this.emsc,
|
return eddsaVerify(p, sig, pub);
|
||||||
Math.floor(wf.startStamp.t_ms / 1000),
|
|
||||||
),
|
|
||||||
wire_fee: new native.Amount(this.emsc, wf.wireFee).toNbo(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const nativeSig = new native.EddsaSignature(this.emsc);
|
|
||||||
nativeSig.loadCrock(wf.sig);
|
|
||||||
const nativePub = native.EddsaPublicKey.fromCrock(this.emsc, masterPub);
|
|
||||||
|
|
||||||
return native.eddsaVerify(
|
|
||||||
native.SignaturePurpose.MASTER_WIRE_FEES,
|
|
||||||
p.toPurpose(),
|
|
||||||
nativeSig,
|
|
||||||
nativePub,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the signature of a denomination is valid.
|
* Check if the signature of a denomination is valid.
|
||||||
*/
|
*/
|
||||||
isValidDenom(denom: DenominationRecord, masterPub: string): boolean {
|
isValidDenom(denom: DenominationRecord, masterPub: string): boolean {
|
||||||
const p = new native.DenominationKeyValidityPS(this.emsc, {
|
const p = buildSigPS(SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
|
||||||
denom_hash: native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub)
|
.put(decodeCrock(masterPub))
|
||||||
.encode()
|
.put(timestampToBuffer(denom.stampStart))
|
||||||
.hash(),
|
.put(timestampToBuffer(denom.stampExpireWithdraw))
|
||||||
expire_legal: native.AbsoluteTimeNbo.fromTalerString(
|
.put(timestampToBuffer(denom.stampExpireDeposit))
|
||||||
this.emsc,
|
.put(timestampToBuffer(denom.stampExpireLegal))
|
||||||
denom.stampExpireLegal,
|
.put(amountToBuffer(denom.value))
|
||||||
),
|
.put(amountToBuffer(denom.feeWithdraw))
|
||||||
expire_spend: native.AbsoluteTimeNbo.fromTalerString(
|
.put(amountToBuffer(denom.feeDeposit))
|
||||||
this.emsc,
|
.put(amountToBuffer(denom.feeRefresh))
|
||||||
denom.stampExpireDeposit,
|
.put(amountToBuffer(denom.feeRefund))
|
||||||
),
|
.put(decodeCrock(denom.denomPubHash))
|
||||||
expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(
|
.build();
|
||||||
this.emsc,
|
const sig = decodeCrock(denom.masterSig);
|
||||||
denom.stampExpireWithdraw,
|
const pub = decodeCrock(masterPub);
|
||||||
),
|
return eddsaVerify(p, sig, pub);
|
||||||
fee_deposit: new native.Amount(this.emsc, denom.feeDeposit).toNbo(),
|
|
||||||
fee_refresh: new native.Amount(this.emsc, denom.feeRefresh).toNbo(),
|
|
||||||
fee_refund: new native.Amount(this.emsc, denom.feeRefund).toNbo(),
|
|
||||||
fee_withdraw: new native.Amount(this.emsc, denom.feeWithdraw).toNbo(),
|
|
||||||
master: native.EddsaPublicKey.fromCrock(this.emsc, masterPub),
|
|
||||||
start: native.AbsoluteTimeNbo.fromTalerString(
|
|
||||||
this.emsc,
|
|
||||||
denom.stampStart,
|
|
||||||
),
|
|
||||||
value: new native.Amount(this.emsc, denom.value).toNbo(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const nativeSig = new native.EddsaSignature(this.emsc);
|
|
||||||
nativeSig.loadCrock(denom.masterSig);
|
|
||||||
|
|
||||||
const nativePub = native.EddsaPublicKey.fromCrock(this.emsc, masterPub);
|
|
||||||
|
|
||||||
return native.eddsaVerify(
|
|
||||||
native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
|
|
||||||
p.toPurpose(),
|
|
||||||
nativeSig,
|
|
||||||
nativePub,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new EdDSA key pair.
|
* Create a new EdDSA key pair.
|
||||||
*/
|
*/
|
||||||
createEddsaKeypair(): { priv: string; pub: string } {
|
createEddsaKeypair(): { priv: string; pub: string } {
|
||||||
const priv = native.EddsaPrivateKey.create(this.emsc);
|
const pair = createEddsaKeyPair();
|
||||||
const pub = priv.getPublicKey();
|
return {
|
||||||
return { priv: priv.toCrock(), pub: pub.toCrock() };
|
priv: encodeCrock(pair.eddsaPriv),
|
||||||
|
pub: encodeCrock(pair.eddsaPub),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unblind a blindly signed value.
|
* Unblind a blindly signed value.
|
||||||
*/
|
*/
|
||||||
rsaUnblind(sig: string, bk: string, pk: string): string {
|
rsaUnblind(sig: string, bk: string, pk: string): string {
|
||||||
const denomSig = native.rsaUnblind(
|
const denomSig = rsaUnblind(
|
||||||
native.RsaSignature.fromCrock(this.emsc, sig),
|
decodeCrock(sig),
|
||||||
native.RsaBlindingKeySecret.fromCrock(this.emsc, bk),
|
decodeCrock(pk),
|
||||||
native.RsaPublicKey.fromCrock(this.emsc, pk),
|
decodeCrock(bk),
|
||||||
);
|
);
|
||||||
return denomSig.encode().toCrock();
|
return encodeCrock(denomSig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -315,79 +340,54 @@ export class CryptoImplementation {
|
|||||||
.amount;
|
.amount;
|
||||||
const total = Amounts.add(fees, totalAmount).amount;
|
const total = Amounts.add(fees, totalAmount).amount;
|
||||||
|
|
||||||
const amountSpent = native.Amount.getZero(
|
let amountSpent = Amounts.getZero(cds[0].coin.currentAmount.currency);
|
||||||
this.emsc,
|
let amountRemaining = total;
|
||||||
cds[0].coin.currentAmount.currency,
|
|
||||||
);
|
|
||||||
const amountRemaining = new native.Amount(this.emsc, total);
|
|
||||||
for (const cd of cds) {
|
for (const cd of cds) {
|
||||||
let coinSpend: native.Amount;
|
|
||||||
const originalCoin = { ...cd.coin };
|
const originalCoin = { ...cd.coin };
|
||||||
|
|
||||||
if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
|
if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
let coinSpend: AmountJson;
|
||||||
amountRemaining.cmp(
|
if (Amounts.cmp(amountRemaining, cd.coin.currentAmount) < 0) {
|
||||||
new native.Amount(this.emsc, cd.coin.currentAmount),
|
coinSpend = amountRemaining;
|
||||||
) < 0
|
|
||||||
) {
|
|
||||||
coinSpend = new native.Amount(this.emsc, amountRemaining.toJson());
|
|
||||||
} else {
|
} else {
|
||||||
coinSpend = new native.Amount(this.emsc, cd.coin.currentAmount);
|
coinSpend = cd.coin.currentAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
amountSpent.add(coinSpend);
|
amountSpent = Amounts.add(amountSpent, coinSpend).amount;
|
||||||
amountRemaining.sub(coinSpend);
|
|
||||||
|
|
||||||
const feeDeposit: native.Amount = new native.Amount(
|
const feeDeposit = cd.denom.feeDeposit;
|
||||||
this.emsc,
|
|
||||||
cd.denom.feeDeposit,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Give the merchant at least the deposit fee, otherwise it'll reject
|
// Give the merchant at least the deposit fee, otherwise it'll reject
|
||||||
// the coin.
|
// the coin.
|
||||||
if (coinSpend.cmp(feeDeposit) < 0) {
|
|
||||||
|
if (Amounts.cmp(coinSpend, feeDeposit) < 0) {
|
||||||
coinSpend = feeDeposit;
|
coinSpend = feeDeposit;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAmount = new native.Amount(this.emsc, cd.coin.currentAmount);
|
const newAmount = Amounts.sub(cd.coin.currentAmount, coinSpend).amount;
|
||||||
newAmount.sub(coinSpend);
|
cd.coin.currentAmount = newAmount;
|
||||||
cd.coin.currentAmount = newAmount.toJson();
|
|
||||||
cd.coin.status = CoinStatus.Dirty;
|
cd.coin.status = CoinStatus.Dirty;
|
||||||
|
|
||||||
const d = new native.DepositRequestPS(this.emsc, {
|
const d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT)
|
||||||
amount_with_fee: coinSpend.toNbo(),
|
.put(decodeCrock(contractTermsHash))
|
||||||
coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, cd.coin.coinPub),
|
.put(decodeCrock(contractTerms.H_wire))
|
||||||
deposit_fee: new native.Amount(this.emsc, cd.denom.feeDeposit).toNbo(),
|
.put(talerTimestampStringToBuffer(contractTerms.timestamp))
|
||||||
h_contract: native.HashCode.fromCrock(this.emsc, contractTermsHash),
|
.put(talerTimestampStringToBuffer(contractTerms.refund_deadline))
|
||||||
h_wire: native.HashCode.fromCrock(this.emsc, contractTerms.H_wire),
|
.put(amountToBuffer(coinSpend))
|
||||||
merchant: native.EddsaPublicKey.fromCrock(
|
.put(amountToBuffer(cd.denom.feeDeposit))
|
||||||
this.emsc,
|
.put(decodeCrock(contractTerms.merchant_pub))
|
||||||
contractTerms.merchant_pub,
|
.put(decodeCrock(cd.coin.coinPub))
|
||||||
),
|
.build();
|
||||||
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(
|
const coinSig = eddsaSign(d, decodeCrock(cd.coin.coinPriv));
|
||||||
this.emsc,
|
|
||||||
contractTerms.refund_deadline,
|
|
||||||
),
|
|
||||||
timestamp: native.AbsoluteTimeNbo.fromTalerString(
|
|
||||||
this.emsc,
|
|
||||||
contractTerms.timestamp,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const coinSig = native
|
|
||||||
.eddsaSign(
|
|
||||||
d.toPurpose(),
|
|
||||||
native.EddsaPrivateKey.fromCrock(this.emsc, cd.coin.coinPriv),
|
|
||||||
)
|
|
||||||
.toCrock();
|
|
||||||
|
|
||||||
const s: CoinPaySig = {
|
const s: CoinPaySig = {
|
||||||
coin_pub: cd.coin.coinPub,
|
coin_pub: cd.coin.coinPub,
|
||||||
coin_sig: coinSig,
|
coin_sig: encodeCrock(coinSig),
|
||||||
contribution: Amounts.toString(coinSpend.toJson()),
|
contribution: Amounts.toString(coinSpend),
|
||||||
denom_pub: cd.coin.denomPub,
|
denom_pub: cd.coin.denomPub,
|
||||||
exchange_url: cd.denom.exchangeBaseUrl,
|
exchange_url: cd.denom.exchangeBaseUrl,
|
||||||
ub_sig: cd.coin.denomSig,
|
ub_sig: cd.coin.denomSig,
|
||||||
@ -419,7 +419,7 @@ export class CryptoImplementation {
|
|||||||
// melt fee
|
// melt fee
|
||||||
valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
|
valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
|
||||||
|
|
||||||
const sessionHc = new native.HashContext(this.emsc);
|
const sessionHc = createHashContext();
|
||||||
|
|
||||||
const transferPubs: string[] = [];
|
const transferPubs: string[] = [];
|
||||||
const transferPrivs: string[] = [];
|
const transferPrivs: string[] = [];
|
||||||
@ -427,79 +427,57 @@ export class CryptoImplementation {
|
|||||||
const preCoinsForGammas: RefreshPreCoinRecord[][] = [];
|
const preCoinsForGammas: RefreshPreCoinRecord[][] = [];
|
||||||
|
|
||||||
for (let i = 0; i < kappa; i++) {
|
for (let i = 0; i < kappa; i++) {
|
||||||
const t = native.EcdhePrivateKey.create(this.emsc);
|
const transferKeyPair = createEcdheKeyPair();
|
||||||
const pub = t.getPublicKey();
|
sessionHc.update(transferKeyPair.ecdhePub);
|
||||||
sessionHc.read(pub);
|
transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv));
|
||||||
transferPrivs.push(t.toCrock());
|
transferPubs.push(encodeCrock(transferKeyPair.ecdhePub));
|
||||||
transferPubs.push(pub.toCrock());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const denom of newCoinDenoms) {
|
for (const denom of newCoinDenoms) {
|
||||||
const r = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
|
const r = decodeCrock(denom.denomPub);
|
||||||
sessionHc.read(r.encode());
|
sessionHc.update(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionHc.read(
|
sessionHc.update(decodeCrock(meltCoin.coinPub));
|
||||||
native.EddsaPublicKey.fromCrock(this.emsc, meltCoin.coinPub),
|
sessionHc.update(amountToBuffer(valueWithFee));
|
||||||
);
|
|
||||||
sessionHc.read(new native.Amount(this.emsc, valueWithFee).toNbo());
|
|
||||||
|
|
||||||
for (let i = 0; i < kappa; i++) {
|
for (let i = 0; i < kappa; i++) {
|
||||||
const preCoins: RefreshPreCoinRecord[] = [];
|
const preCoins: RefreshPreCoinRecord[] = [];
|
||||||
for (let j = 0; j < newCoinDenoms.length; j++) {
|
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||||
const transferPriv = native.EcdhePrivateKey.fromCrock(
|
const transferPriv = decodeCrock(transferPrivs[i]);
|
||||||
this.emsc,
|
const oldCoinPub = decodeCrock(meltCoin.coinPub);
|
||||||
transferPrivs[i],
|
const transferSecret = keyExchangeEcdheEddsa(transferPriv, oldCoinPub);
|
||||||
);
|
|
||||||
const oldCoinPub = native.EddsaPublicKey.fromCrock(
|
|
||||||
this.emsc,
|
|
||||||
meltCoin.coinPub,
|
|
||||||
);
|
|
||||||
const transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub);
|
|
||||||
|
|
||||||
const fresh = native.setupFreshCoin(transferSecret, j);
|
const fresh = setupRefreshPlanchet(transferSecret, j);
|
||||||
|
|
||||||
const coinPriv = fresh.priv;
|
const coinPriv = fresh.coinPriv;
|
||||||
const coinPub = coinPriv.getPublicKey();
|
const coinPub = fresh.coinPub;
|
||||||
const blindingFactor = fresh.blindingKey;
|
const blindingFactor = fresh.bks;
|
||||||
const pubHash: native.HashCode = coinPub.hash();
|
const pubHash = hash(coinPub);
|
||||||
const denomPub = native.RsaPublicKey.fromCrock(
|
const denomPub = decodeCrock(newCoinDenoms[j].denomPub);
|
||||||
this.emsc,
|
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
||||||
newCoinDenoms[j].denomPub,
|
|
||||||
);
|
|
||||||
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
|
|
||||||
if (!ev) {
|
|
||||||
throw Error("couldn't blind (malicious exchange key?)");
|
|
||||||
}
|
|
||||||
const preCoin: RefreshPreCoinRecord = {
|
const preCoin: RefreshPreCoinRecord = {
|
||||||
blindingKey: blindingFactor.toCrock(),
|
blindingKey: encodeCrock(blindingFactor),
|
||||||
coinEv: ev.toCrock(),
|
coinEv: encodeCrock(ev),
|
||||||
privateKey: coinPriv.toCrock(),
|
privateKey: encodeCrock(coinPriv),
|
||||||
publicKey: coinPub.toCrock(),
|
publicKey: encodeCrock(coinPub),
|
||||||
};
|
};
|
||||||
preCoins.push(preCoin);
|
preCoins.push(preCoin);
|
||||||
sessionHc.read(ev);
|
sessionHc.update(ev);
|
||||||
}
|
}
|
||||||
preCoinsForGammas.push(preCoins);
|
preCoinsForGammas.push(preCoins);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionHash = new native.HashCode(this.emsc);
|
const sessionHash = sessionHc.finish();
|
||||||
sessionHash.alloc();
|
|
||||||
sessionHc.finish(sessionHash);
|
|
||||||
|
|
||||||
const confirmData = new native.RefreshMeltCoinAffirmationPS(this.emsc, {
|
const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT)
|
||||||
amount_with_fee: new native.Amount(this.emsc, valueWithFee).toNbo(),
|
.put(sessionHash)
|
||||||
coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, meltCoin.coinPub),
|
.put(amountToBuffer(valueWithFee))
|
||||||
melt_fee: new native.Amount(this.emsc, meltFee).toNbo(),
|
.put(amountToBuffer(meltFee))
|
||||||
session_hash: sessionHash,
|
.put(decodeCrock(meltCoin.coinPub))
|
||||||
});
|
.build();
|
||||||
|
|
||||||
const confirmSig: string = native
|
const confirmSig = eddsaSign(confirmData, decodeCrock(meltCoin.coinPriv));
|
||||||
.eddsaSign(
|
|
||||||
confirmData.toPurpose(),
|
|
||||||
native.EddsaPrivateKey.fromCrock(this.emsc, meltCoin.coinPriv),
|
|
||||||
)
|
|
||||||
.toCrock();
|
|
||||||
|
|
||||||
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
|
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
|
||||||
for (const denom of newCoinDenoms) {
|
for (const denom of newCoinDenoms) {
|
||||||
@ -510,10 +488,10 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
const refreshSession: RefreshSessionRecord = {
|
const refreshSession: RefreshSessionRecord = {
|
||||||
refreshSessionId,
|
refreshSessionId,
|
||||||
confirmSig,
|
confirmSig: encodeCrock(confirmSig),
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
finished: false,
|
finished: false,
|
||||||
hash: sessionHash.toCrock(),
|
hash: encodeCrock(sessionHash),
|
||||||
meltCoinPub: meltCoin.coinPub,
|
meltCoinPub: meltCoin.coinPub,
|
||||||
newDenomHashes: newCoinDenoms.map(d => d.denomPubHash),
|
newDenomHashes: newCoinDenoms.map(d => d.denomPubHash),
|
||||||
newDenoms: newCoinDenoms.map(d => d.denomPub),
|
newDenoms: newCoinDenoms.map(d => d.denomPub),
|
||||||
@ -532,18 +510,16 @@ export class CryptoImplementation {
|
|||||||
* Hash a string including the zero terminator.
|
* Hash a string including the zero terminator.
|
||||||
*/
|
*/
|
||||||
hashString(str: string): string {
|
hashString(str: string): string {
|
||||||
const b = native.ByteArray.fromStringWithNull(this.emsc, str);
|
const ts = new TextEncoder();
|
||||||
return b.hash().toCrock();
|
const b = ts.encode(str + "\0");
|
||||||
|
return encodeCrock(hash(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash a denomination public key.
|
* Hash a denomination public key.
|
||||||
*/
|
*/
|
||||||
hashDenomPub(denomPub: string): string {
|
hashDenomPub(denomPub: string): string {
|
||||||
return native.RsaPublicKey.fromCrock(this.emsc, denomPub)
|
return encodeCrock(hash(decodeCrock(denomPub)));
|
||||||
.encode()
|
|
||||||
.hash()
|
|
||||||
.toCrock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signCoinLink(
|
signCoinLink(
|
||||||
@ -553,20 +529,16 @@ export class CryptoImplementation {
|
|||||||
transferPub: string,
|
transferPub: string,
|
||||||
coinEv: string,
|
coinEv: string,
|
||||||
): string {
|
): string {
|
||||||
const coinEvHash = native.ByteArray.fromCrock(this.emsc, coinEv).hash();
|
const coinEvHash = hash(decodeCrock(coinEv));
|
||||||
|
const coinLink = buildSigPS(SignaturePurpose.WALLET_COIN_LINK)
|
||||||
const coinLink = new native.CoinLinkSignaturePS(this.emsc, {
|
.put(decodeCrock(newDenomHash))
|
||||||
coin_envelope_hash: coinEvHash,
|
.put(decodeCrock(oldCoinPub))
|
||||||
h_denom_pub: native.HashCode.fromCrock(this.emsc, newDenomHash),
|
.put(decodeCrock(transferPub))
|
||||||
old_coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, oldCoinPub),
|
.put(coinEvHash)
|
||||||
transfer_pub: native.EcdhePublicKey.fromCrock(this.emsc, transferPub),
|
.build();
|
||||||
});
|
const coinPriv = decodeCrock(oldCoinPriv);
|
||||||
|
const sig = eddsaSign(coinLink, coinPriv);
|
||||||
const coinPriv = native.EddsaPrivateKey.fromCrock(this.emsc, oldCoinPriv);
|
return encodeCrock(sig);
|
||||||
|
|
||||||
const sig = native.eddsaSign(coinLink.toPurpose(), coinPriv);
|
|
||||||
|
|
||||||
return sig.toCrock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
benchmark(repetitions: number): BenchmarkResult {
|
benchmark(repetitions: number): BenchmarkResult {
|
||||||
@ -578,165 +550,40 @@ export class CryptoImplementation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let time_hash_big = 0;
|
let time_hash_big = 0;
|
||||||
const ba = new native.ByteArray(this.emsc, 4096);
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
for (let i = 0; i < repetitions; i++) {
|
||||||
ba.randomize(native.RandomQuality.WEAK);
|
const ba = randomBytes(4096);
|
||||||
const start = timer.performanceNow();
|
const start = timer.performanceNow();
|
||||||
ba.hash();
|
hash(ba);
|
||||||
time_hash_big += timer.performanceNow() - start;
|
time_hash_big += timer.performanceNow() - start;
|
||||||
}
|
}
|
||||||
|
|
||||||
let time_eddsa_create = 0;
|
let time_eddsa_create = 0;
|
||||||
for (let i = 0; i < repetitions; i++) {
|
for (let i = 0; i < repetitions; i++) {
|
||||||
const start = timer.performanceNow();
|
const start = timer.performanceNow();
|
||||||
const priv: native.EddsaPrivateKey = native.EddsaPrivateKey.create(
|
const pair = createEddsaKeyPair();
|
||||||
this.emsc,
|
|
||||||
);
|
|
||||||
time_eddsa_create += timer.performanceNow() - start;
|
time_eddsa_create += timer.performanceNow() - start;
|
||||||
priv.destroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let time_eddsa_sign = 0;
|
let time_eddsa_sign = 0;
|
||||||
const eddsaPriv: native.EddsaPrivateKey = native.EddsaPrivateKey.create(
|
const p = randomBytes(4096);
|
||||||
this.emsc,
|
|
||||||
);
|
|
||||||
const eddsaPub: native.EddsaPublicKey = eddsaPriv.getPublicKey();
|
|
||||||
const h: native.HashCode = new native.HashCode(this.emsc);
|
|
||||||
h.alloc();
|
|
||||||
h.random(native.RandomQuality.WEAK);
|
|
||||||
|
|
||||||
const ps = new native.PaymentSignaturePS(this.emsc, {
|
const pair = createEddsaKeyPair();
|
||||||
contract_hash: h,
|
|
||||||
});
|
|
||||||
|
|
||||||
const p = ps.toPurpose();
|
|
||||||
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
for (let i = 0; i < repetitions; i++) {
|
||||||
const start = timer.performanceNow();
|
const start = timer.performanceNow();
|
||||||
native.eddsaSign(p, eddsaPriv);
|
eddsaSign(p, pair.eddsaPriv);
|
||||||
time_eddsa_sign += timer.performanceNow() - start;
|
time_eddsa_sign += timer.performanceNow() - start;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eddsaSig = native.eddsaSign(p, eddsaPriv);
|
const sig = eddsaSign(p, pair.eddsaPriv);
|
||||||
|
|
||||||
let time_ecdsa_create = 0;
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
|
||||||
const start = timer.performanceNow();
|
|
||||||
const priv: native.EcdsaPrivateKey = native.EcdsaPrivateKey.create(
|
|
||||||
this.emsc,
|
|
||||||
);
|
|
||||||
time_ecdsa_create += timer.performanceNow() - start;
|
|
||||||
priv.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
let time_eddsa_verify = 0;
|
let time_eddsa_verify = 0;
|
||||||
for (let i = 0; i < repetitions; i++) {
|
for (let i = 0; i < repetitions; i++) {
|
||||||
const start = timer.performanceNow();
|
const start = timer.performanceNow();
|
||||||
native.eddsaVerify(
|
eddsaVerify(p, sig, pair.eddsaPub);
|
||||||
native.SignaturePurpose.MERCHANT_PAYMENT_OK,
|
|
||||||
p,
|
|
||||||
eddsaSig,
|
|
||||||
eddsaPub,
|
|
||||||
);
|
|
||||||
time_eddsa_verify += timer.performanceNow() - start;
|
time_eddsa_verify += timer.performanceNow() - start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rsa 2048 */
|
|
||||||
|
|
||||||
let time_rsa_2048_blind = 0;
|
|
||||||
const rsaPriv2048: native.RsaPrivateKey = native.RsaPrivateKey.create(
|
|
||||||
this.emsc,
|
|
||||||
2048,
|
|
||||||
);
|
|
||||||
const rsaPub2048 = rsaPriv2048.getPublicKey();
|
|
||||||
const blindingSecret2048 = native.RsaBlindingKeySecret.create(this.emsc);
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
|
||||||
const start = timer.performanceNow();
|
|
||||||
native.rsaBlind(h, blindingSecret2048, rsaPub2048);
|
|
||||||
time_rsa_2048_blind += timer.performanceNow() - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blindedMessage2048 = native.rsaBlind(
|
|
||||||
h,
|
|
||||||
blindingSecret2048,
|
|
||||||
rsaPub2048,
|
|
||||||
);
|
|
||||||
if (!blindedMessage2048) {
|
|
||||||
throw Error("should not happen");
|
|
||||||
}
|
|
||||||
const rsaBlindSig2048 = native.rsaSignBlinded(
|
|
||||||
rsaPriv2048,
|
|
||||||
blindedMessage2048,
|
|
||||||
);
|
|
||||||
|
|
||||||
let time_rsa_2048_unblind = 0;
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
|
||||||
const start = timer.performanceNow();
|
|
||||||
native.rsaUnblind(rsaBlindSig2048, blindingSecret2048, rsaPub2048);
|
|
||||||
time_rsa_2048_unblind += timer.performanceNow() - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unblindedSig2048 = native.rsaUnblind(
|
|
||||||
rsaBlindSig2048,
|
|
||||||
blindingSecret2048,
|
|
||||||
rsaPub2048,
|
|
||||||
);
|
|
||||||
|
|
||||||
let time_rsa_2048_verify = 0;
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
|
||||||
const start = timer.performanceNow();
|
|
||||||
native.rsaVerify(h, unblindedSig2048, rsaPub2048);
|
|
||||||
time_rsa_2048_verify += timer.performanceNow() - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rsa 4096 */
|
|
||||||
|
|
||||||
let time_rsa_4096_blind = 0;
|
|
||||||
const rsaPriv4096: native.RsaPrivateKey = native.RsaPrivateKey.create(
|
|
||||||
this.emsc,
|
|
||||||
4096,
|
|
||||||
);
|
|
||||||
const rsaPub4096 = rsaPriv4096.getPublicKey();
|
|
||||||
const blindingSecret4096 = native.RsaBlindingKeySecret.create(this.emsc);
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
|
||||||
const start = timer.performanceNow();
|
|
||||||
native.rsaBlind(h, blindingSecret4096, rsaPub4096);
|
|
||||||
time_rsa_4096_blind += timer.performanceNow() - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blindedMessage4096 = native.rsaBlind(
|
|
||||||
h,
|
|
||||||
blindingSecret4096,
|
|
||||||
rsaPub4096,
|
|
||||||
);
|
|
||||||
if (!blindedMessage4096) {
|
|
||||||
throw Error("should not happen");
|
|
||||||
}
|
|
||||||
const rsaBlindSig4096 = native.rsaSignBlinded(
|
|
||||||
rsaPriv4096,
|
|
||||||
blindedMessage4096,
|
|
||||||
);
|
|
||||||
|
|
||||||
let time_rsa_4096_unblind = 0;
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
|
||||||
const start = timer.performanceNow();
|
|
||||||
native.rsaUnblind(rsaBlindSig4096, blindingSecret4096, rsaPub4096);
|
|
||||||
time_rsa_4096_unblind += timer.performanceNow() - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unblindedSig4096 = native.rsaUnblind(
|
|
||||||
rsaBlindSig4096,
|
|
||||||
blindingSecret4096,
|
|
||||||
rsaPub4096,
|
|
||||||
);
|
|
||||||
|
|
||||||
let time_rsa_4096_verify = 0;
|
|
||||||
for (let i = 0; i < repetitions; i++) {
|
|
||||||
const start = timer.performanceNow();
|
|
||||||
native.rsaVerify(h, unblindedSig4096, rsaPub4096);
|
|
||||||
time_rsa_4096_verify += timer.performanceNow() - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
repetitions,
|
repetitions,
|
||||||
time: {
|
time: {
|
||||||
@ -745,13 +592,6 @@ export class CryptoImplementation {
|
|||||||
eddsa_create: time_eddsa_create,
|
eddsa_create: time_eddsa_create,
|
||||||
eddsa_sign: time_eddsa_sign,
|
eddsa_sign: time_eddsa_sign,
|
||||||
eddsa_verify: time_eddsa_verify,
|
eddsa_verify: time_eddsa_verify,
|
||||||
ecdsa_create: time_ecdsa_create,
|
|
||||||
rsa_2048_blind: time_rsa_2048_blind,
|
|
||||||
rsa_2048_unblind: time_rsa_2048_unblind,
|
|
||||||
rsa_2048_verify: time_rsa_2048_verify,
|
|
||||||
rsa_4096_blind: time_rsa_4096_blind,
|
|
||||||
rsa_4096_unblind: time_rsa_4096_unblind,
|
|
||||||
rsa_4096_verify: time_rsa_4096_verify,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2017 Inria and GNUnet e.V.
|
|
||||||
|
|
||||||
TALER is free software; you can redistribute it and/or modify it under the
|
|
||||||
terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation; either version 3, or (at your option) any later version.
|
|
||||||
|
|
||||||
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
// tslint:disable:max-line-length
|
|
||||||
|
|
||||||
import test from "ava";
|
|
||||||
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
|
|
||||||
import * as native from "./emscInterface";
|
|
||||||
|
|
||||||
import { encodeCrock, decodeCrock } from "./talerCrypto";
|
|
||||||
import { timestampCheck } from "../helpers";
|
|
||||||
|
|
||||||
|
|
||||||
test("string hashing", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const x = native.ByteArray.fromStringWithNull(emsc, "hello taler");
|
|
||||||
const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR";
|
|
||||||
const hc = x.hash().toCrock();
|
|
||||||
console.log(`# hc ${hc}`);
|
|
||||||
t.true(h === hc, "must equal");
|
|
||||||
|
|
||||||
const te = new TextEncoder();
|
|
||||||
|
|
||||||
const x2 = te.encode("hello taler\0");
|
|
||||||
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test("signing", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const x = native.ByteArray.fromStringWithNull(emsc, "hello taler");
|
|
||||||
const priv = native.EddsaPrivateKey.create(emsc);
|
|
||||||
const pub = priv.getPublicKey();
|
|
||||||
const purpose = new native.EccSignaturePurpose(emsc, native.SignaturePurpose.TEST, x);
|
|
||||||
|
|
||||||
const purposeDataCrock = purpose.toCrock();
|
|
||||||
const privCrock = priv.toCrock();
|
|
||||||
const pubCrock = pub.toCrock();
|
|
||||||
const sig = native.eddsaSign(purpose, priv);
|
|
||||||
console.time("a");
|
|
||||||
for (let i = 0; i < 5000; i++) {
|
|
||||||
const sig = native.eddsaSign(purpose, priv);
|
|
||||||
}
|
|
||||||
console.timeEnd("a");
|
|
||||||
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
|
|
||||||
|
|
||||||
const d2 = new Uint8Array(decodeCrock(purposeDataCrock));
|
|
||||||
console.log("sig1:", sig.toCrock());
|
|
||||||
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test("signing-fixed-data", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const x = native.ByteArray.fromStringWithNull(emsc, "hello taler");
|
|
||||||
const purpose = new native.EccSignaturePurpose(emsc, native.SignaturePurpose.TEST, x);
|
|
||||||
const privStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
|
|
||||||
const pubStr = "YHCZB442FQFJ0ET20MWA8YJ53M61EZGJ6QKV1KTJZMRNXDY45WT0";
|
|
||||||
const sigStr = "7V6XY4QGC1406GPMT305MZQ1HDCR7R0S5BP02GTGDQFPSXB6YD2YDN5ZS7NJQCNP61Y39MRHXNXQ1Z15JY4CJY4CPDA6CKQ3313WG38";
|
|
||||||
const priv = native.EddsaPrivateKey.fromCrock(emsc, privStr);
|
|
||||||
t.true(privStr === priv.toCrock());
|
|
||||||
const pub = priv.getPublicKey();
|
|
||||||
t.true(pubStr === pub.toCrock());
|
|
||||||
const sig = native.EddsaSignature.fromCrock(emsc, sigStr);
|
|
||||||
t.true(sigStr === sig.toCrock());
|
|
||||||
const sig2 = native.eddsaSign(purpose, priv);
|
|
||||||
t.true(sig.toCrock() === sig2.toCrock());
|
|
||||||
t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub));
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6MW42DSN8MVKJGHK6WR3CGT18MWMCDSM75138E1K8S0MADSQ68W34DHH6MW4CHA270W4CG9J6GW48DHG8MVK4E9S7523GEA56H0K4E1Q891KCCSG752KGC1M88VMCDSQ6D23CHHG8H33AGHG6MSK8GT26CRKAC1M64V3JCJ56CVKCC228MWMCHA26MS30H1J8MVKEDHJ70TMADHK892KJC1H60TKJDHM710KGGT584T38H9K851KCDHG60W30HJ28CT4CC1G8CR3JGJ28H236DJ28H330H9S890M2D9S8S14AGA369344GA36S248CHS70RKEDSS6MWKGDJ26D136GT465348CSS8S232CHM6GS34C9N8CS3GD9H60W36H1R8MSK2GSQ8MSM6C9R70SKCHHN6MW3ACJ28N0K2CA58RS3GCA26MV42G9P891KAG9Q8N0KGD9M850KEHJ16S130CA27124AE1G852KJCHR6S1KGDSJ8RTKED1S8RR3CCHP68W4CH9Q6GT34GT18GS36EA46N24AGSP6933GCHM60VMAE1S8GV3EHHN74W3GC1J651KEH9N8MSK0CSG6S2KEEA460R32C1M8D144GSR6RWKEC218S0KEGJ4611KEEA36CSKJC2564TM4CSJ6H230E1N74TM8C1P61342CSG60WKCGHH64VK2G9S8CRKAHHK88W30HJ388R3CH1Q6X2K2DHK8GSM4D1Q74WM4HA461146H9S6D33JDJ26D234C9Q6923ECSS60RM6CT46CSKCH1M6S13EH9J8S33GCSN4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00";
|
|
||||||
|
|
||||||
|
|
||||||
test("rsa-encode", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0";
|
|
||||||
const denomPub = native.RsaPublicKey.fromCrock(emsc, denomPubStr1);
|
|
||||||
const pubHash = denomPub.encode().hash();
|
|
||||||
t.true(pubHashStr === pubHash.toCrock());
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test("withdraw-request", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90";
|
|
||||||
const reservePriv = native.EddsaPrivateKey.fromCrock(emsc, reservePrivStr);
|
|
||||||
const reservePub = reservePriv.getPublicKey();
|
|
||||||
const amountWithFee = new native.Amount(emsc, {currency: "KUDOS", value: 1, fraction: 10000});
|
|
||||||
amountWithFee.add(new native.Amount(emsc, {currency: "KUDOS", value: 0, fraction: 20000}));
|
|
||||||
const withdrawFee = new native.Amount(emsc, {currency: "KUDOS", value: 0, fraction: 20000});
|
|
||||||
const denomPub = native.RsaPublicKey.fromCrock(emsc, denomPubStr1);
|
|
||||||
const ev = native.ByteArray.fromStringWithNull(emsc, "hello, world");
|
|
||||||
|
|
||||||
// Signature
|
|
||||||
const withdrawRequest = new native.WithdrawRequestPS(emsc, {
|
|
||||||
amount_with_fee: amountWithFee.toNbo(),
|
|
||||||
h_coin_envelope: ev.hash(),
|
|
||||||
h_denomination_pub: denomPub.encode().hash(),
|
|
||||||
reserve_pub: reservePub,
|
|
||||||
withdraw_fee: withdrawFee.toNbo(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const sigStr = "AD3T8W44NV193J19RAN3NAJHPP6RVB0R3NWV7ZK5G8Q946YDK0B6F8YJBNRRBXSPVTKY31S7BVZPJFFTJJRMY61DH51X4JSXK677428";
|
|
||||||
|
|
||||||
const sig = native.eddsaSign(withdrawRequest.toPurpose(), reservePriv);
|
|
||||||
t.true(native.eddsaVerify(native.SignaturePurpose.RESERVE_WITHDRAW, withdrawRequest.toPurpose(), sig, reservePub));
|
|
||||||
t.true(sig.toCrock() === sigStr);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test("currency-conversion", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const a1 = new native.Amount(emsc, {currency: "KUDOS", value: 1, fraction: 50000000});
|
|
||||||
const a2 = new native.Amount(emsc, {currency: "KUDOS", value: 1, fraction: 50000000});
|
|
||||||
a1.add(a2);
|
|
||||||
const x = a1.toJson();
|
|
||||||
t.true(x.currency === "KUDOS");
|
|
||||||
t.true(x.fraction === 0);
|
|
||||||
t.true(x.value === 3);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test("ecdsa", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const priv = native.EcdsaPrivateKey.create(emsc);
|
|
||||||
const pub1 = priv.getPublicKey();
|
|
||||||
t.truthy(priv);
|
|
||||||
t.truthy(pub1);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test("ecdhe", async (t) => {
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
const emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const priv = native.EcdhePrivateKey.create(emsc);
|
|
||||||
const pub = priv.getPublicKey();
|
|
||||||
t.truthy(priv);
|
|
||||||
t.truthy(pub);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
@ -1,101 +0,0 @@
|
|||||||
|
|
||||||
import { EmscEnvironment } from "./emscInterface";
|
|
||||||
import { CryptoImplementation } from "./cryptoImplementation";
|
|
||||||
|
|
||||||
import fs = require("fs");
|
|
||||||
|
|
||||||
export class NodeEmscriptenLoader {
|
|
||||||
private cachedEmscEnvironment: EmscEnvironment | undefined = undefined;
|
|
||||||
private cachedEmscEnvironmentPromise:
|
|
||||||
| Promise<EmscEnvironment>
|
|
||||||
| undefined = undefined;
|
|
||||||
|
|
||||||
private async getWasmBinary(): Promise<Uint8Array> {
|
|
||||||
// @ts-ignore
|
|
||||||
const akonoGetData = global.__akono_getData;
|
|
||||||
if (akonoGetData) {
|
|
||||||
// We're running embedded node on Android
|
|
||||||
console.log("reading wasm binary from akono");
|
|
||||||
const data = akonoGetData("taler-emscripten-lib.wasm");
|
|
||||||
// The data we get is base64-encoded binary data
|
|
||||||
let buf = new Buffer(data, 'base64');
|
|
||||||
return new Uint8Array(buf);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// We're in a normal node environment
|
|
||||||
const binaryPath = __dirname + "/../../../emscripten/taler-emscripten-lib.wasm";
|
|
||||||
const wasmBinary = new Uint8Array(fs.readFileSync(binaryPath));
|
|
||||||
return wasmBinary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEmscriptenEnvironment(): Promise<EmscEnvironment> {
|
|
||||||
if (this.cachedEmscEnvironment) {
|
|
||||||
return this.cachedEmscEnvironment;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.cachedEmscEnvironmentPromise) {
|
|
||||||
return this.cachedEmscEnvironmentPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lib: any;
|
|
||||||
|
|
||||||
const wasmBinary = await this.getWasmBinary();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// Arguments passed to the emscripten prelude
|
|
||||||
const libArgs = {
|
|
||||||
wasmBinary,
|
|
||||||
onRuntimeInitialized: () => {
|
|
||||||
if (!lib) {
|
|
||||||
console.error("fatal emscripten initialization error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.cachedEmscEnvironmentPromise = undefined;
|
|
||||||
this.cachedEmscEnvironment = new EmscEnvironment(lib);
|
|
||||||
resolve(this.cachedEmscEnvironment);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Make sure that TypeScript doesn't try
|
|
||||||
// to check the taler-emscripten-lib.
|
|
||||||
const indirectRequire = require;
|
|
||||||
|
|
||||||
const g = global;
|
|
||||||
|
|
||||||
// unavoidable hack, so that emscripten detects
|
|
||||||
// the environment as node even though importScripts
|
|
||||||
// is present.
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const savedImportScripts = g.importScripts;
|
|
||||||
// @ts-ignore
|
|
||||||
delete g.importScripts;
|
|
||||||
// @ts-ignore
|
|
||||||
const savedCrypto = g.crypto;
|
|
||||||
// @ts-ignore
|
|
||||||
delete g.crypto;
|
|
||||||
|
|
||||||
// Assume that the code is run from the dist/ directory.
|
|
||||||
const libFn = indirectRequire(
|
|
||||||
"../../../emscripten/taler-emscripten-lib.js",
|
|
||||||
);
|
|
||||||
lib = libFn(libArgs);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
g.importScripts = savedImportScripts;
|
|
||||||
// @ts-ignore
|
|
||||||
g.crypto = savedCrypto;
|
|
||||||
|
|
||||||
if (!lib) {
|
|
||||||
throw Error("could not load taler-emscripten-lib.js");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lib.ccall) {
|
|
||||||
throw Error(
|
|
||||||
"sanity check failed: taler-emscripten lib does not have 'ccall'",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -84,7 +84,6 @@ export class Worker {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.child.on("message", (msg: any) => {
|
this.child.on("message", (msg: any) => {
|
||||||
console.log("nodeProcessWorker got child message", msg);
|
|
||||||
this.dispatchMessage(msg);
|
this.dispatchMessage(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -114,7 +113,6 @@ export class Worker {
|
|||||||
* Forcibly terminate the worker thread.
|
* Forcibly terminate the worker thread.
|
||||||
*/
|
*/
|
||||||
terminate () {
|
terminate () {
|
||||||
console.log("terminating node.js worker");
|
|
||||||
this.child.kill("SIGINT");
|
this.child.kill("SIGINT");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,20 +14,13 @@
|
|||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// tslint:disable:no-var-requires
|
// tslint:disable:no-var-requires
|
||||||
|
|
||||||
import fs = require("fs");
|
|
||||||
import vm = require("vm");
|
|
||||||
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
|
|
||||||
import { CryptoImplementation } from "./cryptoImplementation";
|
import { CryptoImplementation } from "./cryptoImplementation";
|
||||||
|
|
||||||
const loader = new NodeEmscriptenLoader();
|
|
||||||
|
|
||||||
async function handleRequest(operation: string, id: number, args: string[]) {
|
async function handleRequest(operation: string, id: number, args: string[]) {
|
||||||
let emsc = await loader.getEmscriptenEnvironment();
|
|
||||||
|
|
||||||
const impl = new CryptoImplementation(emsc);
|
const impl = new CryptoImplementation();
|
||||||
|
|
||||||
if (!(operation in impl)) {
|
if (!(operation in impl)) {
|
||||||
console.error(`crypto operation '${operation}' not found`);
|
console.error(`crypto operation '${operation}' not found`);
|
||||||
@ -42,16 +35,12 @@ async function handleRequest(operation: string, id: number, args: string[]) {
|
|||||||
console.error("process.send not available");
|
console.error("process.send not available");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("error during operation", e);
|
console.error("error during operation", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("message", (msgStr: any) => {
|
process.on("message", (msgStr: any) => {
|
||||||
console.log("got message in node worker entry", msgStr);
|
|
||||||
|
|
||||||
console.log("typeof msg", typeof msgStr);
|
|
||||||
|
|
||||||
const msg = JSON.parse(msgStr);
|
const msg = JSON.parse(msgStr);
|
||||||
|
|
||||||
const args = msg.data.args;
|
const args = msg.data.args;
|
||||||
@ -76,5 +65,5 @@ process.on("message", (msgStr: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
process.on("uncaughtException", (err: any) => {
|
process.on("uncaughtException", (err: any) => {
|
||||||
console.log("uncaught exception in node worker entry", err);
|
console.error("uncaught exception in node worker entry", err);
|
||||||
});
|
});
|
||||||
|
@ -2342,7 +2342,6 @@ function crypto_hash(out: Uint8Array, m: Uint8Array, n: number) {
|
|||||||
const hh = new Int32Array(8);
|
const hh = new Int32Array(8);
|
||||||
const hl = new Int32Array(8);
|
const hl = new Int32Array(8);
|
||||||
const x = new Uint8Array(256);
|
const x = new Uint8Array(256);
|
||||||
let i;
|
|
||||||
let b = n;
|
let b = n;
|
||||||
|
|
||||||
hh[0] = 0x6a09e667;
|
hh[0] = 0x6a09e667;
|
||||||
@ -2366,7 +2365,7 @@ function crypto_hash(out: Uint8Array, m: Uint8Array, n: number) {
|
|||||||
crypto_hashblocks_hl(hh, hl, m, n);
|
crypto_hashblocks_hl(hh, hl, m, n);
|
||||||
n %= 128;
|
n %= 128;
|
||||||
|
|
||||||
for (i = 0; i < n; i++) x[i] = m[b - n + i];
|
for (let i = 0; i < n; i++) x[i] = m[b - n + i];
|
||||||
x[n] = 128;
|
x[n] = 128;
|
||||||
|
|
||||||
n = 256 - 128 * (n < 112 ? 1 : 0);
|
n = 256 - 128 * (n < 112 ? 1 : 0);
|
||||||
@ -2374,11 +2373,86 @@ function crypto_hash(out: Uint8Array, m: Uint8Array, n: number) {
|
|||||||
ts64(x, n - 8, (b / 0x20000000) | 0, b << 3);
|
ts64(x, n - 8, (b / 0x20000000) | 0, b << 3);
|
||||||
crypto_hashblocks_hl(hh, hl, x, n);
|
crypto_hashblocks_hl(hh, hl, x, n);
|
||||||
|
|
||||||
for (i = 0; i < 8; i++) ts64(out, 8 * i, hh[i], hl[i]);
|
for (let i = 0; i < 8; i++)
|
||||||
|
ts64(out, 8 * i, hh[i], hl[i]);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incremental version of crypto_hash.
|
||||||
|
*/
|
||||||
|
export class HashState {
|
||||||
|
private hh = new Int32Array(8);
|
||||||
|
private hl = new Int32Array(8);
|
||||||
|
|
||||||
|
private next = new Uint8Array(128);
|
||||||
|
private p = 0;
|
||||||
|
private total = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.hh[0] = 0x6a09e667;
|
||||||
|
this.hh[1] = 0xbb67ae85;
|
||||||
|
this.hh[2] = 0x3c6ef372;
|
||||||
|
this.hh[3] = 0xa54ff53a;
|
||||||
|
this.hh[4] = 0x510e527f;
|
||||||
|
this.hh[5] = 0x9b05688c;
|
||||||
|
this.hh[6] = 0x1f83d9ab;
|
||||||
|
this.hh[7] = 0x5be0cd19;
|
||||||
|
|
||||||
|
this.hl[0] = 0xf3bcc908;
|
||||||
|
this.hl[1] = 0x84caa73b;
|
||||||
|
this.hl[2] = 0xfe94f82b;
|
||||||
|
this.hl[3] = 0x5f1d36f1;
|
||||||
|
this.hl[4] = 0xade682d1;
|
||||||
|
this.hl[5] = 0x2b3e6c1f;
|
||||||
|
this.hl[6] = 0xfb41bd6b;
|
||||||
|
this.hl[7] = 0x137e2179;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(data: Uint8Array): HashState {
|
||||||
|
this.total += data.length;
|
||||||
|
let i = 0;
|
||||||
|
while (i < data.length) {
|
||||||
|
const r = 128 - this.p;
|
||||||
|
if (r > (data.length - i)) {
|
||||||
|
for (let j = 0; i + j < data.length; j++) {
|
||||||
|
this.next[this.p + j] = data[i + j];
|
||||||
|
}
|
||||||
|
this.p += data.length - i;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
for (let j = 0; this.p + j < 128; j++) {
|
||||||
|
this.next[this.p + j] = data[i + j];
|
||||||
|
}
|
||||||
|
crypto_hashblocks_hl(this.hh, this.hl, this.next, 128);
|
||||||
|
i += 128 - this.p;
|
||||||
|
this.p = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(): Uint8Array {
|
||||||
|
const out = new Uint8Array(64);
|
||||||
|
let n = this.p;
|
||||||
|
const x = new Uint8Array(256);
|
||||||
|
let b = this.total;
|
||||||
|
for (let i = 0; i < n; i++) x[i] = this.next[i];
|
||||||
|
x[n] = 128;
|
||||||
|
|
||||||
|
n = 256 - 128 * (n < 112 ? 1 : 0);
|
||||||
|
x[n - 9] = 0;
|
||||||
|
ts64(x, n - 8, (b / 0x20000000) | 0, b << 3);
|
||||||
|
crypto_hashblocks_hl(this.hh, this.hl, x, n);
|
||||||
|
|
||||||
|
for (let i = 0; i < 8; i++)
|
||||||
|
ts64(out, 8 * i, this.hh[i], this.hl[i]);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function add(p: Float64Array[], q: Float64Array[]) {
|
function add(p: Float64Array[], q: Float64Array[]) {
|
||||||
var a = gf(),
|
var a = gf(),
|
||||||
b = gf(),
|
b = gf(),
|
@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
import { CryptoImplementation } from "./cryptoImplementation";
|
import { CryptoImplementation } from "./cryptoImplementation";
|
||||||
|
|
||||||
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
|
|
||||||
|
|
||||||
import fs = require("fs");
|
|
||||||
import { CryptoWorkerFactory } from "./cryptoApi";
|
import { CryptoWorkerFactory } from "./cryptoApi";
|
||||||
import { CryptoWorker } from "./cryptoWorker";
|
import { CryptoWorker } from "./cryptoWorker";
|
||||||
|
|
||||||
@ -56,8 +53,6 @@ export class SynchronousCryptoWorker {
|
|||||||
*/
|
*/
|
||||||
onerror: undefined | ((m: any) => void);
|
onerror: undefined | ((m: any) => void);
|
||||||
|
|
||||||
private emscriptenLoader = new NodeEmscriptenLoader();
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.onerror = undefined;
|
this.onerror = undefined;
|
||||||
this.onmessage = undefined;
|
this.onmessage = undefined;
|
||||||
@ -84,9 +79,7 @@ export class SynchronousCryptoWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async handleRequest(operation: string, id: number, args: string[]) {
|
private async handleRequest(operation: string, id: number, args: string[]) {
|
||||||
let emsc = await this.emscriptenLoader.getEmscriptenEnvironment();
|
const impl = new CryptoImplementation();
|
||||||
|
|
||||||
const impl = new CryptoImplementation(emsc);
|
|
||||||
|
|
||||||
if (!(operation in impl)) {
|
if (!(operation in impl)) {
|
||||||
console.error(`crypto operation '${operation}' not found`);
|
console.error(`crypto operation '${operation}' not found`);
|
||||||
|
@ -29,8 +29,8 @@ import {
|
|||||||
rsaUnblind,
|
rsaUnblind,
|
||||||
rsaVerify,
|
rsaVerify,
|
||||||
} from "./talerCrypto";
|
} from "./talerCrypto";
|
||||||
import { hmacSha512, sha512, kdf } from "./kdf";
|
import { sha512, kdf } from "./primitives/kdf";
|
||||||
import nacl = require("./nacl-fast");
|
import nacl = require("./primitives/nacl-fast");
|
||||||
|
|
||||||
function hexToBytes(hex: string) {
|
function hexToBytes(hex: string) {
|
||||||
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||||
@ -159,3 +159,43 @@ test("taler-exchange-tvg blind signing", t => {
|
|||||||
const v = rsaVerify(decodeCrock(messageHash), decodeCrock(sig), decodeCrock(rsaPublicKey));
|
const v = rsaVerify(decodeCrock(messageHash), decodeCrock(sig), decodeCrock(rsaPublicKey));
|
||||||
t.true(v);
|
t.true(v);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("incremental hashing #1", (t) => {
|
||||||
|
const n = 1024;
|
||||||
|
const d = nacl.randomBytes(n);
|
||||||
|
|
||||||
|
const h1 = nacl.hash(d);
|
||||||
|
const h2 = new nacl.HashState().update(d).finish();
|
||||||
|
|
||||||
|
const s = new nacl.HashState();
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const b = new Uint8Array(1);
|
||||||
|
b[0] = d[i];
|
||||||
|
s.update(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
const h3 = s.finish();
|
||||||
|
|
||||||
|
t.deepEqual(encodeCrock(h1), encodeCrock(h2));
|
||||||
|
t.deepEqual(encodeCrock(h1), encodeCrock(h3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("incremental hashing #2", (t) => {
|
||||||
|
const n = 10;
|
||||||
|
const d = nacl.randomBytes(n);
|
||||||
|
|
||||||
|
const h1 = nacl.hash(d);
|
||||||
|
const h2 = new nacl.HashState().update(d).finish();
|
||||||
|
const s = new nacl.HashState();
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const b = new Uint8Array(1);
|
||||||
|
b[0] = d[i];
|
||||||
|
s.update(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
const h3 = s.finish();
|
||||||
|
|
||||||
|
t.deepEqual(encodeCrock(h1), encodeCrock(h3));
|
||||||
|
t.deepEqual(encodeCrock(h1), encodeCrock(h2));
|
||||||
|
});
|
@ -18,9 +18,9 @@
|
|||||||
* Native implementation of GNU Taler crypto.
|
* Native implementation of GNU Taler crypto.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import nacl = require("./nacl-fast");
|
import nacl = require("./primitives/nacl-fast");
|
||||||
import bigint from "big-integer";
|
import bigint from "big-integer";
|
||||||
import { kdf } from "./kdf";
|
import { kdf } from "./primitives/kdf";
|
||||||
|
|
||||||
export function getRandomBytes(n: number): Uint8Array {
|
export function getRandomBytes(n: number): Uint8Array {
|
||||||
return nacl.randomBytes(n);
|
return nacl.randomBytes(n);
|
||||||
@ -131,7 +131,10 @@ export function ecdheGetPublic(ecdhePriv: Uint8Array): Uint8Array {
|
|||||||
return nacl.scalarMult_base(ecdhePriv);
|
return nacl.scalarMult_base(ecdhePriv);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function keyExchangeEddsaEcdhe(eddsaPriv: Uint8Array, ecdhePub: Uint8Array): Uint8Array {
|
export function keyExchangeEddsaEcdhe(
|
||||||
|
eddsaPriv: Uint8Array,
|
||||||
|
ecdhePub: Uint8Array,
|
||||||
|
): Uint8Array {
|
||||||
const ph = nacl.hash(eddsaPriv);
|
const ph = nacl.hash(eddsaPriv);
|
||||||
const a = new Uint8Array(32);
|
const a = new Uint8Array(32);
|
||||||
for (let i = 0; i < 32; i++) {
|
for (let i = 0; i < 32; i++) {
|
||||||
@ -141,7 +144,10 @@ export function keyExchangeEddsaEcdhe(eddsaPriv: Uint8Array, ecdhePub: Uint8Arra
|
|||||||
return nacl.hash(x);
|
return nacl.hash(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function keyExchangeEcdheEddsa(ecdhePriv: Uint8Array, eddsaPub: Uint8Array): Uint8Array {
|
export function keyExchangeEcdheEddsa(
|
||||||
|
ecdhePriv: Uint8Array,
|
||||||
|
eddsaPub: Uint8Array,
|
||||||
|
): Uint8Array {
|
||||||
const curve25519Pub = nacl.sign_ed25519_pk_to_curve25519(eddsaPub);
|
const curve25519Pub = nacl.sign_ed25519_pk_to_curve25519(eddsaPub);
|
||||||
const x = nacl.scalarMult(ecdhePriv, curve25519Pub);
|
const x = nacl.scalarMult(ecdhePriv, curve25519Pub);
|
||||||
return nacl.hash(x);
|
return nacl.hash(x);
|
||||||
@ -172,8 +178,8 @@ function kdfMod(
|
|||||||
while (true) {
|
while (true) {
|
||||||
const ctx = new Uint8Array(info.byteLength + 2);
|
const ctx = new Uint8Array(info.byteLength + 2);
|
||||||
ctx.set(info, 0);
|
ctx.set(info, 0);
|
||||||
ctx[ctx.length - 2] = (counter >>> 8) & 0xFF;
|
ctx[ctx.length - 2] = (counter >>> 8) & 0xff;
|
||||||
ctx[ctx.length - 1] = counter & 0xFF;
|
ctx[ctx.length - 1] = counter & 0xff;
|
||||||
const buf = kdf(buflen, ikm, salt, ctx);
|
const buf = kdf(buflen, ikm, salt, ctx);
|
||||||
const arr = Array.from(buf);
|
const arr = Array.from(buf);
|
||||||
arr[0] = arr[0] & mask;
|
arr[0] = arr[0] & mask;
|
||||||
@ -185,7 +191,7 @@ function kdfMod(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringToBuf(s: string) {
|
export function stringToBytes(s: string) {
|
||||||
const te = new TextEncoder();
|
const te = new TextEncoder();
|
||||||
return te.encode(s);
|
return te.encode(s);
|
||||||
}
|
}
|
||||||
@ -194,9 +200,12 @@ function loadBigInt(arr: Uint8Array) {
|
|||||||
return bigint.fromArray(Array.from(arr), 256, false);
|
return bigint.fromArray(Array.from(arr), 256, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function rsaBlindingKeyDerive(rsaPub: RsaPub, bks: Uint8Array): bigint.BigInteger {
|
function rsaBlindingKeyDerive(
|
||||||
const salt = stringToBuf("Blinding KDF extrator HMAC key");
|
rsaPub: RsaPub,
|
||||||
const info = stringToBuf("Blinding KDF");
|
bks: Uint8Array,
|
||||||
|
): bigint.BigInteger {
|
||||||
|
const salt = stringToBytes("Blinding KDF extrator HMAC key");
|
||||||
|
const info = stringToBytes("Blinding KDF");
|
||||||
return kdfMod(rsaPub.N, bks, salt, info);
|
return kdfMod(rsaPub.N, bks, salt, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +227,7 @@ function rsaGcdValidate(r: bigint.BigInteger, n: bigint.BigInteger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger {
|
function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger {
|
||||||
const info = stringToBuf("RSA-FDA FTpsW!");
|
const info = stringToBytes("RSA-FDA FTpsW!");
|
||||||
const salt = rsaPubEncode(rsaPub);
|
const salt = rsaPubEncode(rsaPub);
|
||||||
const r = kdfMod(rsaPub.N, hm, salt, info);
|
const r = kdfMod(rsaPub.N, hm, salt, info);
|
||||||
rsaGcdValidate(r, rsaPub.N);
|
rsaGcdValidate(r, rsaPub.N);
|
||||||
@ -228,12 +237,15 @@ function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger {
|
|||||||
function rsaPubDecode(rsaPub: Uint8Array): RsaPub {
|
function rsaPubDecode(rsaPub: Uint8Array): RsaPub {
|
||||||
const modulusLength = (rsaPub[0] << 8) | rsaPub[1];
|
const modulusLength = (rsaPub[0] << 8) | rsaPub[1];
|
||||||
const exponentLength = (rsaPub[2] << 8) | rsaPub[3];
|
const exponentLength = (rsaPub[2] << 8) | rsaPub[3];
|
||||||
const modulus = rsaPub.slice(4, 4 + modulusLength)
|
const modulus = rsaPub.slice(4, 4 + modulusLength);
|
||||||
const exponent = rsaPub.slice(4 + modulusLength, 4 + modulusLength + exponentLength);
|
const exponent = rsaPub.slice(
|
||||||
|
4 + modulusLength,
|
||||||
|
4 + modulusLength + exponentLength,
|
||||||
|
);
|
||||||
const res = {
|
const res = {
|
||||||
N: loadBigInt(modulus),
|
N: loadBigInt(modulus),
|
||||||
e: loadBigInt(exponent),
|
e: loadBigInt(exponent),
|
||||||
}
|
};
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,16 +253,20 @@ function rsaPubEncode(rsaPub: RsaPub): Uint8Array {
|
|||||||
const mb = rsaPub.N.toArray(256).value;
|
const mb = rsaPub.N.toArray(256).value;
|
||||||
const eb = rsaPub.e.toArray(256).value;
|
const eb = rsaPub.e.toArray(256).value;
|
||||||
const out = new Uint8Array(4 + mb.length + eb.length);
|
const out = new Uint8Array(4 + mb.length + eb.length);
|
||||||
out[0] = (mb.length >>> 8) & 0xFF;
|
out[0] = (mb.length >>> 8) & 0xff;
|
||||||
out[1] = mb.length & 0xFF;
|
out[1] = mb.length & 0xff;
|
||||||
out[2] = (eb.length >>> 8) & 0xFF;
|
out[2] = (eb.length >>> 8) & 0xff;
|
||||||
out[3] = eb.length & 0xFF;
|
out[3] = eb.length & 0xff;
|
||||||
out.set(mb, 4);
|
out.set(mb, 4);
|
||||||
out.set(eb, 4 + mb.length);
|
out.set(eb, 4 + mb.length);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rsaBlind(hm: Uint8Array, bks: Uint8Array, rsaPubEnc: Uint8Array): Uint8Array {
|
export function rsaBlind(
|
||||||
|
hm: Uint8Array,
|
||||||
|
bks: Uint8Array,
|
||||||
|
rsaPubEnc: Uint8Array,
|
||||||
|
): Uint8Array {
|
||||||
const rsaPub = rsaPubDecode(rsaPubEnc);
|
const rsaPub = rsaPubDecode(rsaPubEnc);
|
||||||
const data = rsaFullDomainHash(hm, rsaPub);
|
const data = rsaFullDomainHash(hm, rsaPub);
|
||||||
const r = rsaBlindingKeyDerive(rsaPub, bks);
|
const r = rsaBlindingKeyDerive(rsaPub, bks);
|
||||||
@ -259,7 +275,11 @@ export function rsaBlind(hm: Uint8Array, bks: Uint8Array, rsaPubEnc: Uint8Array)
|
|||||||
return new Uint8Array(bm.toArray(256).value);
|
return new Uint8Array(bm.toArray(256).value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rsaUnblind(sig: Uint8Array, rsaPubEnc: Uint8Array, bks: Uint8Array): Uint8Array {
|
export function rsaUnblind(
|
||||||
|
sig: Uint8Array,
|
||||||
|
rsaPubEnc: Uint8Array,
|
||||||
|
bks: Uint8Array,
|
||||||
|
): Uint8Array {
|
||||||
const rsaPub = rsaPubDecode(rsaPubEnc);
|
const rsaPub = rsaPubDecode(rsaPubEnc);
|
||||||
const blinded_s = loadBigInt(sig);
|
const blinded_s = loadBigInt(sig);
|
||||||
const r = rsaBlindingKeyDerive(rsaPub, bks);
|
const r = rsaBlindingKeyDerive(rsaPub, bks);
|
||||||
@ -268,10 +288,86 @@ export function rsaUnblind(sig: Uint8Array, rsaPubEnc: Uint8Array, bks: Uint8Arr
|
|||||||
return new Uint8Array(s.toArray(256).value);
|
return new Uint8Array(s.toArray(256).value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rsaVerify(hm: Uint8Array, rsaSig: Uint8Array, rsaPubEnc: Uint8Array): boolean {
|
export function rsaVerify(
|
||||||
|
hm: Uint8Array,
|
||||||
|
rsaSig: Uint8Array,
|
||||||
|
rsaPubEnc: Uint8Array,
|
||||||
|
): boolean {
|
||||||
const rsaPub = rsaPubDecode(rsaPubEnc);
|
const rsaPub = rsaPubDecode(rsaPubEnc);
|
||||||
const d = rsaFullDomainHash(hm, rsaPub);
|
const d = rsaFullDomainHash(hm, rsaPub);
|
||||||
const sig = loadBigInt(rsaSig);
|
const sig = loadBigInt(rsaSig);
|
||||||
const sig_e = sig.modPow(rsaPub.e, rsaPub.N);
|
const sig_e = sig.modPow(rsaPub.e, rsaPub.N);
|
||||||
return sig_e.equals(d);
|
return sig_e.equals(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EddsaKeyPair {
|
||||||
|
eddsaPub: Uint8Array;
|
||||||
|
eddsaPriv: Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EcdheKeyPair {
|
||||||
|
ecdhePub: Uint8Array;
|
||||||
|
ecdhePriv: Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEddsaKeyPair(): EddsaKeyPair {
|
||||||
|
const eddsaPriv = nacl.randomBytes(32);
|
||||||
|
const eddsaPub = eddsaGetPublic(eddsaPriv);
|
||||||
|
return { eddsaPriv, eddsaPub };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEcdheKeyPair(): EcdheKeyPair {
|
||||||
|
const ecdhePriv = nacl.randomBytes(32);
|
||||||
|
const ecdhePub = ecdheGetPublic(ecdhePriv);
|
||||||
|
return { ecdhePriv, ecdhePub };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBlindingKeySecret(): Uint8Array {
|
||||||
|
return nacl.randomBytes(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hash(d: Uint8Array): Uint8Array {
|
||||||
|
return nacl.hash(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function eddsaSign(msg: Uint8Array, eddsaPriv: Uint8Array): Uint8Array {
|
||||||
|
const pair = nacl.sign_keyPair_fromSeed(eddsaPriv);
|
||||||
|
return nacl.sign_detached(msg, pair.secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function eddsaVerify(
|
||||||
|
msg: Uint8Array,
|
||||||
|
sig: Uint8Array,
|
||||||
|
eddsaPub: Uint8Array,
|
||||||
|
): boolean {
|
||||||
|
return nacl.sign_detached_verify(msg, sig, eddsaPub);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createHashContext(): nacl.HashState {
|
||||||
|
return new nacl.HashState();
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FreshCoin {
|
||||||
|
coinPub: Uint8Array;
|
||||||
|
coinPriv: Uint8Array;
|
||||||
|
bks: Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupRefreshPlanchet(
|
||||||
|
secretSeed: Uint8Array,
|
||||||
|
coinNumber: number,
|
||||||
|
): FreshCoin {
|
||||||
|
const info = stringToBytes("taler-coin-derivation");
|
||||||
|
const saltArrBuf = new ArrayBuffer(4);
|
||||||
|
const salt = new Uint8Array(saltArrBuf);
|
||||||
|
const saltDataView = new DataView(saltArrBuf);
|
||||||
|
saltDataView.setUint32(0, coinNumber);
|
||||||
|
const out = kdf(64, secretSeed, salt, info);
|
||||||
|
const coinPriv = out.slice(0, 32);
|
||||||
|
const bks = out.slice(32, 64);
|
||||||
|
return {
|
||||||
|
bks,
|
||||||
|
coinPriv,
|
||||||
|
coinPub: eddsaGetPublic(coinPriv),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -283,26 +283,26 @@ export class DenominationRecord {
|
|||||||
/**
|
/**
|
||||||
* Validity start date of the denomination.
|
* Validity start date of the denomination.
|
||||||
*/
|
*/
|
||||||
@Checkable.String()
|
@Checkable.Value(() => Timestamp)
|
||||||
stampStart: string;
|
stampStart: Timestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date after which the currency can't be withdrawn anymore.
|
* Date after which the currency can't be withdrawn anymore.
|
||||||
*/
|
*/
|
||||||
@Checkable.String()
|
@Checkable.Value(() => Timestamp)
|
||||||
stampExpireWithdraw: string;
|
stampExpireWithdraw: Timestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date after the denomination officially doesn't exist anymore.
|
* Date after the denomination officially doesn't exist anymore.
|
||||||
*/
|
*/
|
||||||
@Checkable.String()
|
@Checkable.Value(() => Timestamp)
|
||||||
stampExpireLegal: string;
|
stampExpireLegal: Timestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data after which coins of this denomination can't be deposited anymore.
|
* Data after which coins of this denomination can't be deposited anymore.
|
||||||
*/
|
*/
|
||||||
@Checkable.String()
|
@Checkable.Value(() => Timestamp)
|
||||||
stampExpireDeposit: string;
|
stampExpireDeposit: Timestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature by the exchange's master key over the denomination
|
* Signature by the exchange's master key over the denomination
|
||||||
|
@ -33,6 +33,7 @@ import * as amounts from "../amounts";
|
|||||||
import { Bank } from "./bank";
|
import { Bank } from "./bank";
|
||||||
|
|
||||||
import fs = require("fs");
|
import fs = require("fs");
|
||||||
|
import { NodeCryptoWorkerFactory } from "../crypto/nodeProcessWorker";
|
||||||
|
|
||||||
const enableTracing = false;
|
const enableTracing = false;
|
||||||
|
|
||||||
@ -188,14 +189,16 @@ export async function getDefaultNodeWallet(
|
|||||||
myUnsupportedUpgrade,
|
myUnsupportedUpgrade,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const worker = new SynchronousCryptoWorkerFactory();
|
||||||
|
//const worker = new NodeCryptoWorkerFactory();
|
||||||
|
|
||||||
return new Wallet(
|
return new Wallet(
|
||||||
myDb,
|
myDb,
|
||||||
myHttpLib,
|
myHttpLib,
|
||||||
myBadge,
|
myBadge,
|
||||||
myNotifier,
|
myNotifier,
|
||||||
new SynchronousCryptoWorkerFactory(),
|
worker,
|
||||||
);
|
);
|
||||||
//const myWallet = new Wallet(myDb, myHttpLib, myBadge, myNotifier, new NodeCryptoWorkerFactory());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function withdrawTestBalance(
|
export async function withdrawTestBalance(
|
||||||
|
@ -353,7 +353,6 @@ testCli
|
|||||||
default: "TESTKUDOS:4",
|
default: "TESTKUDOS:4",
|
||||||
})
|
})
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
console.log("parsed args", args);
|
|
||||||
applyVerbose(args.wallet.verbose);
|
applyVerbose(args.wallet.verbose);
|
||||||
let cmdObj = args.integrationtestCmd;
|
let cmdObj = args.integrationtestCmd;
|
||||||
|
|
||||||
|
@ -139,6 +139,17 @@ export function extractTalerStamp(stamp: string): Timestamp | undefined {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a timestamp from a Taler timestamp string.
|
||||||
|
*/
|
||||||
|
export function extractTalerStampOrThrow(stamp: string): Timestamp {
|
||||||
|
const r = extractTalerStamp(stamp);
|
||||||
|
if (!r) {
|
||||||
|
throw Error("invalid time stamp");
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a timestamp is in the right format.
|
* Check if a timestamp is in the right format.
|
||||||
*/
|
*/
|
||||||
|
@ -391,7 +391,7 @@ export class ContractTerms {
|
|||||||
/**
|
/**
|
||||||
* Deadline for the wire transfer.
|
* Deadline for the wire transfer.
|
||||||
*/
|
*/
|
||||||
@Checkable.String(timestampCheck)
|
@Checkable.String()
|
||||||
wire_transfer_deadline: string;
|
wire_transfer_deadline: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,10 +59,10 @@ function fakeCwd(current: string, value: string, feeDeposit: string): types.Coin
|
|||||||
feeWithdraw: a("EUR:0.0"),
|
feeWithdraw: a("EUR:0.0"),
|
||||||
isOffered: true,
|
isOffered: true,
|
||||||
masterSig: "(mock)",
|
masterSig: "(mock)",
|
||||||
stampExpireDeposit: "(mock)",
|
stampExpireDeposit: { t_ms: 0 },
|
||||||
stampExpireLegal: "(mock)",
|
stampExpireLegal: { t_ms: 0 },
|
||||||
stampExpireWithdraw: "(mock)",
|
stampExpireWithdraw: { t_ms: 0 },
|
||||||
stampStart: "(mock)",
|
stampStart: { t_ms: 0 },
|
||||||
status: dbTypes.DenominationStatus.VerifiedGood,
|
status: dbTypes.DenominationStatus.VerifiedGood,
|
||||||
value: a(value),
|
value: a(value),
|
||||||
},
|
},
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
getTalerStampSec,
|
getTalerStampSec,
|
||||||
strcmp,
|
strcmp,
|
||||||
extractTalerStamp,
|
extractTalerStamp,
|
||||||
|
extractTalerStampOrThrow,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import { HttpRequestLibrary } from "./http";
|
import { HttpRequestLibrary } from "./http";
|
||||||
import * as LibtoolVersion from "./libtoolVersion";
|
import * as LibtoolVersion from "./libtoolVersion";
|
||||||
@ -163,20 +164,10 @@ const builtinCurrencies: CurrencyRecord[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
function isWithdrawableDenom(d: DenominationRecord) {
|
function isWithdrawableDenom(d: DenominationRecord) {
|
||||||
const nowSec = new Date().getTime() / 1000;
|
const now = getTimestampNow();
|
||||||
const stampWithdrawSec = getTalerStampSec(d.stampExpireWithdraw);
|
const started = now.t_ms >= d.stampStart.t_ms;
|
||||||
if (stampWithdrawSec === null) {
|
const stillOkay = d.stampExpireWithdraw.t_ms + (60 * 1000) > now.t_ms;
|
||||||
return false;
|
return started && stillOkay;
|
||||||
}
|
|
||||||
const stampStartSec = getTalerStampSec(d.stampStart);
|
|
||||||
if (stampStartSec === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Withdraw if still possible to withdraw within a minute
|
|
||||||
if (stampWithdrawSec + 60 > nowSec && nowSec >= stampStartSec) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectPayCoinsResult {
|
interface SelectPayCoinsResult {
|
||||||
@ -1374,6 +1365,7 @@ export class Wallet {
|
|||||||
denom.feeWithdraw,
|
denom.feeWithdraw,
|
||||||
);
|
);
|
||||||
if (x.saturated) {
|
if (x.saturated) {
|
||||||
|
// FIXME!!!!
|
||||||
console.error("database inconsistent");
|
console.error("database inconsistent");
|
||||||
throw TransactionAbort;
|
throw TransactionAbort;
|
||||||
}
|
}
|
||||||
@ -1891,10 +1883,10 @@ export class Wallet {
|
|||||||
|
|
||||||
const { isTrusted, isAudited } = await this.getExchangeTrust(exchangeInfo);
|
const { isTrusted, isAudited } = await this.getExchangeTrust(exchangeInfo);
|
||||||
|
|
||||||
let earliestDepositExpiration = Infinity;
|
let earliestDepositExpiration = selectedDenoms[0].stampExpireDeposit;
|
||||||
for (const denom of selectedDenoms) {
|
for (let i = 1; i < selectedDenoms.length; i++) {
|
||||||
const expireDeposit = getTalerStampSec(denom.stampExpireDeposit)!;
|
const expireDeposit = selectedDenoms[i].stampExpireDeposit;
|
||||||
if (expireDeposit < earliestDepositExpiration) {
|
if (expireDeposit.t_ms < earliestDepositExpiration.t_ms) {
|
||||||
earliestDepositExpiration = expireDeposit;
|
earliestDepositExpiration = expireDeposit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2653,6 +2645,7 @@ export class Wallet {
|
|||||||
resp = await this.http.postJson(reqUrl.href(), req);
|
resp = await this.http.postJson(reqUrl.href(), req);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("got error during /refresh/reveal request");
|
console.error("got error during /refresh/reveal request");
|
||||||
|
console.error(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3137,10 +3130,10 @@ export class Wallet {
|
|||||||
feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
|
feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
|
||||||
isOffered: true,
|
isOffered: true,
|
||||||
masterSig: denomIn.master_sig,
|
masterSig: denomIn.master_sig,
|
||||||
stampExpireDeposit: denomIn.stamp_expire_deposit,
|
stampExpireDeposit: extractTalerStampOrThrow(denomIn.stamp_expire_deposit),
|
||||||
stampExpireLegal: denomIn.stamp_expire_legal,
|
stampExpireLegal: extractTalerStampOrThrow(denomIn.stamp_expire_legal),
|
||||||
stampExpireWithdraw: denomIn.stamp_expire_withdraw,
|
stampExpireWithdraw: extractTalerStampOrThrow(denomIn.stamp_expire_withdraw),
|
||||||
stampStart: denomIn.stamp_start,
|
stampStart: extractTalerStampOrThrow(denomIn.stamp_start),
|
||||||
status: DenominationStatus.Unverified,
|
status: DenominationStatus.Unverified,
|
||||||
value: Amounts.parseOrThrow(denomIn.value),
|
value: Amounts.parseOrThrow(denomIn.value),
|
||||||
};
|
};
|
||||||
|
@ -113,7 +113,7 @@ export interface ReserveCreationInfo {
|
|||||||
/**
|
/**
|
||||||
* The earliest deposit expiration of the selected coins.
|
* The earliest deposit expiration of the selected coins.
|
||||||
*/
|
*/
|
||||||
earliestDepositExpiration: number;
|
earliestDepositExpiration: Timestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of currently offered denominations.
|
* Number of currently offered denominations.
|
||||||
@ -591,11 +591,15 @@ export interface HistoryQuery {
|
|||||||
level: number;
|
level: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Timestamp {
|
@Checkable.Class()
|
||||||
|
export class Timestamp {
|
||||||
/**
|
/**
|
||||||
* Timestamp in milliseconds.
|
* Timestamp in milliseconds.
|
||||||
*/
|
*/
|
||||||
|
@Checkable.Number()
|
||||||
t_ms: number;
|
t_ms: number;
|
||||||
|
|
||||||
|
static checked: (obj: any) => Timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Duration {
|
export interface Duration {
|
||||||
|
@ -240,7 +240,7 @@ function FeeDetailsView(props: {
|
|||||||
{i18n.str`Rounding loss:`} {overhead}
|
{i18n.str`Rounding loss:`} {overhead}
|
||||||
</p>
|
</p>
|
||||||
<p>{i18n.str`Earliest expiration (for deposit): ${moment
|
<p>{i18n.str`Earliest expiration (for deposit): ${moment
|
||||||
.unix(rci.earliestDepositExpiration)
|
.unix(rci.earliestDepositExpiration.t_ms / 1000)
|
||||||
.fromNow()}`}</p>
|
.fromNow()}`}</p>
|
||||||
<h3>Coin Fees</h3>
|
<h3>Coin Fees</h3>
|
||||||
<div style={{ overflow: "auto" }}>
|
<div style={{ overflow: "auto" }}>
|
||||||
|
@ -27,18 +27,14 @@
|
|||||||
"src/android/index.ts",
|
"src/android/index.ts",
|
||||||
"src/checkable.ts",
|
"src/checkable.ts",
|
||||||
"src/crypto/browserWorkerEntry.ts",
|
"src/crypto/browserWorkerEntry.ts",
|
||||||
"src/crypto/cryptoApi-test.ts",
|
|
||||||
"src/crypto/cryptoApi.ts",
|
"src/crypto/cryptoApi.ts",
|
||||||
"src/crypto/cryptoImplementation.ts",
|
"src/crypto/cryptoImplementation.ts",
|
||||||
"src/crypto/cryptoWorker.ts",
|
"src/crypto/cryptoWorker.ts",
|
||||||
"src/crypto/emscInterface-test.ts",
|
|
||||||
"src/crypto/emscInterface.ts",
|
|
||||||
"src/crypto/kdf.ts",
|
|
||||||
"src/crypto/nacl-fast.ts",
|
|
||||||
"src/crypto/nodeEmscriptenLoader.ts",
|
|
||||||
"src/crypto/nodeProcessWorker.ts",
|
"src/crypto/nodeProcessWorker.ts",
|
||||||
"src/crypto/nodeWorkerEntry.ts",
|
"src/crypto/nodeWorkerEntry.ts",
|
||||||
"src/crypto/sha256.ts",
|
"src/crypto/primitives/kdf.ts",
|
||||||
|
"src/crypto/primitives/nacl-fast.ts",
|
||||||
|
"src/crypto/primitives/sha256.ts",
|
||||||
"src/crypto/synchronousWorker.ts",
|
"src/crypto/synchronousWorker.ts",
|
||||||
"src/crypto/talerCrypto-test.ts",
|
"src/crypto/talerCrypto-test.ts",
|
||||||
"src/crypto/talerCrypto.ts",
|
"src/crypto/talerCrypto.ts",
|
||||||
|
Loading…
Reference in New Issue
Block a user