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("possibleExchanges", codecForList(codecForExchangeListItem()))
.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;
}
export interface AuditorTrustInfo {
export interface AuditorTrustRecord {
/**
* Currency that we trust this auditor for.
*/
currency: string;
/**
* Base URL of the auditor.
*/
@ -375,7 +380,12 @@ export interface AuditorTrustInfo {
uids: string[];
}
export interface ExchangeTrustInfo {
export interface ExchangeTrustRecord {
/**
* Currency that we trust this exchange for.
*/
currency: string;
/**
* Canonicalized exchange base URL.
*/
@ -393,31 +403,6 @@ export interface ExchangeTrustInfo {
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.
*/
@ -1775,10 +1760,44 @@ class DenominationsStore extends Store<"denominations", DenominationRecord> {
>(this, "exchangeBaseUrlIndex", "exchangeBaseUrl");
}
class CurrenciesStore extends Store<"currencies", CurrencyRecord> {
class AuditorTrustStore extends Store<"auditorTrust", AuditorTrustRecord> {
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>> {
@ -1891,7 +1910,8 @@ class TombstonesStore extends Store<"tombstones", TombstoneRecord> {
export const Stores = {
coins: new CoinsStore(),
config: new ConfigStore(),
currencies: new CurrenciesStore(),
auditorTrustStore: new AuditorTrustStore(),
exchangeTrustStore: new ExchangeTrustStore(),
denominations: new DenominationsStore(),
exchanges: new ExchangesStore(),
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;
}
/**
* 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(
ws: InternalWalletState,

View File

@ -38,7 +38,6 @@ import {
ReserveRecordStatus,
ReserveBankInfo,
ReserveRecord,
CurrencyRecord,
WithdrawalGroupRecord,
} from "../db.js";
import {
@ -158,31 +157,9 @@ export async function createReserve(
throw Error("exchange not updated");
}
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(
[Stores.currencies, Stores.reserves, Stores.bankWithdrawUris],
[Stores.exchangeTrustStore, Stores.reserves, Stores.bankWithdrawUris],
async (tx) => {
// Check if we have already created a reserve for that bankWithdrawStatusUrl
if (reserveRecord.bankInfo?.statusUrl) {
@ -207,7 +184,14 @@ export async function createReserve(
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);
const r: CreateReserveResponse = {
exchange: canonExchange,

View File

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

View File

@ -51,7 +51,7 @@ import {
} from "@gnu-taler/taler-util";
import { InternalWalletState } from "./state";
import { Logger } from "../util/logging";
import { updateExchangeFromUrl, getExchangeTrust } from "./exchanges";
import { updateExchangeFromUrl } from "./exchanges";
import {
WALLET_EXCHANGE_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 { compare } from "@gnu-taler/taler-util";
import { walletCoreDebugFlags } from "../util/debugFlags.js";
import { getExchangeTrust } from "./currencies.js";
/**
* Logger for this file.
@ -882,14 +883,6 @@ export async function getExchangeWithdrawalInfo(
.iterIndex(Stores.denominations.exchangeBaseUrlIndex, baseUrl)
.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;
if (exchangeDetails.protocolVersion) {
versionMatch = LibtoolVersion.compare(
@ -935,7 +928,8 @@ export async function getExchangeWithdrawalInfo(
numOfferedDenoms: possibleDenoms.length,
overhead: Amounts.sub(amount, selectedDenoms.totalWithdrawCost).amount,
selectedDenoms,
trustedAuditorPubs,
// FIXME: delete this field / replace by something we can display to the user
trustedAuditorPubs: [],
versionMatch,
walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
wireFees: exchangeWireInfo,

View File

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