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 { EmscEnvironment } from "./emscInterface";
|
||||
|
||||
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[]) {
|
||||
let emsc = await loader.getEmscriptenEnvironment();
|
||||
|
||||
const impl = new CryptoImplementation(emsc);
|
||||
const impl = new CryptoImplementation();
|
||||
|
||||
if (!(operation in impl)) {
|
||||
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,10 +14,9 @@
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Synchronous implementation of crypto-related functions for the wallet.
|
||||
*
|
||||
*
|
||||
* The functionality is parameterized over an Emscripten environment.
|
||||
*/
|
||||
|
||||
@ -38,19 +37,118 @@ import {
|
||||
} from "../dbTypes";
|
||||
|
||||
import { CoinPaySig, ContractTerms, PaybackRequest } from "../talerTypes";
|
||||
import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes";
|
||||
import { canonicalJson } from "../helpers";
|
||||
import { EmscEnvironment } from "./emscInterface";
|
||||
import * as native from "./emscInterface";
|
||||
import {
|
||||
BenchmarkResult,
|
||||
CoinWithDenom,
|
||||
PayCoinInfo,
|
||||
Timestamp,
|
||||
} from "../walletTypes";
|
||||
import { canonicalJson, getTalerStampSec } from "../helpers";
|
||||
import { AmountJson } from "../amounts";
|
||||
import * as Amounts from "../amounts";
|
||||
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 {
|
||||
static enableTracing: boolean = false;
|
||||
|
||||
constructor(private emsc: EmscEnvironment) {}
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||
@ -60,54 +158,39 @@ export class CryptoImplementation {
|
||||
denom: DenominationRecord,
|
||||
reserve: ReserveRecord,
|
||||
): PreCoinRecord {
|
||||
const reservePriv = new native.EddsaPrivateKey(this.emsc);
|
||||
reservePriv.loadCrock(reserve.reservePriv);
|
||||
const reservePub = new native.EddsaPublicKey(this.emsc);
|
||||
reservePub.loadCrock(reserve.reservePub);
|
||||
const denomPub = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
|
||||
const coinPriv = native.EddsaPrivateKey.create(this.emsc);
|
||||
const coinPub = coinPriv.getPublicKey();
|
||||
const blindingFactor = native.RsaBlindingKeySecret.create(this.emsc);
|
||||
const pubHash: native.HashCode = coinPub.hash();
|
||||
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
|
||||
const reservePub = decodeCrock(reserve.reservePub);
|
||||
const reservePriv = decodeCrock(reserve.reservePriv);
|
||||
const denomPub = decodeCrock(denom.denomPub);
|
||||
const coinKeyPair = createEddsaKeyPair();
|
||||
const blindingFactor = createBlindingKeySecret();
|
||||
const coinPubHash = hash(coinKeyPair.eddsaPub);
|
||||
const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
|
||||
const amountWithFee = Amounts.add(denom.value, denom.feeWithdraw).amount;
|
||||
const denomPubHash = hash(denomPub);
|
||||
const evHash = hash(ev);
|
||||
|
||||
if (!ev) {
|
||||
throw Error("couldn't blind (malicious exchange key?)");
|
||||
}
|
||||
const withdrawRequest = buildSigPS(SignaturePurpose.RESERVE_WITHDRAW)
|
||||
.put(reservePub)
|
||||
.put(amountToBuffer(amountWithFee))
|
||||
.put(amountToBuffer(denom.feeWithdraw))
|
||||
.put(denomPubHash)
|
||||
.put(evHash)
|
||||
.build();
|
||||
|
||||
if (!denom.feeWithdraw) {
|
||||
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 sig = eddsaSign(withdrawRequest, reservePriv);
|
||||
|
||||
const preCoin: PreCoinRecord = {
|
||||
blindingKey: blindingFactor.toCrock(),
|
||||
coinEv: ev.toCrock(),
|
||||
coinPriv: coinPriv.toCrock(),
|
||||
coinPub: coinPub.toCrock(),
|
||||
blindingKey: encodeCrock(blindingFactor),
|
||||
coinEv: encodeCrock(ev),
|
||||
coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
|
||||
coinPub: encodeCrock(coinKeyPair.eddsaPub),
|
||||
coinValue: denom.value,
|
||||
denomPub: denomPub.toCrock(),
|
||||
denomPubHash: denomPubHash.toCrock(),
|
||||
denomPub: encodeCrock(denomPub),
|
||||
denomPubHash: encodeCrock(denomPubHash),
|
||||
exchangeBaseUrl: reserve.exchangeBaseUrl,
|
||||
isFromTip: false,
|
||||
reservePub: reservePub.toCrock(),
|
||||
withdrawSig: sig.toCrock(),
|
||||
reservePub: encodeCrock(reservePub),
|
||||
withdrawSig: encodeCrock(sig),
|
||||
};
|
||||
return preCoin;
|
||||
}
|
||||
@ -116,32 +199,20 @@ export class CryptoImplementation {
|
||||
* Create a planchet used for tipping, including the private keys.
|
||||
*/
|
||||
createTipPlanchet(denom: DenominationRecord): TipPlanchet {
|
||||
const denomPub = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
|
||||
const coinPriv = native.EddsaPrivateKey.create(this.emsc);
|
||||
const coinPub = coinPriv.getPublicKey();
|
||||
const blindingFactor = native.RsaBlindingKeySecret.create(this.emsc);
|
||||
const pubHash: native.HashCode = coinPub.hash();
|
||||
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 denomPub = decodeCrock(denom.denomPub);
|
||||
const coinKeyPair = createEddsaKeyPair();
|
||||
const blindingFactor = createBlindingKeySecret();
|
||||
const coinPubHash = hash(coinKeyPair.eddsaPub);
|
||||
const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
|
||||
|
||||
const tipPlanchet: TipPlanchet = {
|
||||
blindingKey: blindingFactor.toCrock(),
|
||||
coinEv: ev.toCrock(),
|
||||
coinPriv: coinPriv.toCrock(),
|
||||
coinPub: coinPub.toCrock(),
|
||||
blindingKey: encodeCrock(blindingFactor),
|
||||
coinEv: encodeCrock(ev),
|
||||
coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
|
||||
coinPub: encodeCrock(coinKeyPair.eddsaPub),
|
||||
coinValue: denom.value,
|
||||
denomPub: denomPub.encode().toCrock(),
|
||||
denomPubHash: denomPub
|
||||
.encode()
|
||||
.hash()
|
||||
.toCrock(),
|
||||
denomPub: encodeCrock(denomPub),
|
||||
denomPubHash: encodeCrock(hash(denomPub)),
|
||||
};
|
||||
return tipPlanchet;
|
||||
}
|
||||
@ -150,22 +221,18 @@ export class CryptoImplementation {
|
||||
* Create and sign a message to request payback for a coin.
|
||||
*/
|
||||
createPaybackRequest(coin: CoinRecord): PaybackRequest {
|
||||
const p = new native.PaybackRequestPS(this.emsc, {
|
||||
coin_blind: native.RsaBlindingKeySecret.fromCrock(
|
||||
this.emsc,
|
||||
coin.blindingKey,
|
||||
),
|
||||
coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, coin.coinPub),
|
||||
h_denom_pub: native.RsaPublicKey.fromCrock(this.emsc, coin.denomPub)
|
||||
.encode()
|
||||
.hash(),
|
||||
});
|
||||
const coinPriv = native.EddsaPrivateKey.fromCrock(this.emsc, coin.coinPriv);
|
||||
const coinSig = native.eddsaSign(p.toPurpose(), coinPriv);
|
||||
const p = buildSigPS(SignaturePurpose.WALLET_COIN_PAYBACK)
|
||||
.put(decodeCrock(coin.coinPub))
|
||||
.put(decodeCrock(coin.denomPubHash))
|
||||
.put(decodeCrock(coin.blindingKey))
|
||||
.build();
|
||||
|
||||
const coinPriv = decodeCrock(coin.coinPriv);
|
||||
const coinSig = eddsaSign(p, coinPriv);
|
||||
const paybackRequest: PaybackRequest = {
|
||||
coin_blind_key_secret: coin.blindingKey,
|
||||
coin_pub: coin.coinPub,
|
||||
coin_sig: coinSig.toCrock(),
|
||||
coin_sig: encodeCrock(coinSig),
|
||||
denom_pub: coin.denomPub,
|
||||
denom_sig: coin.denomSig,
|
||||
};
|
||||
@ -180,114 +247,72 @@ export class CryptoImplementation {
|
||||
contractHash: string,
|
||||
merchantPub: string,
|
||||
): boolean {
|
||||
const p = new native.PaymentSignaturePS(this.emsc, {
|
||||
contract_hash: native.HashCode.fromCrock(this.emsc, contractHash),
|
||||
});
|
||||
const nativeSig = new native.EddsaSignature(this.emsc);
|
||||
nativeSig.loadCrock(sig);
|
||||
const nativePub = native.EddsaPublicKey.fromCrock(this.emsc, merchantPub);
|
||||
return native.eddsaVerify(
|
||||
native.SignaturePurpose.MERCHANT_PAYMENT_OK,
|
||||
p.toPurpose(),
|
||||
nativeSig,
|
||||
nativePub,
|
||||
);
|
||||
const p = buildSigPS(SignaturePurpose.MERCHANT_PAYMENT_OK)
|
||||
.put(decodeCrock(contractHash))
|
||||
.build();
|
||||
const sigBytes = decodeCrock(sig);
|
||||
const pubBytes = decodeCrock(merchantPub);
|
||||
return eddsaVerify(p, sigBytes, pubBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a wire fee is correctly signed.
|
||||
*/
|
||||
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
||||
const p = new native.MasterWireFeePS(this.emsc, {
|
||||
closing_fee: new native.Amount(this.emsc, wf.closingFee).toNbo(),
|
||||
end_date: native.AbsoluteTimeNbo.fromStampSeconds(this.emsc, (wf.endStamp.t_ms / 1000)),
|
||||
h_wire_method: native.ByteArray.fromStringWithNull(
|
||||
this.emsc,
|
||||
type,
|
||||
).hash(),
|
||||
start_date: native.AbsoluteTimeNbo.fromStampSeconds(
|
||||
this.emsc,
|
||||
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,
|
||||
);
|
||||
const p = buildSigPS(SignaturePurpose.MASTER_WIRE_FEES)
|
||||
.put(hash(stringToBytes(type + "\0")))
|
||||
.put(timestampToBuffer(wf.startStamp))
|
||||
.put(timestampToBuffer(wf.endStamp))
|
||||
.put(amountToBuffer(wf.wireFee))
|
||||
.build();
|
||||
const sig = decodeCrock(wf.sig);
|
||||
const pub = decodeCrock(masterPub);
|
||||
return eddsaVerify(p, sig, pub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the signature of a denomination is valid.
|
||||
*/
|
||||
isValidDenom(denom: DenominationRecord, masterPub: string): boolean {
|
||||
const p = new native.DenominationKeyValidityPS(this.emsc, {
|
||||
denom_hash: native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub)
|
||||
.encode()
|
||||
.hash(),
|
||||
expire_legal: native.AbsoluteTimeNbo.fromTalerString(
|
||||
this.emsc,
|
||||
denom.stampExpireLegal,
|
||||
),
|
||||
expire_spend: native.AbsoluteTimeNbo.fromTalerString(
|
||||
this.emsc,
|
||||
denom.stampExpireDeposit,
|
||||
),
|
||||
expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(
|
||||
this.emsc,
|
||||
denom.stampExpireWithdraw,
|
||||
),
|
||||
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,
|
||||
);
|
||||
const p = buildSigPS(SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
|
||||
.put(decodeCrock(masterPub))
|
||||
.put(timestampToBuffer(denom.stampStart))
|
||||
.put(timestampToBuffer(denom.stampExpireWithdraw))
|
||||
.put(timestampToBuffer(denom.stampExpireDeposit))
|
||||
.put(timestampToBuffer(denom.stampExpireLegal))
|
||||
.put(amountToBuffer(denom.value))
|
||||
.put(amountToBuffer(denom.feeWithdraw))
|
||||
.put(amountToBuffer(denom.feeDeposit))
|
||||
.put(amountToBuffer(denom.feeRefresh))
|
||||
.put(amountToBuffer(denom.feeRefund))
|
||||
.put(decodeCrock(denom.denomPubHash))
|
||||
.build();
|
||||
const sig = decodeCrock(denom.masterSig);
|
||||
const pub = decodeCrock(masterPub);
|
||||
return eddsaVerify(p, sig, pub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new EdDSA key pair.
|
||||
*/
|
||||
createEddsaKeypair(): { priv: string; pub: string } {
|
||||
const priv = native.EddsaPrivateKey.create(this.emsc);
|
||||
const pub = priv.getPublicKey();
|
||||
return { priv: priv.toCrock(), pub: pub.toCrock() };
|
||||
const pair = createEddsaKeyPair();
|
||||
return {
|
||||
priv: encodeCrock(pair.eddsaPriv),
|
||||
pub: encodeCrock(pair.eddsaPub),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblind a blindly signed value.
|
||||
*/
|
||||
rsaUnblind(sig: string, bk: string, pk: string): string {
|
||||
const denomSig = native.rsaUnblind(
|
||||
native.RsaSignature.fromCrock(this.emsc, sig),
|
||||
native.RsaBlindingKeySecret.fromCrock(this.emsc, bk),
|
||||
native.RsaPublicKey.fromCrock(this.emsc, pk),
|
||||
const denomSig = rsaUnblind(
|
||||
decodeCrock(sig),
|
||||
decodeCrock(pk),
|
||||
decodeCrock(bk),
|
||||
);
|
||||
return denomSig.encode().toCrock();
|
||||
return encodeCrock(denomSig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,79 +340,54 @@ export class CryptoImplementation {
|
||||
.amount;
|
||||
const total = Amounts.add(fees, totalAmount).amount;
|
||||
|
||||
const amountSpent = native.Amount.getZero(
|
||||
this.emsc,
|
||||
cds[0].coin.currentAmount.currency,
|
||||
);
|
||||
const amountRemaining = new native.Amount(this.emsc, total);
|
||||
let amountSpent = Amounts.getZero(cds[0].coin.currentAmount.currency);
|
||||
let amountRemaining = total;
|
||||
|
||||
for (const cd of cds) {
|
||||
let coinSpend: native.Amount;
|
||||
const originalCoin = { ...cd.coin };
|
||||
|
||||
if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
amountRemaining.cmp(
|
||||
new native.Amount(this.emsc, cd.coin.currentAmount),
|
||||
) < 0
|
||||
) {
|
||||
coinSpend = new native.Amount(this.emsc, amountRemaining.toJson());
|
||||
let coinSpend: AmountJson;
|
||||
if (Amounts.cmp(amountRemaining, cd.coin.currentAmount) < 0) {
|
||||
coinSpend = amountRemaining;
|
||||
} else {
|
||||
coinSpend = new native.Amount(this.emsc, cd.coin.currentAmount);
|
||||
coinSpend = cd.coin.currentAmount;
|
||||
}
|
||||
|
||||
amountSpent.add(coinSpend);
|
||||
amountRemaining.sub(coinSpend);
|
||||
amountSpent = Amounts.add(amountSpent, coinSpend).amount;
|
||||
|
||||
const feeDeposit: native.Amount = new native.Amount(
|
||||
this.emsc,
|
||||
cd.denom.feeDeposit,
|
||||
);
|
||||
const feeDeposit = cd.denom.feeDeposit;
|
||||
|
||||
// Give the merchant at least the deposit fee, otherwise it'll reject
|
||||
// the coin.
|
||||
if (coinSpend.cmp(feeDeposit) < 0) {
|
||||
|
||||
if (Amounts.cmp(coinSpend, feeDeposit) < 0) {
|
||||
coinSpend = feeDeposit;
|
||||
}
|
||||
|
||||
const newAmount = new native.Amount(this.emsc, cd.coin.currentAmount);
|
||||
newAmount.sub(coinSpend);
|
||||
cd.coin.currentAmount = newAmount.toJson();
|
||||
const newAmount = Amounts.sub(cd.coin.currentAmount, coinSpend).amount;
|
||||
cd.coin.currentAmount = newAmount;
|
||||
cd.coin.status = CoinStatus.Dirty;
|
||||
|
||||
const d = new native.DepositRequestPS(this.emsc, {
|
||||
amount_with_fee: coinSpend.toNbo(),
|
||||
coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, cd.coin.coinPub),
|
||||
deposit_fee: new native.Amount(this.emsc, cd.denom.feeDeposit).toNbo(),
|
||||
h_contract: native.HashCode.fromCrock(this.emsc, contractTermsHash),
|
||||
h_wire: native.HashCode.fromCrock(this.emsc, contractTerms.H_wire),
|
||||
merchant: native.EddsaPublicKey.fromCrock(
|
||||
this.emsc,
|
||||
contractTerms.merchant_pub,
|
||||
),
|
||||
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(
|
||||
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 d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT)
|
||||
.put(decodeCrock(contractTermsHash))
|
||||
.put(decodeCrock(contractTerms.H_wire))
|
||||
.put(talerTimestampStringToBuffer(contractTerms.timestamp))
|
||||
.put(talerTimestampStringToBuffer(contractTerms.refund_deadline))
|
||||
.put(amountToBuffer(coinSpend))
|
||||
.put(amountToBuffer(cd.denom.feeDeposit))
|
||||
.put(decodeCrock(contractTerms.merchant_pub))
|
||||
.put(decodeCrock(cd.coin.coinPub))
|
||||
.build();
|
||||
const coinSig = eddsaSign(d, decodeCrock(cd.coin.coinPriv));
|
||||
|
||||
const s: CoinPaySig = {
|
||||
coin_pub: cd.coin.coinPub,
|
||||
coin_sig: coinSig,
|
||||
contribution: Amounts.toString(coinSpend.toJson()),
|
||||
coin_sig: encodeCrock(coinSig),
|
||||
contribution: Amounts.toString(coinSpend),
|
||||
denom_pub: cd.coin.denomPub,
|
||||
exchange_url: cd.denom.exchangeBaseUrl,
|
||||
ub_sig: cd.coin.denomSig,
|
||||
@ -419,7 +419,7 @@ export class CryptoImplementation {
|
||||
// melt fee
|
||||
valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
|
||||
|
||||
const sessionHc = new native.HashContext(this.emsc);
|
||||
const sessionHc = createHashContext();
|
||||
|
||||
const transferPubs: string[] = [];
|
||||
const transferPrivs: string[] = [];
|
||||
@ -427,79 +427,57 @@ export class CryptoImplementation {
|
||||
const preCoinsForGammas: RefreshPreCoinRecord[][] = [];
|
||||
|
||||
for (let i = 0; i < kappa; i++) {
|
||||
const t = native.EcdhePrivateKey.create(this.emsc);
|
||||
const pub = t.getPublicKey();
|
||||
sessionHc.read(pub);
|
||||
transferPrivs.push(t.toCrock());
|
||||
transferPubs.push(pub.toCrock());
|
||||
const transferKeyPair = createEcdheKeyPair();
|
||||
sessionHc.update(transferKeyPair.ecdhePub);
|
||||
transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv));
|
||||
transferPubs.push(encodeCrock(transferKeyPair.ecdhePub));
|
||||
}
|
||||
|
||||
for (const denom of newCoinDenoms) {
|
||||
const r = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
|
||||
sessionHc.read(r.encode());
|
||||
const r = decodeCrock(denom.denomPub);
|
||||
sessionHc.update(r);
|
||||
}
|
||||
|
||||
sessionHc.read(
|
||||
native.EddsaPublicKey.fromCrock(this.emsc, meltCoin.coinPub),
|
||||
);
|
||||
sessionHc.read(new native.Amount(this.emsc, valueWithFee).toNbo());
|
||||
sessionHc.update(decodeCrock(meltCoin.coinPub));
|
||||
sessionHc.update(amountToBuffer(valueWithFee));
|
||||
|
||||
for (let i = 0; i < kappa; i++) {
|
||||
const preCoins: RefreshPreCoinRecord[] = [];
|
||||
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||
const transferPriv = native.EcdhePrivateKey.fromCrock(
|
||||
this.emsc,
|
||||
transferPrivs[i],
|
||||
);
|
||||
const oldCoinPub = native.EddsaPublicKey.fromCrock(
|
||||
this.emsc,
|
||||
meltCoin.coinPub,
|
||||
);
|
||||
const transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub);
|
||||
const transferPriv = decodeCrock(transferPrivs[i]);
|
||||
const oldCoinPub = decodeCrock(meltCoin.coinPub);
|
||||
const transferSecret = keyExchangeEcdheEddsa(transferPriv, oldCoinPub);
|
||||
|
||||
const fresh = native.setupFreshCoin(transferSecret, j);
|
||||
const fresh = setupRefreshPlanchet(transferSecret, j);
|
||||
|
||||
const coinPriv = fresh.priv;
|
||||
const coinPub = coinPriv.getPublicKey();
|
||||
const blindingFactor = fresh.blindingKey;
|
||||
const pubHash: native.HashCode = coinPub.hash();
|
||||
const denomPub = native.RsaPublicKey.fromCrock(
|
||||
this.emsc,
|
||||
newCoinDenoms[j].denomPub,
|
||||
);
|
||||
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
|
||||
if (!ev) {
|
||||
throw Error("couldn't blind (malicious exchange key?)");
|
||||
}
|
||||
const coinPriv = fresh.coinPriv;
|
||||
const coinPub = fresh.coinPub;
|
||||
const blindingFactor = fresh.bks;
|
||||
const pubHash = hash(coinPub);
|
||||
const denomPub = decodeCrock(newCoinDenoms[j].denomPub);
|
||||
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
||||
const preCoin: RefreshPreCoinRecord = {
|
||||
blindingKey: blindingFactor.toCrock(),
|
||||
coinEv: ev.toCrock(),
|
||||
privateKey: coinPriv.toCrock(),
|
||||
publicKey: coinPub.toCrock(),
|
||||
blindingKey: encodeCrock(blindingFactor),
|
||||
coinEv: encodeCrock(ev),
|
||||
privateKey: encodeCrock(coinPriv),
|
||||
publicKey: encodeCrock(coinPub),
|
||||
};
|
||||
preCoins.push(preCoin);
|
||||
sessionHc.read(ev);
|
||||
sessionHc.update(ev);
|
||||
}
|
||||
preCoinsForGammas.push(preCoins);
|
||||
}
|
||||
|
||||
const sessionHash = new native.HashCode(this.emsc);
|
||||
sessionHash.alloc();
|
||||
sessionHc.finish(sessionHash);
|
||||
const sessionHash = sessionHc.finish();
|
||||
|
||||
const confirmData = new native.RefreshMeltCoinAffirmationPS(this.emsc, {
|
||||
amount_with_fee: new native.Amount(this.emsc, valueWithFee).toNbo(),
|
||||
coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, meltCoin.coinPub),
|
||||
melt_fee: new native.Amount(this.emsc, meltFee).toNbo(),
|
||||
session_hash: sessionHash,
|
||||
});
|
||||
const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT)
|
||||
.put(sessionHash)
|
||||
.put(amountToBuffer(valueWithFee))
|
||||
.put(amountToBuffer(meltFee))
|
||||
.put(decodeCrock(meltCoin.coinPub))
|
||||
.build();
|
||||
|
||||
const confirmSig: string = native
|
||||
.eddsaSign(
|
||||
confirmData.toPurpose(),
|
||||
native.EddsaPrivateKey.fromCrock(this.emsc, meltCoin.coinPriv),
|
||||
)
|
||||
.toCrock();
|
||||
const confirmSig = eddsaSign(confirmData, decodeCrock(meltCoin.coinPriv));
|
||||
|
||||
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
|
||||
for (const denom of newCoinDenoms) {
|
||||
@ -510,10 +488,10 @@ export class CryptoImplementation {
|
||||
|
||||
const refreshSession: RefreshSessionRecord = {
|
||||
refreshSessionId,
|
||||
confirmSig,
|
||||
confirmSig: encodeCrock(confirmSig),
|
||||
exchangeBaseUrl,
|
||||
finished: false,
|
||||
hash: sessionHash.toCrock(),
|
||||
hash: encodeCrock(sessionHash),
|
||||
meltCoinPub: meltCoin.coinPub,
|
||||
newDenomHashes: newCoinDenoms.map(d => d.denomPubHash),
|
||||
newDenoms: newCoinDenoms.map(d => d.denomPub),
|
||||
@ -532,18 +510,16 @@ export class CryptoImplementation {
|
||||
* Hash a string including the zero terminator.
|
||||
*/
|
||||
hashString(str: string): string {
|
||||
const b = native.ByteArray.fromStringWithNull(this.emsc, str);
|
||||
return b.hash().toCrock();
|
||||
const ts = new TextEncoder();
|
||||
const b = ts.encode(str + "\0");
|
||||
return encodeCrock(hash(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a denomination public key.
|
||||
*/
|
||||
hashDenomPub(denomPub: string): string {
|
||||
return native.RsaPublicKey.fromCrock(this.emsc, denomPub)
|
||||
.encode()
|
||||
.hash()
|
||||
.toCrock();
|
||||
return encodeCrock(hash(decodeCrock(denomPub)));
|
||||
}
|
||||
|
||||
signCoinLink(
|
||||
@ -553,20 +529,16 @@ export class CryptoImplementation {
|
||||
transferPub: string,
|
||||
coinEv: string,
|
||||
): string {
|
||||
const coinEvHash = native.ByteArray.fromCrock(this.emsc, coinEv).hash();
|
||||
|
||||
const coinLink = new native.CoinLinkSignaturePS(this.emsc, {
|
||||
coin_envelope_hash: coinEvHash,
|
||||
h_denom_pub: native.HashCode.fromCrock(this.emsc, newDenomHash),
|
||||
old_coin_pub: native.EddsaPublicKey.fromCrock(this.emsc, oldCoinPub),
|
||||
transfer_pub: native.EcdhePublicKey.fromCrock(this.emsc, transferPub),
|
||||
});
|
||||
|
||||
const coinPriv = native.EddsaPrivateKey.fromCrock(this.emsc, oldCoinPriv);
|
||||
|
||||
const sig = native.eddsaSign(coinLink.toPurpose(), coinPriv);
|
||||
|
||||
return sig.toCrock();
|
||||
const coinEvHash = hash(decodeCrock(coinEv));
|
||||
const coinLink = buildSigPS(SignaturePurpose.WALLET_COIN_LINK)
|
||||
.put(decodeCrock(newDenomHash))
|
||||
.put(decodeCrock(oldCoinPub))
|
||||
.put(decodeCrock(transferPub))
|
||||
.put(coinEvHash)
|
||||
.build();
|
||||
const coinPriv = decodeCrock(oldCoinPriv);
|
||||
const sig = eddsaSign(coinLink, coinPriv);
|
||||
return encodeCrock(sig);
|
||||
}
|
||||
|
||||
benchmark(repetitions: number): BenchmarkResult {
|
||||
@ -578,165 +550,40 @@ export class CryptoImplementation {
|
||||
}
|
||||
|
||||
let time_hash_big = 0;
|
||||
const ba = new native.ByteArray(this.emsc, 4096);
|
||||
for (let i = 0; i < repetitions; i++) {
|
||||
ba.randomize(native.RandomQuality.WEAK);
|
||||
const ba = randomBytes(4096);
|
||||
const start = timer.performanceNow();
|
||||
ba.hash();
|
||||
hash(ba);
|
||||
time_hash_big += timer.performanceNow() - start;
|
||||
}
|
||||
|
||||
let time_eddsa_create = 0;
|
||||
for (let i = 0; i < repetitions; i++) {
|
||||
const start = timer.performanceNow();
|
||||
const priv: native.EddsaPrivateKey = native.EddsaPrivateKey.create(
|
||||
this.emsc,
|
||||
);
|
||||
const pair = createEddsaKeyPair();
|
||||
time_eddsa_create += timer.performanceNow() - start;
|
||||
priv.destroy();
|
||||
}
|
||||
|
||||
let time_eddsa_sign = 0;
|
||||
const eddsaPriv: native.EddsaPrivateKey = native.EddsaPrivateKey.create(
|
||||
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 p = randomBytes(4096);
|
||||
|
||||
const ps = new native.PaymentSignaturePS(this.emsc, {
|
||||
contract_hash: h,
|
||||
});
|
||||
|
||||
const p = ps.toPurpose();
|
||||
const pair = createEddsaKeyPair();
|
||||
|
||||
for (let i = 0; i < repetitions; i++) {
|
||||
const start = timer.performanceNow();
|
||||
native.eddsaSign(p, eddsaPriv);
|
||||
eddsaSign(p, pair.eddsaPriv);
|
||||
time_eddsa_sign += timer.performanceNow() - start;
|
||||
}
|
||||
|
||||
const eddsaSig = native.eddsaSign(p, 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();
|
||||
}
|
||||
const sig = eddsaSign(p, pair.eddsaPriv);
|
||||
|
||||
let time_eddsa_verify = 0;
|
||||
for (let i = 0; i < repetitions; i++) {
|
||||
const start = timer.performanceNow();
|
||||
native.eddsaVerify(
|
||||
native.SignaturePurpose.MERCHANT_PAYMENT_OK,
|
||||
p,
|
||||
eddsaSig,
|
||||
eddsaPub,
|
||||
);
|
||||
eddsaVerify(p, sig, pair.eddsaPub);
|
||||
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 {
|
||||
repetitions,
|
||||
time: {
|
||||
@ -745,13 +592,6 @@ export class CryptoImplementation {
|
||||
eddsa_create: time_eddsa_create,
|
||||
eddsa_sign: time_eddsa_sign,
|
||||
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) => {
|
||||
console.log("nodeProcessWorker got child message", msg);
|
||||
this.dispatchMessage(msg);
|
||||
});
|
||||
}
|
||||
@ -114,7 +113,6 @@ export class Worker {
|
||||
* Forcibly terminate the worker thread.
|
||||
*/
|
||||
terminate () {
|
||||
console.log("terminating node.js worker");
|
||||
this.child.kill("SIGINT");
|
||||
}
|
||||
}
|
||||
|
@ -14,20 +14,13 @@
|
||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
|
||||
// tslint:disable:no-var-requires
|
||||
|
||||
import fs = require("fs");
|
||||
import vm = require("vm");
|
||||
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
|
||||
import { CryptoImplementation } from "./cryptoImplementation";
|
||||
|
||||
const loader = new NodeEmscriptenLoader();
|
||||
|
||||
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)) {
|
||||
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");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error during operation", e);
|
||||
console.error("error during operation", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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 args = msg.data.args;
|
||||
@ -76,5 +65,5 @@ process.on("message", (msgStr: 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 hl = new Int32Array(8);
|
||||
const x = new Uint8Array(256);
|
||||
let i;
|
||||
let b = n;
|
||||
|
||||
hh[0] = 0x6a09e667;
|
||||
@ -2366,7 +2365,7 @@ function crypto_hash(out: Uint8Array, m: Uint8Array, n: number) {
|
||||
crypto_hashblocks_hl(hh, hl, m, n);
|
||||
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;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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[]) {
|
||||
var a = gf(),
|
||||
b = gf(),
|
@ -16,9 +16,6 @@
|
||||
|
||||
import { CryptoImplementation } from "./cryptoImplementation";
|
||||
|
||||
import { NodeEmscriptenLoader } from "./nodeEmscriptenLoader";
|
||||
|
||||
import fs = require("fs");
|
||||
import { CryptoWorkerFactory } from "./cryptoApi";
|
||||
import { CryptoWorker } from "./cryptoWorker";
|
||||
|
||||
@ -56,8 +53,6 @@ export class SynchronousCryptoWorker {
|
||||
*/
|
||||
onerror: undefined | ((m: any) => void);
|
||||
|
||||
private emscriptenLoader = new NodeEmscriptenLoader();
|
||||
|
||||
constructor() {
|
||||
this.onerror = undefined;
|
||||
this.onmessage = undefined;
|
||||
@ -84,9 +79,7 @@ export class SynchronousCryptoWorker {
|
||||
}
|
||||
|
||||
private async handleRequest(operation: string, id: number, args: string[]) {
|
||||
let emsc = await this.emscriptenLoader.getEmscriptenEnvironment();
|
||||
|
||||
const impl = new CryptoImplementation(emsc);
|
||||
const impl = new CryptoImplementation();
|
||||
|
||||
if (!(operation in impl)) {
|
||||
console.error(`crypto operation '${operation}' not found`);
|
||||
|
@ -29,8 +29,8 @@ import {
|
||||
rsaUnblind,
|
||||
rsaVerify,
|
||||
} from "./talerCrypto";
|
||||
import { hmacSha512, sha512, kdf } from "./kdf";
|
||||
import nacl = require("./nacl-fast");
|
||||
import { sha512, kdf } from "./primitives/kdf";
|
||||
import nacl = require("./primitives/nacl-fast");
|
||||
|
||||
function hexToBytes(hex: string) {
|
||||
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));
|
||||
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.
|
||||
*/
|
||||
|
||||
import nacl = require("./nacl-fast");
|
||||
import nacl = require("./primitives/nacl-fast");
|
||||
import bigint from "big-integer";
|
||||
import { kdf } from "./kdf";
|
||||
import { kdf } from "./primitives/kdf";
|
||||
|
||||
export function getRandomBytes(n: number): Uint8Array {
|
||||
return nacl.randomBytes(n);
|
||||
@ -123,7 +123,7 @@ export function decodeCrock(encoded: string): Uint8Array {
|
||||
}
|
||||
|
||||
export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array {
|
||||
const pair = nacl.sign_keyPair_fromSeed(eddsaPriv);
|
||||
const pair = nacl.sign_keyPair_fromSeed(eddsaPriv);
|
||||
return pair.publicKey;
|
||||
}
|
||||
|
||||
@ -131,7 +131,10 @@ export function ecdheGetPublic(ecdhePriv: Uint8Array): Uint8Array {
|
||||
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 a = new Uint8Array(32);
|
||||
for (let i = 0; i < 32; i++) {
|
||||
@ -141,7 +144,10 @@ export function keyExchangeEddsaEcdhe(eddsaPriv: Uint8Array, ecdhePub: Uint8Arra
|
||||
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 x = nacl.scalarMult(ecdhePriv, curve25519Pub);
|
||||
return nacl.hash(x);
|
||||
@ -172,8 +178,8 @@ function kdfMod(
|
||||
while (true) {
|
||||
const ctx = new Uint8Array(info.byteLength + 2);
|
||||
ctx.set(info, 0);
|
||||
ctx[ctx.length - 2] = (counter >>> 8) & 0xFF;
|
||||
ctx[ctx.length - 1] = counter & 0xFF;
|
||||
ctx[ctx.length - 2] = (counter >>> 8) & 0xff;
|
||||
ctx[ctx.length - 1] = counter & 0xff;
|
||||
const buf = kdf(buflen, ikm, salt, ctx);
|
||||
const arr = Array.from(buf);
|
||||
arr[0] = arr[0] & mask;
|
||||
@ -185,7 +191,7 @@ function kdfMod(
|
||||
}
|
||||
}
|
||||
|
||||
function stringToBuf(s: string) {
|
||||
export function stringToBytes(s: string) {
|
||||
const te = new TextEncoder();
|
||||
return te.encode(s);
|
||||
}
|
||||
@ -194,9 +200,12 @@ function loadBigInt(arr: Uint8Array) {
|
||||
return bigint.fromArray(Array.from(arr), 256, false);
|
||||
}
|
||||
|
||||
function rsaBlindingKeyDerive(rsaPub: RsaPub, bks: Uint8Array): bigint.BigInteger {
|
||||
const salt = stringToBuf("Blinding KDF extrator HMAC key");
|
||||
const info = stringToBuf("Blinding KDF");
|
||||
function rsaBlindingKeyDerive(
|
||||
rsaPub: RsaPub,
|
||||
bks: Uint8Array,
|
||||
): bigint.BigInteger {
|
||||
const salt = stringToBytes("Blinding KDF extrator HMAC key");
|
||||
const info = stringToBytes("Blinding KDF");
|
||||
return kdfMod(rsaPub.N, bks, salt, info);
|
||||
}
|
||||
|
||||
@ -206,7 +215,7 @@ function rsaBlindingKeyDerive(rsaPub: RsaPub, bks: Uint8Array): bigint.BigIntege
|
||||
* Assuming n is an RSA modulous and r is generated using a call to
|
||||
* GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a
|
||||
* malicious RSA key designed to deanomize the user.
|
||||
*
|
||||
*
|
||||
* @param r KDF result
|
||||
* @param n RSA modulus of the public key
|
||||
*/
|
||||
@ -218,7 +227,7 @@ function rsaGcdValidate(r: bigint.BigInteger, n: 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 r = kdfMod(rsaPub.N, hm, salt, info);
|
||||
rsaGcdValidate(r, rsaPub.N);
|
||||
@ -228,12 +237,15 @@ function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger {
|
||||
function rsaPubDecode(rsaPub: Uint8Array): RsaPub {
|
||||
const modulusLength = (rsaPub[0] << 8) | rsaPub[1];
|
||||
const exponentLength = (rsaPub[2] << 8) | rsaPub[3];
|
||||
const modulus = rsaPub.slice(4, 4 + modulusLength)
|
||||
const exponent = rsaPub.slice(4 + modulusLength, 4 + modulusLength + exponentLength);
|
||||
const modulus = rsaPub.slice(4, 4 + modulusLength);
|
||||
const exponent = rsaPub.slice(
|
||||
4 + modulusLength,
|
||||
4 + modulusLength + exponentLength,
|
||||
);
|
||||
const res = {
|
||||
N: loadBigInt(modulus),
|
||||
e: loadBigInt(exponent),
|
||||
}
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -241,16 +253,20 @@ function rsaPubEncode(rsaPub: RsaPub): Uint8Array {
|
||||
const mb = rsaPub.N.toArray(256).value;
|
||||
const eb = rsaPub.e.toArray(256).value;
|
||||
const out = new Uint8Array(4 + mb.length + eb.length);
|
||||
out[0] = (mb.length >>> 8) & 0xFF;
|
||||
out[1] = mb.length & 0xFF;
|
||||
out[2] = (eb.length >>> 8) & 0xFF;
|
||||
out[3] = eb.length & 0xFF;
|
||||
out[0] = (mb.length >>> 8) & 0xff;
|
||||
out[1] = mb.length & 0xff;
|
||||
out[2] = (eb.length >>> 8) & 0xff;
|
||||
out[3] = eb.length & 0xff;
|
||||
out.set(mb, 4);
|
||||
out.set(eb, 4 + mb.length);
|
||||
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 data = rsaFullDomainHash(hm, rsaPub);
|
||||
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);
|
||||
}
|
||||
|
||||
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 blinded_s = loadBigInt(sig);
|
||||
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);
|
||||
}
|
||||
|
||||
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 d = rsaFullDomainHash(hm, rsaPub);
|
||||
const sig = loadBigInt(rsaSig);
|
||||
const sig_e = sig.modPow(rsaPub.e, rsaPub.N);
|
||||
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.
|
||||
*/
|
||||
@Checkable.String()
|
||||
stampStart: string;
|
||||
@Checkable.Value(() => Timestamp)
|
||||
stampStart: Timestamp;
|
||||
|
||||
/**
|
||||
* Date after which the currency can't be withdrawn anymore.
|
||||
*/
|
||||
@Checkable.String()
|
||||
stampExpireWithdraw: string;
|
||||
@Checkable.Value(() => Timestamp)
|
||||
stampExpireWithdraw: Timestamp;
|
||||
|
||||
/**
|
||||
* Date after the denomination officially doesn't exist anymore.
|
||||
*/
|
||||
@Checkable.String()
|
||||
stampExpireLegal: string;
|
||||
@Checkable.Value(() => Timestamp)
|
||||
stampExpireLegal: Timestamp;
|
||||
|
||||
/**
|
||||
* Data after which coins of this denomination can't be deposited anymore.
|
||||
*/
|
||||
@Checkable.String()
|
||||
stampExpireDeposit: string;
|
||||
@Checkable.Value(() => Timestamp)
|
||||
stampExpireDeposit: Timestamp;
|
||||
|
||||
/**
|
||||
* Signature by the exchange's master key over the denomination
|
||||
|
@ -33,6 +33,7 @@ import * as amounts from "../amounts";
|
||||
import { Bank } from "./bank";
|
||||
|
||||
import fs = require("fs");
|
||||
import { NodeCryptoWorkerFactory } from "../crypto/nodeProcessWorker";
|
||||
|
||||
const enableTracing = false;
|
||||
|
||||
@ -188,14 +189,16 @@ export async function getDefaultNodeWallet(
|
||||
myUnsupportedUpgrade,
|
||||
);
|
||||
|
||||
const worker = new SynchronousCryptoWorkerFactory();
|
||||
//const worker = new NodeCryptoWorkerFactory();
|
||||
|
||||
return new Wallet(
|
||||
myDb,
|
||||
myHttpLib,
|
||||
myBadge,
|
||||
myNotifier,
|
||||
new SynchronousCryptoWorkerFactory(),
|
||||
worker,
|
||||
);
|
||||
//const myWallet = new Wallet(myDb, myHttpLib, myBadge, myNotifier, new NodeCryptoWorkerFactory());
|
||||
}
|
||||
|
||||
export async function withdrawTestBalance(
|
||||
|
@ -353,7 +353,6 @@ testCli
|
||||
default: "TESTKUDOS:4",
|
||||
})
|
||||
.action(async args => {
|
||||
console.log("parsed args", args);
|
||||
applyVerbose(args.wallet.verbose);
|
||||
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.
|
||||
*/
|
||||
|
@ -388,10 +388,10 @@ export class ContractTerms {
|
||||
@Checkable.String(timestampCheck)
|
||||
refund_deadline: string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Deadline for the wire transfer.
|
||||
*/
|
||||
@Checkable.String(timestampCheck)
|
||||
@Checkable.String()
|
||||
wire_transfer_deadline: string;
|
||||
|
||||
/**
|
||||
|
@ -59,10 +59,10 @@ function fakeCwd(current: string, value: string, feeDeposit: string): types.Coin
|
||||
feeWithdraw: a("EUR:0.0"),
|
||||
isOffered: true,
|
||||
masterSig: "(mock)",
|
||||
stampExpireDeposit: "(mock)",
|
||||
stampExpireLegal: "(mock)",
|
||||
stampExpireWithdraw: "(mock)",
|
||||
stampStart: "(mock)",
|
||||
stampExpireDeposit: { t_ms: 0 },
|
||||
stampExpireLegal: { t_ms: 0 },
|
||||
stampExpireWithdraw: { t_ms: 0 },
|
||||
stampStart: { t_ms: 0 },
|
||||
status: dbTypes.DenominationStatus.VerifiedGood,
|
||||
value: a(value),
|
||||
},
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
getTalerStampSec,
|
||||
strcmp,
|
||||
extractTalerStamp,
|
||||
extractTalerStampOrThrow,
|
||||
} from "./helpers";
|
||||
import { HttpRequestLibrary } from "./http";
|
||||
import * as LibtoolVersion from "./libtoolVersion";
|
||||
@ -163,20 +164,10 @@ const builtinCurrencies: CurrencyRecord[] = [
|
||||
];
|
||||
|
||||
function isWithdrawableDenom(d: DenominationRecord) {
|
||||
const nowSec = new Date().getTime() / 1000;
|
||||
const stampWithdrawSec = getTalerStampSec(d.stampExpireWithdraw);
|
||||
if (stampWithdrawSec === null) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
const now = getTimestampNow();
|
||||
const started = now.t_ms >= d.stampStart.t_ms;
|
||||
const stillOkay = d.stampExpireWithdraw.t_ms + (60 * 1000) > now.t_ms;
|
||||
return started && stillOkay;
|
||||
}
|
||||
|
||||
interface SelectPayCoinsResult {
|
||||
@ -1374,6 +1365,7 @@ export class Wallet {
|
||||
denom.feeWithdraw,
|
||||
);
|
||||
if (x.saturated) {
|
||||
// FIXME!!!!
|
||||
console.error("database inconsistent");
|
||||
throw TransactionAbort;
|
||||
}
|
||||
@ -1891,10 +1883,10 @@ export class Wallet {
|
||||
|
||||
const { isTrusted, isAudited } = await this.getExchangeTrust(exchangeInfo);
|
||||
|
||||
let earliestDepositExpiration = Infinity;
|
||||
for (const denom of selectedDenoms) {
|
||||
const expireDeposit = getTalerStampSec(denom.stampExpireDeposit)!;
|
||||
if (expireDeposit < earliestDepositExpiration) {
|
||||
let earliestDepositExpiration = selectedDenoms[0].stampExpireDeposit;
|
||||
for (let i = 1; i < selectedDenoms.length; i++) {
|
||||
const expireDeposit = selectedDenoms[i].stampExpireDeposit;
|
||||
if (expireDeposit.t_ms < earliestDepositExpiration.t_ms) {
|
||||
earliestDepositExpiration = expireDeposit;
|
||||
}
|
||||
}
|
||||
@ -2653,6 +2645,7 @@ export class Wallet {
|
||||
resp = await this.http.postJson(reqUrl.href(), req);
|
||||
} catch (e) {
|
||||
console.error("got error during /refresh/reveal request");
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3137,10 +3130,10 @@ export class Wallet {
|
||||
feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
|
||||
isOffered: true,
|
||||
masterSig: denomIn.master_sig,
|
||||
stampExpireDeposit: denomIn.stamp_expire_deposit,
|
||||
stampExpireLegal: denomIn.stamp_expire_legal,
|
||||
stampExpireWithdraw: denomIn.stamp_expire_withdraw,
|
||||
stampStart: denomIn.stamp_start,
|
||||
stampExpireDeposit: extractTalerStampOrThrow(denomIn.stamp_expire_deposit),
|
||||
stampExpireLegal: extractTalerStampOrThrow(denomIn.stamp_expire_legal),
|
||||
stampExpireWithdraw: extractTalerStampOrThrow(denomIn.stamp_expire_withdraw),
|
||||
stampStart: extractTalerStampOrThrow(denomIn.stamp_start),
|
||||
status: DenominationStatus.Unverified,
|
||||
value: Amounts.parseOrThrow(denomIn.value),
|
||||
};
|
||||
|
@ -113,7 +113,7 @@ export interface ReserveCreationInfo {
|
||||
/**
|
||||
* The earliest deposit expiration of the selected coins.
|
||||
*/
|
||||
earliestDepositExpiration: number;
|
||||
earliestDepositExpiration: Timestamp;
|
||||
|
||||
/**
|
||||
* Number of currently offered denominations.
|
||||
@ -591,11 +591,15 @@ export interface HistoryQuery {
|
||||
level: number;
|
||||
}
|
||||
|
||||
export interface Timestamp {
|
||||
@Checkable.Class()
|
||||
export class Timestamp {
|
||||
/**
|
||||
* Timestamp in milliseconds.
|
||||
*/
|
||||
@Checkable.Number()
|
||||
t_ms: number;
|
||||
|
||||
static checked: (obj: any) => Timestamp;
|
||||
}
|
||||
|
||||
export interface Duration {
|
||||
|
@ -240,7 +240,7 @@ function FeeDetailsView(props: {
|
||||
{i18n.str`Rounding loss:`} {overhead}
|
||||
</p>
|
||||
<p>{i18n.str`Earliest expiration (for deposit): ${moment
|
||||
.unix(rci.earliestDepositExpiration)
|
||||
.unix(rci.earliestDepositExpiration.t_ms / 1000)
|
||||
.fromNow()}`}</p>
|
||||
<h3>Coin Fees</h3>
|
||||
<div style={{ overflow: "auto" }}>
|
||||
|
@ -27,18 +27,14 @@
|
||||
"src/android/index.ts",
|
||||
"src/checkable.ts",
|
||||
"src/crypto/browserWorkerEntry.ts",
|
||||
"src/crypto/cryptoApi-test.ts",
|
||||
"src/crypto/cryptoApi.ts",
|
||||
"src/crypto/cryptoImplementation.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/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/talerCrypto-test.ts",
|
||||
"src/crypto/talerCrypto.ts",
|
||||
|
Loading…
Reference in New Issue
Block a user