derive refresh info from secret seed
This commit is contained in:
parent
80a0fab126
commit
12234083ec
@ -389,3 +389,19 @@ export function setupRefreshPlanchet(
|
|||||||
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -47,6 +47,10 @@ import {
|
|||||||
|
|
||||||
import * as timer from "../../util/timer";
|
import * as timer from "../../util/timer";
|
||||||
import { Logger } from "../../util/logging";
|
import { Logger } from "../../util/logging";
|
||||||
|
import {
|
||||||
|
DerivedRefreshSession,
|
||||||
|
DeriveRefreshSessionRequest,
|
||||||
|
} from "../../types/cryptoTypes";
|
||||||
|
|
||||||
const logger = new Logger("cryptoApi.ts");
|
const logger = new Logger("cryptoApi.ts");
|
||||||
|
|
||||||
@ -417,22 +421,10 @@ export class CryptoApi {
|
|||||||
return this.doRpc<RecoupRequest>("createRecoupRequest", 1, coin);
|
return this.doRpc<RecoupRequest>("createRecoupRequest", 1, coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
createRefreshSession(
|
deriveRefreshSession(
|
||||||
exchangeBaseUrl: string,
|
req: DeriveRefreshSessionRequest,
|
||||||
kappa: number,
|
): Promise<DerivedRefreshSession> {
|
||||||
meltCoin: CoinRecord,
|
return this.doRpc<DerivedRefreshSession>("deriveRefreshSession", 4, req);
|
||||||
newCoinDenoms: DenominationSelectionInfo,
|
|
||||||
meltFee: AmountJson,
|
|
||||||
): Promise<RefreshSessionRecord> {
|
|
||||||
return this.doRpc<RefreshSessionRecord>(
|
|
||||||
"createRefreshSession",
|
|
||||||
4,
|
|
||||||
exchangeBaseUrl,
|
|
||||||
kappa,
|
|
||||||
meltCoin,
|
|
||||||
newCoinDenoms,
|
|
||||||
meltFee,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signCoinLink(
|
signCoinLink(
|
||||||
|
@ -63,6 +63,8 @@ import {
|
|||||||
keyExchangeEcdheEddsa,
|
keyExchangeEcdheEddsa,
|
||||||
setupRefreshPlanchet,
|
setupRefreshPlanchet,
|
||||||
rsaVerify,
|
rsaVerify,
|
||||||
|
getRandomBytes,
|
||||||
|
setupRefreshTransferPub,
|
||||||
} 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";
|
||||||
@ -73,6 +75,10 @@ import {
|
|||||||
} from "../../util/time";
|
} from "../../util/time";
|
||||||
|
|
||||||
import { Logger } from "../../util/logging";
|
import { Logger } from "../../util/logging";
|
||||||
|
import {
|
||||||
|
DerivedRefreshSession,
|
||||||
|
DeriveRefreshSessionRequest,
|
||||||
|
} from "../../types/cryptoTypes";
|
||||||
|
|
||||||
const logger = new Logger("cryptoImplementation.ts");
|
const logger = new Logger("cryptoImplementation.ts");
|
||||||
|
|
||||||
@ -375,21 +381,24 @@ export class CryptoImplementation {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
deriveRefreshSession(
|
||||||
* Create a new refresh session.
|
req: DeriveRefreshSessionRequest,
|
||||||
*/
|
): DerivedRefreshSession {
|
||||||
createRefreshSession(
|
const {
|
||||||
exchangeBaseUrl: string,
|
newCoinDenoms,
|
||||||
kappa: number,
|
feeRefresh: meltFee,
|
||||||
meltCoin: CoinRecord,
|
kappa,
|
||||||
newCoinDenoms: DenominationSelectionInfo,
|
meltCoinDenomPubHash,
|
||||||
meltFee: AmountJson,
|
meltCoinPriv,
|
||||||
): RefreshSessionRecord {
|
meltCoinPub,
|
||||||
const currency = newCoinDenoms.selectedDenoms[0].denom.value.currency;
|
sessionSecretSeed: refreshSessionSecretSeed,
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const currency = newCoinDenoms[0].value.currency;
|
||||||
let valueWithFee = Amounts.getZero(currency);
|
let valueWithFee = Amounts.getZero(currency);
|
||||||
|
|
||||||
for (const ncd of newCoinDenoms.selectedDenoms) {
|
for (const ncd of newCoinDenoms) {
|
||||||
const t = Amounts.add(ncd.denom.value, ncd.denom.feeWithdraw).amount;
|
const t = Amounts.add(ncd.value, ncd.feeWithdraw).amount;
|
||||||
valueWithFee = Amounts.add(
|
valueWithFee = Amounts.add(
|
||||||
valueWithFee,
|
valueWithFee,
|
||||||
Amounts.mult(t, ncd.count).amount,
|
Amounts.mult(t, ncd.count).amount,
|
||||||
@ -409,7 +418,10 @@ export class CryptoImplementation {
|
|||||||
logger.trace("starting RC computation");
|
logger.trace("starting RC computation");
|
||||||
|
|
||||||
for (let i = 0; i < kappa; i++) {
|
for (let i = 0; i < kappa; i++) {
|
||||||
const transferKeyPair = createEcdheKeyPair();
|
const transferKeyPair = setupRefreshTransferPub(
|
||||||
|
decodeCrock(refreshSessionSecretSeed),
|
||||||
|
i,
|
||||||
|
);
|
||||||
sessionHc.update(transferKeyPair.ecdhePub);
|
sessionHc.update(transferKeyPair.ecdhePub);
|
||||||
logger.trace(
|
logger.trace(
|
||||||
`HASH transfer_pub ${encodeCrock(transferKeyPair.ecdhePub)}`,
|
`HASH transfer_pub ${encodeCrock(transferKeyPair.ecdhePub)}`,
|
||||||
@ -418,16 +430,16 @@ export class CryptoImplementation {
|
|||||||
transferPubs.push(encodeCrock(transferKeyPair.ecdhePub));
|
transferPubs.push(encodeCrock(transferKeyPair.ecdhePub));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const denomSel of newCoinDenoms.selectedDenoms) {
|
for (const denomSel of newCoinDenoms) {
|
||||||
for (let i = 0; i < denomSel.count; i++) {
|
for (let i = 0; i < denomSel.count; i++) {
|
||||||
const r = decodeCrock(denomSel.denom.denomPub);
|
const r = decodeCrock(denomSel.denomPub);
|
||||||
sessionHc.update(r);
|
sessionHc.update(r);
|
||||||
logger.trace(`HASH new_coins ${encodeCrock(r)}`);
|
logger.trace(`HASH new_coins ${encodeCrock(r)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionHc.update(decodeCrock(meltCoin.coinPub));
|
sessionHc.update(decodeCrock(meltCoinPub));
|
||||||
logger.trace(`HASH coin_pub ${meltCoin.coinPub}`);
|
logger.trace(`HASH coin_pub ${meltCoinPub}`);
|
||||||
sessionHc.update(amountToBuffer(valueWithFee));
|
sessionHc.update(amountToBuffer(valueWithFee));
|
||||||
logger.trace(
|
logger.trace(
|
||||||
`HASH melt_amount ${encodeCrock(amountToBuffer(valueWithFee))}`,
|
`HASH melt_amount ${encodeCrock(amountToBuffer(valueWithFee))}`,
|
||||||
@ -435,12 +447,12 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
for (let i = 0; i < kappa; i++) {
|
for (let i = 0; i < kappa; i++) {
|
||||||
const planchets: RefreshPlanchet[] = [];
|
const planchets: RefreshPlanchet[] = [];
|
||||||
for (let j = 0; j < newCoinDenoms.selectedDenoms.length; j++) {
|
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||||
const denomSel = newCoinDenoms.selectedDenoms[j];
|
const denomSel = newCoinDenoms[j];
|
||||||
for (let k = 0; k < denomSel.count; k++) {
|
for (let k = 0; k < denomSel.count; k++) {
|
||||||
const coinNumber = planchets.length;
|
const coinNumber = planchets.length;
|
||||||
const transferPriv = decodeCrock(transferPrivs[i]);
|
const transferPriv = decodeCrock(transferPrivs[i]);
|
||||||
const oldCoinPub = decodeCrock(meltCoin.coinPub);
|
const oldCoinPub = decodeCrock(meltCoinPub);
|
||||||
const transferSecret = keyExchangeEcdheEddsa(
|
const transferSecret = keyExchangeEcdheEddsa(
|
||||||
transferPriv,
|
transferPriv,
|
||||||
oldCoinPub,
|
oldCoinPub,
|
||||||
@ -450,7 +462,7 @@ export class CryptoImplementation {
|
|||||||
const coinPub = fresh.coinPub;
|
const coinPub = fresh.coinPub;
|
||||||
const blindingFactor = fresh.bks;
|
const blindingFactor = fresh.bks;
|
||||||
const pubHash = hash(coinPub);
|
const pubHash = hash(coinPub);
|
||||||
const denomPub = decodeCrock(denomSel.denom.denomPub);
|
const denomPub = decodeCrock(denomSel.denomPub);
|
||||||
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
||||||
const planchet: RefreshPlanchet = {
|
const planchet: RefreshPlanchet = {
|
||||||
blindingKey: encodeCrock(blindingFactor),
|
blindingKey: encodeCrock(blindingFactor),
|
||||||
@ -481,49 +493,22 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT)
|
const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT)
|
||||||
.put(sessionHash)
|
.put(sessionHash)
|
||||||
.put(decodeCrock(meltCoin.denomPubHash))
|
.put(decodeCrock(meltCoinDenomPubHash))
|
||||||
.put(amountToBuffer(valueWithFee))
|
.put(amountToBuffer(valueWithFee))
|
||||||
.put(amountToBuffer(meltFee))
|
.put(amountToBuffer(meltFee))
|
||||||
.put(decodeCrock(meltCoin.coinPub))
|
.put(decodeCrock(meltCoinPub))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
const confirmSig = eddsaSign(confirmData, decodeCrock(meltCoin.coinPriv));
|
const confirmSig = eddsaSign(confirmData, decodeCrock(meltCoinPriv));
|
||||||
|
|
||||||
let valueOutput = Amounts.getZero(currency);
|
const refreshSession: DerivedRefreshSession = {
|
||||||
for (const denomSel of newCoinDenoms.selectedDenoms) {
|
|
||||||
const denom = denomSel.denom;
|
|
||||||
for (let i = 0; i < denomSel.count; i++) {
|
|
||||||
valueOutput = Amounts.add(valueOutput, denom.value).amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newDenoms: string[] = [];
|
|
||||||
const newDenomHashes: string[] = [];
|
|
||||||
|
|
||||||
for (const denomSel of newCoinDenoms.selectedDenoms) {
|
|
||||||
const denom = denomSel.denom;
|
|
||||||
for (let i = 0; i < denomSel.count; i++) {
|
|
||||||
newDenoms.push(denom.denomPub);
|
|
||||||
newDenomHashes.push(denom.denomPubHash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshSession: RefreshSessionRecord = {
|
|
||||||
confirmSig: encodeCrock(confirmSig),
|
confirmSig: encodeCrock(confirmSig),
|
||||||
exchangeBaseUrl,
|
|
||||||
hash: encodeCrock(sessionHash),
|
hash: encodeCrock(sessionHash),
|
||||||
meltCoinPub: meltCoin.coinPub,
|
meltCoinPub: meltCoinPub,
|
||||||
newDenomHashes,
|
|
||||||
newDenoms,
|
|
||||||
norevealIndex: undefined,
|
|
||||||
planchetsForGammas: planchetsForGammas,
|
planchetsForGammas: planchetsForGammas,
|
||||||
transferPrivs,
|
transferPrivs,
|
||||||
transferPubs,
|
transferPubs,
|
||||||
amountRefreshOutput: valueOutput,
|
meltValueWithFee: valueWithFee,
|
||||||
amountRefreshInput: valueWithFee,
|
|
||||||
timestampCreated: getTimestampNow(),
|
|
||||||
finishedTimestamp: undefined,
|
|
||||||
lastError: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return refreshSession;
|
return refreshSession;
|
||||||
|
@ -60,6 +60,8 @@ import {
|
|||||||
import { URL } from "../util/url";
|
import { URL } from "../util/url";
|
||||||
import { checkDbInvariant } from "../util/invariants";
|
import { checkDbInvariant } from "../util/invariants";
|
||||||
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
|
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
|
||||||
|
import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions";
|
||||||
|
import { RefreshNewDenomInfo } from "../types/cryptoTypes";
|
||||||
|
|
||||||
const logger = new Logger("refresh.ts");
|
const logger = new Logger("refresh.ts");
|
||||||
|
|
||||||
@ -182,13 +184,7 @@ async function refreshCreateSession(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshSession: RefreshSessionRecord = await ws.cryptoApi.createRefreshSession(
|
const sessionSecretSeed = encodeCrock(getRandomBytes(64));
|
||||||
exchange.baseUrl,
|
|
||||||
3,
|
|
||||||
coin,
|
|
||||||
newCoinDenoms,
|
|
||||||
oldDenom.feeRefresh,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Store refresh session for this coin in the database.
|
// Store refresh session for this coin in the database.
|
||||||
await ws.db.runWithWriteTransaction(
|
await ws.db.runWithWriteTransaction(
|
||||||
@ -201,7 +197,15 @@ async function refreshCreateSession(
|
|||||||
if (rg.refreshSessionPerCoin[coinIndex]) {
|
if (rg.refreshSessionPerCoin[coinIndex]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rg.refreshSessionPerCoin[coinIndex] = refreshSession;
|
rg.refreshSessionPerCoin[coinIndex] = {
|
||||||
|
norevealIndex: undefined,
|
||||||
|
sessionSecretSeed: sessionSecretSeed,
|
||||||
|
newDenoms: newCoinDenoms.selectedDenoms.map((x) => ({
|
||||||
|
count: x.count,
|
||||||
|
denomPubHash: x.denom.denomPubHash,
|
||||||
|
})),
|
||||||
|
amountRefreshOutput: newCoinDenoms.totalCoinValue,
|
||||||
|
};
|
||||||
await tx.put(Stores.refreshGroups, rg);
|
await tx.put(Stores.refreshGroups, rg);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -232,24 +236,57 @@ async function refreshMelt(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coin = await ws.db.get(Stores.coins, refreshSession.meltCoinPub);
|
const oldCoin = await ws.db.get(
|
||||||
|
Stores.coins,
|
||||||
|
refreshGroup.oldCoinPubs[coinIndex],
|
||||||
|
);
|
||||||
|
checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
|
||||||
|
const oldDenom = await ws.db.get(Stores.denominations, [
|
||||||
|
oldCoin.exchangeBaseUrl,
|
||||||
|
oldCoin.denomPubHash,
|
||||||
|
]);
|
||||||
|
checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
|
||||||
|
|
||||||
if (!coin) {
|
const newCoinDenoms: RefreshNewDenomInfo[] = [];
|
||||||
console.error("can't melt coin, it does not exist");
|
|
||||||
return;
|
for (const dh of refreshSession.newDenoms) {
|
||||||
|
const newDenom = await ws.db.get(Stores.denominations, [
|
||||||
|
oldCoin.exchangeBaseUrl,
|
||||||
|
dh.denomPubHash,
|
||||||
|
]);
|
||||||
|
checkDbInvariant(
|
||||||
|
!!newDenom,
|
||||||
|
"new denomination for refresh not in database",
|
||||||
|
);
|
||||||
|
newCoinDenoms.push({
|
||||||
|
count: dh.count,
|
||||||
|
denomPub: newDenom.denomPub,
|
||||||
|
feeWithdraw: newDenom.feeWithdraw,
|
||||||
|
value: newDenom.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const derived = await ws.cryptoApi.deriveRefreshSession({
|
||||||
|
kappa: 3,
|
||||||
|
meltCoinDenomPubHash: oldCoin.denomPubHash,
|
||||||
|
meltCoinPriv: oldCoin.coinPriv,
|
||||||
|
meltCoinPub: oldCoin.coinPub,
|
||||||
|
feeRefresh: oldDenom.feeRefresh,
|
||||||
|
newCoinDenoms,
|
||||||
|
sessionSecretSeed: refreshSession.sessionSecretSeed,
|
||||||
|
});
|
||||||
|
|
||||||
const reqUrl = new URL(
|
const reqUrl = new URL(
|
||||||
`coins/${coin.coinPub}/melt`,
|
`coins/${oldCoin.coinPub}/melt`,
|
||||||
refreshSession.exchangeBaseUrl,
|
oldCoin.exchangeBaseUrl,
|
||||||
);
|
);
|
||||||
const meltReq = {
|
const meltReq = {
|
||||||
coin_pub: coin.coinPub,
|
coin_pub: oldCoin.coinPub,
|
||||||
confirm_sig: refreshSession.confirmSig,
|
confirm_sig: derived.confirmSig,
|
||||||
denom_pub_hash: coin.denomPubHash,
|
denom_pub_hash: oldCoin.denomPubHash,
|
||||||
denom_sig: coin.denomSig,
|
denom_sig: oldCoin.denomSig,
|
||||||
rc: refreshSession.hash,
|
rc: derived.hash,
|
||||||
value_with_fee: Amounts.stringify(refreshSession.amountRefreshInput),
|
value_with_fee: Amounts.stringify(derived.meltValueWithFee),
|
||||||
};
|
};
|
||||||
logger.trace(`melt request for coin:`, meltReq);
|
logger.trace(`melt request for coin:`, meltReq);
|
||||||
|
|
||||||
@ -270,15 +307,15 @@ async function refreshMelt(
|
|||||||
|
|
||||||
await ws.db.mutate(Stores.refreshGroups, refreshGroupId, (rg) => {
|
await ws.db.mutate(Stores.refreshGroups, refreshGroupId, (rg) => {
|
||||||
const rs = rg.refreshSessionPerCoin[coinIndex];
|
const rs = rg.refreshSessionPerCoin[coinIndex];
|
||||||
|
if (rg.timestampFinished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!rs) {
|
if (!rs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rs.norevealIndex !== undefined) {
|
if (rs.norevealIndex !== undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rs.finishedTimestamp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rs.norevealIndex = norevealIndex;
|
rs.norevealIndex = norevealIndex;
|
||||||
return rg;
|
return rg;
|
||||||
});
|
});
|
||||||
@ -305,48 +342,95 @@ async function refreshReveal(
|
|||||||
if (norevealIndex === undefined) {
|
if (norevealIndex === undefined) {
|
||||||
throw Error("can't reveal without melting first");
|
throw Error("can't reveal without melting first");
|
||||||
}
|
}
|
||||||
const privs = Array.from(refreshSession.transferPrivs);
|
|
||||||
|
const oldCoin = await ws.db.get(
|
||||||
|
Stores.coins,
|
||||||
|
refreshGroup.oldCoinPubs[coinIndex],
|
||||||
|
);
|
||||||
|
checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
|
||||||
|
const oldDenom = await ws.db.get(Stores.denominations, [
|
||||||
|
oldCoin.exchangeBaseUrl,
|
||||||
|
oldCoin.denomPubHash,
|
||||||
|
]);
|
||||||
|
checkDbInvariant(!!oldDenom, "denomination for melted coin doesn't exist");
|
||||||
|
|
||||||
|
const newCoinDenoms: RefreshNewDenomInfo[] = [];
|
||||||
|
|
||||||
|
for (const dh of refreshSession.newDenoms) {
|
||||||
|
const newDenom = await ws.db.get(Stores.denominations, [
|
||||||
|
oldCoin.exchangeBaseUrl,
|
||||||
|
dh.denomPubHash,
|
||||||
|
]);
|
||||||
|
checkDbInvariant(
|
||||||
|
!!newDenom,
|
||||||
|
"new denomination for refresh not in database",
|
||||||
|
);
|
||||||
|
newCoinDenoms.push({
|
||||||
|
count: dh.count,
|
||||||
|
denomPub: newDenom.denomPub,
|
||||||
|
feeWithdraw: newDenom.feeWithdraw,
|
||||||
|
value: newDenom.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const derived = await ws.cryptoApi.deriveRefreshSession({
|
||||||
|
kappa: 3,
|
||||||
|
meltCoinDenomPubHash: oldCoin.denomPubHash,
|
||||||
|
meltCoinPriv: oldCoin.coinPriv,
|
||||||
|
meltCoinPub: oldCoin.coinPub,
|
||||||
|
feeRefresh: oldDenom.feeRefresh,
|
||||||
|
newCoinDenoms,
|
||||||
|
sessionSecretSeed: refreshSession.sessionSecretSeed,
|
||||||
|
});
|
||||||
|
|
||||||
|
const privs = Array.from(derived.transferPrivs);
|
||||||
privs.splice(norevealIndex, 1);
|
privs.splice(norevealIndex, 1);
|
||||||
|
|
||||||
const planchets = refreshSession.planchetsForGammas[norevealIndex];
|
const planchets = derived.planchetsForGammas[norevealIndex];
|
||||||
if (!planchets) {
|
if (!planchets) {
|
||||||
throw Error("refresh index error");
|
throw Error("refresh index error");
|
||||||
}
|
}
|
||||||
|
|
||||||
const meltCoinRecord = await ws.db.get(
|
const meltCoinRecord = await ws.db.get(
|
||||||
Stores.coins,
|
Stores.coins,
|
||||||
refreshSession.meltCoinPub,
|
refreshGroup.oldCoinPubs[coinIndex],
|
||||||
);
|
);
|
||||||
if (!meltCoinRecord) {
|
if (!meltCoinRecord) {
|
||||||
throw Error("inconsistent database");
|
throw Error("inconsistent database");
|
||||||
}
|
}
|
||||||
|
|
||||||
const evs = planchets.map((x: RefreshPlanchet) => x.coinEv);
|
const evs = planchets.map((x: RefreshPlanchet) => x.coinEv);
|
||||||
|
const newDenomsFlat: string[] = [];
|
||||||
const linkSigs: string[] = [];
|
const linkSigs: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < refreshSession.newDenoms.length; i++) {
|
for (let i = 0; i < refreshSession.newDenoms.length; i++) {
|
||||||
const linkSig = await ws.cryptoApi.signCoinLink(
|
const dsel = refreshSession.newDenoms[i];
|
||||||
meltCoinRecord.coinPriv,
|
for (let j = 0; j < dsel.count; j++) {
|
||||||
refreshSession.newDenomHashes[i],
|
const newCoinIndex = linkSigs.length;
|
||||||
refreshSession.meltCoinPub,
|
const linkSig = await ws.cryptoApi.signCoinLink(
|
||||||
refreshSession.transferPubs[norevealIndex],
|
meltCoinRecord.coinPriv,
|
||||||
planchets[i].coinEv,
|
dsel.denomPubHash,
|
||||||
);
|
meltCoinRecord.coinPub,
|
||||||
linkSigs.push(linkSig);
|
derived.transferPubs[norevealIndex],
|
||||||
|
planchets[newCoinIndex].coinEv,
|
||||||
|
);
|
||||||
|
linkSigs.push(linkSig);
|
||||||
|
newDenomsFlat.push(dsel.denomPubHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
coin_evs: evs,
|
coin_evs: evs,
|
||||||
new_denoms_h: refreshSession.newDenomHashes,
|
new_denoms_h: newDenomsFlat,
|
||||||
rc: refreshSession.hash,
|
rc: derived.hash,
|
||||||
transfer_privs: privs,
|
transfer_privs: privs,
|
||||||
transfer_pub: refreshSession.transferPubs[norevealIndex],
|
transfer_pub: derived.transferPubs[norevealIndex],
|
||||||
link_sigs: linkSigs,
|
link_sigs: linkSigs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const reqUrl = new URL(
|
const reqUrl = new URL(
|
||||||
`refreshes/${refreshSession.hash}/reveal`,
|
`refreshes/${derived.hash}/reveal`,
|
||||||
refreshSession.exchangeBaseUrl,
|
oldCoin.exchangeBaseUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
|
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
|
||||||
@ -362,39 +446,42 @@ async function refreshReveal(
|
|||||||
|
|
||||||
const coins: CoinRecord[] = [];
|
const coins: CoinRecord[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < reveal.ev_sigs.length; i++) {
|
for (let i = 0; i < refreshSession.newDenoms.length; i++) {
|
||||||
const denom = await ws.db.get(Stores.denominations, [
|
for (let j = 0; j < refreshSession.newDenoms[i].count; j++) {
|
||||||
refreshSession.exchangeBaseUrl,
|
const newCoinIndex = coins.length;
|
||||||
refreshSession.newDenomHashes[i],
|
const denom = await ws.db.get(Stores.denominations, [
|
||||||
]);
|
oldCoin.exchangeBaseUrl,
|
||||||
if (!denom) {
|
refreshSession.newDenoms[i].denomPubHash,
|
||||||
console.error("denom not found");
|
]);
|
||||||
continue;
|
if (!denom) {
|
||||||
}
|
console.error("denom not found");
|
||||||
const pc = refreshSession.planchetsForGammas[norevealIndex][i];
|
continue;
|
||||||
const denomSig = await ws.cryptoApi.rsaUnblind(
|
}
|
||||||
reveal.ev_sigs[i].ev_sig,
|
const pc = derived.planchetsForGammas[norevealIndex][newCoinIndex];
|
||||||
pc.blindingKey,
|
const denomSig = await ws.cryptoApi.rsaUnblind(
|
||||||
denom.denomPub,
|
reveal.ev_sigs[newCoinIndex].ev_sig,
|
||||||
);
|
pc.blindingKey,
|
||||||
const coin: CoinRecord = {
|
denom.denomPub,
|
||||||
blindingKey: pc.blindingKey,
|
);
|
||||||
coinPriv: pc.privateKey,
|
const coin: CoinRecord = {
|
||||||
coinPub: pc.publicKey,
|
blindingKey: pc.blindingKey,
|
||||||
currentAmount: denom.value,
|
coinPriv: pc.privateKey,
|
||||||
denomPub: denom.denomPub,
|
coinPub: pc.publicKey,
|
||||||
denomPubHash: denom.denomPubHash,
|
currentAmount: denom.value,
|
||||||
denomSig,
|
denomPub: denom.denomPub,
|
||||||
exchangeBaseUrl: refreshSession.exchangeBaseUrl,
|
denomPubHash: denom.denomPubHash,
|
||||||
status: CoinStatus.Fresh,
|
denomSig,
|
||||||
coinSource: {
|
exchangeBaseUrl: oldCoin.exchangeBaseUrl,
|
||||||
type: CoinSourceType.Refresh,
|
status: CoinStatus.Fresh,
|
||||||
oldCoinPub: refreshSession.meltCoinPub,
|
coinSource: {
|
||||||
},
|
type: CoinSourceType.Refresh,
|
||||||
suspended: false,
|
oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
|
||||||
};
|
},
|
||||||
|
suspended: false,
|
||||||
|
};
|
||||||
|
|
||||||
coins.push(coin);
|
coins.push(coin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws.db.runWithWriteTransaction(
|
await ws.db.runWithWriteTransaction(
|
||||||
@ -409,11 +496,6 @@ async function refreshReveal(
|
|||||||
if (!rs) {
|
if (!rs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rs.finishedTimestamp) {
|
|
||||||
logger.warn("refresh session already finished");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rs.finishedTimestamp = getTimestampNow();
|
|
||||||
rg.finishedPerCoin[coinIndex] = true;
|
rg.finishedPerCoin[coinIndex] = true;
|
||||||
let allDone = true;
|
let allDone = true;
|
||||||
for (const f of rg.finishedPerCoin) {
|
for (const f of rg.finishedPerCoin) {
|
||||||
|
111
packages/taler-wallet-core/src/types/cryptoTypes.ts
Normal file
111
packages/taler-wallet-core/src/types/cryptoTypes.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2020 Taler Systems SA
|
||||||
|
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types used by the wallet crypto worker.
|
||||||
|
*
|
||||||
|
* These types are defined in a separate file make tree shaking easier, since
|
||||||
|
* some components use these types (via RPC) but do not depend on the wallet
|
||||||
|
* code directly.
|
||||||
|
*
|
||||||
|
* @author Florian Dold <dold@taler.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import { AmountJson } from "../util/amounts";
|
||||||
|
|
||||||
|
export interface RefreshNewDenomInfo {
|
||||||
|
count: number;
|
||||||
|
value: AmountJson;
|
||||||
|
feeWithdraw: AmountJson;
|
||||||
|
denomPub: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to derive a refresh session from the refresh session
|
||||||
|
* secret seed.
|
||||||
|
*/
|
||||||
|
export interface DeriveRefreshSessionRequest {
|
||||||
|
sessionSecretSeed: string;
|
||||||
|
kappa: number;
|
||||||
|
meltCoinPub: string;
|
||||||
|
meltCoinPriv: string;
|
||||||
|
meltCoinDenomPubHash: string;
|
||||||
|
newCoinDenoms: RefreshNewDenomInfo[];
|
||||||
|
feeRefresh: AmountJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface DerivedRefreshSession {
|
||||||
|
/**
|
||||||
|
* Public key that's being melted in this session.
|
||||||
|
*/
|
||||||
|
meltCoinPub: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signature to confirm the melting.
|
||||||
|
*/
|
||||||
|
confirmSig: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Planchets for each cut-and-choose instance.
|
||||||
|
*/
|
||||||
|
planchetsForGammas: {
|
||||||
|
/**
|
||||||
|
* Public key for the coin.
|
||||||
|
*/
|
||||||
|
publicKey: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private key for the coin.
|
||||||
|
*/
|
||||||
|
privateKey: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blinded public key.
|
||||||
|
*/
|
||||||
|
coinEv: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blinding key used.
|
||||||
|
*/
|
||||||
|
blindingKey: string;
|
||||||
|
}[][];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transfer keys, kappa of them.
|
||||||
|
*/
|
||||||
|
transferPubs: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private keys for the transfer public keys.
|
||||||
|
*/
|
||||||
|
transferPrivs: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash of the session.
|
||||||
|
*/
|
||||||
|
hash: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exact value that is being melted.
|
||||||
|
*/
|
||||||
|
meltValueWithFee: AmountJson;
|
||||||
|
}
|
@ -557,7 +557,7 @@ export interface ExchangeRecord {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Terms of service text or undefined if not downloaded yet.
|
* Terms of service text or undefined if not downloaded yet.
|
||||||
*
|
*
|
||||||
* This is just used as a cache of the last downloaded ToS.
|
* This is just used as a cache of the last downloaded ToS.
|
||||||
*/
|
*/
|
||||||
termsOfServiceText: string | undefined;
|
termsOfServiceText: string | undefined;
|
||||||
@ -664,14 +664,17 @@ export interface RefreshPlanchet {
|
|||||||
* Public key for the coin.
|
* Public key for the coin.
|
||||||
*/
|
*/
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private key for the coin.
|
* Private key for the coin.
|
||||||
*/
|
*/
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blinded public key.
|
* Blinded public key.
|
||||||
*/
|
*/
|
||||||
coinEv: string;
|
coinEv: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blinding key used.
|
* Blinding key used.
|
||||||
*/
|
*/
|
||||||
@ -991,18 +994,14 @@ export interface RefreshGroupRecord {
|
|||||||
* Ongoing refresh
|
* Ongoing refresh
|
||||||
*/
|
*/
|
||||||
export interface RefreshSessionRecord {
|
export interface RefreshSessionRecord {
|
||||||
lastError: TalerErrorDetails | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public key that's being melted in this session.
|
* 512-bit secret that can be used to derive
|
||||||
|
* the other cryptographic material for the refresh session.
|
||||||
|
*
|
||||||
|
* FIXME: We currently store the derived material, but
|
||||||
|
* should always derive it.
|
||||||
*/
|
*/
|
||||||
meltCoinPub: string;
|
sessionSecretSeed: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* How much of the coin's value is melted away
|
|
||||||
* with this refresh session?
|
|
||||||
*/
|
|
||||||
amountRefreshInput: AmountJson;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sum of the value of denominations we want
|
* Sum of the value of denominations we want
|
||||||
@ -1011,59 +1010,17 @@ export interface RefreshSessionRecord {
|
|||||||
amountRefreshOutput: AmountJson;
|
amountRefreshOutput: AmountJson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature to confirm the melting.
|
* Hashed denominations of the newly requested coins.
|
||||||
*/
|
*/
|
||||||
confirmSig: string;
|
newDenoms: {
|
||||||
|
denomPubHash: string;
|
||||||
/**
|
count: number;
|
||||||
* Hased denominations of the newly requested coins.
|
}[];
|
||||||
*/
|
|
||||||
newDenomHashes: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Denominations of the newly requested coins.
|
|
||||||
*/
|
|
||||||
newDenoms: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Planchets for each cut-and-choose instance.
|
|
||||||
*/
|
|
||||||
planchetsForGammas: RefreshPlanchet[][];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The transfer keys, kappa of them.
|
|
||||||
*/
|
|
||||||
transferPubs: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private keys for the transfer public keys.
|
|
||||||
*/
|
|
||||||
transferPrivs: string[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The no-reveal-index after we've done the melting.
|
* The no-reveal-index after we've done the melting.
|
||||||
*/
|
*/
|
||||||
norevealIndex?: number;
|
norevealIndex?: number;
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash of the session.
|
|
||||||
*/
|
|
||||||
hash: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timestamp when the refresh session finished.
|
|
||||||
*/
|
|
||||||
finishedTimestamp: Timestamp | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When has this refresh session been created?
|
|
||||||
*/
|
|
||||||
timestampCreated: Timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base URL for the exchange we're doing the refresh with.
|
|
||||||
*/
|
|
||||||
exchangeBaseUrl: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1602,7 +1559,7 @@ class PurchasesStore extends Store<"purchases", PurchaseRecord> {
|
|||||||
string,
|
string,
|
||||||
PurchaseRecord
|
PurchaseRecord
|
||||||
>(this, "fulfillmentUrlIndex", "contractData.fulfillmentUrl");
|
>(this, "fulfillmentUrlIndex", "contractData.fulfillmentUrl");
|
||||||
|
|
||||||
orderIdIndex = new Index<"purchases", "orderIdIndex", string, PurchaseRecord>(
|
orderIdIndex = new Index<"purchases", "orderIdIndex", string, PurchaseRecord>(
|
||||||
this,
|
this,
|
||||||
"orderIdIndex",
|
"orderIdIndex",
|
||||||
@ -1712,7 +1669,6 @@ class BankWithdrawUrisStore extends Store<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
class BackupProvidersStore extends Store<
|
class BackupProvidersStore extends Store<
|
||||||
|
Loading…
Reference in New Issue
Block a user