wallet: allow using RPC crypto in more places

This commit is contained in:
Florian Dold 2022-03-24 01:59:08 +01:00
parent 454b55aa56
commit 303c6e99b3
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 173 additions and 83 deletions

View File

@ -700,25 +700,6 @@ export function bufferForUint32(n: number): Uint8Array {
return buf; return buf;
} }
export function setupWithdrawPlanchet(
secretSeed: Uint8Array,
coinNumber: number,
): FreshCoin {
const info = stringToBytes("taler-withdrawal-coin-derivation");
const saltArrBuf = new ArrayBuffer(4);
const salt = new Uint8Array(saltArrBuf);
const saltDataView = new DataView(saltArrBuf);
saltDataView.setUint32(0, coinNumber);
const out = kdf(64, secretSeed, salt, info);
const coinPriv = out.slice(0, 32);
const bks = out.slice(32, 64);
return {
bks,
coinPriv,
coinPub: eddsaGetPublic(coinPriv),
};
}
export function setupTipPlanchet( export function setupTipPlanchet(
secretSeed: Uint8Array, secretSeed: Uint8Array,
coinNumber: number, coinNumber: number,
@ -737,23 +718,6 @@ export function setupTipPlanchet(
coinPub: eddsaGetPublic(coinPriv), coinPub: eddsaGetPublic(coinPriv),
}; };
} }
export function setupRefreshTransferPub(
secretSeed: Uint8Array,
transferPubIndex: number,
): EcdheKeyPair {
const info = stringToBytes("taler-transfer-pub-derivation");
const saltArrBuf = new ArrayBuffer(4);
const salt = new Uint8Array(saltArrBuf);
const saltDataView = new DataView(saltArrBuf);
saltDataView.setUint32(0, transferPubIndex);
const out = kdf(32, secretSeed, salt, info);
return {
ecdhePriv: out,
ecdhePub: ecdheGetPublic(out),
};
}
/** /**
* *
* @param paytoUri * @param paytoUri

View File

@ -31,7 +31,6 @@ import {
depositCoin, depositCoin,
downloadExchangeInfo, downloadExchangeInfo,
findDenomOrThrow, findDenomOrThrow,
generateReserveKeypair,
NodeHttpLib, NodeHttpLib,
refreshCoin, refreshCoin,
SynchronousCryptoWorkerFactory, SynchronousCryptoWorkerFactory,
@ -64,7 +63,7 @@ export async function runBench2(configJson: any): Promise<void> {
for (let i = 0; i < numIter; i++) { for (let i = 0; i < numIter; i++) {
const exchangeInfo = await downloadExchangeInfo(benchConf.exchange, http); const exchangeInfo = await downloadExchangeInfo(benchConf.exchange, http);
const reserveKeyPair = generateReserveKeypair(); const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
console.log("creating fakebank reserve"); console.log("creating fakebank reserve");
@ -73,12 +72,12 @@ export async function runBench2(configJson: any): Promise<void> {
exchangeInfo, exchangeInfo,
fakebankBaseUrl: benchConf.bank, fakebankBaseUrl: benchConf.bank,
http, http,
reservePub: reserveKeyPair.reservePub, reservePub: reserveKeyPair.pub,
}); });
console.log("waiting for reserve"); console.log("waiting for reserve");
await checkReserve(http, benchConf.exchange, reserveKeyPair.reservePub); await checkReserve(http, benchConf.exchange, reserveKeyPair.pub);
console.log("reserve found"); console.log("reserve found");
@ -89,7 +88,10 @@ export async function runBench2(configJson: any): Promise<void> {
const coin = await withdrawCoin({ const coin = await withdrawCoin({
http, http,
cryptoApi, cryptoApi,
reserveKeyPair, reserveKeyPair: {
reservePriv: reserveKeyPair.priv,
reservePub: reserveKeyPair.pub,
},
denom: d1, denom: d1,
exchangeBaseUrl: benchConf.exchange, exchangeBaseUrl: benchConf.exchange,
}); });

View File

@ -24,7 +24,6 @@ import {
depositCoin, depositCoin,
downloadExchangeInfo, downloadExchangeInfo,
findDenomOrThrow, findDenomOrThrow,
generateReserveKeypair,
NodeHttpLib, NodeHttpLib,
refreshCoin, refreshCoin,
SynchronousCryptoWorkerFactory, SynchronousCryptoWorkerFactory,
@ -52,11 +51,11 @@ export async function runWalletDblessTest(t: GlobalTestState) {
const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http); const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http);
const reserveKeyPair = generateReserveKeypair(); const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
await topupReserveWithDemobank( await topupReserveWithDemobank(
http, http,
reserveKeyPair.reservePub, reserveKeyPair.pub,
bank.baseUrl, bank.baseUrl,
exchangeInfo, exchangeInfo,
"TESTKUDOS:10", "TESTKUDOS:10",
@ -64,14 +63,17 @@ export async function runWalletDblessTest(t: GlobalTestState) {
await exchange.runWirewatchOnce(); await exchange.runWirewatchOnce();
await checkReserve(http, exchange.baseUrl, reserveKeyPair.reservePub); await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);
const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8"); const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8");
const coin = await withdrawCoin({ const coin = await withdrawCoin({
http, http,
cryptoApi, cryptoApi,
reserveKeyPair, reserveKeyPair: {
reservePriv: reserveKeyPair.priv,
reservePub: reserveKeyPair.pub,
},
denom: d1, denom: d1,
exchangeBaseUrl: exchange.baseUrl, exchangeBaseUrl: exchange.baseUrl,
}); });

View File

@ -28,7 +28,6 @@
import { import {
AmountJson, AmountJson,
Amounts, Amounts,
BenchmarkResult,
buildSigPS, buildSigPS,
CoinDepositPermission, CoinDepositPermission,
CoinEnvelope, CoinEnvelope,
@ -58,9 +57,7 @@ import {
rsaBlind, rsaBlind,
rsaUnblind, rsaUnblind,
rsaVerify, rsaVerify,
setupRefreshTransferPub,
setupTipPlanchet, setupTipPlanchet,
setupWithdrawPlanchet,
stringToBytes, stringToBytes,
TalerSignaturePurpose, TalerSignaturePurpose,
BlindedDenominationSignature, BlindedDenominationSignature,
@ -69,9 +66,12 @@ import {
TalerProtocolTimestamp, TalerProtocolTimestamp,
kdfKw, kdfKw,
bufferForUint32, bufferForUint32,
kdf,
ecdheGetPublic,
getRandomBytes,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import bigint from "big-integer"; import bigint from "big-integer";
import { DenominationRecord, WireFee } from "../db.js"; import { DenominationRecord, TipCoinSource, WireFee } from "../db.js";
import { import {
CreateRecoupRefreshReqRequest, CreateRecoupRefreshReqRequest,
CreateRecoupReqRequest, CreateRecoupReqRequest,
@ -130,7 +130,7 @@ export interface TalerCryptoInterface {
createEddsaKeypair(req: {}): Promise<EddsaKeypair>; createEddsaKeypair(req: {}): Promise<EddsaKeypair>;
eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaKeypair>; eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
unblindDenominationSignature( unblindDenominationSignature(
req: UnblindDenominationSignatureRequest, req: UnblindDenominationSignatureRequest,
@ -160,9 +160,19 @@ export interface TalerCryptoInterface {
req: SetupRefreshPlanchetRequest, req: SetupRefreshPlanchetRequest,
): Promise<FreshCoinEncoded>; ): Promise<FreshCoinEncoded>;
setupWithdrawalPlanchet(
req: SetupWithdrawalPlanchetRequest,
): Promise<FreshCoinEncoded>;
keyExchangeEcdheEddsa( keyExchangeEcdheEddsa(
req: KeyExchangeEcdheEddsaRequest, req: KeyExchangeEcdheEddsaRequest,
): Promise<KeyExchangeResult>; ): Promise<KeyExchangeResult>;
ecdheGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
setupRefreshTransferPub(
req: SetupRefreshTransferPubRequest,
): Promise<TransferPubResponse>;
} }
/** /**
@ -279,6 +289,21 @@ export const nullCrypto: TalerCryptoInterface = {
): Promise<KeyExchangeResult> { ): Promise<KeyExchangeResult> {
throw new Error("Function not implemented."); throw new Error("Function not implemented.");
}, },
setupWithdrawalPlanchet: function (
req: SetupWithdrawalPlanchetRequest,
): Promise<FreshCoinEncoded> {
throw new Error("Function not implemented.");
},
ecdheGetPublic: function (
req: EddsaGetPublicRequest,
): Promise<EddsaGetPublicResponse> {
throw new Error("Function not implemented.");
},
setupRefreshTransferPub: function (
req: SetupRefreshTransferPubRequest,
): Promise<TransferPubResponse> {
throw new Error("Function not implemented.");
},
}; };
export type WithArg<X> = X extends (req: infer T) => infer R export type WithArg<X> = X extends (req: infer T) => infer R
@ -302,6 +327,11 @@ export interface SetupRefreshPlanchetRequest {
coinNumber: number; coinNumber: number;
} }
export interface SetupWithdrawalPlanchetRequest {
secretSeed: string;
coinNumber: number;
}
export interface RsaVerificationRequest { export interface RsaVerificationRequest {
hm: string; hm: string;
sig: string; sig: string;
@ -369,6 +399,18 @@ export interface EddsaGetPublicRequest {
priv: string; priv: string;
} }
export interface EddsaGetPublicResponse {
pub: string;
}
export interface EcdheGetPublicRequest {
priv: string;
}
export interface EcdheGetPublicResponse {
pub: string;
}
export interface UnblindDenominationSignatureRequest { export interface UnblindDenominationSignatureRequest {
planchet: PlanchetUnblindInfo; planchet: PlanchetUnblindInfo;
evSig: BlindedDenominationSignature; evSig: BlindedDenominationSignature;
@ -403,6 +445,16 @@ export interface KeyExchangeResult {
h: string; h: string;
} }
export interface SetupRefreshTransferPubRequest {
secretSeed: string;
transferPubIndex: number;
}
export interface TransferPubResponse {
transferPub: string;
transferPriv: string;
}
export const nativeCryptoR: TalerCryptoInterfaceR = { export const nativeCryptoR: TalerCryptoInterfaceR = {
async eddsaSign( async eddsaSign(
tci: TalerCryptoInterfaceR, tci: TalerCryptoInterfaceR,
@ -453,10 +505,38 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
salt: stringToBytes("bks"), salt: stringToBytes("bks"),
}); });
const coinPrivEnc = encodeCrock(coinPriv);
const coinPubRes = await tci.eddsaGetPublic(tci, {
priv: coinPrivEnc,
});
return { return {
bks: encodeCrock(bks), bks: encodeCrock(bks),
coinPriv: encodeCrock(coinPriv), coinPriv: coinPrivEnc,
coinPub: encodeCrock(eddsaGetPublic(coinPriv)), coinPub: coinPubRes.pub,
};
},
async setupWithdrawalPlanchet(
tci: TalerCryptoInterfaceR,
req: SetupWithdrawalPlanchetRequest,
): Promise<FreshCoinEncoded> {
const info = stringToBytes("taler-withdrawal-coin-derivation");
const saltArrBuf = new ArrayBuffer(4);
const salt = new Uint8Array(saltArrBuf);
const saltDataView = new DataView(saltArrBuf);
saltDataView.setUint32(0, req.coinNumber);
const out = kdf(64, decodeCrock(req.secretSeed), salt, info);
const coinPriv = out.slice(0, 32);
const bks = out.slice(32, 64);
const coinPrivEnc = encodeCrock(coinPriv);
const coinPubRes = await tci.eddsaGetPublic(tci, {
priv: coinPrivEnc,
});
return {
bks: encodeCrock(bks),
coinPriv: coinPrivEnc,
coinPub: coinPubRes.pub,
}; };
}, },
@ -468,13 +548,13 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
if (denomPub.cipher === DenomKeyType.Rsa) { if (denomPub.cipher === DenomKeyType.Rsa) {
const reservePub = decodeCrock(req.reservePub); const reservePub = decodeCrock(req.reservePub);
const denomPubRsa = decodeCrock(denomPub.rsa_public_key); const denomPubRsa = decodeCrock(denomPub.rsa_public_key);
const derivedPlanchet = setupWithdrawPlanchet( const derivedPlanchet = await tci.setupWithdrawalPlanchet(tci, {
decodeCrock(req.secretSeed), coinNumber: req.coinIndex,
req.coinIndex, secretSeed: req.secretSeed,
); });
const coinPubHash = hash(derivedPlanchet.coinPub); const coinPubHash = hash(decodeCrock(derivedPlanchet.coinPub));
const blindResp = await tci.rsaBlind(tci, { const blindResp = await tci.rsaBlind(tci, {
bks: encodeCrock(derivedPlanchet.bks), bks: derivedPlanchet.bks,
hm: encodeCrock(coinPubHash), hm: encodeCrock(coinPubHash),
pub: denomPub.rsa_public_key, pub: denomPub.rsa_public_key,
}); });
@ -499,10 +579,10 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
}); });
const planchet: WithdrawalPlanchet = { const planchet: WithdrawalPlanchet = {
blindingKey: encodeCrock(derivedPlanchet.bks), blindingKey: derivedPlanchet.bks,
coinEv, coinEv,
coinPriv: encodeCrock(derivedPlanchet.coinPriv), coinPriv: derivedPlanchet.coinPriv,
coinPub: encodeCrock(derivedPlanchet.coinPub), coinPub: derivedPlanchet.coinPub,
coinValue: req.value, coinValue: req.value,
denomPub, denomPub,
denomPubHash: encodeCrock(denomPubHash), denomPubHash: encodeCrock(denomPubHash),
@ -715,10 +795,13 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
* Create a new EdDSA key pair. * Create a new EdDSA key pair.
*/ */
async createEddsaKeypair(tci: TalerCryptoInterfaceR): Promise<EddsaKeypair> { async createEddsaKeypair(tci: TalerCryptoInterfaceR): Promise<EddsaKeypair> {
const pair = createEddsaKeyPair(); const eddsaPriv = encodeCrock(getRandomBytes(32));
const eddsaPubRes = await tci.eddsaGetPublic(tci, {
priv: eddsaPriv,
});
return { return {
priv: encodeCrock(pair.eddsaPriv), priv: eddsaPriv,
pub: encodeCrock(pair.eddsaPub), pub: eddsaPubRes.pub,
}; };
}, },
@ -876,13 +959,13 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
const planchetsForGammas: RefreshPlanchetInfo[][] = []; const planchetsForGammas: RefreshPlanchetInfo[][] = [];
for (let i = 0; i < kappa; i++) { for (let i = 0; i < kappa; i++) {
const transferKeyPair = setupRefreshTransferPub( const transferKeyPair = await tci.setupRefreshTransferPub(tci, {
decodeCrock(refreshSessionSecretSeed), secretSeed: refreshSessionSecretSeed,
i, transferPubIndex: i,
); });
sessionHc.update(transferKeyPair.ecdhePub); sessionHc.update(decodeCrock(transferKeyPair.transferPub));
transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv)); transferPrivs.push(transferKeyPair.transferPriv);
transferPubs.push(encodeCrock(transferKeyPair.ecdhePub)); transferPubs.push(transferKeyPair.transferPub);
} }
for (const denomSel of newCoinDenoms) { for (const denomSel of newCoinDenoms) {
@ -1041,6 +1124,30 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
), ),
}; };
}, },
async ecdheGetPublic(
tci: TalerCryptoInterfaceR,
req: EcdheGetPublicRequest,
): Promise<EcdheGetPublicResponse> {
return {
pub: encodeCrock(ecdheGetPublic(decodeCrock(req.priv))),
};
},
async setupRefreshTransferPub(
tci: TalerCryptoInterfaceR,
req: SetupRefreshTransferPubRequest,
): Promise<TransferPubResponse> {
const info = stringToBytes("taler-transfer-pub-derivation");
const saltArrBuf = new ArrayBuffer(4);
const salt = new Uint8Array(saltArrBuf);
const saltDataView = new DataView(saltArrBuf);
saltDataView.setUint32(0, req.transferPubIndex);
const out = kdf(32, decodeCrock(req.secretSeed), salt, info);
const transferPriv = encodeCrock(out);
return {
transferPriv,
transferPub: (await tci.ecdheGetPublic(tci, { priv: transferPriv })).pub,
};
},
}; };
function amountToBuffer(amount: AmountJson): Uint8Array { function amountToBuffer(amount: AmountJson): Uint8Array {

View File

@ -86,7 +86,6 @@ export class SynchronousCryptoWorker {
blinded: res.blinded, blinded: res.blinded,
}; };
}; };
this.cryptoImplR.keyExchangeEcdheEddsa = async (_, req) => { this.cryptoImplR.keyExchangeEcdheEddsa = async (_, req) => {
const res = await rpc.queueRequest({ const res = await rpc.queueRequest({
op: "kx_ecdhe_eddsa", op: "kx_ecdhe_eddsa",
@ -99,6 +98,28 @@ export class SynchronousCryptoWorker {
h: res.h, h: res.h,
}; };
}; };
this.cryptoImplR.eddsaGetPublic = async (_, req) => {
const res = await rpc.queueRequest({
op: "eddsa_get_public",
args: {
eddsa_priv: req.priv,
},
});
return {
pub: res.eddsa_pub,
};
};
this.cryptoImplR.ecdheGetPublic = async (_, req) => {
const res = await rpc.queueRequest({
op: "ecdhe_get_public",
args: {
ecdhe_priv: req.priv,
},
});
return {
pub: res.ecdhe_pub,
};
};
} }
} }

View File

@ -80,15 +80,6 @@ export interface CoinInfo {
feeRefresh: string; feeRefresh: string;
} }
export function generateReserveKeypair(): ReserveKeypair {
const priv = getRandomBytes(32);
const pub = eddsaGetPublic(priv);
return {
reservePriv: encodeCrock(priv),
reservePub: encodeCrock(pub),
};
}
/** /**
* Check the status of a reserve, use long-polling to wait * Check the status of a reserve, use long-polling to wait
* until the reserve actually has been created. * until the reserve actually has been created.

View File

@ -927,7 +927,10 @@ async function startDownloadProposal(
let noncePair: EddsaKeypair; let noncePair: EddsaKeypair;
if (noncePriv) { if (noncePriv) {
noncePair = await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv }); noncePair = {
priv: noncePriv,
pub: (await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv })).pub,
};
} else { } else {
noncePair = await ws.cryptoApi.createEddsaKeypair({}); noncePair = await ws.cryptoApi.createEddsaKeypair({});
} }