adding global fee info from exchange
This commit is contained in:
parent
ef95914cfc
commit
cb44202440
@ -1086,6 +1086,23 @@ export interface BackupExchangeWireFee {
|
|||||||
sig: string;
|
sig: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global fee as stored in the wallet's database.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface BackupExchangeGlobalFees {
|
||||||
|
start_date: TalerProtocolTimestamp;
|
||||||
|
end_date: TalerProtocolTimestamp;
|
||||||
|
kyc_fee: BackupAmountString;
|
||||||
|
history_fee: BackupAmountString;
|
||||||
|
account_fee: BackupAmountString;
|
||||||
|
purse_fee: BackupAmountString;
|
||||||
|
history_expiration: TalerProtocolDuration;
|
||||||
|
account_kyc_timeout: TalerProtocolDuration;
|
||||||
|
purse_account_limit: number;
|
||||||
|
purse_timeout: TalerProtocolDuration;
|
||||||
|
master_sig: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Structure of one exchange signing key in the /keys response.
|
* Structure of one exchange signing key in the /keys response.
|
||||||
*/
|
*/
|
||||||
@ -1206,6 +1223,8 @@ export interface BackupExchangeDetails {
|
|||||||
|
|
||||||
wire_fees: BackupExchangeWireFee[];
|
wire_fees: BackupExchangeWireFee[];
|
||||||
|
|
||||||
|
global_fees: BackupExchangeGlobalFees[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bank accounts offered by the exchange;
|
* Bank accounts offered by the exchange;
|
||||||
*/
|
*/
|
||||||
|
@ -63,9 +63,9 @@ export function addPaytoQueryParams(
|
|||||||
): string {
|
): string {
|
||||||
const [acct, search] = s.slice(paytoPfx.length).split("?");
|
const [acct, search] = s.slice(paytoPfx.length).split("?");
|
||||||
const searchParams = new URLSearchParams(search || "");
|
const searchParams = new URLSearchParams(search || "");
|
||||||
const keys = Object.keys(params)
|
const keys = Object.keys(params);
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
return paytoPfx + acct
|
return paytoPfx + acct;
|
||||||
}
|
}
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
searchParams.set(k, params[k]);
|
searchParams.set(k, params[k]);
|
||||||
@ -83,9 +83,7 @@ export function stringifyPaytoUri(p: PaytoUri): string {
|
|||||||
const url = `${paytoPfx}${p.targetType}/${p.targetPath}`;
|
const url = `${paytoPfx}${p.targetType}/${p.targetPath}`;
|
||||||
const paramList = !p.params ? [] : Object.entries(p.params);
|
const paramList = !p.params ? [] : Object.entries(p.params);
|
||||||
if (paramList.length > 0) {
|
if (paramList.length > 0) {
|
||||||
const search = paramList
|
const search = paramList.map(([key, value]) => `${key}=${value}`).join("&");
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
|
||||||
.join("&");
|
|
||||||
return `${url}?${search}`;
|
return `${url}?${search}`;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
|
@ -793,6 +793,7 @@ export enum TalerSignaturePurpose {
|
|||||||
MERCHANT_TRACK_TRANSACTION = 1103,
|
MERCHANT_TRACK_TRANSACTION = 1103,
|
||||||
WALLET_RESERVE_WITHDRAW = 1200,
|
WALLET_RESERVE_WITHDRAW = 1200,
|
||||||
WALLET_COIN_DEPOSIT = 1201,
|
WALLET_COIN_DEPOSIT = 1201,
|
||||||
|
GLOBAL_FEES = 1022,
|
||||||
MASTER_DENOMINATION_KEY_VALIDITY = 1025,
|
MASTER_DENOMINATION_KEY_VALIDITY = 1025,
|
||||||
MASTER_WIRE_FEES = 1028,
|
MASTER_WIRE_FEES = 1028,
|
||||||
MASTER_WIRE_DETAILS = 1030,
|
MASTER_WIRE_DETAILS = 1030,
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
import { strcmp } from "./helpers.js";
|
import { strcmp } from "./helpers.js";
|
||||||
import { AgeCommitmentProof, Edx25519PublicKeyEnc } from "./talerCrypto.js";
|
import { AgeCommitmentProof, Edx25519PublicKeyEnc } from "./talerCrypto.js";
|
||||||
import {
|
import {
|
||||||
|
codecForAbsoluteTime,
|
||||||
codecForDuration,
|
codecForDuration,
|
||||||
codecForTimestamp,
|
codecForTimestamp,
|
||||||
TalerProtocolDuration,
|
TalerProtocolDuration,
|
||||||
@ -757,8 +758,64 @@ export class ExchangeKeysJson {
|
|||||||
version: string;
|
version: string;
|
||||||
|
|
||||||
reserve_closing_delay: TalerProtocolDuration;
|
reserve_closing_delay: TalerProtocolDuration;
|
||||||
|
|
||||||
|
global_fees: GlobalFees[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GlobalFees {
|
||||||
|
// What date (inclusive) does these fees go into effect?
|
||||||
|
start_date: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
// What date (exclusive) does this fees stop going into effect?
|
||||||
|
end_date: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
// KYC fee, charged when a user wants to create an account.
|
||||||
|
// The first year of the account_annual_fee after the KYC is
|
||||||
|
// always included.
|
||||||
|
kyc_fee: AmountString;
|
||||||
|
|
||||||
|
// Account history fee, charged when a user wants to
|
||||||
|
// obtain a reserve/account history.
|
||||||
|
history_fee: AmountString;
|
||||||
|
|
||||||
|
// Annual fee charged for having an open account at the
|
||||||
|
// exchange. Charged to the account. If the account
|
||||||
|
// balance is insufficient to cover this fee, the account
|
||||||
|
// is automatically deleted/closed. (Note that the exchange
|
||||||
|
// will keep the account history around for longer for
|
||||||
|
// regulatory reasons.)
|
||||||
|
account_fee: AmountString;
|
||||||
|
|
||||||
|
// Purse fee, charged only if a purse is abandoned
|
||||||
|
// and was not covered by the account limit.
|
||||||
|
purse_fee: AmountString;
|
||||||
|
|
||||||
|
// How long will the exchange preserve the account history?
|
||||||
|
// After an account was deleted/closed, the exchange will
|
||||||
|
// retain the account history for legal reasons until this time.
|
||||||
|
history_expiration: TalerProtocolDuration;
|
||||||
|
|
||||||
|
// How long does the exchange promise to keep funds
|
||||||
|
// an account for which the KYC has never happened
|
||||||
|
// after a purse was merged into an account? Basically,
|
||||||
|
// after this time funds in an account without KYC are
|
||||||
|
// forfeit.
|
||||||
|
account_kyc_timeout: TalerProtocolDuration;
|
||||||
|
|
||||||
|
// Non-negative number of concurrent purses that any
|
||||||
|
// account holder is allowed to create without having
|
||||||
|
// to pay the purse_fee.
|
||||||
|
purse_account_limit: number;
|
||||||
|
|
||||||
|
// How long does an exchange keep a purse around after a purse
|
||||||
|
// has expired (or been successfully merged)? A 'GET' request
|
||||||
|
// for a purse will succeed until the purse expiration time
|
||||||
|
// plus this value.
|
||||||
|
purse_timeout: TalerProtocolDuration;
|
||||||
|
|
||||||
|
// Signature of TALER_GlobalFeesPS.
|
||||||
|
master_sig: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Wire fees as announced by the exchange.
|
* Wire fees as announced by the exchange.
|
||||||
*/
|
*/
|
||||||
@ -1188,13 +1245,6 @@ export namespace DenominationPubKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForDenominationPubKey = () =>
|
|
||||||
buildCodecForUnion<DenominationPubKey>()
|
|
||||||
.discriminateOn("cipher")
|
|
||||||
.alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey())
|
|
||||||
.alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
|
|
||||||
.build("DenominationPubKey");
|
|
||||||
|
|
||||||
export const codecForRsaDenominationPubKey = () =>
|
export const codecForRsaDenominationPubKey = () =>
|
||||||
buildCodecForObject<RsaDenominationPubKey>()
|
buildCodecForObject<RsaDenominationPubKey>()
|
||||||
.property("cipher", codecForConstString(DenomKeyType.Rsa))
|
.property("cipher", codecForConstString(DenomKeyType.Rsa))
|
||||||
@ -1209,6 +1259,13 @@ export const codecForCsDenominationPubKey = () =>
|
|||||||
.property("age_mask", codecForNumber())
|
.property("age_mask", codecForNumber())
|
||||||
.build("CsDenominationPubKey");
|
.build("CsDenominationPubKey");
|
||||||
|
|
||||||
|
export const codecForDenominationPubKey = () =>
|
||||||
|
buildCodecForUnion<DenominationPubKey>()
|
||||||
|
.discriminateOn("cipher")
|
||||||
|
.alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey())
|
||||||
|
.alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
|
||||||
|
.build("DenominationPubKey");
|
||||||
|
|
||||||
export const codecForBankWithdrawalOperationPostResponse =
|
export const codecForBankWithdrawalOperationPostResponse =
|
||||||
(): Codec<BankWithdrawalOperationPostResponse> =>
|
(): Codec<BankWithdrawalOperationPostResponse> =>
|
||||||
buildCodecForObject<BankWithdrawalOperationPostResponse>()
|
buildCodecForObject<BankWithdrawalOperationPostResponse>()
|
||||||
@ -1385,6 +1442,21 @@ export const codecForExchangeSigningKey = (): Codec<ExchangeSignKeyJson> =>
|
|||||||
.property("stamp_expire", codecForTimestamp)
|
.property("stamp_expire", codecForTimestamp)
|
||||||
.build("ExchangeSignKeyJson");
|
.build("ExchangeSignKeyJson");
|
||||||
|
|
||||||
|
export const codecForGlobalFees = (): Codec<GlobalFees> =>
|
||||||
|
buildCodecForObject<GlobalFees>()
|
||||||
|
.property("start_date", codecForTimestamp)
|
||||||
|
.property("end_date", codecForTimestamp)
|
||||||
|
.property("kyc_fee", codecForAmountString())
|
||||||
|
.property("history_fee", codecForAmountString())
|
||||||
|
.property("account_fee", codecForAmountString())
|
||||||
|
.property("purse_fee", codecForAmountString())
|
||||||
|
.property("history_expiration", codecForDuration)
|
||||||
|
.property("account_kyc_timeout", codecForDuration)
|
||||||
|
.property("purse_account_limit", codecForNumber())
|
||||||
|
.property("purse_timeout", codecForDuration)
|
||||||
|
.property("master_sig", codecForString())
|
||||||
|
.build("GlobalFees");
|
||||||
|
|
||||||
export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
|
export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
|
||||||
buildCodecForObject<ExchangeKeysJson>()
|
buildCodecForObject<ExchangeKeysJson>()
|
||||||
.property("denoms", codecForList(codecForDenomination()))
|
.property("denoms", codecForList(codecForDenomination()))
|
||||||
@ -1395,6 +1467,7 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
|
|||||||
.property("signkeys", codecForList(codecForExchangeSigningKey()))
|
.property("signkeys", codecForList(codecForExchangeSigningKey()))
|
||||||
.property("version", codecForString())
|
.property("version", codecForString())
|
||||||
.property("reserve_closing_delay", codecForDuration)
|
.property("reserve_closing_delay", codecForDuration)
|
||||||
|
.property("global_fees", codecForList(codecForGlobalFees()))
|
||||||
.build("ExchangeKeysJson");
|
.build("ExchangeKeysJson");
|
||||||
|
|
||||||
export const codecForWireFeesJson = (): Codec<WireFeesJson> =>
|
export const codecForWireFeesJson = (): Codec<WireFeesJson> =>
|
||||||
@ -1583,6 +1656,32 @@ export interface AbortResponse {
|
|||||||
refunds: MerchantAbortPayRefundStatus[];
|
refunds: MerchantAbortPayRefundStatus[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const codecForMerchantAbortPayRefundSuccessStatus =
|
||||||
|
(): Codec<MerchantAbortPayRefundSuccessStatus> =>
|
||||||
|
buildCodecForObject<MerchantAbortPayRefundSuccessStatus>()
|
||||||
|
.property("exchange_pub", codecForString())
|
||||||
|
.property("exchange_sig", codecForString())
|
||||||
|
.property("exchange_status", codecForConstNumber(200))
|
||||||
|
.property("type", codecForConstString("success"))
|
||||||
|
.build("MerchantAbortPayRefundSuccessStatus");
|
||||||
|
|
||||||
|
export const codecForMerchantAbortPayRefundFailureStatus =
|
||||||
|
(): Codec<MerchantAbortPayRefundFailureStatus> =>
|
||||||
|
buildCodecForObject<MerchantAbortPayRefundFailureStatus>()
|
||||||
|
.property("exchange_code", codecForNumber())
|
||||||
|
.property("exchange_reply", codecForAny())
|
||||||
|
.property("exchange_status", codecForNumber())
|
||||||
|
.property("type", codecForConstString("failure"))
|
||||||
|
.build("MerchantAbortPayRefundFailureStatus");
|
||||||
|
|
||||||
|
export const codecForMerchantAbortPayRefundStatus =
|
||||||
|
(): Codec<MerchantAbortPayRefundStatus> =>
|
||||||
|
buildCodecForUnion<MerchantAbortPayRefundStatus>()
|
||||||
|
.discriminateOn("type")
|
||||||
|
.alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
|
||||||
|
.alternative("failure", codecForMerchantAbortPayRefundFailureStatus())
|
||||||
|
.build("MerchantAbortPayRefundStatus");
|
||||||
|
|
||||||
export const codecForAbortResponse = (): Codec<AbortResponse> =>
|
export const codecForAbortResponse = (): Codec<AbortResponse> =>
|
||||||
buildCodecForObject<AbortResponse>()
|
buildCodecForObject<AbortResponse>()
|
||||||
.property("refunds", codecForList(codecForMerchantAbortPayRefundStatus()))
|
.property("refunds", codecForList(codecForMerchantAbortPayRefundStatus()))
|
||||||
@ -1629,32 +1728,6 @@ export interface MerchantAbortPayRefundSuccessStatus {
|
|||||||
exchange_pub: string;
|
exchange_pub: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForMerchantAbortPayRefundSuccessStatus =
|
|
||||||
(): Codec<MerchantAbortPayRefundSuccessStatus> =>
|
|
||||||
buildCodecForObject<MerchantAbortPayRefundSuccessStatus>()
|
|
||||||
.property("exchange_pub", codecForString())
|
|
||||||
.property("exchange_sig", codecForString())
|
|
||||||
.property("exchange_status", codecForConstNumber(200))
|
|
||||||
.property("type", codecForConstString("success"))
|
|
||||||
.build("MerchantAbortPayRefundSuccessStatus");
|
|
||||||
|
|
||||||
export const codecForMerchantAbortPayRefundFailureStatus =
|
|
||||||
(): Codec<MerchantAbortPayRefundFailureStatus> =>
|
|
||||||
buildCodecForObject<MerchantAbortPayRefundFailureStatus>()
|
|
||||||
.property("exchange_code", codecForNumber())
|
|
||||||
.property("exchange_reply", codecForAny())
|
|
||||||
.property("exchange_status", codecForNumber())
|
|
||||||
.property("type", codecForConstString("failure"))
|
|
||||||
.build("MerchantAbortPayRefundFailureStatus");
|
|
||||||
|
|
||||||
export const codecForMerchantAbortPayRefundStatus =
|
|
||||||
(): Codec<MerchantAbortPayRefundStatus> =>
|
|
||||||
buildCodecForUnion<MerchantAbortPayRefundStatus>()
|
|
||||||
.discriminateOn("type")
|
|
||||||
.alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
|
|
||||||
.alternative("failure", codecForMerchantAbortPayRefundFailureStatus())
|
|
||||||
.build("MerchantAbortPayRefundStatus");
|
|
||||||
|
|
||||||
export interface TalerConfigResponse {
|
export interface TalerConfigResponse {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
@ -51,6 +51,7 @@ import {
|
|||||||
encryptContractForMerge,
|
encryptContractForMerge,
|
||||||
ExchangeProtocolVersion,
|
ExchangeProtocolVersion,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
|
GlobalFees,
|
||||||
hash,
|
hash,
|
||||||
HashCodeString,
|
HashCodeString,
|
||||||
hashCoinEv,
|
hashCoinEv,
|
||||||
@ -74,6 +75,7 @@ import {
|
|||||||
rsaVerify,
|
rsaVerify,
|
||||||
setupTipPlanchet,
|
setupTipPlanchet,
|
||||||
stringToBytes,
|
stringToBytes,
|
||||||
|
TalerProtocolDuration,
|
||||||
TalerProtocolTimestamp,
|
TalerProtocolTimestamp,
|
||||||
TalerSignaturePurpose,
|
TalerSignaturePurpose,
|
||||||
UnblindedSignature,
|
UnblindedSignature,
|
||||||
@ -142,6 +144,10 @@ export interface TalerCryptoInterface {
|
|||||||
|
|
||||||
isValidWireFee(req: WireFeeValidationRequest): Promise<ValidationResult>;
|
isValidWireFee(req: WireFeeValidationRequest): Promise<ValidationResult>;
|
||||||
|
|
||||||
|
isValidGlobalFees(
|
||||||
|
req: GlobalFeesValidationRequest,
|
||||||
|
): Promise<ValidationResult>;
|
||||||
|
|
||||||
isValidDenom(req: DenominationValidationRequest): Promise<ValidationResult>;
|
isValidDenom(req: DenominationValidationRequest): Promise<ValidationResult>;
|
||||||
|
|
||||||
isValidWireAccount(
|
isValidWireAccount(
|
||||||
@ -152,7 +158,7 @@ export interface TalerCryptoInterface {
|
|||||||
req: ContractTermsValidationRequest,
|
req: ContractTermsValidationRequest,
|
||||||
): Promise<ValidationResult>;
|
): Promise<ValidationResult>;
|
||||||
|
|
||||||
createEddsaKeypair(req: {}): Promise<EddsaKeypair>;
|
createEddsaKeypair(req: unknown): Promise<EddsaKeypair>;
|
||||||
|
|
||||||
eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
|
eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
|
||||||
|
|
||||||
@ -283,12 +289,17 @@ export const nullCrypto: TalerCryptoInterface = {
|
|||||||
): Promise<ValidationResult> {
|
): Promise<ValidationResult> {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
|
isValidGlobalFees: function (
|
||||||
|
req: GlobalFeesValidationRequest,
|
||||||
|
): Promise<ValidationResult> {
|
||||||
|
throw new Error("Function not implemented.");
|
||||||
|
},
|
||||||
isValidContractTermsSignature: function (
|
isValidContractTermsSignature: function (
|
||||||
req: ContractTermsValidationRequest,
|
req: ContractTermsValidationRequest,
|
||||||
): Promise<ValidationResult> {
|
): Promise<ValidationResult> {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
createEddsaKeypair: function (req: {}): Promise<EddsaKeypair> {
|
createEddsaKeypair: function (req: unknown): Promise<EddsaKeypair> {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
eddsaGetPublic: function (req: EddsaGetPublicRequest): Promise<EddsaKeypair> {
|
eddsaGetPublic: function (req: EddsaGetPublicRequest): Promise<EddsaKeypair> {
|
||||||
@ -484,6 +495,11 @@ export interface WireFeeValidationRequest {
|
|||||||
masterPub: string;
|
masterPub: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GlobalFeesValidationRequest {
|
||||||
|
gf: GlobalFees;
|
||||||
|
masterPub: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DenominationValidationRequest {
|
export interface DenominationValidationRequest {
|
||||||
denom: DenominationRecord;
|
denom: DenominationRecord;
|
||||||
masterPub: string;
|
masterPub: string;
|
||||||
@ -887,6 +903,30 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
|||||||
return { valid: eddsaVerify(p, sig, pub) };
|
return { valid: eddsaVerify(p, sig, pub) };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a global fee is correctly signed.
|
||||||
|
*/
|
||||||
|
async isValidGlobalFees(
|
||||||
|
tci: TalerCryptoInterfaceR,
|
||||||
|
req: GlobalFeesValidationRequest,
|
||||||
|
): Promise<ValidationResult> {
|
||||||
|
const { gf, masterPub } = req;
|
||||||
|
const p = buildSigPS(TalerSignaturePurpose.GLOBAL_FEES)
|
||||||
|
.put(timestampRoundedToBuffer(gf.start_date))
|
||||||
|
.put(timestampRoundedToBuffer(gf.end_date))
|
||||||
|
.put(durationRoundedToBuffer(gf.purse_timeout))
|
||||||
|
.put(durationRoundedToBuffer(gf.account_kyc_timeout))
|
||||||
|
.put(durationRoundedToBuffer(gf.history_expiration))
|
||||||
|
.put(amountToBuffer(Amounts.parseOrThrow(gf.history_fee)))
|
||||||
|
.put(amountToBuffer(Amounts.parseOrThrow(gf.kyc_fee)))
|
||||||
|
.put(amountToBuffer(Amounts.parseOrThrow(gf.account_fee)))
|
||||||
|
.put(amountToBuffer(Amounts.parseOrThrow(gf.purse_fee)))
|
||||||
|
.put(bufferForUint32(gf.purse_account_limit))
|
||||||
|
.build();
|
||||||
|
const sig = decodeCrock(gf.master_sig);
|
||||||
|
const pub = decodeCrock(masterPub);
|
||||||
|
return { valid: eddsaVerify(p, sig, pub) };
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Check if the signature of a denomination is valid.
|
* Check if the signature of a denomination is valid.
|
||||||
*/
|
*/
|
||||||
@ -1630,6 +1670,24 @@ function timestampRoundedToBuffer(ts: TalerProtocolTimestamp): Uint8Array {
|
|||||||
return new Uint8Array(b);
|
return new Uint8Array(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function durationRoundedToBuffer(ts: TalerProtocolDuration): Uint8Array {
|
||||||
|
const b = new ArrayBuffer(8);
|
||||||
|
const v = new DataView(b);
|
||||||
|
// The buffer we sign over represents the timestamp in microseconds.
|
||||||
|
if (typeof v.setBigUint64 !== "undefined") {
|
||||||
|
const s = BigInt(ts.d_us);
|
||||||
|
v.setBigUint64(0, s);
|
||||||
|
} else {
|
||||||
|
const s = ts.d_us === "forever" ? bigint.zero : bigint(ts.d_us);
|
||||||
|
const arr = s.toArray(2 ** 8).value;
|
||||||
|
let offset = 8 - arr.length;
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
v.setUint8(offset++, arr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Uint8Array(b);
|
||||||
|
}
|
||||||
|
|
||||||
export interface EddsaSignRequest {
|
export interface EddsaSignRequest {
|
||||||
msg: string;
|
msg: string;
|
||||||
priv: string;
|
priv: string;
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
Location,
|
Location,
|
||||||
WireInfo,
|
WireInfo,
|
||||||
DenominationInfo,
|
DenominationInfo,
|
||||||
|
GlobalFees,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { RetryInfo, RetryTags } from "./util/retries.js";
|
import { RetryInfo, RetryTags } from "./util/retries.js";
|
||||||
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
||||||
@ -424,6 +425,10 @@ export interface ExchangeDetailsRecord {
|
|||||||
|
|
||||||
reserveClosingDelay: TalerProtocolDuration;
|
reserveClosingDelay: TalerProtocolDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fees for exchange services
|
||||||
|
*/
|
||||||
|
globalFees: GlobalFees[];
|
||||||
/**
|
/**
|
||||||
* Signing keys we got from the exchange, can also contain
|
* Signing keys we got from the exchange, can also contain
|
||||||
* older signing keys that are not returned by /keys anymore.
|
* older signing keys that are not returned by /keys anymore.
|
||||||
|
@ -345,6 +345,7 @@ export async function exportBackup(
|
|||||||
stamp_expire: x.stamp_expire,
|
stamp_expire: x.stamp_expire,
|
||||||
stamp_start: x.stamp_start,
|
stamp_start: x.stamp_start,
|
||||||
})),
|
})),
|
||||||
|
global_fees: ex.globalFees,
|
||||||
tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
|
tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
|
||||||
tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
|
tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
|
||||||
denominations:
|
denominations:
|
||||||
|
@ -405,6 +405,7 @@ export async function importBackup(
|
|||||||
masterPublicKey: backupExchangeDetails.master_public_key,
|
masterPublicKey: backupExchangeDetails.master_public_key,
|
||||||
protocolVersion: backupExchangeDetails.protocol_version,
|
protocolVersion: backupExchangeDetails.protocol_version,
|
||||||
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
|
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
|
||||||
|
globalFees: backupExchangeDetails.global_fees,
|
||||||
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
|
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
|
||||||
key: x.key,
|
key: x.key,
|
||||||
master_sig: x.master_sig,
|
master_sig: x.master_sig,
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
ExchangeDenomination,
|
ExchangeDenomination,
|
||||||
ExchangeSignKeyJson,
|
ExchangeSignKeyJson,
|
||||||
ExchangeWireJson,
|
ExchangeWireJson,
|
||||||
|
GlobalFees,
|
||||||
hashDenomPub,
|
hashDenomPub,
|
||||||
j2s,
|
j2s,
|
||||||
LibtoolVersion,
|
LibtoolVersion,
|
||||||
@ -269,6 +270,32 @@ async function validateWireInfo(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function validateGlobalFees(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
fees: GlobalFees[],
|
||||||
|
masterPub: string,
|
||||||
|
): Promise<GlobalFees[]> {
|
||||||
|
for (const gf of fees) {
|
||||||
|
logger.trace("validating exchange global fees");
|
||||||
|
let isValid = false;
|
||||||
|
if (ws.insecureTrustExchange) {
|
||||||
|
isValid = true;
|
||||||
|
} else {
|
||||||
|
const { valid: v } = await ws.cryptoApi.isValidGlobalFees({
|
||||||
|
masterPub,
|
||||||
|
gf,
|
||||||
|
});
|
||||||
|
isValid = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
throw Error("exchange global fees signature invalid: " + gf.master_sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fees;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExchangeInfo {
|
export interface ExchangeInfo {
|
||||||
wire: ExchangeWireJson;
|
wire: ExchangeWireJson;
|
||||||
keys: ExchangeKeysDownloadResult;
|
keys: ExchangeKeysDownloadResult;
|
||||||
@ -359,6 +386,7 @@ interface ExchangeKeysDownloadResult {
|
|||||||
expiry: TalerProtocolTimestamp;
|
expiry: TalerProtocolTimestamp;
|
||||||
recoup: Recoup[];
|
recoup: Recoup[];
|
||||||
listIssueDate: TalerProtocolTimestamp;
|
listIssueDate: TalerProtocolTimestamp;
|
||||||
|
globalFees: GlobalFees[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -432,6 +460,7 @@ async function downloadExchangeKeysInfo(
|
|||||||
),
|
),
|
||||||
recoup: exchangeKeysJsonUnchecked.recoup ?? [],
|
recoup: exchangeKeysJsonUnchecked.recoup ?? [],
|
||||||
listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
|
listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
|
||||||
|
globalFees: exchangeKeysJsonUnchecked.global_fees,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,6 +581,12 @@ export async function updateExchangeFromUrlHandler(
|
|||||||
keysInfo.masterPublicKey,
|
keysInfo.masterPublicKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const globalFees = await validateGlobalFees(
|
||||||
|
ws,
|
||||||
|
keysInfo.globalFees,
|
||||||
|
keysInfo.masterPublicKey,
|
||||||
|
);
|
||||||
|
|
||||||
logger.info("finished validating exchange /wire info");
|
logger.info("finished validating exchange /wire info");
|
||||||
|
|
||||||
const tosDownload = await downloadTosFromAcceptedFormat(
|
const tosDownload = await downloadTosFromAcceptedFormat(
|
||||||
@ -594,6 +629,7 @@ export async function updateExchangeFromUrlHandler(
|
|||||||
protocolVersion: keysInfo.protocolVersion,
|
protocolVersion: keysInfo.protocolVersion,
|
||||||
signingKeys: keysInfo.signingKeys,
|
signingKeys: keysInfo.signingKeys,
|
||||||
reserveClosingDelay: keysInfo.reserveClosingDelay,
|
reserveClosingDelay: keysInfo.reserveClosingDelay,
|
||||||
|
globalFees,
|
||||||
exchangeBaseUrl: r.baseUrl,
|
exchangeBaseUrl: r.baseUrl,
|
||||||
wireInfo,
|
wireInfo,
|
||||||
termsOfServiceText: tosDownload.tosText,
|
termsOfServiceText: tosDownload.tosText,
|
||||||
|
Loading…
Reference in New Issue
Block a user