add UIDs for deletion tombstones to auditor/exchange trust management

This commit is contained in:
Florian Dold 2021-05-20 13:14:47 +02:00
parent 0299e719ce
commit 851ac5602c
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 174 additions and 127 deletions

View File

@ -933,3 +933,16 @@ export const codecForWithdrawUriInfoResponse = (): Codec<WithdrawUriInfoResponse
.property("defaultExchangeBaseUrl", codecOptional(codecForString())) .property("defaultExchangeBaseUrl", codecOptional(codecForString()))
.property("possibleExchanges", codecForList(codecForExchangeListItem())) .property("possibleExchanges", codecForList(codecForExchangeListItem()))
.build("WithdrawUriInfoResponse"); .build("WithdrawUriInfoResponse");
export interface WalletCurrencyInfo {
trustedAuditors: {
currency: string;
auditorPub: string;
auditorBaseUrl: string;
}[];
trustedExchanges: {
currency: string;
exchangeMasterPub: string;
exchangeBaseUrl: string;
}[];
}

View File

@ -357,7 +357,12 @@ export interface AuditorRecord {
expirationStamp: number; expirationStamp: number;
} }
export interface AuditorTrustInfo { export interface AuditorTrustRecord {
/**
* Currency that we trust this auditor for.
*/
currency: string;
/** /**
* Base URL of the auditor. * Base URL of the auditor.
*/ */
@ -375,7 +380,12 @@ export interface AuditorTrustInfo {
uids: string[]; uids: string[];
} }
export interface ExchangeTrustInfo { export interface ExchangeTrustRecord {
/**
* Currency that we trust this exchange for.
*/
currency: string;
/** /**
* Canonicalized exchange base URL. * Canonicalized exchange base URL.
*/ */
@ -393,31 +403,6 @@ export interface ExchangeTrustInfo {
uids: string[]; uids: string[];
} }
/**
* Information about a currency as displayed in the wallet's database.
*/
export interface CurrencyRecord {
/**
* Name of the currency.
*/
name: string;
/**
* Number of fractional digits to show when rendering the currency.
*/
fractionalDigits: number;
/**
* Auditors that the wallet trusts for this currency.
*/
auditors: AuditorTrustInfo[];
/**
* Exchanges that the wallet trusts for this currency.
*/
exchanges: ExchangeTrustInfo[];
}
/** /**
* Status of a denomination. * Status of a denomination.
*/ */
@ -1775,10 +1760,44 @@ class DenominationsStore extends Store<"denominations", DenominationRecord> {
>(this, "exchangeBaseUrlIndex", "exchangeBaseUrl"); >(this, "exchangeBaseUrlIndex", "exchangeBaseUrl");
} }
class CurrenciesStore extends Store<"currencies", CurrencyRecord> { class AuditorTrustStore extends Store<"auditorTrust", AuditorTrustRecord> {
constructor() { constructor() {
super("currencies", { keyPath: "name" }); super("auditorTrust", {
keyPath: ["currency", "auditorBaseUrl", "auditorPub"],
});
} }
auditorPubIndex = new Index<
"auditorTrust",
"auditorPubIndex",
string,
AuditorTrustRecord
>(this, "auditorPubIndex", "auditorPub");
uidIndex = new Index<"auditorTrust", "uidIndex", string, AuditorTrustRecord>(
this,
"uidIndex",
"uids",
{ multiEntry: true },
);
}
class ExchangeTrustStore extends Store<"exchangeTrust", ExchangeTrustRecord> {
constructor() {
super("exchangeTrust", {
keyPath: ["currency", "exchangeBaseUrl", "exchangePub"],
});
}
exchangeMasterPubIndex = new Index<
"exchangeTrust",
"exchangeMasterPubIndex",
string,
ExchangeTrustRecord
>(this, "exchangeMasterPubIndex", "exchangePub");
uidIndex = new Index<
"exchangeTrust",
"uidIndex",
string,
ExchangeTrustRecord
>(this, "uidIndex", "uids", { multiEntry: true });
} }
class ConfigStore extends Store<"config", ConfigRecord<any>> { class ConfigStore extends Store<"config", ConfigRecord<any>> {
@ -1891,7 +1910,8 @@ class TombstonesStore extends Store<"tombstones", TombstoneRecord> {
export const Stores = { export const Stores = {
coins: new CoinsStore(), coins: new CoinsStore(),
config: new ConfigStore(), config: new ConfigStore(),
currencies: new CurrenciesStore(), auditorTrustStore: new AuditorTrustStore(),
exchangeTrustStore: new ExchangeTrustStore(),
denominations: new DenominationsStore(), denominations: new DenominationsStore(),
exchanges: new ExchangesStore(), exchanges: new ExchangesStore(),
proposals: new ProposalsStore(), proposals: new ProposalsStore(),

View File

@ -0,0 +1,68 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU 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.
GNU 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
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Imports.
*/
import { ExchangeRecord, Stores } from "../db.js";
import { Logger } from "../index.js";
import { InternalWalletState } from "./state.js";
const logger = new Logger("currencies.ts");
export interface TrustInfo {
isTrusted: boolean;
isAudited: boolean;
}
/**
* Check if and how an exchange is trusted and/or audited.
*/
export async function getExchangeTrust(
ws: InternalWalletState,
exchangeInfo: ExchangeRecord,
): Promise<TrustInfo> {
let isTrusted = false;
let isAudited = false;
const exchangeDetails = exchangeInfo.details;
if (!exchangeDetails) {
throw Error(`exchange ${exchangeInfo.baseUrl} details not available`);
}
const exchangeTrustRecord = await ws.db.getIndexed(
Stores.exchangeTrustStore.exchangeMasterPubIndex,
exchangeDetails.masterPublicKey,
);
if (
exchangeTrustRecord &&
exchangeTrustRecord.uids.length > 0 &&
exchangeTrustRecord.currency === exchangeDetails.currency
) {
isTrusted = true;
}
for (const auditor of exchangeDetails.auditors) {
const auditorTrustRecord = await ws.db.getIndexed(
Stores.auditorTrustStore.auditorPubIndex,
auditor.auditor_pub,
);
if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) {
isAudited = true;
break;
}
}
return { isTrusted, isAudited };
}

View File

@ -527,43 +527,6 @@ async function updateExchangeFromUrlImpl(
return updatedExchange; return updatedExchange;
} }
/**
* Check if and how an exchange is trusted and/or audited.
*/
export async function getExchangeTrust(
ws: InternalWalletState,
exchangeInfo: ExchangeRecord,
): Promise<{ isTrusted: boolean; isAudited: boolean }> {
let isTrusted = false;
let isAudited = false;
const exchangeDetails = exchangeInfo.details;
if (!exchangeDetails) {
throw Error(`exchange ${exchangeInfo.baseUrl} details not available`);
}
const currencyRecord = await ws.db.get(
Stores.currencies,
exchangeDetails.currency,
);
if (currencyRecord) {
for (const trustedExchange of currencyRecord.exchanges) {
if (
trustedExchange.exchangeMasterPub === exchangeDetails.masterPublicKey
) {
isTrusted = true;
break;
}
}
for (const trustedAuditor of currencyRecord.auditors) {
for (const exchangeAuditor of exchangeDetails.auditors) {
if (trustedAuditor.auditorPub === exchangeAuditor.auditor_pub) {
isAudited = true;
break;
}
}
}
}
return { isTrusted, isAudited };
}
export async function getExchangePaytoUri( export async function getExchangePaytoUri(
ws: InternalWalletState, ws: InternalWalletState,

View File

@ -38,7 +38,6 @@ import {
ReserveRecordStatus, ReserveRecordStatus,
ReserveBankInfo, ReserveBankInfo,
ReserveRecord, ReserveRecord,
CurrencyRecord,
WithdrawalGroupRecord, WithdrawalGroupRecord,
} from "../db.js"; } from "../db.js";
import { import {
@ -158,31 +157,9 @@ export async function createReserve(
throw Error("exchange not updated"); throw Error("exchange not updated");
} }
const { isAudited, isTrusted } = await getExchangeTrust(ws, exchangeInfo); const { isAudited, isTrusted } = await getExchangeTrust(ws, exchangeInfo);
let currencyRecord = await ws.db.get(
Stores.currencies,
exchangeDetails.currency,
);
if (!currencyRecord) {
currencyRecord = {
auditors: [],
exchanges: [],
fractionalDigits: 2,
name: exchangeDetails.currency,
};
}
if (!isAudited && !isTrusted) {
currencyRecord.exchanges.push({
exchangeBaseUrl: req.exchange,
exchangeMasterPub: exchangeDetails.masterPublicKey,
uids: [encodeCrock(getRandomBytes(32))],
});
}
const cr: CurrencyRecord = currencyRecord;
const resp = await ws.db.runWithWriteTransaction( const resp = await ws.db.runWithWriteTransaction(
[Stores.currencies, Stores.reserves, Stores.bankWithdrawUris], [Stores.exchangeTrustStore, Stores.reserves, Stores.bankWithdrawUris],
async (tx) => { async (tx) => {
// Check if we have already created a reserve for that bankWithdrawStatusUrl // Check if we have already created a reserve for that bankWithdrawStatusUrl
if (reserveRecord.bankInfo?.statusUrl) { if (reserveRecord.bankInfo?.statusUrl) {
@ -207,7 +184,14 @@ export async function createReserve(
talerWithdrawUri: reserveRecord.bankInfo.statusUrl, talerWithdrawUri: reserveRecord.bankInfo.statusUrl,
}); });
} }
await tx.put(Stores.currencies, cr); if (!isAudited && !isAudited) {
await tx.put(Stores.exchangeTrustStore, {
currency: reserveRecord.currency,
exchangeBaseUrl: reserveRecord.exchangeBaseUrl,
exchangeMasterPub: exchangeDetails.masterPublicKey,
uids: [encodeCrock(getRandomBytes(32))],
});
}
await tx.put(Stores.reserves, reserveRecord); await tx.put(Stores.reserves, reserveRecord);
const r: CreateReserveResponse = { const r: CreateReserveResponse = {
exchange: canonExchange, exchange: canonExchange,

View File

@ -82,7 +82,6 @@ export async function getTransactions(
await ws.db.runWithReadTransaction( await ws.db.runWithReadTransaction(
[ [
Stores.currencies,
Stores.coins, Stores.coins,
Stores.denominations, Stores.denominations,
Stores.exchanges, Stores.exchanges,

View File

@ -51,7 +51,7 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
import { updateExchangeFromUrl, getExchangeTrust } from "./exchanges"; import { updateExchangeFromUrl } from "./exchanges";
import { import {
WALLET_EXCHANGE_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION,
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
@ -76,6 +76,7 @@ import { TalerErrorCode } from "@gnu-taler/taler-util";
import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries"; import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries";
import { compare } from "@gnu-taler/taler-util"; import { compare } from "@gnu-taler/taler-util";
import { walletCoreDebugFlags } from "../util/debugFlags.js"; import { walletCoreDebugFlags } from "../util/debugFlags.js";
import { getExchangeTrust } from "./currencies.js";
/** /**
* Logger for this file. * Logger for this file.
@ -882,14 +883,6 @@ export async function getExchangeWithdrawalInfo(
.iterIndex(Stores.denominations.exchangeBaseUrlIndex, baseUrl) .iterIndex(Stores.denominations.exchangeBaseUrlIndex, baseUrl)
.filter((d) => d.isOffered); .filter((d) => d.isOffered);
const trustedAuditorPubs = [];
const currencyRecord = await ws.db.get(Stores.currencies, amount.currency);
if (currencyRecord) {
trustedAuditorPubs.push(
...currencyRecord.auditors.map((a) => a.auditorPub),
);
}
let versionMatch; let versionMatch;
if (exchangeDetails.protocolVersion) { if (exchangeDetails.protocolVersion) {
versionMatch = LibtoolVersion.compare( versionMatch = LibtoolVersion.compare(
@ -935,7 +928,8 @@ export async function getExchangeWithdrawalInfo(
numOfferedDenoms: possibleDenoms.length, numOfferedDenoms: possibleDenoms.length,
overhead: Amounts.sub(amount, selectedDenoms.totalWithdrawCost).amount, overhead: Amounts.sub(amount, selectedDenoms.totalWithdrawCost).amount,
selectedDenoms, selectedDenoms,
trustedAuditorPubs, // FIXME: delete this field / replace by something we can display to the user
trustedAuditorPubs: [],
versionMatch, versionMatch,
walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION, walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
wireFees: exchangeWireInfo, wireFees: exchangeWireInfo,

View File

@ -26,6 +26,7 @@ import {
BackupRecovery, BackupRecovery,
codecForAny, codecForAny,
TalerErrorCode, TalerErrorCode,
WalletCurrencyInfo,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi"; import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi";
import { import {
@ -56,7 +57,6 @@ import {
import { import {
acceptExchangeTermsOfService, acceptExchangeTermsOfService,
getExchangePaytoUri, getExchangePaytoUri,
getExchangeTrust,
updateExchangeFromUrl, updateExchangeFromUrl,
} from "./operations/exchanges"; } from "./operations/exchanges";
import { import {
@ -99,9 +99,9 @@ import {
processWithdrawGroup, processWithdrawGroup,
} from "./operations/withdraw"; } from "./operations/withdraw";
import { import {
AuditorTrustRecord,
CoinRecord, CoinRecord,
CoinSourceType, CoinSourceType,
CurrencyRecord,
DenominationRecord, DenominationRecord,
ExchangeRecord, ExchangeRecord,
PurchaseRecord, PurchaseRecord,
@ -179,20 +179,15 @@ import { AsyncCondition } from "./util/promiseUtils";
import { Database } from "./util/query"; import { Database } from "./util/query";
import { Duration, durationMin } from "@gnu-taler/taler-util"; import { Duration, durationMin } from "@gnu-taler/taler-util";
import { TimerGroup } from "./util/timer"; import { TimerGroup } from "./util/timer";
import { getExchangeTrust } from "./operations/currencies.js";
const builtinCurrencies: CurrencyRecord[] = [ const builtinAuditors: AuditorTrustRecord[] = [
{
auditors: [
{ {
currency: "KUDOS",
auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0", auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
auditorBaseUrl: "https://auditor.demo.taler.net/", auditorBaseUrl: "https://auditor.demo.taler.net/",
uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"], uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"],
}, },
],
exchanges: [],
fractionalDigits: 2,
name: "KUDOS",
},
]; ];
const logger = new Logger("wallet.ts"); const logger = new Logger("wallet.ts");
@ -484,7 +479,7 @@ export class Wallet {
*/ */
async fillDefaults(): Promise<void> { async fillDefaults(): Promise<void> {
await this.db.runWithWriteTransaction( await this.db.runWithWriteTransaction(
[Stores.config, Stores.currencies], [Stores.config, Stores.auditorTrustStore],
async (tx) => { async (tx) => {
let applied = false; let applied = false;
await tx.iter(Stores.config).forEach((x) => { await tx.iter(Stores.config).forEach((x) => {
@ -493,8 +488,8 @@ export class Wallet {
} }
}); });
if (!applied) { if (!applied) {
for (const c of builtinCurrencies) { for (const c of builtinAuditors) {
await tx.put(Stores.currencies, c); await tx.put(Stores.auditorTrustStore, c);
} }
} }
}, },
@ -676,7 +671,6 @@ export class Wallet {
return await this.db.iter(Stores.exchanges).toArray(); return await this.db.iter(Stores.exchanges).toArray();
} }
async getExchanges(): Promise<ExchangesListRespose> { async getExchanges(): Promise<ExchangesListRespose> {
const exchanges: (ExchangeListItem | undefined)[] = await this.db const exchanges: (ExchangeListItem | undefined)[] = await this.db
.iter(Stores.exchanges) .iter(Stores.exchanges)
@ -702,13 +696,25 @@ export class Wallet {
}; };
} }
async getCurrencies(): Promise<CurrencyRecord[]> { async getCurrencies(): Promise<WalletCurrencyInfo> {
return await this.db.iter(Stores.currencies).toArray(); const trustedAuditors = await this.db
} .iter(Stores.auditorTrustStore)
.toArray();
async updateCurrency(currencyRecord: CurrencyRecord): Promise<void> { const trustedExchanges = await this.db
logger.trace("updating currency to", currencyRecord); .iter(Stores.exchangeTrustStore)
await this.db.put(Stores.currencies, currencyRecord); .toArray();
return {
trustedAuditors: trustedAuditors.map((x) => ({
currency: x.currency,
auditorBaseUrl: x.auditorBaseUrl,
auditorPub: x.auditorPub,
})),
trustedExchanges: trustedExchanges.map((x) => ({
currency: x.currency,
exchangeBaseUrl: x.exchangeBaseUrl,
exchangeMasterPub: x.exchangeMasterPub,
})),
};
} }
async getReserves(exchangeBaseUrl?: string): Promise<ReserveRecord[]> { async getReserves(exchangeBaseUrl?: string): Promise<ReserveRecord[]> {