implement JS-only Taler, remove emscripten

This commit is contained in:
Florian Dold 2019-11-28 00:46:34 +01:00
parent c3ca556aff
commit 706c07fa1d
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
27 changed files with 574 additions and 2664 deletions

View File

@ -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.

View File

@ -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`);

View File

@ -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();
});

View File

@ -14,10 +14,9 @@
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.
* *
* The functionality is parameterized over an Emscripten environment. * The functionality is parameterized over an Emscripten environment.
*/ */
@ -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,
}, },
}; };
} }

View File

@ -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

View File

@ -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'",
);
}
});
}
}

View File

@ -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");
} }
} }

View File

@ -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);
}); });

View File

@ -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(),

View File

@ -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`);

View File

@ -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));
});

View File

@ -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);
@ -123,7 +123,7 @@ export function decodeCrock(encoded: string): Uint8Array {
} }
export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array { export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array {
const pair = nacl.sign_keyPair_fromSeed(eddsaPriv); const pair = nacl.sign_keyPair_fromSeed(eddsaPriv);
return pair.publicKey; return pair.publicKey;
} }
@ -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);
} }
@ -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 * 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 * GNUNET_CRYPTO_kdf_mod_mpi, if gcd(r,n) != 1 then n must be a
* malicious RSA key designed to deanomize the user. * malicious RSA key designed to deanomize the user.
* *
* @param r KDF result * @param r KDF result
* @param n RSA modulus of the public key * @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 { 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),
};
}

View File

@ -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

View File

@ -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(

View File

@ -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;

View File

@ -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.
*/ */

View File

@ -388,10 +388,10 @@ export class ContractTerms {
@Checkable.String(timestampCheck) @Checkable.String(timestampCheck)
refund_deadline: string; refund_deadline: string;
/** /**
* Deadline for the wire transfer. * Deadline for the wire transfer.
*/ */
@Checkable.String(timestampCheck) @Checkable.String()
wire_transfer_deadline: string; wire_transfer_deadline: string;
/** /**

View File

@ -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),
}, },

View File

@ -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),
}; };

View File

@ -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 {

View File

@ -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" }}>

View File

@ -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",