2019-08-15 23:34:08 +02:00
|
|
|
/*
|
|
|
|
This file is part of GNU Taler
|
2020-03-13 14:34:16 +01:00
|
|
|
(C) 2019-2020 Taler Systems SA
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
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.
|
2019-11-28 00:46:34 +01:00
|
|
|
*
|
2019-08-15 23:34:08 +02:00
|
|
|
* The functionality is parameterized over an Emscripten environment.
|
2020-03-13 14:34:16 +01:00
|
|
|
*
|
|
|
|
* @author Florian Dold <dold@taler.net>
|
2019-08-15 23:34:08 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {
|
|
|
|
CoinRecord,
|
|
|
|
DenominationRecord,
|
2019-11-30 00:36:20 +01:00
|
|
|
RefreshPlanchetRecord,
|
2019-08-15 23:34:08 +02:00
|
|
|
RefreshSessionRecord,
|
|
|
|
TipPlanchet,
|
|
|
|
WireFee,
|
2020-03-11 20:14:28 +01:00
|
|
|
CoinSourceType,
|
2019-12-12 20:53:15 +01:00
|
|
|
} from "../../types/dbTypes";
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2020-04-07 10:07:32 +02:00
|
|
|
import { CoinDepositPermission, RecoupRequest } from "../../types/talerTypes";
|
2019-11-28 00:46:34 +01:00
|
|
|
import {
|
|
|
|
BenchmarkResult,
|
2019-11-30 00:36:20 +01:00
|
|
|
PlanchetCreationResult,
|
2019-12-02 00:42:40 +01:00
|
|
|
PlanchetCreationRequest,
|
2019-12-25 19:11:20 +01:00
|
|
|
DepositInfo,
|
2019-12-12 20:53:15 +01:00
|
|
|
} from "../../types/walletTypes";
|
2020-04-02 17:03:01 +02:00
|
|
|
import { AmountJson, Amounts } from "../../util/amounts";
|
2019-12-05 19:38:19 +01:00
|
|
|
import * as timer from "../../util/timer";
|
2019-11-28 00:46:34 +01:00
|
|
|
import {
|
|
|
|
encodeCrock,
|
|
|
|
decodeCrock,
|
|
|
|
createEddsaKeyPair,
|
|
|
|
createBlindingKeySecret,
|
|
|
|
hash,
|
|
|
|
rsaBlind,
|
|
|
|
eddsaVerify,
|
|
|
|
eddsaSign,
|
|
|
|
rsaUnblind,
|
|
|
|
stringToBytes,
|
|
|
|
createHashContext,
|
|
|
|
createEcdheKeyPair,
|
|
|
|
keyExchangeEcdheEddsa,
|
|
|
|
setupRefreshPlanchet,
|
2019-12-05 19:38:19 +01:00
|
|
|
rsaVerify,
|
|
|
|
} from "../talerCrypto";
|
|
|
|
import { randomBytes } from "../primitives/nacl-fast";
|
2019-12-05 23:07:46 +01:00
|
|
|
import { kdf } from "../primitives/kdf";
|
2020-04-07 10:07:32 +02:00
|
|
|
import { Timestamp, getTimestampNow } from "../../util/time";
|
2019-11-28 00:46:34 +01:00
|
|
|
|
|
|
|
enum SignaturePurpose {
|
|
|
|
RESERVE_WITHDRAW = 1200,
|
|
|
|
WALLET_COIN_DEPOSIT = 1201,
|
|
|
|
MASTER_DENOMINATION_KEY_VALIDITY = 1025,
|
2019-12-05 23:07:46 +01:00
|
|
|
MASTER_WIRE_FEES = 1028,
|
|
|
|
MASTER_WIRE_DETAILS = 1030,
|
2019-11-28 00:46:34 +01:00
|
|
|
WALLET_COIN_MELT = 1202,
|
|
|
|
TEST = 4242,
|
|
|
|
MERCHANT_PAYMENT_OK = 1104,
|
2020-03-11 20:14:28 +01:00
|
|
|
WALLET_COIN_RECOUP = 1203,
|
2019-11-28 00:46:34 +01:00
|
|
|
WALLET_COIN_LINK = 1204,
|
2020-03-13 14:34:16 +01:00
|
|
|
EXCHANGE_CONFIRM_RECOUP = 1039,
|
|
|
|
EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
|
2019-11-28 00:46:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function amountToBuffer(amount: AmountJson): Uint8Array {
|
|
|
|
const buffer = new ArrayBuffer(8 + 4 + 12);
|
|
|
|
const dvbuf = new DataView(buffer);
|
|
|
|
const u8buf = new Uint8Array(buffer);
|
|
|
|
const te = new TextEncoder();
|
|
|
|
const curr = te.encode(amount.currency);
|
|
|
|
dvbuf.setBigUint64(0, BigInt(amount.value));
|
|
|
|
dvbuf.setUint32(8, amount.fraction);
|
|
|
|
u8buf.set(curr, 8 + 4);
|
|
|
|
|
|
|
|
return u8buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
function timestampToBuffer(ts: Timestamp): Uint8Array {
|
|
|
|
const b = new ArrayBuffer(8);
|
|
|
|
const v = new DataView(b);
|
|
|
|
const s = BigInt(ts.t_ms) * BigInt(1000);
|
|
|
|
v.setBigUint64(0, s);
|
|
|
|
return new Uint8Array(b);
|
|
|
|
}
|
|
|
|
class SignaturePurposeBuilder {
|
|
|
|
private chunks: Uint8Array[] = [];
|
|
|
|
|
|
|
|
constructor(private purposeNum: number) {}
|
|
|
|
|
|
|
|
put(bytes: Uint8Array): SignaturePurposeBuilder {
|
|
|
|
this.chunks.push(Uint8Array.from(bytes));
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
build(): Uint8Array {
|
|
|
|
let payloadLen = 0;
|
2020-04-06 17:45:41 +02:00
|
|
|
for (const c of this.chunks) {
|
2019-11-28 00:46:34 +01:00
|
|
|
payloadLen += c.byteLength;
|
|
|
|
}
|
|
|
|
const buf = new ArrayBuffer(4 + 4 + payloadLen);
|
|
|
|
const u8buf = new Uint8Array(buf);
|
|
|
|
let p = 8;
|
2020-04-06 17:45:41 +02:00
|
|
|
for (const c of this.chunks) {
|
2019-11-28 00:46:34 +01:00
|
|
|
u8buf.set(c, p);
|
|
|
|
p += c.byteLength;
|
|
|
|
}
|
|
|
|
const dvbuf = new DataView(buf);
|
|
|
|
dvbuf.setUint32(0, payloadLen + 4 + 4);
|
|
|
|
dvbuf.setUint32(4, this.purposeNum);
|
|
|
|
return u8buf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildSigPS(purposeNum: number): SignaturePurposeBuilder {
|
|
|
|
return new SignaturePurposeBuilder(purposeNum);
|
|
|
|
}
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
export class CryptoImplementation {
|
2020-04-06 17:45:41 +02:00
|
|
|
static enableTracing = false;
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a pre-coin of the given denomination to be withdrawn from then given
|
|
|
|
* reserve.
|
|
|
|
*/
|
2019-12-05 23:07:46 +01:00
|
|
|
createPlanchet(req: PlanchetCreationRequest): PlanchetCreationResult {
|
2019-12-02 00:42:40 +01:00
|
|
|
const reservePub = decodeCrock(req.reservePub);
|
|
|
|
const reservePriv = decodeCrock(req.reservePriv);
|
|
|
|
const denomPub = decodeCrock(req.denomPub);
|
2019-11-28 00:46:34 +01:00
|
|
|
const coinKeyPair = createEddsaKeyPair();
|
|
|
|
const blindingFactor = createBlindingKeySecret();
|
|
|
|
const coinPubHash = hash(coinKeyPair.eddsaPub);
|
|
|
|
const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
|
2019-12-02 00:42:40 +01:00
|
|
|
const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount;
|
2019-11-28 00:46:34 +01:00
|
|
|
const denomPubHash = hash(denomPub);
|
|
|
|
const evHash = hash(ev);
|
|
|
|
|
|
|
|
const withdrawRequest = buildSigPS(SignaturePurpose.RESERVE_WITHDRAW)
|
|
|
|
.put(reservePub)
|
|
|
|
.put(amountToBuffer(amountWithFee))
|
2019-12-02 00:42:40 +01:00
|
|
|
.put(amountToBuffer(req.feeWithdraw))
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(denomPubHash)
|
|
|
|
.put(evHash)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
const sig = eddsaSign(withdrawRequest, reservePriv);
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2019-11-30 00:36:20 +01:00
|
|
|
const planchet: PlanchetCreationResult = {
|
2019-11-28 00:46:34 +01:00
|
|
|
blindingKey: encodeCrock(blindingFactor),
|
|
|
|
coinEv: encodeCrock(ev),
|
|
|
|
coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
|
|
|
|
coinPub: encodeCrock(coinKeyPair.eddsaPub),
|
2019-12-02 00:42:40 +01:00
|
|
|
coinValue: req.value,
|
2019-11-28 00:46:34 +01:00
|
|
|
denomPub: encodeCrock(denomPub),
|
|
|
|
denomPubHash: encodeCrock(denomPubHash),
|
|
|
|
reservePub: encodeCrock(reservePub),
|
|
|
|
withdrawSig: encodeCrock(sig),
|
2020-04-02 17:03:01 +02:00
|
|
|
coinEvHash: encodeCrock(evHash),
|
2019-08-15 23:34:08 +02:00
|
|
|
};
|
2019-11-30 00:36:20 +01:00
|
|
|
return planchet;
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a planchet used for tipping, including the private keys.
|
|
|
|
*/
|
|
|
|
createTipPlanchet(denom: DenominationRecord): TipPlanchet {
|
2019-11-28 00:46:34 +01:00
|
|
|
const denomPub = decodeCrock(denom.denomPub);
|
|
|
|
const coinKeyPair = createEddsaKeyPair();
|
|
|
|
const blindingFactor = createBlindingKeySecret();
|
|
|
|
const coinPubHash = hash(coinKeyPair.eddsaPub);
|
|
|
|
const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
const tipPlanchet: TipPlanchet = {
|
2019-11-28 00:46:34 +01:00
|
|
|
blindingKey: encodeCrock(blindingFactor),
|
|
|
|
coinEv: encodeCrock(ev),
|
|
|
|
coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
|
|
|
|
coinPub: encodeCrock(coinKeyPair.eddsaPub),
|
2019-08-15 23:34:08 +02:00
|
|
|
coinValue: denom.value,
|
2019-11-28 00:46:34 +01:00
|
|
|
denomPub: encodeCrock(denomPub),
|
|
|
|
denomPubHash: encodeCrock(hash(denomPub)),
|
2019-08-15 23:34:08 +02:00
|
|
|
};
|
|
|
|
return tipPlanchet;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-03-11 20:14:28 +01:00
|
|
|
* Create and sign a message to recoup a coin.
|
2019-08-15 23:34:08 +02:00
|
|
|
*/
|
2020-03-11 20:14:28 +01:00
|
|
|
createRecoupRequest(coin: CoinRecord): RecoupRequest {
|
|
|
|
const p = buildSigPS(SignaturePurpose.WALLET_COIN_RECOUP)
|
2019-11-28 00:46:34 +01:00
|
|
|
.put(decodeCrock(coin.coinPub))
|
|
|
|
.put(decodeCrock(coin.denomPubHash))
|
|
|
|
.put(decodeCrock(coin.blindingKey))
|
|
|
|
.build();
|
|
|
|
|
|
|
|
const coinPriv = decodeCrock(coin.coinPriv);
|
|
|
|
const coinSig = eddsaSign(p, coinPriv);
|
2020-03-11 20:14:28 +01:00
|
|
|
const paybackRequest: RecoupRequest = {
|
2019-08-15 23:34:08 +02:00
|
|
|
coin_blind_key_secret: coin.blindingKey,
|
|
|
|
coin_pub: coin.coinPub,
|
2019-11-28 00:46:34 +01:00
|
|
|
coin_sig: encodeCrock(coinSig),
|
2020-03-12 14:55:38 +01:00
|
|
|
denom_pub_hash: coin.denomPubHash,
|
2019-08-15 23:34:08 +02:00
|
|
|
denom_sig: coin.denomSig,
|
2020-03-13 14:34:16 +01:00
|
|
|
refreshed: coin.coinSource.type === CoinSourceType.Refresh,
|
2019-08-15 23:34:08 +02:00
|
|
|
};
|
|
|
|
return paybackRequest;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a payment signature is valid.
|
|
|
|
*/
|
|
|
|
isValidPaymentSignature(
|
|
|
|
sig: string,
|
|
|
|
contractHash: string,
|
|
|
|
merchantPub: string,
|
|
|
|
): boolean {
|
2019-11-28 00:46:34 +01:00
|
|
|
const p = buildSigPS(SignaturePurpose.MERCHANT_PAYMENT_OK)
|
|
|
|
.put(decodeCrock(contractHash))
|
|
|
|
.build();
|
|
|
|
const sigBytes = decodeCrock(sig);
|
|
|
|
const pubBytes = decodeCrock(merchantPub);
|
|
|
|
return eddsaVerify(p, sigBytes, pubBytes);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a wire fee is correctly signed.
|
|
|
|
*/
|
|
|
|
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
2019-11-28 00:46:34 +01:00
|
|
|
const p = buildSigPS(SignaturePurpose.MASTER_WIRE_FEES)
|
|
|
|
.put(hash(stringToBytes(type + "\0")))
|
|
|
|
.put(timestampToBuffer(wf.startStamp))
|
|
|
|
.put(timestampToBuffer(wf.endStamp))
|
|
|
|
.put(amountToBuffer(wf.wireFee))
|
2019-12-05 23:07:46 +01:00
|
|
|
.put(amountToBuffer(wf.closingFee))
|
2019-11-28 00:46:34 +01:00
|
|
|
.build();
|
|
|
|
const sig = decodeCrock(wf.sig);
|
|
|
|
const pub = decodeCrock(masterPub);
|
|
|
|
return eddsaVerify(p, sig, pub);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the signature of a denomination is valid.
|
|
|
|
*/
|
|
|
|
isValidDenom(denom: DenominationRecord, masterPub: string): boolean {
|
2019-11-28 00:46:34 +01:00
|
|
|
const p = buildSigPS(SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
|
|
|
|
.put(decodeCrock(masterPub))
|
|
|
|
.put(timestampToBuffer(denom.stampStart))
|
|
|
|
.put(timestampToBuffer(denom.stampExpireWithdraw))
|
|
|
|
.put(timestampToBuffer(denom.stampExpireDeposit))
|
|
|
|
.put(timestampToBuffer(denom.stampExpireLegal))
|
|
|
|
.put(amountToBuffer(denom.value))
|
|
|
|
.put(amountToBuffer(denom.feeWithdraw))
|
|
|
|
.put(amountToBuffer(denom.feeDeposit))
|
|
|
|
.put(amountToBuffer(denom.feeRefresh))
|
|
|
|
.put(amountToBuffer(denom.feeRefund))
|
|
|
|
.put(decodeCrock(denom.denomPubHash))
|
|
|
|
.build();
|
|
|
|
const sig = decodeCrock(denom.masterSig);
|
|
|
|
const pub = decodeCrock(masterPub);
|
|
|
|
return eddsaVerify(p, sig, pub);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
2019-12-05 23:07:46 +01:00
|
|
|
isValidWireAccount(
|
|
|
|
paytoUri: string,
|
|
|
|
sig: string,
|
|
|
|
masterPub: string,
|
|
|
|
): boolean {
|
|
|
|
const h = kdf(
|
|
|
|
64,
|
|
|
|
stringToBytes("exchange-wire-signature"),
|
|
|
|
stringToBytes(paytoUri + "\0"),
|
|
|
|
new Uint8Array(0),
|
|
|
|
);
|
2020-03-30 12:39:32 +02:00
|
|
|
const p = buildSigPS(SignaturePurpose.MASTER_WIRE_DETAILS).put(h).build();
|
2019-12-05 23:07:46 +01:00
|
|
|
return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));
|
|
|
|
}
|
|
|
|
|
2019-08-15 23:34:08 +02:00
|
|
|
/**
|
|
|
|
* Create a new EdDSA key pair.
|
|
|
|
*/
|
|
|
|
createEddsaKeypair(): { priv: string; pub: string } {
|
2019-11-28 00:46:34 +01:00
|
|
|
const pair = createEddsaKeyPair();
|
|
|
|
return {
|
|
|
|
priv: encodeCrock(pair.eddsaPriv),
|
|
|
|
pub: encodeCrock(pair.eddsaPub),
|
|
|
|
};
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unblind a blindly signed value.
|
|
|
|
*/
|
2019-12-05 19:38:19 +01:00
|
|
|
rsaUnblind(blindedSig: string, bk: string, pk: string): string {
|
2019-11-28 00:46:34 +01:00
|
|
|
const denomSig = rsaUnblind(
|
2019-12-05 19:38:19 +01:00
|
|
|
decodeCrock(blindedSig),
|
2019-11-28 00:46:34 +01:00
|
|
|
decodeCrock(pk),
|
|
|
|
decodeCrock(bk),
|
2019-08-15 23:34:08 +02:00
|
|
|
);
|
2019-11-28 00:46:34 +01:00
|
|
|
return encodeCrock(denomSig);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
2019-12-05 19:38:19 +01:00
|
|
|
/**
|
|
|
|
* Unblind a blindly signed value.
|
|
|
|
*/
|
|
|
|
rsaVerify(hm: string, sig: string, pk: string): boolean {
|
|
|
|
return rsaVerify(hash(decodeCrock(hm)), decodeCrock(sig), decodeCrock(pk));
|
|
|
|
}
|
|
|
|
|
2019-08-15 23:34:08 +02:00
|
|
|
/**
|
|
|
|
* Generate updated coins (to store in the database)
|
|
|
|
* and deposit permissions for each given coin.
|
|
|
|
*/
|
2019-12-25 19:11:20 +01:00
|
|
|
signDepositPermission(depositInfo: DepositInfo): CoinDepositPermission {
|
|
|
|
const d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT)
|
|
|
|
.put(decodeCrock(depositInfo.contractTermsHash))
|
|
|
|
.put(decodeCrock(depositInfo.wireInfoHash))
|
|
|
|
.put(timestampToBuffer(depositInfo.timestamp))
|
|
|
|
.put(timestampToBuffer(depositInfo.refundDeadline))
|
|
|
|
.put(amountToBuffer(depositInfo.spendAmount))
|
|
|
|
.put(amountToBuffer(depositInfo.feeDeposit))
|
|
|
|
.put(decodeCrock(depositInfo.merchantPub))
|
|
|
|
.put(decodeCrock(depositInfo.coinPub))
|
|
|
|
.build();
|
|
|
|
const coinSig = eddsaSign(d, decodeCrock(depositInfo.coinPriv));
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2019-12-25 19:11:20 +01:00
|
|
|
const s: CoinDepositPermission = {
|
|
|
|
coin_pub: depositInfo.coinPub,
|
|
|
|
coin_sig: encodeCrock(coinSig),
|
2020-04-02 17:03:01 +02:00
|
|
|
contribution: Amounts.stringify(depositInfo.spendAmount),
|
2019-12-25 19:11:20 +01:00
|
|
|
denom_pub: depositInfo.denomPub,
|
|
|
|
exchange_url: depositInfo.exchangeBaseUrl,
|
|
|
|
ub_sig: depositInfo.denomSig,
|
|
|
|
};
|
|
|
|
return s;
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const sessionHc = createHashContext();
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
const transferPubs: string[] = [];
|
|
|
|
const transferPrivs: string[] = [];
|
|
|
|
|
2019-11-30 00:36:20 +01:00
|
|
|
const planchetsForGammas: RefreshPlanchetRecord[][] = [];
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
for (let i = 0; i < kappa; i++) {
|
2019-11-28 00:46:34 +01:00
|
|
|
const transferKeyPair = createEcdheKeyPair();
|
|
|
|
sessionHc.update(transferKeyPair.ecdhePub);
|
|
|
|
transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv));
|
|
|
|
transferPubs.push(encodeCrock(transferKeyPair.ecdhePub));
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const denom of newCoinDenoms) {
|
2019-11-28 00:46:34 +01:00
|
|
|
const r = decodeCrock(denom.denomPub);
|
|
|
|
sessionHc.update(r);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
sessionHc.update(decodeCrock(meltCoin.coinPub));
|
|
|
|
sessionHc.update(amountToBuffer(valueWithFee));
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
for (let i = 0; i < kappa; i++) {
|
2019-11-30 00:36:20 +01:00
|
|
|
const planchets: RefreshPlanchetRecord[] = [];
|
2019-08-15 23:34:08 +02:00
|
|
|
for (let j = 0; j < newCoinDenoms.length; j++) {
|
2019-11-28 00:46:34 +01:00
|
|
|
const transferPriv = decodeCrock(transferPrivs[i]);
|
|
|
|
const oldCoinPub = decodeCrock(meltCoin.coinPub);
|
|
|
|
const transferSecret = keyExchangeEcdheEddsa(transferPriv, oldCoinPub);
|
|
|
|
|
|
|
|
const fresh = setupRefreshPlanchet(transferSecret, j);
|
|
|
|
|
|
|
|
const coinPriv = fresh.coinPriv;
|
|
|
|
const coinPub = fresh.coinPub;
|
|
|
|
const blindingFactor = fresh.bks;
|
|
|
|
const pubHash = hash(coinPub);
|
|
|
|
const denomPub = decodeCrock(newCoinDenoms[j].denomPub);
|
|
|
|
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
2019-11-30 00:36:20 +01:00
|
|
|
const planchet: RefreshPlanchetRecord = {
|
2019-11-28 00:46:34 +01:00
|
|
|
blindingKey: encodeCrock(blindingFactor),
|
|
|
|
coinEv: encodeCrock(ev),
|
|
|
|
privateKey: encodeCrock(coinPriv),
|
|
|
|
publicKey: encodeCrock(coinPub),
|
2019-08-15 23:34:08 +02:00
|
|
|
};
|
2019-11-30 00:36:20 +01:00
|
|
|
planchets.push(planchet);
|
2019-11-28 00:46:34 +01:00
|
|
|
sessionHc.update(ev);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
2019-11-30 00:36:20 +01:00
|
|
|
planchetsForGammas.push(planchets);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const sessionHash = sessionHc.finish();
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT)
|
|
|
|
.put(sessionHash)
|
|
|
|
.put(amountToBuffer(valueWithFee))
|
|
|
|
.put(amountToBuffer(meltFee))
|
|
|
|
.put(decodeCrock(meltCoin.coinPub))
|
|
|
|
.build();
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const confirmSig = eddsaSign(confirmData, decodeCrock(meltCoin.coinPriv));
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
|
|
|
|
for (const denom of newCoinDenoms) {
|
|
|
|
valueOutput = Amounts.add(valueOutput, denom.value).amount;
|
|
|
|
}
|
|
|
|
|
|
|
|
const refreshSession: RefreshSessionRecord = {
|
2019-11-28 00:46:34 +01:00
|
|
|
confirmSig: encodeCrock(confirmSig),
|
2019-08-15 23:34:08 +02:00
|
|
|
exchangeBaseUrl,
|
2019-11-28 00:46:34 +01:00
|
|
|
hash: encodeCrock(sessionHash),
|
2019-08-15 23:34:08 +02:00
|
|
|
meltCoinPub: meltCoin.coinPub,
|
2020-03-30 12:39:32 +02:00
|
|
|
newDenomHashes: newCoinDenoms.map((d) => d.denomPubHash),
|
|
|
|
newDenoms: newCoinDenoms.map((d) => d.denomPub),
|
2019-08-15 23:34:08 +02:00
|
|
|
norevealIndex: undefined,
|
2019-11-30 00:36:20 +01:00
|
|
|
planchetsForGammas: planchetsForGammas,
|
2019-08-15 23:34:08 +02:00
|
|
|
transferPrivs,
|
|
|
|
transferPubs,
|
2019-12-16 16:20:45 +01:00
|
|
|
amountRefreshOutput: valueOutput,
|
|
|
|
amountRefreshInput: valueWithFee,
|
|
|
|
timestampCreated: getTimestampNow(),
|
2019-12-05 19:38:19 +01:00
|
|
|
finishedTimestamp: undefined,
|
|
|
|
lastError: undefined,
|
2019-08-15 23:34:08 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
return refreshSession;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hash a string including the zero terminator.
|
|
|
|
*/
|
|
|
|
hashString(str: string): string {
|
2019-11-28 00:46:34 +01:00
|
|
|
const ts = new TextEncoder();
|
|
|
|
const b = ts.encode(str + "\0");
|
|
|
|
return encodeCrock(hash(b));
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-04-02 17:03:01 +02:00
|
|
|
* Hash a crockford encoded value.
|
2019-08-15 23:34:08 +02:00
|
|
|
*/
|
2020-04-02 17:03:01 +02:00
|
|
|
hashEncoded(encodedBytes: string): string {
|
|
|
|
return encodeCrock(hash(decodeCrock(encodedBytes)));
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
signCoinLink(
|
|
|
|
oldCoinPriv: string,
|
|
|
|
newDenomHash: string,
|
|
|
|
oldCoinPub: string,
|
|
|
|
transferPub: string,
|
|
|
|
coinEv: string,
|
|
|
|
): string {
|
2019-11-28 00:46:34 +01:00
|
|
|
const coinEvHash = hash(decodeCrock(coinEv));
|
|
|
|
const coinLink = buildSigPS(SignaturePurpose.WALLET_COIN_LINK)
|
|
|
|
.put(decodeCrock(newDenomHash))
|
|
|
|
.put(decodeCrock(oldCoinPub))
|
|
|
|
.put(decodeCrock(transferPub))
|
|
|
|
.put(coinEvHash)
|
|
|
|
.build();
|
|
|
|
const coinPriv = decodeCrock(oldCoinPriv);
|
|
|
|
const sig = eddsaSign(coinLink, coinPriv);
|
|
|
|
return encodeCrock(sig);
|
2019-08-15 23:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
for (let i = 0; i < repetitions; i++) {
|
2019-11-28 00:46:34 +01:00
|
|
|
const ba = randomBytes(4096);
|
2019-08-15 23:34:08 +02:00
|
|
|
const start = timer.performanceNow();
|
2019-11-28 00:46:34 +01:00
|
|
|
hash(ba);
|
2019-08-15 23:34:08 +02:00
|
|
|
time_hash_big += timer.performanceNow() - start;
|
|
|
|
}
|
|
|
|
|
|
|
|
let time_eddsa_create = 0;
|
|
|
|
for (let i = 0; i < repetitions; i++) {
|
|
|
|
const start = timer.performanceNow();
|
2020-04-07 10:07:32 +02:00
|
|
|
createEddsaKeyPair();
|
2019-08-15 23:34:08 +02:00
|
|
|
time_eddsa_create += timer.performanceNow() - start;
|
|
|
|
}
|
|
|
|
|
|
|
|
let time_eddsa_sign = 0;
|
2019-11-28 00:46:34 +01:00
|
|
|
const p = randomBytes(4096);
|
2019-08-15 23:34:08 +02:00
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const pair = createEddsaKeyPair();
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
for (let i = 0; i < repetitions; i++) {
|
|
|
|
const start = timer.performanceNow();
|
2019-11-28 00:46:34 +01:00
|
|
|
eddsaSign(p, pair.eddsaPriv);
|
2019-08-15 23:34:08 +02:00
|
|
|
time_eddsa_sign += timer.performanceNow() - start;
|
|
|
|
}
|
|
|
|
|
2019-11-28 00:46:34 +01:00
|
|
|
const sig = eddsaSign(p, pair.eddsaPriv);
|
2019-08-15 23:34:08 +02:00
|
|
|
|
|
|
|
let time_eddsa_verify = 0;
|
|
|
|
for (let i = 0; i < repetitions; i++) {
|
|
|
|
const start = timer.performanceNow();
|
2019-11-28 00:46:34 +01:00
|
|
|
eddsaVerify(p, sig, pair.eddsaPub);
|
2019-08-15 23:34:08 +02:00
|
|
|
time_eddsa_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,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|