diff options
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | src/crypto/cryptoApi.ts | 40 | ||||
-rw-r--r-- | src/crypto/cryptoImplementation.ts | 754 | ||||
-rw-r--r-- | src/crypto/cryptoWorker.ts | 741 | ||||
-rw-r--r-- | src/crypto/emscInterface-test.ts | 83 | ||||
-rw-r--r-- | src/crypto/emscInterface.ts | 402 | ||||
-rw-r--r-- | src/crypto/nodeProcessWorker.ts (renamed from src/crypto/nodeWorker.ts) | 0 | ||||
-rw-r--r-- | src/crypto/synchronousWorker.ts | 162 | ||||
-rw-r--r-- | src/headless/taler-wallet-cli.ts | 5 | ||||
-rw-r--r-- | src/wallet.ts | 22 | ||||
-rw-r--r-- | tsconfig.json | 5 | ||||
-rw-r--r-- | yarn.lock | 8 |
12 files changed, 1230 insertions, 996 deletions
diff --git a/package.json b/package.json index afa6f4e73..d72316e7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "taler-wallet", - "version": "0.0.1", + "version": "0.0.2", "description": "", "main": "dist/node/index.js", "repository": { @@ -48,7 +48,7 @@ "dependencies": { "axios": "^0.19.0", "commander": "^2.20.0", - "idb-bridge": "^0.0.1", + "idb-bridge": "0.0.2", "source-map-support": "^0.5.12", "urijs": "^1.18.10" } diff --git a/src/crypto/cryptoApi.ts b/src/crypto/cryptoApi.ts index accebd651..c68b10700 100644 --- a/src/crypto/cryptoApi.ts +++ b/src/crypto/cryptoApi.ts @@ -39,7 +39,6 @@ import { ContractTerms, PaybackRequest } from "../talerTypes"; import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes"; import * as timer from "../timer"; -import { string } from "prop-types"; /** * State of a crypto worker. @@ -103,33 +102,31 @@ export interface CryptoWorkerFactory { * Query the number of workers that should be * run at the same time. */ - getConcurrency(): number + getConcurrency(): number; } - export class NodeCryptoWorkerFactory implements CryptoWorkerFactory { startWorker(): CryptoWorker { if (typeof require === "undefined") { throw Error("cannot make worker, require(...) not defined"); } - const workerCtor = require("./nodeWorker").Worker; + const workerCtor = require("./nodeProcessWorker").Worker; const workerPath = __dirname + "/cryptoWorker.js"; return new workerCtor(workerPath); } - + getConcurrency(): number { return 2; } } - export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory { startWorker(): CryptoWorker { const workerCtor = Worker; const workerPath = "/dist/cryptoWorker-bundle.js"; return new workerCtor(workerPath) as CryptoWorker; } - + getConcurrency(): number { let concurrency = 2; try { @@ -144,6 +141,23 @@ export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory { } } +/** + * The synchronous crypto worker produced by this factory doesn't run in the + * background, but actually blocks the caller until the operation is done. + */ +export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory { + startWorker(): CryptoWorker { + if (typeof require === "undefined") { + throw Error("cannot make worker, require(...) not defined"); + } + const workerCtor = require("./synchronousWorker").SynchronousCryptoWorker; + return new workerCtor(); + } + + getConcurrency(): number { + return 1; + } +} /** * Crypto API that interfaces manages a background crypto thread @@ -166,8 +180,7 @@ export class CryptoApi { */ private stopped: boolean = false; - public enableTracing = false; - + public enableTracing = true; /** * Terminate all worker threads. @@ -295,10 +308,11 @@ export class CryptoApi { return; } - this.enableTracing && console.log( - `rpc ${currentWorkItem.operation} took ${timer.performanceNow() - - currentWorkItem.startTime}ms`, - ); + this.enableTracing && + console.log( + `rpc ${currentWorkItem.operation} took ${timer.performanceNow() - + currentWorkItem.startTime}ms`, + ); currentWorkItem.resolve(msg.data.result); } diff --git a/src/crypto/cryptoImplementation.ts b/src/crypto/cryptoImplementation.ts new file mode 100644 index 000000000..d50d40027 --- /dev/null +++ b/src/crypto/cryptoImplementation.ts @@ -0,0 +1,754 @@ +/* + This file is part of GNU Taler + (C) 2019 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/> + */ + + +/** + * Synchronous implementation of crypto-related functions for the wallet. + * + * The functionality is parameterized over an Emscripten environment. + */ + +/** + * Imports. + */ + +import { + CoinRecord, + CoinStatus, + DenominationRecord, + PreCoinRecord, + RefreshPreCoinRecord, + RefreshSessionRecord, + ReserveRecord, + TipPlanchet, + WireFee, +} 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 { AmountJson } from "../amounts"; +import * as Amounts from "../amounts"; +import * as timer from "../timer"; + +export class CryptoImplementation { + static enableTracing: boolean = false; + + constructor(private emsc: EmscEnvironment) {} + + /** + * Create a pre-coin of the given denomination to be withdrawn from then given + * reserve. + */ + createPreCoin( + denom: DenominationRecord, + reserve: ReserveRecord, + ): PreCoinRecord { + const reservePriv = new native.EddsaPrivateKey(this.emsc); + reservePriv.loadCrock(reserve.reserve_priv); + const reservePub = new native.EddsaPublicKey(this.emsc); + reservePub.loadCrock(reserve.reserve_pub); + 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 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 = { + blindingKey: blindingFactor.toCrock(), + coinEv: ev.toCrock(), + coinPriv: coinPriv.toCrock(), + coinPub: coinPub.toCrock(), + coinValue: denom.value, + denomPub: denomPub.toCrock(), + denomPubHash: denomPubHash.toCrock(), + exchangeBaseUrl: reserve.exchange_base_url, + isFromTip: false, + reservePub: reservePub.toCrock(), + withdrawSig: sig.toCrock(), + }; + return preCoin; + } + + /** + * 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 tipPlanchet: TipPlanchet = { + blindingKey: blindingFactor.toCrock(), + coinEv: ev.toCrock(), + coinPriv: coinPriv.toCrock(), + coinPub: coinPub.toCrock(), + coinValue: denom.value, + denomPub: denomPub.encode().toCrock(), + denomPubHash: denomPub + .encode() + .hash() + .toCrock(), + }; + return tipPlanchet; + } + + /** + * 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 paybackRequest: PaybackRequest = { + coin_blind_key_secret: coin.blindingKey, + coin_pub: coin.coinPub, + coin_sig: coinSig.toCrock(), + denom_pub: coin.denomPub, + denom_sig: coin.denomSig, + }; + return paybackRequest; + } + + /** + * Check if a payment signature is valid. + */ + isValidPaymentSignature( + sig: string, + 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, + ); + } + + /** + * 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), + h_wire_method: native.ByteArray.fromStringWithNull( + this.emsc, + type, + ).hash(), + start_date: native.AbsoluteTimeNbo.fromStampSeconds( + this.emsc, + wf.startStamp, + ), + 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. + */ + 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, + ); + } + + /** + * 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() }; + } + + /** + * 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), + ); + return denomSig.encode().toCrock(); + } + + /** + * Generate updated coins (to store in the database) + * and deposit permissions for each given coin. + */ + signDeposit( + contractTerms: ContractTerms, + cds: CoinWithDenom[], + totalAmount: AmountJson, + ): PayCoinInfo { + const ret: PayCoinInfo = { + originalCoins: [], + sigs: [], + updatedCoins: [], + }; + + const contractTermsHash = this.hashString(canonicalJson(contractTerms)); + + const feeList: AmountJson[] = cds.map(x => x.denom.feeDeposit); + let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList) + .amount; + // okay if saturates + fees = Amounts.sub(fees, Amounts.parseOrThrow(contractTerms.max_fee)) + .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); + 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()); + } else { + coinSpend = new native.Amount(this.emsc, cd.coin.currentAmount); + } + + amountSpent.add(coinSpend); + amountRemaining.sub(coinSpend); + + const feeDeposit: native.Amount = new native.Amount( + this.emsc, + cd.denom.feeDeposit, + ); + + // Give the merchant at least the deposit fee, otherwise it'll reject + // the coin. + if (coinSpend.cmp(feeDeposit) < 0) { + coinSpend = feeDeposit; + } + + const newAmount = new native.Amount(this.emsc, cd.coin.currentAmount); + newAmount.sub(coinSpend); + cd.coin.currentAmount = newAmount.toJson(); + cd.coin.status = CoinStatus.PurchasePending; + + 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 s: CoinPaySig = { + coin_pub: cd.coin.coinPub, + coin_sig: coinSig, + contribution: Amounts.toString(coinSpend.toJson()), + denom_pub: cd.coin.denomPub, + exchange_url: cd.denom.exchangeBaseUrl, + ub_sig: cd.coin.denomSig, + }; + ret.sigs.push(s); + ret.updatedCoins.push(cd.coin); + ret.originalCoins.push(originalCoin); + } + return ret; + } + + /** + * Create a new refresh session. + */ + createRefreshSession( + exchangeBaseUrl: string, + kappa: number, + meltCoin: CoinRecord, + newCoinDenoms: DenominationRecord[], + meltFee: AmountJson, + ): RefreshSessionRecord { + let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency); + + for (const ncd of newCoinDenoms) { + valueWithFee = Amounts.add(valueWithFee, ncd.value, ncd.feeWithdraw) + .amount; + } + + // melt fee + valueWithFee = Amounts.add(valueWithFee, meltFee).amount; + + const sessionHc = new native.HashContext(this.emsc); + + const transferPubs: string[] = []; + const transferPrivs: string[] = []; + + 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()); + } + + for (const denom of newCoinDenoms) { + const r = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub); + sessionHc.read(r.encode()); + } + + sessionHc.read( + native.EddsaPublicKey.fromCrock(this.emsc, meltCoin.coinPub), + ); + sessionHc.read(new native.Amount(this.emsc, valueWithFee).toNbo()); + + 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 fresh = native.setupFreshCoin(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 preCoin: RefreshPreCoinRecord = { + blindingKey: blindingFactor.toCrock(), + coinEv: ev.toCrock(), + privateKey: coinPriv.toCrock(), + publicKey: coinPub.toCrock(), + }; + preCoins.push(preCoin); + sessionHc.read(ev); + } + preCoinsForGammas.push(preCoins); + } + + const sessionHash = new native.HashCode(this.emsc); + sessionHash.alloc(); + sessionHc.finish(sessionHash); + + 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 confirmSig: string = native + .eddsaSign( + confirmData.toPurpose(), + native.EddsaPrivateKey.fromCrock(this.emsc, meltCoin.coinPriv), + ) + .toCrock(); + + let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency); + for (const denom of newCoinDenoms) { + valueOutput = Amounts.add(valueOutput, denom.value).amount; + } + + const refreshSession: RefreshSessionRecord = { + confirmSig, + exchangeBaseUrl, + finished: false, + hash: sessionHash.toCrock(), + meltCoinPub: meltCoin.coinPub, + newDenomHashes: newCoinDenoms.map(d => d.denomPubHash), + newDenoms: newCoinDenoms.map(d => d.denomPub), + norevealIndex: undefined, + preCoinsForGammas, + transferPrivs, + transferPubs, + valueOutput, + valueWithFee, + }; + + return refreshSession; + } + + /** + * Hash a string including the zero terminator. + */ + hashString(str: string): string { + const b = native.ByteArray.fromStringWithNull(this.emsc, str); + return b.hash().toCrock(); + } + + /** + * Hash a denomination public key. + */ + hashDenomPub(denomPub: string): string { + return native.RsaPublicKey.fromCrock(this.emsc, denomPub) + .encode() + .hash() + .toCrock(); + } + + signCoinLink( + oldCoinPriv: string, + newDenomHash: string, + oldCoinPub: string, + 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(); + } + + benchmark(repetitions: number): BenchmarkResult { + let time_hash = 0; + for (let i = 0; i < repetitions; i++) { + const start = timer.performanceNow(); + this.hashString("hello world"); + time_hash += timer.performanceNow() - start; + } + + 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 start = timer.performanceNow(); + ba.hash(); + 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, + ); + 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 ps = new native.PaymentSignaturePS(this.emsc, { + contract_hash: h, + }); + + const p = ps.toPurpose(); + + for (let i = 0; i < repetitions; i++) { + const start = timer.performanceNow(); + native.eddsaSign(p, 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(); + } + + 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, + ); + 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: { + hash_small: time_hash, + hash_big: time_hash_big, + 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, + }, + }; + } +} diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts index 5acda9053..11e3d964c 100644 --- a/src/crypto/cryptoWorker.ts +++ b/src/crypto/cryptoWorker.ts @@ -21,742 +21,57 @@ /** * Imports. */ -import * as Amounts from "../amounts"; -import { AmountJson } from "../amounts"; - -import * as timer from "../timer"; - -import { - CoinRecord, - CoinStatus, - DenominationRecord, - PreCoinRecord, - RefreshPreCoinRecord, - RefreshSessionRecord, - ReserveRecord, - TipPlanchet, - WireFee, -} from "../dbTypes"; - -import { CoinPaySig, ContractTerms, PaybackRequest } from "../talerTypes"; - -import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes"; - -import { canonicalJson } from "../helpers"; import * as emscLoader from "./emscLoader"; -import { - Amount, - EddsaPublicKey, - HashCode, - HashContext, - RefreshMeltCoinAffirmationPS, -} from "./emscInterface"; -import * as native from "./emscInterface"; - -namespace RpcFunctions { - - export let enableTracing: boolean = false; - - /** - * Create a pre-coin of the given denomination to be withdrawn from then given - * reserve. - */ - export function createPreCoin( - denom: DenominationRecord, - reserve: ReserveRecord, - ): PreCoinRecord { - const reservePriv = new native.EddsaPrivateKey(); - reservePriv.loadCrock(reserve.reserve_priv); - const reservePub = new native.EddsaPublicKey(); - reservePub.loadCrock(reserve.reserve_pub); - const denomPub = native.RsaPublicKey.fromCrock(denom.denomPub); - const coinPriv = native.EddsaPrivateKey.create(); - const coinPub = coinPriv.getPublicKey(); - const blindingFactor = native.RsaBlindingKeySecret.create(); - 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 amountWithFee = new native.Amount(denom.value); - amountWithFee.add(new native.Amount(denom.feeWithdraw)); - const withdrawFee = new native.Amount(denom.feeWithdraw); - - const denomPubHash = denomPub.encode().hash(); - - // Signature - const withdrawRequest = new native.WithdrawRequestPS({ - 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 = { - blindingKey: blindingFactor.toCrock(), - coinEv: ev.toCrock(), - coinPriv: coinPriv.toCrock(), - coinPub: coinPub.toCrock(), - coinValue: denom.value, - denomPub: denomPub.toCrock(), - denomPubHash: denomPubHash.toCrock(), - exchangeBaseUrl: reserve.exchange_base_url, - isFromTip: false, - reservePub: reservePub.toCrock(), - withdrawSig: sig.toCrock(), - }; - return preCoin; - } - - /** - * Create a planchet used for tipping, including the private keys. - */ - export function createTipPlanchet(denom: DenominationRecord): TipPlanchet { - const denomPub = native.RsaPublicKey.fromCrock(denom.denomPub); - const coinPriv = native.EddsaPrivateKey.create(); - const coinPub = coinPriv.getPublicKey(); - const blindingFactor = native.RsaBlindingKeySecret.create(); - 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 tipPlanchet: TipPlanchet = { - blindingKey: blindingFactor.toCrock(), - coinEv: ev.toCrock(), - coinPriv: coinPriv.toCrock(), - coinPub: coinPub.toCrock(), - coinValue: denom.value, - denomPub: denomPub.encode().toCrock(), - denomPubHash: denomPub - .encode() - .hash() - .toCrock(), - }; - return tipPlanchet; - } - - /** - * Create and sign a message to request payback for a coin. - */ - export function createPaybackRequest(coin: CoinRecord): PaybackRequest { - const p = new native.PaybackRequestPS({ - coin_blind: native.RsaBlindingKeySecret.fromCrock(coin.blindingKey), - coin_pub: native.EddsaPublicKey.fromCrock(coin.coinPub), - h_denom_pub: native.RsaPublicKey.fromCrock(coin.denomPub) - .encode() - .hash(), - }); - const coinPriv = native.EddsaPrivateKey.fromCrock(coin.coinPriv); - const coinSig = native.eddsaSign(p.toPurpose(), coinPriv); - const paybackRequest: PaybackRequest = { - coin_blind_key_secret: coin.blindingKey, - coin_pub: coin.coinPub, - coin_sig: coinSig.toCrock(), - denom_pub: coin.denomPub, - denom_sig: coin.denomSig, - }; - return paybackRequest; - } - - /** - * Check if a payment signature is valid. - */ - export function isValidPaymentSignature( - sig: string, - contractHash: string, - merchantPub: string, - ): boolean { - const p = new native.PaymentSignaturePS({ - contract_hash: native.HashCode.fromCrock(contractHash), - }); - const nativeSig = new native.EddsaSignature(); - nativeSig.loadCrock(sig); - const nativePub = native.EddsaPublicKey.fromCrock(merchantPub); - return native.eddsaVerify( - native.SignaturePurpose.MERCHANT_PAYMENT_OK, - p.toPurpose(), - nativeSig, - nativePub, - ); - } - - /** - * Check if a wire fee is correctly signed. - */ - export function isValidWireFee( - type: string, - wf: WireFee, - masterPub: string, - ): boolean { - const p = new native.MasterWireFeePS({ - closing_fee: new native.Amount(wf.closingFee).toNbo(), - end_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.endStamp), - h_wire_method: native.ByteArray.fromStringWithNull(type).hash(), - start_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.startStamp), - wire_fee: new native.Amount(wf.wireFee).toNbo(), - }); - - const nativeSig = new native.EddsaSignature(); - nativeSig.loadCrock(wf.sig); - const nativePub = native.EddsaPublicKey.fromCrock(masterPub); - - return native.eddsaVerify( - native.SignaturePurpose.MASTER_WIRE_FEES, - p.toPurpose(), - nativeSig, - nativePub, - ); - } - - /** - * Check if the signature of a denomination is valid. - */ - export function isValidDenom( - denom: DenominationRecord, - masterPub: string, - ): boolean { - const p = new native.DenominationKeyValidityPS({ - denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub) - .encode() - .hash(), - expire_legal: native.AbsoluteTimeNbo.fromTalerString( - denom.stampExpireLegal, - ), - expire_spend: native.AbsoluteTimeNbo.fromTalerString( - denom.stampExpireDeposit, - ), - expire_withdraw: native.AbsoluteTimeNbo.fromTalerString( - denom.stampExpireWithdraw, - ), - fee_deposit: new native.Amount(denom.feeDeposit).toNbo(), - fee_refresh: new native.Amount(denom.feeRefresh).toNbo(), - fee_refund: new native.Amount(denom.feeRefund).toNbo(), - fee_withdraw: new native.Amount(denom.feeWithdraw).toNbo(), - master: native.EddsaPublicKey.fromCrock(masterPub), - start: native.AbsoluteTimeNbo.fromTalerString(denom.stampStart), - value: new native.Amount(denom.value).toNbo(), - }); - - const nativeSig = new native.EddsaSignature(); - nativeSig.loadCrock(denom.masterSig); - - const nativePub = native.EddsaPublicKey.fromCrock(masterPub); - - return native.eddsaVerify( - native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY, - p.toPurpose(), - nativeSig, - nativePub, - ); - } - - /** - * Create a new EdDSA key pair. - */ - export function createEddsaKeypair(): { priv: string; pub: string } { - const priv = native.EddsaPrivateKey.create(); - const pub = priv.getPublicKey(); - return { priv: priv.toCrock(), pub: pub.toCrock() }; - } - - /** - * Unblind a blindly signed value. - */ - export function rsaUnblind(sig: string, bk: string, pk: string): string { - const denomSig = native.rsaUnblind( - native.RsaSignature.fromCrock(sig), - native.RsaBlindingKeySecret.fromCrock(bk), - native.RsaPublicKey.fromCrock(pk), - ); - return denomSig.encode().toCrock(); - } - - /** - * Generate updated coins (to store in the database) - * and deposit permissions for each given coin. - */ - export function signDeposit( - contractTerms: ContractTerms, - cds: CoinWithDenom[], - totalAmount: AmountJson, - ): PayCoinInfo { - const ret: PayCoinInfo = { - originalCoins: [], - sigs: [], - updatedCoins: [], - }; - - const contractTermsHash = hashString(canonicalJson(contractTerms)); - - const feeList: AmountJson[] = cds.map(x => x.denom.feeDeposit); - let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList) - .amount; - // okay if saturates - fees = Amounts.sub(fees, Amounts.parseOrThrow(contractTerms.max_fee)) - .amount; - const total = Amounts.add(fees, totalAmount).amount; - - const amountSpent = native.Amount.getZero( - cds[0].coin.currentAmount.currency, - ); - const amountRemaining = new native.Amount(total); - for (const cd of cds) { - let coinSpend: Amount; - const originalCoin = { ...cd.coin }; - - if (amountRemaining.value === 0 && amountRemaining.fraction === 0) { - break; - } - - if (amountRemaining.cmp(new native.Amount(cd.coin.currentAmount)) < 0) { - coinSpend = new native.Amount(amountRemaining.toJson()); - } else { - coinSpend = new native.Amount(cd.coin.currentAmount); - } - - amountSpent.add(coinSpend); - amountRemaining.sub(coinSpend); - - const feeDeposit: Amount = new native.Amount(cd.denom.feeDeposit); - - // Give the merchant at least the deposit fee, otherwise it'll reject - // the coin. - if (coinSpend.cmp(feeDeposit) < 0) { - coinSpend = feeDeposit; - } - - const newAmount = new native.Amount(cd.coin.currentAmount); - newAmount.sub(coinSpend); - cd.coin.currentAmount = newAmount.toJson(); - cd.coin.status = CoinStatus.PurchasePending; - - const d = new native.DepositRequestPS({ - amount_with_fee: coinSpend.toNbo(), - coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub), - deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(), - h_contract: native.HashCode.fromCrock(contractTermsHash), - h_wire: native.HashCode.fromCrock(contractTerms.H_wire), - merchant: native.EddsaPublicKey.fromCrock(contractTerms.merchant_pub), - refund_deadline: native.AbsoluteTimeNbo.fromTalerString( - contractTerms.refund_deadline, - ), - timestamp: native.AbsoluteTimeNbo.fromTalerString( - contractTerms.timestamp, - ), - }); - - const coinSig = native - .eddsaSign( - d.toPurpose(), - native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv), - ) - .toCrock(); - - const s: CoinPaySig = { - coin_pub: cd.coin.coinPub, - coin_sig: coinSig, - contribution: Amounts.toString(coinSpend.toJson()), - denom_pub: cd.coin.denomPub, - exchange_url: cd.denom.exchangeBaseUrl, - ub_sig: cd.coin.denomSig, - }; - ret.sigs.push(s); - ret.updatedCoins.push(cd.coin); - ret.originalCoins.push(originalCoin); - } - return ret; - } - - /** - * Create a new refresh session. - */ - export function createRefreshSession( - exchangeBaseUrl: string, - kappa: number, - meltCoin: CoinRecord, - newCoinDenoms: DenominationRecord[], - meltFee: AmountJson, - ): RefreshSessionRecord { - let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency); - - for (const ncd of newCoinDenoms) { - valueWithFee = Amounts.add(valueWithFee, ncd.value, ncd.feeWithdraw) - .amount; - } - - // melt fee - valueWithFee = Amounts.add(valueWithFee, meltFee).amount; - - const sessionHc = new HashContext(); - - const transferPubs: string[] = []; - const transferPrivs: string[] = []; - - const preCoinsForGammas: RefreshPreCoinRecord[][] = []; - - for (let i = 0; i < kappa; i++) { - const t = native.EcdhePrivateKey.create(); - const pub = t.getPublicKey(); - sessionHc.read(pub); - transferPrivs.push(t.toCrock()); - transferPubs.push(pub.toCrock()); - } - - for (const denom of newCoinDenoms) { - const r = native.RsaPublicKey.fromCrock(denom.denomPub); - sessionHc.read(r.encode()); - } +import { CryptoImplementation } from "./cryptoImplementation"; +import { EmscEnvironment } from "./emscInterface"; - sessionHc.read(native.EddsaPublicKey.fromCrock(meltCoin.coinPub)); - sessionHc.read(new native.Amount(valueWithFee).toNbo()); - - for (let i = 0; i < kappa; i++) { - const preCoins: RefreshPreCoinRecord[] = []; - for (let j = 0; j < newCoinDenoms.length; j++) { - const transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]); - const oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub); - const transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub); - - const fresh = native.setupFreshCoin(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( - newCoinDenoms[j].denomPub, - ); - const ev = native.rsaBlind(pubHash, blindingFactor, denomPub); - if (!ev) { - throw Error("couldn't blind (malicious exchange key?)"); - } - const preCoin: RefreshPreCoinRecord = { - blindingKey: blindingFactor.toCrock(), - coinEv: ev.toCrock(), - privateKey: coinPriv.toCrock(), - publicKey: coinPub.toCrock(), - }; - preCoins.push(preCoin); - sessionHc.read(ev); - } - preCoinsForGammas.push(preCoins); - } - - const sessionHash = new HashCode(); - sessionHash.alloc(); - sessionHc.finish(sessionHash); - - const confirmData = new RefreshMeltCoinAffirmationPS({ - amount_with_fee: new Amount(valueWithFee).toNbo(), - coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub), - melt_fee: new Amount(meltFee).toNbo(), - session_hash: sessionHash, - }); - - const confirmSig: string = native - .eddsaSign( - confirmData.toPurpose(), - native.EddsaPrivateKey.fromCrock(meltCoin.coinPriv), - ) - .toCrock(); - - let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency); - for (const denom of newCoinDenoms) { - valueOutput = Amounts.add(valueOutput, denom.value).amount; - } - - const refreshSession: RefreshSessionRecord = { - confirmSig, - exchangeBaseUrl, - finished: false, - hash: sessionHash.toCrock(), - meltCoinPub: meltCoin.coinPub, - newDenomHashes: newCoinDenoms.map(d => d.denomPubHash), - newDenoms: newCoinDenoms.map(d => d.denomPub), - norevealIndex: undefined, - preCoinsForGammas, - transferPrivs, - transferPubs, - valueOutput, - valueWithFee, - }; - - return refreshSession; - } - - /** - * Hash a string including the zero terminator. - */ - export function hashString(str: string): string { - const b = native.ByteArray.fromStringWithNull(str); - return b.hash().toCrock(); - } - - /** - * Hash a denomination public key. - */ - export function hashDenomPub(denomPub: string): string { - return native.RsaPublicKey.fromCrock(denomPub) - .encode() - .hash() - .toCrock(); - } - - export function signCoinLink( - oldCoinPriv: string, - newDenomHash: string, - oldCoinPub: string, - transferPub: string, - coinEv: string, - ): string { - const coinEvHash = native.ByteArray.fromCrock(coinEv).hash(); - - const coinLink = new native.CoinLinkSignaturePS({ - coin_envelope_hash: coinEvHash, - h_denom_pub: native.HashCode.fromCrock(newDenomHash), - old_coin_pub: native.EddsaPublicKey.fromCrock(oldCoinPub), - transfer_pub: native.EcdhePublicKey.fromCrock(transferPub), - }); - - const coinPriv = native.EddsaPrivateKey.fromCrock(oldCoinPriv); - - const sig = native.eddsaSign(coinLink.toPurpose(), coinPriv); - - return sig.toCrock(); - } - - export function benchmark(repetitions: number): BenchmarkResult { - let time_hash = 0; - for (let i = 0; i < repetitions; i++) { - const start = timer.performanceNow(); - hashString("hello world"); - time_hash += timer.performanceNow() - start; - } - - let time_hash_big = 0; - const ba = new native.ByteArray(4096); - for (let i = 0; i < repetitions; i++) { - ba.randomize(native.RandomQuality.WEAK); - const start = timer.performanceNow(); - ba.hash(); - 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(); - time_eddsa_create += timer.performanceNow() - start; - priv.destroy(); - } - - let time_eddsa_sign = 0; - const eddsaPriv: native.EddsaPrivateKey = native.EddsaPrivateKey.create(); - const eddsaPub: native.EddsaPublicKey = eddsaPriv.getPublicKey(); - const h: native.HashCode = new native.HashCode(); - h.alloc(); - h.random(native.RandomQuality.WEAK); - - const ps = new native.PaymentSignaturePS({ - contract_hash: h, - }); - - const p = ps.toPurpose(); - - for (let i = 0; i < repetitions; i++) { - const start = timer.performanceNow(); - native.eddsaSign(p, 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(); - time_ecdsa_create += timer.performanceNow() - start; - priv.destroy(); - } - - 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, - ); - time_eddsa_verify += timer.performanceNow() - start; - } - - /* rsa 2048 */ - - let time_rsa_2048_blind = 0; - const rsaPriv2048: native.RsaPrivateKey = native.RsaPrivateKey.create(2048); - const rsaPub2048 = rsaPriv2048.getPublicKey(); - const blindingSecret2048 = native.RsaBlindingKeySecret.create(); - 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(4096); - const rsaPub4096 = rsaPriv4096.getPublicKey(); - const blindingSecret4096 = native.RsaBlindingKeySecret.create(); - 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; - } +const worker: Worker = (self as any) as Worker; - return { - repetitions, - time: { - hash_small: time_hash, - hash_big: time_hash_big, - 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, - }, - }; - } -} +let impl: CryptoImplementation | undefined; -const worker: Worker = (self as any) as Worker; worker.onmessage = (msg: MessageEvent) => { - if (!Array.isArray(msg.data.args)) { + const args = msg.data.args; + if (!Array.isArray(args)) { console.error("args must be array"); return; } - if (typeof msg.data.id !== "number") { + const id = msg.data.id; + if (typeof id !== "number") { console.error("RPC id must be number"); + return; } - if (typeof msg.data.operation !== "string") { + const operation = msg.data.operation; + if (typeof operation !== "string") { console.error("RPC operation must be string"); - } - const f = (RpcFunctions as any)[msg.data.operation]; - if (!f) { - console.error(`unknown operation: '${msg.data.operation}'`); return; } - if (RpcFunctions.enableTracing) { - console.log("onmessage with", msg.data.operation); + if (CryptoImplementation.enableTracing) { + console.log("onmessage with", operation); } emscLoader.getLib().then(p => { const lib = p.lib; - if (!native.isInitialized()) { - if (RpcFunctions.enableTracing) { - console.log("initializing emscripten for then first time with lib"); - } - native.initialize(lib); + const emsc = new EmscEnvironment(lib); + const impl = new CryptoImplementation(emsc); + + if (!(operation in impl)) { + console.error(`unknown operation: '${operation}'`); + return; } - if (RpcFunctions.enableTracing) { - console.log("about to execute", msg.data.operation); + + if (CryptoImplementation.enableTracing) { + console.log("about to execute", operation); } - const res = f(...msg.data.args); - if (RpcFunctions.enableTracing) { - console.log("finished executing", msg.data.operation); + + const result = (impl as any)[operation](...args); + + if (CryptoImplementation.enableTracing) { + console.log("finished executing", operation); } - worker.postMessage({ result: res, id: msg.data.id }); + worker.postMessage({ result, id }); }); }; diff --git a/src/crypto/emscInterface-test.ts b/src/crypto/emscInterface-test.ts index 305e50ff4..51f2d58b6 100644 --- a/src/crypto/emscInterface-test.ts +++ b/src/crypto/emscInterface-test.ts @@ -20,13 +20,12 @@ import test from "ava"; import * as emscLoader from "./emscLoader"; import * as native from "./emscInterface"; -test.before(async () => { + +test("string hashing", async (t) => { const { lib } = await emscLoader.getLib(); - native.initialize(lib); -}); + const emsc = new native.EmscEnvironment(lib); -test("string hashing", (t) => { - const x = native.ByteArray.fromStringWithNull("hello taler"); + const x = native.ByteArray.fromStringWithNull(emsc, "hello taler"); const h = "8RDMADB3YNF3QZBS3V467YZVJAMC2QAQX0TZGVZ6Q5PFRRAJFT70HHN0QF661QR9QWKYMMC7YEMPD679D2RADXCYK8Y669A2A5MKQFR"; const hc = x.hash().toCrock(); console.log(`# hc ${hc}`); @@ -35,28 +34,34 @@ test("string hashing", (t) => { }); -test("signing", (t) => { - const x = native.ByteArray.fromStringWithNull("hello taler"); - const priv = native.EddsaPrivateKey.create(); +test("signing", async (t) => { + const { lib } = await emscLoader.getLib(); + const emsc = new native.EmscEnvironment(lib); + + const x = native.ByteArray.fromStringWithNull(emsc, "hello taler"); + const priv = native.EddsaPrivateKey.create(emsc, ); const pub = priv.getPublicKey(); - const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x); + const purpose = new native.EccSignaturePurpose(emsc, native.SignaturePurpose.TEST, x); const sig = native.eddsaSign(purpose, priv); t.true(native.eddsaVerify(native.SignaturePurpose.TEST, purpose, sig, pub)); t.pass(); }); -test("signing-fixed-data", (t) => { - const x = native.ByteArray.fromStringWithNull("hello taler"); - const purpose = new native.EccSignaturePurpose(native.SignaturePurpose.TEST, x); +test("signing-fixed-data", async (t) => { + const { lib } = await emscLoader.getLib(); + const emsc = new native.EmscEnvironment(lib); + + 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(privStr); + 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(sigStr); + const sig = native.EddsaSignature.fromCrock(emsc, sigStr); t.true(sigStr === sig.toCrock()); const sig2 = native.eddsaSign(purpose, priv); t.true(sig.toCrock() === sig2.toCrock()); @@ -68,27 +73,33 @@ test("signing-fixed-data", (t) => { const denomPubStr1 = "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30G9R64VK6HHS6MW42DSN8MVKJGHK6WR3CGT18MWMCDSM75138E1K8S0MADSQ68W34DHH6MW4CHA270W4CG9J6GW48DHG8MVK4E9S7523GEA56H0K4E1Q891KCCSG752KGC1M88VMCDSQ6D23CHHG8H33AGHG6MSK8GT26CRKAC1M64V3JCJ56CVKCC228MWMCHA26MS30H1J8MVKEDHJ70TMADHK892KJC1H60TKJDHM710KGGT584T38H9K851KCDHG60W30HJ28CT4CC1G8CR3JGJ28H236DJ28H330H9S890M2D9S8S14AGA369344GA36S248CHS70RKEDSS6MWKGDJ26D136GT465348CSS8S232CHM6GS34C9N8CS3GD9H60W36H1R8MSK2GSQ8MSM6C9R70SKCHHN6MW3ACJ28N0K2CA58RS3GCA26MV42G9P891KAG9Q8N0KGD9M850KEHJ16S130CA27124AE1G852KJCHR6S1KGDSJ8RTKED1S8RR3CCHP68W4CH9Q6GT34GT18GS36EA46N24AGSP6933GCHM60VMAE1S8GV3EHHN74W3GC1J651KEH9N8MSK0CSG6S2KEEA460R32C1M8D144GSR6RWKEC218S0KEGJ4611KEEA36CSKJC2564TM4CSJ6H230E1N74TM8C1P61342CSG60WKCGHH64VK2G9S8CRKAHHK88W30HJ388R3CH1Q6X2K2DHK8GSM4D1Q74WM4HA461146H9S6D33JDJ26D234C9Q6923ECSS60RM6CT46CSKCH1M6S13EH9J8S33GCSN4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00"; -test("rsa-encode", (t) => { +test("rsa-encode", async (t) => { + const { lib } = await emscLoader.getLib(); + const emsc = new native.EmscEnvironment(lib); + const pubHashStr = "JM63YM5X7X547164QJ3MGJZ4WDD47GEQR5DW5SH35G4JFZXEJBHE5JBNZM5K8XN5C4BRW25BE6GSVAYBF790G2BZZ13VW91D41S4DS0"; - const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1); + const denomPub = native.RsaPublicKey.fromCrock(emsc, denomPubStr1); const pubHash = denomPub.encode().hash(); t.true(pubHashStr === pubHash.toCrock()); t.pass(); }); -test("withdraw-request", (t) => { +test("withdraw-request", async (t) => { + const { lib } = await emscLoader.getLib(); + const emsc = new native.EmscEnvironment(lib); + const reservePrivStr = "G9R8KRRCAFKPD0KW7PW48CC2T03VQ8K2AN9J6J6K2YW27J5MHN90"; - const reservePriv = native.EddsaPrivateKey.fromCrock(reservePrivStr); + const reservePriv = native.EddsaPrivateKey.fromCrock(emsc, reservePrivStr); const reservePub = reservePriv.getPublicKey(); - const amountWithFee = new native.Amount({currency: "KUDOS", value: 1, fraction: 10000}); - amountWithFee.add(new native.Amount({currency: "KUDOS", value: 0, fraction: 20000})); - const withdrawFee = new native.Amount({currency: "KUDOS", value: 0, fraction: 20000}); - const denomPub = native.RsaPublicKey.fromCrock(denomPubStr1); - const ev = native.ByteArray.fromStringWithNull("hello, world"); + 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({ + const withdrawRequest = new native.WithdrawRequestPS(emsc, { amount_with_fee: amountWithFee.toNbo(), h_coin_envelope: ev.hash(), h_denomination_pub: denomPub.encode().hash(), @@ -105,9 +116,13 @@ test("withdraw-request", (t) => { }); -test("currency-conversion", (t) => { - const a1 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000}); - const a2 = new native.Amount({currency: "KUDOS", value: 1, fraction: 50000000}); +test("currency-conversion", async (t) => { + + const { lib } = await emscLoader.getLib(); + const emsc = new native.EmscEnvironment(lib); + + 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"); @@ -117,8 +132,11 @@ test("currency-conversion", (t) => { }); -test("ecdsa", (t) => { - const priv = native.EcdsaPrivateKey.create(); +test("ecdsa", async (t) => { + const { lib } = await emscLoader.getLib(); + const emsc = new native.EmscEnvironment(lib); + + const priv = native.EcdsaPrivateKey.create(emsc); const pub1 = priv.getPublicKey(); t.truthy(priv); t.truthy(pub1); @@ -126,8 +144,11 @@ test("ecdsa", (t) => { }); -test("ecdhe", (t) => { - const priv = native.EcdhePrivateKey.create(); +test("ecdhe", async (t) => { + const { lib } = await emscLoader.getLib(); + const emsc = new native.EmscEnvironment(lib); + + const priv = native.EcdhePrivateKey.create(emsc); const pub = priv.getPublicKey(); t.truthy(priv); t.truthy(pub); diff --git a/src/crypto/emscInterface.ts b/src/crypto/emscInterface.ts index 70a85cda1..96e94e3fb 100644 --- a/src/crypto/emscInterface.ts +++ b/src/crypto/emscInterface.ts @@ -30,37 +30,6 @@ import { AmountJson } from "../amounts"; import { EmscFunGen, EmscLib } from "./emscLoader"; - -// Will be set only after initialization. -let maybeEmscEnv: EmscEnvironment | undefined = undefined; - -export function isInitialized() { - return !!maybeEmscEnv; -} - - -export function initialize(lib: EmscLib) { - if (!lib) { - throw Error("library must be object"); - } - if (!lib.ccall) { - throw Error("sanity check failed: EmscLib does not have 'ccall'"); - } - if (maybeEmscEnv) { - throw Error("emsc lib already initialized"); - } - maybeEmscEnv = new EmscEnvironment(lib); -} - - -function emsc() { - if (maybeEmscEnv) { - return maybeEmscEnv; - } - throw Error("cannot use taler emscripten before initialization"); -} - - /** * Size of a native pointer. Must match the size * use when compiling via emscripten. @@ -132,7 +101,7 @@ interface EmscAllocFunctions { rsa_unblind(a1: number, a2: number, a3: number): number; } -class EmscEnvironment { +export class EmscEnvironment { /** * Emscripten functions that don't do any memory allocations. @@ -254,8 +223,8 @@ interface ArenaObject { export class HashContext implements ArenaObject { private hashContextPtr: number | undefined; - constructor() { - this.hashContextPtr = emsc().allocFuncs.hash_context_start(); + constructor(private emsc: EmscEnvironment) { + this.hashContextPtr = emsc.allocFuncs.hash_context_start(); } /** @@ -265,7 +234,7 @@ export class HashContext implements ArenaObject { if (!this.hashContextPtr) { throw Error("assertion failed"); } - emsc().funcs.hash_context_read(this.hashContextPtr, obj.nativePtr, obj.size()); + this.emsc.funcs.hash_context_read(this.hashContextPtr, obj.nativePtr, obj.size()); } /** @@ -276,7 +245,7 @@ export class HashContext implements ArenaObject { throw Error("assertion failed"); } h.alloc(); - emsc().funcs.hash_context_finish(this.hashContextPtr, h.nativePtr); + this.emsc.funcs.hash_context_finish(this.hashContextPtr, h.nativePtr); } /** @@ -284,7 +253,7 @@ export class HashContext implements ArenaObject { */ destroy(): void { if (this.hashContextPtr) { - emsc().funcs.hash_context_abort(this.hashContextPtr); + this.emsc.funcs.hash_context_abort(this.hashContextPtr); } this.hashContextPtr = undefined; } @@ -304,12 +273,12 @@ abstract class MallocArenaObject implements ArenaObject { destroy(): void { if (this._nativePtr && !this.isWeak) { - emsc().funcs.free(this.nativePtr); + this.emsc.funcs.free(this.nativePtr); this._nativePtr = undefined; } } - constructor(arena?: Arena) { + constructor(public emsc: EmscEnvironment, arena?: Arena) { if (!arena) { if (arenaStack.length === 0) { throw Error("No arena available"); @@ -323,7 +292,7 @@ abstract class MallocArenaObject implements ArenaObject { if (this._nativePtr !== undefined) { throw Error("Double allocation"); } - this.nativePtr = emsc().allocFuncs.malloc(size); + this.nativePtr = this.emsc.allocFuncs.malloc(size); } set nativePtr(v: number) { @@ -414,21 +383,21 @@ arenaStack.push(new SyncArena()); * Representation of monetary value in a given currency. */ export class Amount extends MallocArenaObject { - constructor(args?: AmountJson, arena?: Arena) { - super(arena); + constructor(emsc: EmscEnvironment, args?: AmountJson, arena?: Arena) { + super(emsc, arena); if (args) { - this.nativePtr = emsc().allocFuncs.get_amount(args.value, + this.nativePtr = emsc.allocFuncs.get_amount(args.value, 0, args.fraction, args.currency); } else { - this.nativePtr = emsc().allocFuncs.get_amount(0, 0, 0, ""); + this.nativePtr = emsc.allocFuncs.get_amount(0, 0, 0, ""); } } - static getZero(currency: string, a?: Arena): Amount { - const am = new Amount(undefined, a); - const r = emsc().funcs.amount_get_zero(currency, am.nativePtr); + static getZero(emsc: EmscEnvironment, currency: string, a?: Arena): Amount { + const am = new Amount(emsc, undefined, a); + const r = emsc.funcs.amount_get_zero(currency, am.nativePtr); if (r !== GNUNET_OK) { throw Error("invalid currency"); } @@ -437,33 +406,33 @@ export class Amount extends MallocArenaObject { toNbo(a?: Arena): AmountNbo { - const x = new AmountNbo(a); + const x = new AmountNbo(this.emsc, a); x.alloc(); - emsc().funcs.amount_hton(x.nativePtr, this.nativePtr); + this.emsc.funcs.amount_hton(x.nativePtr, this.nativePtr); return x; } fromNbo(nbo: AmountNbo): void { - emsc().funcs.amount_ntoh(this.nativePtr, nbo.nativePtr); + this.emsc.funcs.amount_ntoh(this.nativePtr, nbo.nativePtr); } get value() { - return emsc().funcs.get_value(this.nativePtr); + return this.emsc.funcs.get_value(this.nativePtr); } get fraction() { - return emsc().funcs.get_fraction(this.nativePtr); + return this.emsc.funcs.get_fraction(this.nativePtr); } get currency(): string { - return emsc().funcs.get_currency(this.nativePtr); + return this.emsc.funcs.get_currency(this.nativePtr); } toJson(): AmountJson { return { - currency: emsc().funcs.get_currency(this.nativePtr), - fraction: emsc().funcs.get_fraction(this.nativePtr), - value: emsc().funcs.get_value(this.nativePtr), + currency: this.emsc.funcs.get_currency(this.nativePtr), + fraction: this.emsc.funcs.get_fraction(this.nativePtr), + value: this.emsc.funcs.get_value(this.nativePtr), }; } @@ -471,7 +440,7 @@ export class Amount extends MallocArenaObject { * Add an amount to this amount. */ add(a: Amount) { - const res = emsc().funcs.amount_add(this.nativePtr, a.nativePtr, this.nativePtr); + const res = this.emsc.funcs.amount_add(this.nativePtr, a.nativePtr, this.nativePtr); if (res < 1) { // Overflow return false; @@ -484,7 +453,7 @@ export class Amount extends MallocArenaObject { */ sub(a: Amount) { // this = this - a - const res = emsc().funcs.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr); + const res = this.emsc.funcs.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr); if (res === 0) { // Underflow return false; @@ -500,11 +469,11 @@ export class Amount extends MallocArenaObject { if (this.currency !== a.currency) { throw Error(`incomparable currencies (${this.currency} and ${a.currency})`); } - return emsc().funcs.amount_cmp(this.nativePtr, a.nativePtr); + return this.emsc.funcs.amount_cmp(this.nativePtr, a.nativePtr); } normalize() { - emsc().funcs.amount_normalize(this.nativePtr); + this.emsc.funcs.amount_normalize(this.nativePtr); } } @@ -541,18 +510,18 @@ function countUtf8Bytes(str: string): number { abstract class PackedArenaObject extends MallocArenaObject { abstract size(): number; - constructor(a?: Arena) { - super(a); + constructor(emsc: EmscEnvironment, a?: Arena) { + super(emsc, a); } randomize(qual: RandomQuality = RandomQuality.STRONG): void { - emsc().funcs.random_block(qual, this.nativePtr, this.size()); + this.emsc.funcs.random_block(qual, this.nativePtr, this.size()); } toCrock(): string { - const d = emsc().allocFuncs.data_to_string_alloc(this.nativePtr, this.size()); - const s = emsc().lib.Pointer_stringify(d); - emsc().funcs.free(d); + const d = this.emsc.allocFuncs.data_to_string_alloc(this.nativePtr, this.size()); + const s = this.emsc.lib.Pointer_stringify(d); + this.emsc.funcs.free(d); return s; } @@ -567,8 +536,8 @@ abstract class PackedArenaObject extends MallocArenaObject { this.alloc(); // We need to get the javascript string // to the emscripten heap first. - const buf = ByteArray.fromStringWithNull(s); - const res = emsc().funcs.string_to_data(buf.nativePtr, + const buf = ByteArray.fromStringWithNull(this.emsc, s); + const res = this.emsc.funcs.string_to_data(buf.nativePtr, s.length, this.nativePtr, this.size()); @@ -581,21 +550,21 @@ abstract class PackedArenaObject extends MallocArenaObject { alloc() { // FIXME: should the client be allowed to call alloc multiple times? if (!this._nativePtr) { - this.nativePtr = emsc().allocFuncs.malloc(this.size()); + this.nativePtr = this.emsc.allocFuncs.malloc(this.size()); } } hash(): HashCode { - const x = new HashCode(); + const x = new HashCode(this.emsc); x.alloc(); - emsc().funcs.hash(this.nativePtr, this.size(), x.nativePtr); + this.emsc.funcs.hash(this.nativePtr, this.size(), x.nativePtr); return x; } hexdump() { const bytes: string[] = []; for (let i = 0; i < this.size(); i++) { - let b = emsc().lib.getValue(this.nativePtr + i, "i8"); + let b = this.emsc.lib.getValue(this.nativePtr + i, "i8"); b = (b + 256) % 256; bytes.push("0".concat(b.toString(16)).slice(-2)); } @@ -618,7 +587,7 @@ export class AmountNbo extends PackedArenaObject { toJson(): any { const a = new SimpleArena(); - const am = new Amount(undefined, a); + const am = new Amount(this.emsc, undefined, a); am.fromNbo(this); const json = am.toJson(); a.destroy(); @@ -630,8 +599,8 @@ export class AmountNbo extends PackedArenaObject { /** * Create a packed arena object from the base32 crockford encoding. */ -function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T { - const x: T = new ctor(); +function fromCrock<T extends PackedArenaObject>(emsc: EmscEnvironment, s: string, ctor: Ctor<T>): T { + const x: T = new ctor(emsc); x.alloc(); x.loadCrock(s); return x; @@ -642,11 +611,11 @@ function fromCrock<T extends PackedArenaObject>(s: string, ctor: Ctor<T>): T { * Create a packed arena object from the base32 crockford encoding for objects * that have a special decoding function. */ -function fromCrockDecoded<T extends MallocArenaObject>(s: string, +function fromCrockDecoded<T extends MallocArenaObject>(emsc: EmscEnvironment, s: string, ctor: Ctor<T>, decodeFn: (p: number, s: number) => number): T { - const obj = new ctor(); - const buf = ByteArray.fromCrock(s); + const obj = new ctor(emsc); + const buf = ByteArray.fromCrock(emsc, s); obj.nativePtr = decodeFn(buf.nativePtr, buf.size()); buf.destroy(); return obj; @@ -657,11 +626,11 @@ function fromCrockDecoded<T extends MallocArenaObject>(s: string, * Encode an object using a special encoding function. */ function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Arena): ByteArray { - const ptr = emsc().allocFuncs.malloc(PTR_SIZE); + const ptr = obj.emsc.allocFuncs.malloc(PTR_SIZE); const len = encodeFn(obj.nativePtr, ptr); - const res = new ByteArray(len, undefined, arena); - res.nativePtr = emsc().lib.getValue(ptr, "*"); - emsc().funcs.free(ptr); + const res = new ByteArray(obj.emsc, len, undefined, arena); + res.nativePtr = obj.emsc.lib.getValue(ptr, "*"); + obj.emsc.funcs.free(ptr); return res; } @@ -670,9 +639,9 @@ function encode<T extends MallocArenaObject>(obj: T, encodeFn: any, arena?: Aren * Private EdDSA key. */ export class EddsaPrivateKey extends PackedArenaObject { - static create(a?: Arena): EddsaPrivateKey { - const obj = new EddsaPrivateKey(a); - obj.nativePtr = emsc().allocFuncs.eddsa_key_create(); + static create(emsc: EmscEnvironment, a?: Arena): EddsaPrivateKey { + const obj = new EddsaPrivateKey(emsc, a); + obj.nativePtr = emsc.allocFuncs.eddsa_key_create(); return obj; } @@ -681,13 +650,13 @@ export class EddsaPrivateKey extends PackedArenaObject { } getPublicKey(a?: Arena): EddsaPublicKey { - const obj = new EddsaPublicKey(a); - obj.nativePtr = emsc().allocFuncs.eddsa_public_key_from_private(this.nativePtr); + const obj = new EddsaPublicKey(this.emsc, a); + obj.nativePtr = this.emsc.allocFuncs.eddsa_public_key_from_private(this.nativePtr); return obj; } - static fromCrock(s: string): EddsaPrivateKey { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): EddsaPrivateKey { + return fromCrock(emsc, s, this); } } @@ -696,9 +665,9 @@ export class EddsaPrivateKey extends PackedArenaObject { * Low-level handle to an EdDSA private key. */ export class EcdsaPrivateKey extends PackedArenaObject { - static create(a?: Arena): EcdsaPrivateKey { - const obj = new EcdsaPrivateKey(a); - obj.nativePtr = emsc().allocFuncs.ecdsa_key_create(); + static create(emsc: EmscEnvironment, a?: Arena): EcdsaPrivateKey { + const obj = new EcdsaPrivateKey(emsc, a); + obj.nativePtr = emsc.allocFuncs.ecdsa_key_create(); return obj; } @@ -707,13 +676,13 @@ export class EcdsaPrivateKey extends PackedArenaObject { } getPublicKey(a?: Arena): EcdsaPublicKey { - const obj = new EcdsaPublicKey(a); - obj.nativePtr = emsc().allocFuncs.ecdsa_public_key_from_private(this.nativePtr); + const obj = new EcdsaPublicKey(this.emsc, a); + obj.nativePtr = this.emsc.allocFuncs.ecdsa_public_key_from_private(this.nativePtr); return obj; } - static fromCrock(s: string): EcdsaPrivateKey { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): EcdsaPrivateKey { + return fromCrock(emsc, s, this); } } @@ -722,9 +691,9 @@ export class EcdsaPrivateKey extends PackedArenaObject { * Low-level handle to an ECDHE private key. */ export class EcdhePrivateKey extends PackedArenaObject { - static create(a?: Arena): EcdhePrivateKey { - const obj = new EcdhePrivateKey(a); - obj.nativePtr = emsc().allocFuncs.ecdhe_key_create(); + static create(emsc: EmscEnvironment, a?: Arena): EcdhePrivateKey { + const obj = new EcdhePrivateKey(emsc, a); + obj.nativePtr = emsc.allocFuncs.ecdhe_key_create(); return obj; } @@ -733,13 +702,13 @@ export class EcdhePrivateKey extends PackedArenaObject { } getPublicKey(a?: Arena): EcdhePublicKey { - const obj = new EcdhePublicKey(a); - obj.nativePtr = emsc().allocFuncs.ecdhe_public_key_from_private(this.nativePtr); + const obj = new EcdhePublicKey(this.emsc, a); + obj.nativePtr = this.emsc.allocFuncs.ecdhe_public_key_from_private(this.nativePtr); return obj; } - static fromCrock(s: string): EcdhePrivateKey { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): EcdhePrivateKey { + return fromCrock(emsc, s, this); } } @@ -748,7 +717,7 @@ export class EcdhePrivateKey extends PackedArenaObject { * Constructor for a given type. */ interface Ctor<T> { - new(): T; + new(emsc: EmscEnvironment): T; } @@ -760,8 +729,8 @@ export class EddsaPublicKey extends PackedArenaObject { return 32; } - static fromCrock(s: string): EddsaPublicKey { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): EddsaPublicKey { + return fromCrock(emsc, s, this); } } @@ -773,8 +742,8 @@ export class EcdsaPublicKey extends PackedArenaObject { return 32; } - static fromCrock(s: string): EcdsaPublicKey { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): EcdsaPublicKey { + return fromCrock(emsc, s, this); } } @@ -787,8 +756,8 @@ export class EcdhePublicKey extends PackedArenaObject { return 32; } - static fromCrock(s: string): EcdhePublicKey { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): EcdhePublicKey { + return fromCrock(emsc, s, this); } } @@ -804,15 +773,15 @@ export class RsaBlindingKeySecret extends PackedArenaObject { /** * Create a random blinding key secret. */ - static create(a?: Arena): RsaBlindingKeySecret { - const o = new RsaBlindingKeySecret(a); + static create(emsc: EmscEnvironment, a?: Arena): RsaBlindingKeySecret { + const o = new RsaBlindingKeySecret(emsc, a); o.alloc(); o.randomize(); return o; } - static fromCrock(s: string): RsaBlindingKeySecret { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): RsaBlindingKeySecret { + return fromCrock(emsc, s, this); } } @@ -825,13 +794,13 @@ export class HashCode extends PackedArenaObject { return 64; } - static fromCrock(s: string): HashCode { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): HashCode { + return fromCrock(emsc, s, this); } random(qual: RandomQuality = RandomQuality.STRONG) { this.alloc(); - emsc().funcs.hash_create_random(qual, this.nativePtr); + this.emsc.funcs.hash_create_random(qual, this.nativePtr); } } @@ -846,42 +815,42 @@ export class ByteArray extends PackedArenaObject { return this.allocatedSize; } - constructor(desiredSize: number, init?: number, a?: Arena) { - super(a); + constructor(public emsc: EmscEnvironment, desiredSize: number, init?: number, a?: Arena) { + super(emsc, a); if (init === undefined) { - this.nativePtr = emsc().allocFuncs.malloc(desiredSize); + this.nativePtr = this.emsc.allocFuncs.malloc(desiredSize); } else { this.nativePtr = init; } this.allocatedSize = desiredSize; } - static fromStringWithoutNull(s: string, a?: Arena): ByteArray { + static fromStringWithoutNull(emsc: EmscEnvironment, s: string, a?: Arena): ByteArray { // UTF-8 bytes, including 0-terminator const terminatedByteLength = countUtf8Bytes(s) + 1; - const hstr = emsc().allocFuncs.malloc(terminatedByteLength); - emsc().lib.stringToUTF8(s, hstr, terminatedByteLength); - return new ByteArray(terminatedByteLength - 1, hstr, a); + const hstr = emsc.allocFuncs.malloc(terminatedByteLength); + emsc.lib.stringToUTF8(s, hstr, terminatedByteLength); + return new ByteArray(emsc, terminatedByteLength - 1, hstr, a); } - static fromStringWithNull(s: string, a?: Arena): ByteArray { + static fromStringWithNull(emsc: EmscEnvironment, s: string, a?: Arena): ByteArray { // UTF-8 bytes, including 0-terminator const terminatedByteLength = countUtf8Bytes(s) + 1; - const hstr = emsc().allocFuncs.malloc(terminatedByteLength); - emsc().lib.stringToUTF8(s, hstr, terminatedByteLength); - return new ByteArray(terminatedByteLength, hstr, a); + const hstr = emsc.allocFuncs.malloc(terminatedByteLength); + emsc.lib.stringToUTF8(s, hstr, terminatedByteLength); + return new ByteArray(emsc, terminatedByteLength, hstr, a); } - static fromCrock(s: string, a?: Arena): ByteArray { + static fromCrock(emsc: EmscEnvironment, s: string, a?: Arena): ByteArray { // this one is a bit more complicated than the other fromCrock functions, // since we don't have a fixed size const byteLength = countUtf8Bytes(s); - const hstr = emsc().allocFuncs.malloc(byteLength + 1); - emsc().lib.stringToUTF8(s, hstr, byteLength + 1); + const hstr = emsc.allocFuncs.malloc(byteLength + 1); + emsc.lib.stringToUTF8(s, hstr, byteLength + 1); const decodedLen = Math.floor((byteLength * 5) / 8); - const ba = new ByteArray(decodedLen, undefined, a); - const res = emsc().funcs.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen); - emsc().funcs.free(hstr); + const ba = new ByteArray(emsc, decodedLen, undefined, a); + const res = emsc.funcs.string_to_data(hstr, byteLength, ba.nativePtr, decodedLen); + emsc.funcs.free(hstr); if (res !== GNUNET_OK) { throw Error("decoding failed"); } @@ -901,11 +870,12 @@ export class EccSignaturePurpose extends PackedArenaObject { private payloadSize: number; - constructor(purpose: SignaturePurpose, + constructor(emsc: EmscEnvironment, + purpose: SignaturePurpose, payload: PackedArenaObject, a?: Arena) { - super(a); - this.nativePtr = emsc().allocFuncs.purpose_create(purpose, + super(emsc, a); + this.nativePtr = emsc.allocFuncs.purpose_create(purpose, payload.nativePtr, payload.size()); this.payloadSize = payload.size(); @@ -920,7 +890,7 @@ abstract class SignatureStruct { private members: any = {}; - constructor(x: { [name: string]: any }) { + constructor(public emsc: EmscEnvironment, x: { [name: string]: any }) { for (const k in x) { this.set(k, x[k]); } @@ -937,17 +907,17 @@ abstract class SignatureStruct { totalSize += member.size(); } - const buf = emsc().allocFuncs.malloc(totalSize); + const buf = this.emsc.allocFuncs.malloc(totalSize); let ptr = buf; for (const f of this.fieldTypes()) { const name = f[0]; const member = this.members[name]; const size = member.size(); - emsc().funcs.memmove(ptr, member.nativePtr, size); + this.emsc.funcs.memmove(ptr, member.nativePtr, size); ptr += size; } - const ba = new ByteArray(totalSize, buf, a); - return new EccSignaturePurpose(this.purpose(), ba); + const ba = new ByteArray(this.emsc, totalSize, buf, a); + return new EccSignaturePurpose(this.emsc, this.purpose(), ba); } @@ -1012,8 +982,8 @@ export interface WithdrawRequestPS_Args { * Low-level handle to a WithdrawRequest signature structure. */ export class WithdrawRequestPS extends SignatureStruct { - constructor(w: WithdrawRequestPS_Args) { - super(w); + constructor(emsc: EmscEnvironment, w: WithdrawRequestPS_Args) { + super(emsc, w); } purpose() { @@ -1046,8 +1016,8 @@ export interface PaybackRequestPS_args { * Low-level handle to a PaybackRequest signature structure. */ export class PaybackRequestPS extends SignatureStruct { - constructor(w: PaybackRequestPS_args) { - super(w); + constructor(emsc: EmscEnvironment, w: PaybackRequestPS_args) { + super(emsc, w); } purpose() { @@ -1078,8 +1048,8 @@ interface RefreshMeltCoinAffirmationPS_Args { * Low-level handle to a RefreshMeltCoinAffirmationPS signature structure. */ export class RefreshMeltCoinAffirmationPS extends SignatureStruct { - constructor(w: RefreshMeltCoinAffirmationPS_Args) { - super(w); + constructor(emsc: EmscEnvironment, w: RefreshMeltCoinAffirmationPS_Args) { + super(emsc, w); } purpose() { @@ -1128,8 +1098,8 @@ interface MasterWireFeePS_Args { * Low-level handle to a structure being signed over. */ export class MasterWireFeePS extends SignatureStruct { - constructor(w: MasterWireFeePS_Args) { - super(w); + constructor(emsc: EmscEnvironment, w: MasterWireFeePS_Args) { + super(emsc, w); } purpose() { @@ -1152,8 +1122,8 @@ export class MasterWireFeePS extends SignatureStruct { * Low-level handle to an absolute time in network byte order (NBO). */ export class AbsoluteTimeNbo extends PackedArenaObject { - static fromTalerString(s: string): AbsoluteTimeNbo { - const x = new AbsoluteTimeNbo(); + static fromTalerString(emsc: EmscEnvironment, s: string): AbsoluteTimeNbo { + const x = new AbsoluteTimeNbo(emsc); x.alloc(); const r = /Date\(([0-9]+)\)/; const m = r.exec(s); @@ -1162,15 +1132,15 @@ export class AbsoluteTimeNbo extends PackedArenaObject { } const n = parseInt(m[1], 10) * 1000000; // XXX: This only works up to 54 bit numbers. - set64(x.nativePtr, n); + set64(emsc, x.nativePtr, n); return x; } - static fromStampSeconds(stamp: number): AbsoluteTimeNbo { - const x = new AbsoluteTimeNbo(); + static fromStampSeconds(emsc: EmscEnvironment, stamp: number): AbsoluteTimeNbo { + const x = new AbsoluteTimeNbo(emsc); x.alloc(); // XXX: This only works up to 54 bit numbers. - set64(x.nativePtr, stamp * 1000000); + set64(emsc, x.nativePtr, stamp * 1000000); return x; } @@ -1182,17 +1152,17 @@ export class AbsoluteTimeNbo extends PackedArenaObject { // XXX: This only works up to 54 bit numbers. -function set64(p: number, n: number) { +function set64(emsc: EmscEnvironment, p: number, n: number) { for (let i = 0; i < 8; ++i) { - emsc().lib.setValue(p + (7 - i), n & 0xFF, "i8"); + emsc.lib.setValue(p + (7 - i), n & 0xFF, "i8"); n = Math.floor(n / 256); } } // XXX: This only works up to 54 bit numbers. -function set32(p: number, n: number) { +function set32(emsc: EmscEnvironment, p: number, n: number) { for (let i = 0; i < 4; ++i) { - emsc().lib.setValue(p + (3 - i), n & 0xFF, "i8"); + emsc.lib.setValue(p + (3 - i), n & 0xFF, "i8"); n = Math.floor(n / 256); } } @@ -1202,10 +1172,10 @@ function set32(p: number, n: number) { * Low-level handle to an unsigned 64-bit value. */ export class UInt64 extends PackedArenaObject { - static fromNumber(n: number): UInt64 { - const x = new UInt64(); + static fromNumber(emsc: EmscEnvironment, n: number): UInt64 { + const x = new UInt64(emsc); x.alloc(); - set64(x.nativePtr, n); + set64(emsc, x.nativePtr, n); return x; } @@ -1219,10 +1189,10 @@ export class UInt64 extends PackedArenaObject { * Low-level handle to an unsigned 32-bit value. */ export class UInt32 extends PackedArenaObject { - static fromNumber(n: number): UInt32 { - const x = new UInt32(); + static fromNumber(emsc: EmscEnvironment, n: number): UInt32 { + const x = new UInt32(emsc); x.alloc(); - set32(x.nativePtr, n); + set32(emsc, x.nativePtr, n); return x; } @@ -1275,8 +1245,8 @@ export interface DepositRequestPS_Args { * Low-level handle to a struct being signed over. */ export class DepositRequestPS extends SignatureStruct { - constructor(w: DepositRequestPS_Args) { - super(w); + constructor(emsc: EmscEnvironment, w: DepositRequestPS_Args) { + super(emsc, w); } purpose() { @@ -1307,8 +1277,8 @@ interface CoinLinkSignaturePS_args { export class CoinLinkSignaturePS extends SignatureStruct { - constructor(w: CoinLinkSignaturePS_args) { - super(w); + constructor(emsc: EmscEnvironment, w: CoinLinkSignaturePS_args) { + super(emsc, w); } purpose() { @@ -1348,8 +1318,8 @@ export interface DenominationKeyValidityPS_args { * Low-level handle to a structure being signed over. */ export class DenominationKeyValidityPS extends SignatureStruct { - constructor(w: DenominationKeyValidityPS_args) { - super(w); + constructor(emsc: EmscEnvironment, w: DenominationKeyValidityPS_args) { + super(emsc, w); } purpose() { @@ -1388,8 +1358,8 @@ export interface PaymentSignaturePS_args { * Low-level handle to a structure being signed over. */ export class PaymentSignaturePS extends SignatureStruct { - constructor(w: PaymentSignaturePS_args) { - super(w); + constructor(emsc: EmscEnvironment, w: PaymentSignaturePS_args) { + super(emsc, w); } purpose() { @@ -1408,13 +1378,13 @@ export class PaymentSignaturePS extends SignatureStruct { * Low-level handle to an RsaPrivateKey. */ export class RsaPrivateKey extends MallocArenaObject { - static fromCrock(s: string): RsaPrivateKey { - return fromCrockDecoded(s, this, emsc().allocFuncs.rsa_private_key_decode); + static fromCrock(emsc: EmscEnvironment, s: string): RsaPrivateKey { + return fromCrockDecoded(emsc, s, this, emsc.allocFuncs.rsa_private_key_decode); } - static create(bitLen: number, a?: Arena): RsaPrivateKey { - const obj = new RsaPrivateKey(a); - obj.nativePtr = emsc().allocFuncs.rsa_private_key_create(bitLen); + static create(emsc: EmscEnvironment, bitLen: number, a?: Arena): RsaPrivateKey { + const obj = new RsaPrivateKey(emsc, a); + obj.nativePtr = emsc.allocFuncs.rsa_private_key_create(bitLen); return obj; } @@ -1424,18 +1394,18 @@ export class RsaPrivateKey extends MallocArenaObject { getPublicKey(a?: Arena): RsaPublicKey { - const obj = new RsaPublicKey(a); - obj.nativePtr = emsc().allocFuncs.rsa_private_key_get_public(this.nativePtr); + const obj = new RsaPublicKey(this.emsc, a); + obj.nativePtr = this.emsc.allocFuncs.rsa_private_key_get_public(this.nativePtr); return obj; } destroy() { - emsc().funcs.rsa_public_key_free(this.nativePtr); + this.emsc.funcs.rsa_public_key_free(this.nativePtr); this.nativePtr = 0; } encode(arena?: Arena): ByteArray { - return encode(this, emsc().allocFuncs.rsa_private_key_encode); + return encode(this, this.emsc.allocFuncs.rsa_private_key_encode); } } @@ -1444,8 +1414,8 @@ export class RsaPrivateKey extends MallocArenaObject { * Low-level handle to an RsaPublicKey. */ export class RsaPublicKey extends MallocArenaObject { - static fromCrock(s: string): RsaPublicKey { - return fromCrockDecoded(s, this, emsc().allocFuncs.rsa_public_key_decode); + static fromCrock(emsc: EmscEnvironment, s: string): RsaPublicKey { + return fromCrockDecoded(emsc, s, this, emsc.allocFuncs.rsa_public_key_decode); } toCrock() { @@ -1453,12 +1423,12 @@ export class RsaPublicKey extends MallocArenaObject { } destroy() { - emsc().funcs.rsa_public_key_free(this.nativePtr); + this.emsc.funcs.rsa_public_key_free(this.nativePtr); this.nativePtr = 0; } encode(arena?: Arena): ByteArray { - return encode(this, emsc().allocFuncs.rsa_public_key_encode); + return encode(this, this.emsc.allocFuncs.rsa_public_key_encode); } } @@ -1470,8 +1440,8 @@ export class EddsaSignature extends PackedArenaObject { size() { return 64; } - static fromCrock(s: string): EddsaSignature { - return fromCrock(s, this); + static fromCrock(emsc: EmscEnvironment, s: string): EddsaSignature { + return fromCrock(emsc, s, this); } } @@ -1480,16 +1450,16 @@ export class EddsaSignature extends PackedArenaObject { * Low-level handle to an RsaSignature. */ export class RsaSignature extends MallocArenaObject { - static fromCrock(s: string, a?: Arena) { - return fromCrockDecoded(s, this, emsc().allocFuncs.rsa_signature_decode); + static fromCrock(emsc: EmscEnvironment, s: string, a?: Arena) { + return fromCrockDecoded(emsc, s, this, emsc.allocFuncs.rsa_signature_decode); } encode(arena?: Arena): ByteArray { - return encode(this, emsc().allocFuncs.rsa_signature_encode); + return encode(this, this.emsc.allocFuncs.rsa_signature_encode); } destroy() { - emsc().funcs.rsa_signature_free(this.nativePtr); + this.emsc.funcs.rsa_signature_free(this.nativePtr); this.nativePtr = 0; } } @@ -1502,22 +1472,23 @@ export function rsaBlind(hashCode: HashCode, blindingKey: RsaBlindingKeySecret, pkey: RsaPublicKey, arena?: Arena): ByteArray|null { - const buf_ptr_out = emsc().allocFuncs.malloc(PTR_SIZE); - const buf_size_out = emsc().allocFuncs.malloc(PTR_SIZE); - const res = emsc().allocFuncs.rsa_blind(hashCode.nativePtr, + const emsc: EmscEnvironment = hashCode.emsc; + const buf_ptr_out = emsc.allocFuncs.malloc(PTR_SIZE); + const buf_size_out = emsc.allocFuncs.malloc(PTR_SIZE); + const res = emsc.allocFuncs.rsa_blind(hashCode.nativePtr, blindingKey.nativePtr, pkey.nativePtr, buf_ptr_out, buf_size_out); - const buf_ptr = emsc().lib.getValue(buf_ptr_out, "*"); - const buf_size = emsc().lib.getValue(buf_size_out, "*"); - emsc().funcs.free(buf_ptr_out); - emsc().funcs.free(buf_size_out); + const buf_ptr = emsc.lib.getValue(buf_ptr_out, "*"); + const buf_size = emsc.lib.getValue(buf_size_out, "*"); + emsc.funcs.free(buf_ptr_out); + emsc.funcs.free(buf_size_out); if (res !== GNUNET_OK) { // malicious key return null; } - return new ByteArray(buf_size, buf_ptr, arena); + return new ByteArray(emsc, buf_size, buf_ptr, arena); } @@ -1527,9 +1498,9 @@ export function rsaBlind(hashCode: HashCode, export function eddsaSign(purpose: EccSignaturePurpose, priv: EddsaPrivateKey, a?: Arena): EddsaSignature { - const sig = new EddsaSignature(a); + const sig = new EddsaSignature(purpose.emsc, a); sig.alloc(); - const res = emsc().funcs.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr); + const res = purpose.emsc.funcs.eddsa_sign(priv.nativePtr, purpose.nativePtr, sig.nativePtr); if (res < 1) { throw Error("EdDSA signing failed"); } @@ -1545,7 +1516,7 @@ export function eddsaVerify(purposeNum: number, sig: EddsaSignature, pub: EddsaPublicKey, a?: Arena): boolean { - const r = emsc().funcs.eddsa_verify(purposeNum, + const r = verify.emsc.funcs.eddsa_verify(purposeNum, verify.nativePtr, sig.nativePtr, pub.nativePtr); @@ -1556,7 +1527,7 @@ export function eddsaVerify(purposeNum: number, export function rsaVerify(h: HashCode, sig: RsaSignature, pub: RsaPublicKey) { - const r = emsc().funcs.rsa_verify(h.nativePtr, + const r = h.emsc.funcs.rsa_verify(h.nativePtr, sig.nativePtr, pub.nativePtr); return r === GNUNET_OK; @@ -1570,8 +1541,8 @@ export function rsaUnblind(sig: RsaSignature, bk: RsaBlindingKeySecret, pk: RsaPublicKey, a?: Arena): RsaSignature { - const x = new RsaSignature(a); - x.nativePtr = emsc().allocFuncs.rsa_unblind(sig.nativePtr, + const x = new RsaSignature(sig.emsc, a); + x.nativePtr = sig.emsc.allocFuncs.rsa_unblind(sig.nativePtr, bk.nativePtr, pk.nativePtr); return x; @@ -1600,9 +1571,9 @@ export interface FreshCoin { */ export function ecdhEddsa(priv: EcdhePrivateKey, pub: EddsaPublicKey): HashCode { - const h = new HashCode(); + const h = new HashCode(priv.emsc); h.alloc(); - const res = emsc().funcs.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr); + const res = priv.emsc.funcs.ecdh_eddsa(priv.nativePtr, pub.nativePtr, h.nativePtr); if (res !== GNUNET_OK) { throw Error("ecdh_eddsa failed"); } @@ -1611,8 +1582,8 @@ export function ecdhEddsa(priv: EcdhePrivateKey, export function rsaSignBlinded(priv: RsaPrivateKey, msg: ByteArray): RsaSignature { - const sig = new RsaSignature(); - sig.nativePtr = emsc().allocFuncs.rsa_sign_blinded (priv.nativePtr, + const sig = new RsaSignature(priv.emsc); + sig.nativePtr = priv.emsc.allocFuncs.rsa_sign_blinded (priv.nativePtr, msg.nativePtr, msg.size()); return sig; @@ -1625,13 +1596,14 @@ export function rsaSignBlinded(priv: RsaPrivateKey, */ export function setupFreshCoin(secretSeed: TransferSecretP, coinIndex: number): FreshCoin { - const priv = new EddsaPrivateKey(); + const emsc: EmscEnvironment = secretSeed.emsc; + const priv = new EddsaPrivateKey(emsc); priv.isWeak = true; - const blindingKey = new RsaBlindingKeySecret(); + const blindingKey = new RsaBlindingKeySecret(emsc); blindingKey.isWeak = true; - const buf = new ByteArray(priv.size() + blindingKey.size()); + const buf = new ByteArray(emsc, priv.size() + blindingKey.size()); - emsc().funcs.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr); + emsc.funcs.setup_fresh_coin(secretSeed.nativePtr, coinIndex, buf.nativePtr); priv.nativePtr = buf.nativePtr; blindingKey.nativePtr = buf.nativePtr + priv.size(); diff --git a/src/crypto/nodeWorker.ts b/src/crypto/nodeProcessWorker.ts index b5a2e8b44..b5a2e8b44 100644 --- a/src/crypto/nodeWorker.ts +++ b/src/crypto/nodeProcessWorker.ts diff --git a/src/crypto/synchronousWorker.ts b/src/crypto/synchronousWorker.ts new file mode 100644 index 000000000..d8a3d83cb --- /dev/null +++ b/src/crypto/synchronousWorker.ts @@ -0,0 +1,162 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + GNU 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. + + GNU 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 + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { EmscEnvironment } from "./emscInterface"; +import { CryptoImplementation } from "./cryptoImplementation"; + +/** + * Worker implementation that uses node subprocesses. + */ +export class SynchronousCryptoWorker { + private cachedEmscEnvironment: EmscEnvironment | undefined = undefined; + private cachedEmscEnvironmentPromise: Promise<EmscEnvironment> | undefined = undefined; + + /** + * Function to be called when we receive a message from the worker thread. + */ + onmessage: undefined | ((m: any) => void); + + /** + * Function to be called when we receive an error from the worker thread. + */ + onerror: undefined | ((m: any) => void); + + constructor() { + this.onerror = undefined; + this.onmessage = undefined; + } + + /** + * Add an event listener for either an "error" or "message" event. + */ + addEventListener(event: "message" | "error", fn: (x: any) => void): void { + switch (event) { + case "message": + this.onmessage = fn; + break; + case "error": + this.onerror = fn; + break; + } + } + + private getEmscriptenEnvironment(): Promise<EmscEnvironment> { + if (this.cachedEmscEnvironment) { + return Promise.resolve(this.cachedEmscEnvironment); + } + + if (this.cachedEmscEnvironmentPromise) { + return this.cachedEmscEnvironmentPromise; + } + + // 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; + + // Assume that the code is run from the build/ directory. + const libFn = indirectRequire( + "../../../emscripten/taler-emscripten-lib.js", + ); + const lib = libFn(); + // @ts-ignore + g.importScripts = savedImportScripts; + + 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'", + ); + } + + this.cachedEmscEnvironmentPromise = new Promise((resolve, reject) => { + lib.onRuntimeInitialized = () => { + this.cachedEmscEnvironmentPromise = undefined; + this.cachedEmscEnvironment = new EmscEnvironment(lib); + resolve(this.cachedEmscEnvironment); + }; + }); + return this.cachedEmscEnvironmentPromise; + } + + private dispatchMessage(msg: any) { + if (this.onmessage) { + this.onmessage({ data: msg }); + } + } + + private async handleRequest(operation: string, id: number, args: string[]) { + let emsc = await this.getEmscriptenEnvironment(); + + const impl = new CryptoImplementation(emsc); + + if (!(operation in impl)) { + console.error(`crypto operation '${operation}' not found`); + return; + } + + try { + const result = (impl as any)[operation](...args); + this.dispatchMessage({ result, id }); + } catch (e) { + console.log("error during operation", e); + return; + } + } + + /** + * Send a message to the worker thread. + */ + postMessage(msg: any) { + const args = msg.args; + if (!Array.isArray(args)) { + console.error("args must be array"); + return; + } + const id = msg.id; + if (typeof id !== "number") { + console.error("RPC id must be number"); + return; + } + const operation = msg.operation; + if (typeof operation !== "string") { + console.error("RPC operation must be string"); + return; + } + + this.handleRequest(operation, id, args); + } + + /** + * Forcibly terminate the worker thread. + */ + terminate() { + console.log("terminating synchronous worker (no-op)"); + } +} diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts index 95ee40e7d..1e501cdc6 100644 --- a/src/headless/taler-wallet-cli.ts +++ b/src/headless/taler-wallet-cli.ts @@ -26,7 +26,7 @@ import URI = require("urijs"); import querystring = require("querystring"); import { CheckPaymentResponse } from "../talerTypes"; -import { NodeCryptoWorkerFactory } from "../crypto/cryptoApi"; +import { NodeCryptoWorkerFactory, SynchronousCryptoWorkerFactory } from "../crypto/cryptoApi"; const enableTracing = false; @@ -269,7 +269,8 @@ export async function main() { myUnsupportedUpgrade, ); - const myWallet = new Wallet(myDb, myHttpLib, myBadge, myNotifier, new NodeCryptoWorkerFactory()); + const myWallet = new Wallet(myDb, myHttpLib, myBadge, myNotifier, new SynchronousCryptoWorkerFactory()); + //const myWallet = new Wallet(myDb, myHttpLib, myBadge, myNotifier, new NodeCryptoWorkerFactory()); const reserveResponse = await myWallet.createReserve({ amount: amounts.parseOrThrow("TESTKUDOS:10.0"), diff --git a/src/wallet.ts b/src/wallet.ts index e3b609351..ffdb60fa3 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -242,7 +242,7 @@ export function selectPayCoins( ) >= 0; const isBelowFee = Amounts.cmp(accDepositFee, depositFeeLimit) <= 0; - console.log("coin selection", { + console.log("candidate coin selection", { coversAmount, isBelowFee, accDepositFee, @@ -536,8 +536,8 @@ export class Wallet { } /** - * Get exchanges and associated coins that are still spendable, - * but only if the sum the coins' remaining value exceeds the payment amount. + * Get exchanges and associated coins that are still spendable, but only + * if the sum the coins' remaining value covers the payment amount and fees. */ private async getCoinsForPayment( args: CoinsForPaymentArgs, @@ -592,6 +592,7 @@ export class Wallet { const coins: CoinRecord[] = await this.q() .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl) .toArray(); + const denoms = await this.q() .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl) .toArray(); @@ -644,12 +645,6 @@ export class Wallet { continue; } - console.log("payment coins: wireFeeLimit", wireFeeLimit); - console.log("payment coins: wireFeeAmortization", wireFeeAmortization); - console.log("payment coins: fees", fees); - console.log("payment coins: wireMethod", wireMethod); - console.log("payment coins: wireFeeTime", wireFeeTime); - let totalFees = Amounts.getZero(currency); let wireFee: AmountJson | undefined; for (const fee of fees.feesForType[wireMethod] || []) { @@ -659,8 +654,6 @@ export class Wallet { } } - console.log("payment coins: current wire fees", wireFee); - if (wireFee) { const amortizedWireFee = Amounts.divide(wireFee, wireFeeAmortization); if (Amounts.cmp(wireFeeLimit, amortizedWireFee) < 0) { @@ -671,6 +664,7 @@ export class Wallet { } const res = selectPayCoins(denoms, cds, remainingAmount, depositFeeLimit); + if (res) { totalFees = Amounts.add(totalFees, res.totalFees).amount; return { @@ -748,7 +742,6 @@ export class Wallet { console.log("contract download failed", e); throw e; } - console.log("got response", resp); const proposal = Proposal.checked(resp.data); @@ -919,7 +912,6 @@ export class Wallet { wireMethod: proposal.contractTerms.wire_method, }); - console.log("max_fee", proposal.contractTerms.max_fee); console.log("coin selection result", res); if (!res) { @@ -1033,6 +1025,8 @@ export class Wallet { return { status: "insufficient-balance" }; } + console.log("checkPay: payment possible!"); + // Only create speculative signature if we don't already have one for this proposal if ( !this.speculativePayData || @@ -1051,6 +1045,7 @@ export class Wallet { proposal, proposalId, }; + console.log("created speculative pay data for payment"); } return { status: "payment-possible", coinSelection: res }; @@ -1156,7 +1151,6 @@ export class Wallet { return op.promise; } - //console.log("executing processPreCoin", preCoin); this.processPreCoinConcurrent++; try { diff --git a/tsconfig.json b/tsconfig.json index 3f29f4208..8104054ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,14 +29,15 @@ "src/checkable.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/emscLoader.d.ts", "src/crypto/emscLoader.js", - "src/crypto/nodeWorker.ts", + "src/crypto/nodeProcessWorker.ts", "src/crypto/nodeWorkerEntry.ts", - "src/crypto/startWorker.js", + "src/crypto/synchronousWorker.ts", "src/db.ts", "src/dbTypes.ts", "src/headless/taler-wallet-cli.ts", @@ -3216,10 +3216,10 @@ iconv-lite@^0.4.4, iconv-lite@~0.4.13: dependencies: safer-buffer ">= 2.1.2 < 3" -idb-bridge@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.1.tgz#4498704b79f354dcd3a628825656967939003614" - integrity sha512-GTFqRkjFk4C98zvPA65cKpB1JnBt+bFftn+Kkwucoy+hLmxVfdmbwZ6hj2ZieBHOppMtRs0il3zbzSzLofWrDg== +idb-bridge@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.2.tgz#daa46d75060bd6a116b26155c314446bea355570" + integrity sha512-PEfZmdbIQUV4vxJRSSXhan7niclJDJGPGUSJ2WlHCYCgdFK6n25UD8z/lsLoqWKfcp+xPuL+9MI+h9Ql8XFzkw== ieee754@^1.1.4: version "1.1.13" |