add UIDs for deletion tombstones to auditor/exchange trust management
This commit is contained in:
parent
0299e719ce
commit
851ac5602c
@ -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;
|
||||
}[];
|
||||
}
|
@ -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(),
|
||||
|
68
packages/taler-wallet-core/src/operations/currencies.ts
Normal file
68
packages/taler-wallet-core/src/operations/currencies.ts
Normal 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 };
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -82,7 +82,6 @@ export async function getTransactions(
|
||||
|
||||
await ws.db.runWithReadTransaction(
|
||||
[
|
||||
Stores.currencies,
|
||||
Stores.coins,
|
||||
Stores.denominations,
|
||||
Stores.exchanges,
|
||||
|
@ -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,
|
||||
|
@ -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,19 +179,14 @@ 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[] = [
|
||||
const builtinAuditors: AuditorTrustRecord[] = [
|
||||
{
|
||||
auditors: [
|
||||
{
|
||||
auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
|
||||
auditorBaseUrl: "https://auditor.demo.taler.net/",
|
||||
uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"],
|
||||
},
|
||||
],
|
||||
exchanges: [],
|
||||
fractionalDigits: 2,
|
||||
name: "KUDOS",
|
||||
currency: "KUDOS",
|
||||
auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
|
||||
auditorBaseUrl: "https://auditor.demo.taler.net/",
|
||||
uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"],
|
||||
},
|
||||
];
|
||||
|
||||
@ -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[]> {
|
||||
|
Loading…
Reference in New Issue
Block a user