export complete backup, derive planchets in withdrawal

This commit is contained in:
Florian Dold 2020-12-17 12:21:03 +01:00
parent bafb52edff
commit 84d5b5e5ef
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 91 additions and 35 deletions

View File

@ -390,6 +390,25 @@ export function setupRefreshPlanchet(
}; };
} }
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,

View File

@ -61,13 +61,11 @@ import {
rsaVerify, rsaVerify,
setupRefreshTransferPub, setupRefreshTransferPub,
setupTipPlanchet, setupTipPlanchet,
setupWithdrawPlanchet,
} from "../talerCrypto"; } from "../talerCrypto";
import { randomBytes } from "../primitives/nacl-fast"; import { randomBytes } from "../primitives/nacl-fast";
import { kdf } from "../primitives/kdf"; import { kdf } from "../primitives/kdf";
import { import { Timestamp, timestampTruncateToSecond } from "../../util/time";
Timestamp,
timestampTruncateToSecond,
} from "../../util/time";
import { Logger } from "../../util/logging"; import { Logger } from "../../util/logging";
import { import {
@ -161,10 +159,12 @@ export class CryptoImplementation {
const reservePub = decodeCrock(req.reservePub); const reservePub = decodeCrock(req.reservePub);
const reservePriv = decodeCrock(req.reservePriv); const reservePriv = decodeCrock(req.reservePriv);
const denomPub = decodeCrock(req.denomPub); const denomPub = decodeCrock(req.denomPub);
const coinKeyPair = createEddsaKeyPair(); const derivedPlanchet = setupWithdrawPlanchet(
const blindingFactor = createBlindingKeySecret(); decodeCrock(req.secretSeed),
const coinPubHash = hash(coinKeyPair.eddsaPub); req.coinIndex,
const ev = rsaBlind(coinPubHash, blindingFactor, denomPub); );
const coinPubHash = hash(derivedPlanchet.coinPub);
const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPub);
const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount; const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount;
const denomPubHash = hash(denomPub); const denomPubHash = hash(denomPub);
const evHash = hash(ev); const evHash = hash(ev);
@ -179,10 +179,10 @@ export class CryptoImplementation {
const sig = eddsaSign(withdrawRequest, reservePriv); const sig = eddsaSign(withdrawRequest, reservePriv);
const planchet: PlanchetCreationResult = { const planchet: PlanchetCreationResult = {
blindingKey: encodeCrock(blindingFactor), blindingKey: encodeCrock(derivedPlanchet.bks),
coinEv: encodeCrock(ev), coinEv: encodeCrock(ev),
coinPriv: encodeCrock(coinKeyPair.eddsaPriv), coinPriv: encodeCrock(derivedPlanchet.coinPriv),
coinPub: encodeCrock(coinKeyPair.eddsaPub), coinPub: encodeCrock(derivedPlanchet.coinPub),
coinValue: req.value, coinValue: req.value,
denomPub: encodeCrock(denomPub), denomPub: encodeCrock(denomPub),
denomPubHash: encodeCrock(denomPubHash), denomPubHash: encodeCrock(denomPubHash),

View File

@ -198,17 +198,17 @@ export async function exportBackup(
const withdrawalGroups = (withdrawalGroupsByReserve[ const withdrawalGroups = (withdrawalGroupsByReserve[
wg.reservePub wg.reservePub
] ??= []); ] ??= []);
// FIXME: finish! withdrawalGroups.push({
// withdrawalGroups.push({ raw_withdrawal_amount: Amounts.stringify(wg.rawWithdrawalAmount),
// raw_withdrawal_amount: Amounts.stringify(wg.rawWithdrawalAmount), selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({
// selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({ count: x.count,
// count: x.count, denom_pub_hash: x.denomPubHash,
// denom_pub_hash: x.denomPubHash, })),
// })), timestamp_start: wg.timestampStart,
// timestamp_start: wg.timestampStart, timestamp_finish: wg.timestampFinish,
// timestamp_finish: wg.timestampFinish, withdrawal_group_id: wg.withdrawalGroupId,
// withdrawal_group_id: wg.withdrawalGroupId, secret_seed: wg.secretSeed
// }); });
}); });
await tx.iter(Stores.reserves).forEach((reserve) => { await tx.iter(Stores.reserves).forEach((reserve) => {
@ -572,11 +572,29 @@ export async function encryptBackup(
throw Error("not implemented"); throw Error("not implemented");
} }
export function importBackup( export async function importBackup(
ws: InternalWalletState, ws: InternalWalletState,
backupRequest: BackupRequest, backupRequest: BackupRequest,
): Promise<void> { ): Promise<void> {
throw Error("not implemented"); await provideBackupState(ws);
return ws.db.runWithWriteTransaction(
[
Stores.config,
Stores.exchanges,
Stores.coins,
Stores.denominations,
Stores.purchases,
Stores.proposals,
Stores.refreshGroups,
Stores.backupProviders,
Stores.tips,
Stores.recoupGroups,
Stores.reserves,
Stores.withdrawalGroups,
],
async (tx) => {
});
} }
function deriveAccountKeyPair( function deriveAccountKeyPair(

View File

@ -623,6 +623,7 @@ async function updateReserve(
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastError: undefined, lastError: undefined,
denomsSel: denomSelectionInfoToState(denomSelInfo), denomsSel: denomSelectionInfoToState(denomSelInfo),
secretSeed: encodeCrock(getRandomBytes(64)),
}; };
newReserve.lastError = undefined; newReserve.lastError = undefined;

View File

@ -48,7 +48,7 @@ import {
} from "../util/http"; } from "../util/http";
import { URL } from "../util/url"; import { URL } from "../util/url";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
import { checkDbInvariant } from "../util/invariants"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants";
import { TalerErrorCode } from "../TalerErrorCode"; import { TalerErrorCode } from "../TalerErrorCode";
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
import { j2s } from "../util/helpers"; import { j2s } from "../util/helpers";
@ -221,13 +221,13 @@ async function processTipImpl(
dh.denomPubHash, dh.denomPubHash,
]); ]);
checkDbInvariant(!!denom, "denomination should be in database"); checkDbInvariant(!!denom, "denomination should be in database");
denomForPlanchet[planchets.length] = denom;
for (let i = 0; i < dh.count; i++) { for (let i = 0; i < dh.count; i++) {
const p = await ws.cryptoApi.createTipPlanchet({ const p = await ws.cryptoApi.createTipPlanchet({
denomPub: dh.denomPubHash, denomPub: denom.denomPub,
planchetIndex: planchets.length, planchetIndex: planchets.length,
secretSeed: tipRecord.secretSeed, secretSeed: tipRecord.secretSeed,
}); });
denomForPlanchet[planchets.length] = denom;
planchets.push(p); planchets.push(p);
planchetsDetail.push({ planchetsDetail.push({
coin_ev: p.coinEv, coin_ev: p.coinEv,
@ -275,7 +275,9 @@ async function processTipImpl(
const blindedSig = response.blind_sigs[i].blind_sig; const blindedSig = response.blind_sigs[i].blind_sig;
const denom = denomForPlanchet[i]; const denom = denomForPlanchet[i];
checkLogicInvariant(!!denom);
const planchet = planchets[i]; const planchet = planchets[i];
checkLogicInvariant(!!planchet);
const denomSig = await ws.cryptoApi.rsaUnblind( const denomSig = await ws.cryptoApi.rsaUnblind(
blindedSig, blindedSig,

View File

@ -284,6 +284,8 @@ async function processPlanchetGenerate(
reservePriv: reserve.reservePriv, reservePriv: reserve.reservePriv,
reservePub: reserve.reservePub, reservePub: reserve.reservePub,
value: denom.value, value: denom.value,
coinIndex: coinIdx,
secretSeed: withdrawalGroup.secretSeed,
}); });
const newPlanchet: PlanchetRecord = { const newPlanchet: PlanchetRecord = {
blindingKey: r.blindingKey, blindingKey: r.blindingKey,

View File

@ -625,6 +625,11 @@ export interface BackupRefreshGroup {
export interface BackupWithdrawalGroup { export interface BackupWithdrawalGroup {
withdrawal_group_id: string; withdrawal_group_id: string;
/**
* Secret seed to derive the planchets.
*/
secret_seed: string;
/** /**
* When was the withdrawal operation started started? * When was the withdrawal operation started started?
* Timestamp in milliseconds. * Timestamp in milliseconds.
@ -653,14 +658,6 @@ export interface BackupWithdrawalGroup {
denom_pub_hash: string; denom_pub_hash: string;
count: number; count: number;
}[]; }[];
/**
* One planchet/coin for each selected denomination.
*/
planchets: {
blinding_key: string;
coin_priv: string;
}[];
} }
export enum BackupRefundState { export enum BackupRefundState {

View File

@ -1322,6 +1322,11 @@ export interface DenomSelectionState {
export interface WithdrawalGroupRecord { export interface WithdrawalGroupRecord {
withdrawalGroupId: string; withdrawalGroupId: string;
/**
* Secret seed used to derive planchets.
*/
secretSeed: string;
reservePub: string; reservePub: string;
exchangeBaseUrl: string; exchangeBaseUrl: string;

View File

@ -557,6 +557,8 @@ export interface PlanchetCreationResult {
} }
export interface PlanchetCreationRequest { export interface PlanchetCreationRequest {
secretSeed: string;
coinIndex: number;
value: AmountJson; value: AmountJson;
feeWithdraw: AmountJson; feeWithdraw: AmountJson;
denomPub: string; denomPub: string;

View File

@ -27,3 +27,13 @@ export function checkDbInvariant(b: boolean, m?: string): asserts b {
} }
} }
} }
export function checkLogicInvariant(b: boolean, m?: string): asserts b {
if (!b) {
if (m) {
throw Error(`BUG: logic invariant failed (${m})`);
} else {
throw Error("BUG: logic invariant failed");
}
}
}