export complete backup, derive planchets in withdrawal
This commit is contained in:
parent
bafb52edff
commit
84d5b5e5ef
@ -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,
|
||||||
|
@ -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),
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user