towards implementing breaking exchange protocol changes
This commit is contained in:
parent
606be7577b
commit
5c93f15157
@ -22,10 +22,15 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import * as nacl from "./nacl-fast.js";
|
import * as nacl from "./nacl-fast.js";
|
||||||
import { kdf } from "./kdf.js";
|
import { kdf, kdfKw } from "./kdf.js";
|
||||||
import bigint from "big-integer";
|
import bigint from "big-integer";
|
||||||
import { DenominationPubKey, DenomKeyType } from "./talerTypes.js";
|
import {
|
||||||
import { AssertionError, equal } from "assert";
|
CoinEnvelope,
|
||||||
|
DenominationPubKey,
|
||||||
|
DenomKeyType,
|
||||||
|
HashCodeString,
|
||||||
|
} from "./talerTypes.js";
|
||||||
|
import { Logger } from "./logging.js";
|
||||||
|
|
||||||
export function getRandomBytes(n: number): Uint8Array {
|
export function getRandomBytes(n: number): Uint8Array {
|
||||||
return nacl.randomBytes(n);
|
return nacl.randomBytes(n);
|
||||||
@ -365,7 +370,7 @@ export type CsBlindingSecrets = {
|
|||||||
beta: [Uint8Array, Uint8Array];
|
beta: [Uint8Array, Uint8Array];
|
||||||
};
|
};
|
||||||
|
|
||||||
function typedArrayConcat(chunks: Uint8Array[]): Uint8Array {
|
export function typedArrayConcat(chunks: Uint8Array[]): Uint8Array {
|
||||||
let payloadLen = 0;
|
let payloadLen = 0;
|
||||||
for (const c of chunks) {
|
for (const c of chunks) {
|
||||||
payloadLen += c.byteLength;
|
payloadLen += c.byteLength;
|
||||||
@ -490,9 +495,7 @@ export function deriveBSeed(
|
|||||||
* @param coinPriv coin private key
|
* @param coinPriv coin private key
|
||||||
* @returns nonce
|
* @returns nonce
|
||||||
*/
|
*/
|
||||||
export function deriveWithdrawNonce(
|
export function deriveWithdrawNonce(coinPriv: Uint8Array): Uint8Array {
|
||||||
coinPriv: Uint8Array,
|
|
||||||
): Uint8Array {
|
|
||||||
const outLen = 32;
|
const outLen = 32;
|
||||||
const salt = stringToBytes("n");
|
const salt = stringToBytes("n");
|
||||||
return kdf(outLen, coinPriv, salt);
|
return kdf(outLen, coinPriv, salt);
|
||||||
@ -539,7 +542,7 @@ export async function csUnblind(
|
|||||||
csSig: CsBlindSignature,
|
csSig: CsBlindSignature,
|
||||||
): Promise<CsSignature> {
|
): Promise<CsSignature> {
|
||||||
if (b != 0 && b != 1) {
|
if (b != 0 && b != 1) {
|
||||||
throw new AssertionError();
|
throw new Error();
|
||||||
}
|
}
|
||||||
const secrets = deriveSecrets(bseed);
|
const secrets = deriveSecrets(bseed);
|
||||||
const rPubDash = (await calcRBlind(csPub, secrets, rPub))[b];
|
const rPubDash = (await calcRBlind(csPub, secrets, rPub))[b];
|
||||||
@ -595,9 +598,38 @@ export function hash(d: Uint8Array): Uint8Array {
|
|||||||
return nacl.hash(d);
|
return nacl.hash(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hashCoinEv(
|
||||||
|
coinEv: CoinEnvelope,
|
||||||
|
denomPubHash: HashCodeString,
|
||||||
|
): Uint8Array {
|
||||||
|
const hashContext = createHashContext();
|
||||||
|
hashContext.update(decodeCrock(denomPubHash));
|
||||||
|
hashCoinEvInner(coinEv, hashContext);
|
||||||
|
return hashContext.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = new Logger("talerCrypto.ts");
|
||||||
|
|
||||||
|
export function hashCoinEvInner(
|
||||||
|
coinEv: CoinEnvelope,
|
||||||
|
hashState: nacl.HashState,
|
||||||
|
): void {
|
||||||
|
const hashInputBuf = new ArrayBuffer(4);
|
||||||
|
const uint8ArrayBuf = new Uint8Array(hashInputBuf);
|
||||||
|
const dv = new DataView(hashInputBuf);
|
||||||
|
dv.setUint32(0, DenomKeyType.toIntTag(coinEv.cipher));
|
||||||
|
hashState.update(uint8ArrayBuf);
|
||||||
|
switch (coinEv.cipher) {
|
||||||
|
case DenomKeyType.Rsa:
|
||||||
|
hashState.update(decodeCrock(coinEv.rsa_blinded_planchet));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash a denomination public key according to the
|
* Hash a denomination public key.
|
||||||
* algorithm of exchange protocol v10.
|
|
||||||
*/
|
*/
|
||||||
export function hashDenomPub(pub: DenominationPubKey): Uint8Array {
|
export function hashDenomPub(pub: DenominationPubKey): Uint8Array {
|
||||||
if (pub.cipher === DenomKeyType.Rsa) {
|
if (pub.cipher === DenomKeyType.Rsa) {
|
||||||
@ -606,18 +638,16 @@ export function hashDenomPub(pub: DenominationPubKey): Uint8Array {
|
|||||||
const uint8ArrayBuf = new Uint8Array(hashInputBuf);
|
const uint8ArrayBuf = new Uint8Array(hashInputBuf);
|
||||||
const dv = new DataView(hashInputBuf);
|
const dv = new DataView(hashInputBuf);
|
||||||
dv.setUint32(0, pub.age_mask ?? 0);
|
dv.setUint32(0, pub.age_mask ?? 0);
|
||||||
dv.setUint32(4, pub.cipher);
|
dv.setUint32(4, DenomKeyType.toIntTag(pub.cipher));
|
||||||
uint8ArrayBuf.set(pubBuf, 8);
|
uint8ArrayBuf.set(pubBuf, 8);
|
||||||
return nacl.hash(uint8ArrayBuf);
|
return nacl.hash(uint8ArrayBuf);
|
||||||
} else if (pub.cipher === DenomKeyType.LegacyRsa) {
|
|
||||||
return hash(decodeCrock(pub.rsa_public_key));
|
|
||||||
} else if (pub.cipher === DenomKeyType.ClauseSchnorr) {
|
} else if (pub.cipher === DenomKeyType.ClauseSchnorr) {
|
||||||
const pubBuf = decodeCrock(pub.cs_public_key);
|
const pubBuf = decodeCrock(pub.cs_public_key);
|
||||||
const hashInputBuf = new ArrayBuffer(pubBuf.length + 4 + 4);
|
const hashInputBuf = new ArrayBuffer(pubBuf.length + 4 + 4);
|
||||||
const uint8ArrayBuf = new Uint8Array(hashInputBuf);
|
const uint8ArrayBuf = new Uint8Array(hashInputBuf);
|
||||||
const dv = new DataView(hashInputBuf);
|
const dv = new DataView(hashInputBuf);
|
||||||
dv.setUint32(0, pub.age_mask ?? 0);
|
dv.setUint32(0, pub.age_mask ?? 0);
|
||||||
dv.setUint32(4, pub.cipher);
|
dv.setUint32(4, DenomKeyType.toIntTag(pub.cipher));
|
||||||
uint8ArrayBuf.set(pubBuf, 8);
|
uint8ArrayBuf.set(pubBuf, 8);
|
||||||
return nacl.hash(uint8ArrayBuf);
|
return nacl.hash(uint8ArrayBuf);
|
||||||
} else {
|
} else {
|
||||||
@ -652,18 +682,57 @@ export interface FreshCoin {
|
|||||||
bks: Uint8Array;
|
bks: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export function setupRefreshPlanchet(
|
||||||
|
// secretSeed: Uint8Array,
|
||||||
|
// coinNumber: number,
|
||||||
|
// ): FreshCoin {
|
||||||
|
// const info = stringToBytes("taler-coin-derivation");
|
||||||
|
// const saltArrBuf = new ArrayBuffer(4);
|
||||||
|
// const salt = new Uint8Array(saltArrBuf);
|
||||||
|
// const saltDataView = new DataView(saltArrBuf);
|
||||||
|
// saltDataView.setUint32(0, coinNumber);
|
||||||
|
// const out = kdf(64, secretSeed, salt, info);
|
||||||
|
// const coinPriv = out.slice(0, 32);
|
||||||
|
// const bks = out.slice(32, 64);
|
||||||
|
// return {
|
||||||
|
// bks,
|
||||||
|
// coinPriv,
|
||||||
|
// coinPub: eddsaGetPublic(coinPriv),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
function bufferForUint32(n: number): Uint8Array {
|
||||||
|
const arrBuf = new ArrayBuffer(4);
|
||||||
|
const buf = new Uint8Array(arrBuf);
|
||||||
|
const dv = new DataView(arrBuf);
|
||||||
|
dv.setUint32(0, n);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
export function setupRefreshPlanchet(
|
export function setupRefreshPlanchet(
|
||||||
secretSeed: Uint8Array,
|
transferSecret: Uint8Array,
|
||||||
coinNumber: number,
|
coinNumber: number,
|
||||||
): FreshCoin {
|
): FreshCoin {
|
||||||
const info = stringToBytes("taler-coin-derivation");
|
// See TALER_transfer_secret_to_planchet_secret in C impl
|
||||||
const saltArrBuf = new ArrayBuffer(4);
|
const planchetMasterSecret = kdfKw({
|
||||||
const salt = new Uint8Array(saltArrBuf);
|
ikm: transferSecret,
|
||||||
const saltDataView = new DataView(saltArrBuf);
|
outputLength: 32,
|
||||||
saltDataView.setUint32(0, coinNumber);
|
salt: bufferForUint32(coinNumber),
|
||||||
const out = kdf(64, secretSeed, salt, info);
|
info: stringToBytes("taler-coin-derivation"),
|
||||||
const coinPriv = out.slice(0, 32);
|
});
|
||||||
const bks = out.slice(32, 64);
|
|
||||||
|
const coinPriv = kdfKw({
|
||||||
|
ikm: planchetMasterSecret,
|
||||||
|
outputLength: 32,
|
||||||
|
salt: stringToBytes("coin"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const bks = kdfKw({
|
||||||
|
ikm: planchetMasterSecret,
|
||||||
|
outputLength: 32,
|
||||||
|
salt: stringToBytes("bks"),
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bks,
|
bks,
|
||||||
coinPriv,
|
coinPriv,
|
||||||
|
@ -60,11 +60,8 @@ export class ExchangeDenomination {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Public signing key of the denomination.
|
* Public signing key of the denomination.
|
||||||
*
|
|
||||||
* The "string" alternative is for the old exchange protocol (v9) that
|
|
||||||
* only supports RSA keys.
|
|
||||||
*/
|
*/
|
||||||
denom_pub: DenominationPubKey | string;
|
denom_pub: DenominationPubKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fee for withdrawing.
|
* Fee for withdrawing.
|
||||||
@ -162,7 +159,7 @@ export interface RecoupRequest {
|
|||||||
*
|
*
|
||||||
* The string variant is for the legacy exchange protocol.
|
* The string variant is for the legacy exchange protocol.
|
||||||
*/
|
*/
|
||||||
denom_sig: UnblindedSignature | string;
|
denom_sig: UnblindedSignature;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blinding key that was used during withdraw,
|
* Blinding key that was used during withdraw,
|
||||||
@ -188,7 +185,7 @@ export interface RecoupRefreshRequest {
|
|||||||
*
|
*
|
||||||
* The string variant is for the legacy exchange protocol.
|
* The string variant is for the legacy exchange protocol.
|
||||||
*/
|
*/
|
||||||
denom_sig: UnblindedSignature | string;
|
denom_sig: UnblindedSignature;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coin's blinding factor.
|
* Coin's blinding factor.
|
||||||
@ -218,20 +215,13 @@ export interface RecoupConfirmation {
|
|||||||
old_coin_pub?: string;
|
old_coin_pub?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UnblindedSignature =
|
export type UnblindedSignature = RsaUnblindedSignature;
|
||||||
| RsaUnblindedSignature
|
|
||||||
| LegacyRsaUnblindedSignature;
|
|
||||||
|
|
||||||
export interface RsaUnblindedSignature {
|
export interface RsaUnblindedSignature {
|
||||||
cipher: DenomKeyType.Rsa;
|
cipher: DenomKeyType.Rsa;
|
||||||
rsa_signature: string;
|
rsa_signature: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LegacyRsaUnblindedSignature {
|
|
||||||
cipher: DenomKeyType.LegacyRsa;
|
|
||||||
rsa_signature: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deposit permission for a single coin.
|
* Deposit permission for a single coin.
|
||||||
*/
|
*/
|
||||||
@ -252,7 +242,7 @@ export interface CoinDepositPermission {
|
|||||||
* The string variant is for legacy protocol support.
|
* The string variant is for legacy protocol support.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ub_sig: UnblindedSignature | string;
|
ub_sig: UnblindedSignature;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The denomination public key associated with this coin.
|
* The denomination public key associated with this coin.
|
||||||
@ -841,9 +831,19 @@ export class TipPickupGetResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum DenomKeyType {
|
export enum DenomKeyType {
|
||||||
Rsa = 1,
|
Rsa = "RSA",
|
||||||
ClauseSchnorr = 2,
|
ClauseSchnorr = "CS",
|
||||||
LegacyRsa = 3,
|
}
|
||||||
|
|
||||||
|
export namespace DenomKeyType {
|
||||||
|
export function toIntTag(t: DenomKeyType): number {
|
||||||
|
switch (t) {
|
||||||
|
case DenomKeyType.Rsa:
|
||||||
|
return 1;
|
||||||
|
case DenomKeyType.ClauseSchnorr:
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RsaBlindedDenominationSignature {
|
export interface RsaBlindedDenominationSignature {
|
||||||
@ -851,44 +851,28 @@ export interface RsaBlindedDenominationSignature {
|
|||||||
blinded_rsa_signature: string;
|
blinded_rsa_signature: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LegacyRsaBlindedDenominationSignature {
|
|
||||||
cipher: DenomKeyType.LegacyRsa;
|
|
||||||
blinded_rsa_signature: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CSBlindedDenominationSignature {
|
export interface CSBlindedDenominationSignature {
|
||||||
cipher: DenomKeyType.ClauseSchnorr;
|
cipher: DenomKeyType.ClauseSchnorr;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BlindedDenominationSignature =
|
export type BlindedDenominationSignature =
|
||||||
| RsaBlindedDenominationSignature
|
| RsaBlindedDenominationSignature
|
||||||
| CSBlindedDenominationSignature
|
| CSBlindedDenominationSignature;
|
||||||
| LegacyRsaBlindedDenominationSignature;
|
|
||||||
|
|
||||||
export const codecForBlindedDenominationSignature = () =>
|
export const codecForBlindedDenominationSignature = () =>
|
||||||
buildCodecForUnion<BlindedDenominationSignature>()
|
buildCodecForUnion<BlindedDenominationSignature>()
|
||||||
.discriminateOn("cipher")
|
.discriminateOn("cipher")
|
||||||
.alternative(1, codecForRsaBlindedDenominationSignature())
|
.alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature())
|
||||||
.alternative(3, codecForLegacyRsaBlindedDenominationSignature())
|
|
||||||
.build("BlindedDenominationSignature");
|
.build("BlindedDenominationSignature");
|
||||||
|
|
||||||
export const codecForRsaBlindedDenominationSignature = () =>
|
export const codecForRsaBlindedDenominationSignature = () =>
|
||||||
buildCodecForObject<RsaBlindedDenominationSignature>()
|
buildCodecForObject<RsaBlindedDenominationSignature>()
|
||||||
.property("cipher", codecForConstNumber(1))
|
.property("cipher", codecForConstString(DenomKeyType.Rsa))
|
||||||
.property("blinded_rsa_signature", codecForString())
|
.property("blinded_rsa_signature", codecForString())
|
||||||
.build("RsaBlindedDenominationSignature");
|
.build("RsaBlindedDenominationSignature");
|
||||||
|
|
||||||
export const codecForLegacyRsaBlindedDenominationSignature = () =>
|
|
||||||
buildCodecForObject<LegacyRsaBlindedDenominationSignature>()
|
|
||||||
.property("cipher", codecForConstNumber(1))
|
|
||||||
.property("blinded_rsa_signature", codecForString())
|
|
||||||
.build("LegacyRsaBlindedDenominationSignature");
|
|
||||||
|
|
||||||
export class WithdrawResponse {
|
export class WithdrawResponse {
|
||||||
/**
|
ev_sig: BlindedDenominationSignature;
|
||||||
* The string variant is for legacy protocol support.
|
|
||||||
*/
|
|
||||||
ev_sig: BlindedDenominationSignature | string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -983,10 +967,7 @@ export interface ExchangeMeltResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeRevealItem {
|
export interface ExchangeRevealItem {
|
||||||
/**
|
ev_sig: BlindedDenominationSignature;
|
||||||
* The string variant is for the legacy v9 protocol.
|
|
||||||
*/
|
|
||||||
ev_sig: BlindedDenominationSignature | string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeRevealResponse {
|
export interface ExchangeRevealResponse {
|
||||||
@ -1105,26 +1086,18 @@ export interface BankWithdrawalOperationPostResponse {
|
|||||||
transfer_done: boolean;
|
transfer_done: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DenominationPubKey =
|
export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey;
|
||||||
| RsaDenominationPubKey
|
|
||||||
| CsDenominationPubKey
|
|
||||||
| LegacyRsaDenominationPubKey;
|
|
||||||
|
|
||||||
export interface LegacyRsaDenominationPubKey {
|
|
||||||
cipher: DenomKeyType.LegacyRsa;
|
|
||||||
rsa_public_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RsaDenominationPubKey {
|
export interface RsaDenominationPubKey {
|
||||||
cipher: DenomKeyType.Rsa;
|
readonly cipher: DenomKeyType.Rsa;
|
||||||
rsa_public_key: string;
|
readonly rsa_public_key: string;
|
||||||
age_mask?: number;
|
readonly age_mask?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CsDenominationPubKey {
|
export interface CsDenominationPubKey {
|
||||||
cipher: DenomKeyType.ClauseSchnorr;
|
readonly cipher: DenomKeyType.ClauseSchnorr;
|
||||||
age_mask: number;
|
readonly age_mask: number;
|
||||||
cs_public_key: string;
|
readonly cs_public_key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace DenominationPubKey {
|
export namespace DenominationPubKey {
|
||||||
@ -1136,12 +1109,6 @@ export namespace DenominationPubKey {
|
|||||||
return -1;
|
return -1;
|
||||||
} else if (p1.cipher > p2.cipher) {
|
} else if (p1.cipher > p2.cipher) {
|
||||||
return +1;
|
return +1;
|
||||||
}
|
|
||||||
if (
|
|
||||||
p1.cipher === DenomKeyType.LegacyRsa &&
|
|
||||||
p2.cipher === DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
return strcmp(p1.rsa_public_key, p2.rsa_public_key);
|
|
||||||
} else if (
|
} else if (
|
||||||
p1.cipher === DenomKeyType.Rsa &&
|
p1.cipher === DenomKeyType.Rsa &&
|
||||||
p2.cipher === DenomKeyType.Rsa
|
p2.cipher === DenomKeyType.Rsa
|
||||||
@ -1166,41 +1133,24 @@ export namespace DenominationPubKey {
|
|||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lift(p1: DenominationPubKey | string): DenominationPubKey {
|
|
||||||
if (typeof p1 === "string") {
|
|
||||||
return {
|
|
||||||
cipher: DenomKeyType.LegacyRsa,
|
|
||||||
rsa_public_key: p1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return p1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForDenominationPubKey = () =>
|
export const codecForDenominationPubKey = () =>
|
||||||
buildCodecForUnion<DenominationPubKey>()
|
buildCodecForUnion<DenominationPubKey>()
|
||||||
.discriminateOn("cipher")
|
.discriminateOn("cipher")
|
||||||
.alternative(1, codecForRsaDenominationPubKey())
|
.alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey())
|
||||||
.alternative(2, codecForCsDenominationPubKey())
|
.alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
|
||||||
.alternative(3, codecForLegacyRsaDenominationPubKey())
|
|
||||||
.build("DenominationPubKey");
|
.build("DenominationPubKey");
|
||||||
|
|
||||||
export const codecForRsaDenominationPubKey = () =>
|
export const codecForRsaDenominationPubKey = () =>
|
||||||
buildCodecForObject<RsaDenominationPubKey>()
|
buildCodecForObject<RsaDenominationPubKey>()
|
||||||
.property("cipher", codecForConstNumber(1))
|
.property("cipher", codecForConstString(DenomKeyType.Rsa))
|
||||||
.property("rsa_public_key", codecForString())
|
.property("rsa_public_key", codecForString())
|
||||||
.build("DenominationPubKey");
|
.build("DenominationPubKey");
|
||||||
|
|
||||||
export const codecForLegacyRsaDenominationPubKey = () =>
|
|
||||||
buildCodecForObject<LegacyRsaDenominationPubKey>()
|
|
||||||
.property("cipher", codecForConstNumber(3))
|
|
||||||
.property("rsa_public_key", codecForString())
|
|
||||||
.build("LegacyRsaDenominationPubKey");
|
|
||||||
|
|
||||||
export const codecForCsDenominationPubKey = () =>
|
export const codecForCsDenominationPubKey = () =>
|
||||||
buildCodecForObject<CsDenominationPubKey>()
|
buildCodecForObject<CsDenominationPubKey>()
|
||||||
.property("cipher", codecForConstNumber(2))
|
.property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr))
|
||||||
.property("cs_public_key", codecForString())
|
.property("cs_public_key", codecForString())
|
||||||
.build("CsDenominationPubKey");
|
.build("CsDenominationPubKey");
|
||||||
|
|
||||||
@ -1219,10 +1169,7 @@ export type CoinPublicKeyString = string;
|
|||||||
export const codecForDenomination = (): Codec<ExchangeDenomination> =>
|
export const codecForDenomination = (): Codec<ExchangeDenomination> =>
|
||||||
buildCodecForObject<ExchangeDenomination>()
|
buildCodecForObject<ExchangeDenomination>()
|
||||||
.property("value", codecForString())
|
.property("value", codecForString())
|
||||||
.property(
|
.property("denom_pub", codecForDenominationPubKey())
|
||||||
"denom_pub",
|
|
||||||
codecForEither(codecForDenominationPubKey(), codecForString()),
|
|
||||||
)
|
|
||||||
.property("fee_withdraw", codecForString())
|
.property("fee_withdraw", codecForString())
|
||||||
.property("fee_deposit", codecForString())
|
.property("fee_deposit", codecForString())
|
||||||
.property("fee_refresh", codecForString())
|
.property("fee_refresh", codecForString())
|
||||||
@ -1470,10 +1417,7 @@ export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> =>
|
|||||||
|
|
||||||
export const codecForWithdrawResponse = (): Codec<WithdrawResponse> =>
|
export const codecForWithdrawResponse = (): Codec<WithdrawResponse> =>
|
||||||
buildCodecForObject<WithdrawResponse>()
|
buildCodecForObject<WithdrawResponse>()
|
||||||
.property(
|
.property("ev_sig", codecForBlindedDenominationSignature())
|
||||||
"ev_sig",
|
|
||||||
codecForEither(codecForBlindedDenominationSignature(), codecForString()),
|
|
||||||
)
|
|
||||||
.build("WithdrawResponse");
|
.build("WithdrawResponse");
|
||||||
|
|
||||||
export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> =>
|
export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> =>
|
||||||
@ -1491,10 +1435,7 @@ export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> =>
|
|||||||
|
|
||||||
export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> =>
|
export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> =>
|
||||||
buildCodecForObject<ExchangeRevealItem>()
|
buildCodecForObject<ExchangeRevealItem>()
|
||||||
.property(
|
.property("ev_sig", codecForBlindedDenominationSignature())
|
||||||
"ev_sig",
|
|
||||||
codecForEither(codecForBlindedDenominationSignature(), codecForString()),
|
|
||||||
)
|
|
||||||
.build("ExchangeRevealItem");
|
.build("ExchangeRevealItem");
|
||||||
|
|
||||||
export const codecForExchangeRevealResponse =
|
export const codecForExchangeRevealResponse =
|
||||||
@ -1711,17 +1652,48 @@ export const codecForMerchantConfigResponse =
|
|||||||
.build("MerchantConfigResponse");
|
.build("MerchantConfigResponse");
|
||||||
|
|
||||||
export enum ExchangeProtocolVersion {
|
export enum ExchangeProtocolVersion {
|
||||||
V9 = 9,
|
/**
|
||||||
|
* Current version supported by the wallet.
|
||||||
|
*/
|
||||||
V12 = 12,
|
V12 = 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MerchantProtocolVersion {
|
export enum MerchantProtocolVersion {
|
||||||
/**
|
|
||||||
* Legacy version that is still supported.
|
|
||||||
*/
|
|
||||||
V1 = 1,
|
|
||||||
/**
|
/**
|
||||||
* Current version supported by the wallet.
|
* Current version supported by the wallet.
|
||||||
*/
|
*/
|
||||||
V3 = 3,
|
V3 = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CoinEnvelope = CoinEnvelopeRsa | CoinEnvelopeCs;
|
||||||
|
|
||||||
|
export interface CoinEnvelopeRsa {
|
||||||
|
cipher: DenomKeyType.Rsa;
|
||||||
|
rsa_blinded_planchet: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoinEnvelopeCs {
|
||||||
|
cipher: DenomKeyType.ClauseSchnorr;
|
||||||
|
// FIXME: add remaining fields
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HashCodeString = string;
|
||||||
|
|
||||||
|
export interface ExchangeWithdrawRequest {
|
||||||
|
denom_pub_hash: HashCodeString;
|
||||||
|
reserve_sig: EddsaSignatureString;
|
||||||
|
coin_ev: CoinEnvelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExchangeRefreshRevealRequest {
|
||||||
|
new_denoms_h: HashCodeString[];
|
||||||
|
coin_evs: CoinEnvelope[];
|
||||||
|
/**
|
||||||
|
* kappa - 1 transfer private keys (ephemeral ECDHE keys).
|
||||||
|
*/
|
||||||
|
transfer_privs: string[];
|
||||||
|
|
||||||
|
transfer_pub: EddsaPublicKeyString;
|
||||||
|
|
||||||
|
link_sigs: EddsaSignatureString[];
|
||||||
|
}
|
||||||
|
@ -47,6 +47,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
AmountString,
|
AmountString,
|
||||||
codecForContractTerms,
|
codecForContractTerms,
|
||||||
|
CoinEnvelope,
|
||||||
ContractTerms,
|
ContractTerms,
|
||||||
DenominationPubKey,
|
DenominationPubKey,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
@ -136,11 +137,12 @@ export interface ConfirmPayResultPending {
|
|||||||
|
|
||||||
export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
|
export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
|
||||||
|
|
||||||
export const codecForConfirmPayResultPending = (): Codec<ConfirmPayResultPending> =>
|
export const codecForConfirmPayResultPending =
|
||||||
buildCodecForObject<ConfirmPayResultPending>()
|
(): Codec<ConfirmPayResultPending> =>
|
||||||
.property("lastError", codecForAny())
|
buildCodecForObject<ConfirmPayResultPending>()
|
||||||
.property("type", codecForConstString(ConfirmPayResultType.Pending))
|
.property("lastError", codecForAny())
|
||||||
.build("ConfirmPayResultPending");
|
.property("type", codecForConstString(ConfirmPayResultType.Pending))
|
||||||
|
.build("ConfirmPayResultPending");
|
||||||
|
|
||||||
export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
|
export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
|
||||||
buildCodecForObject<ConfirmPayResultDone>()
|
buildCodecForObject<ConfirmPayResultDone>()
|
||||||
@ -322,45 +324,48 @@ export enum PreparePayResultType {
|
|||||||
AlreadyConfirmed = "already-confirmed",
|
AlreadyConfirmed = "already-confirmed",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForPreparePayResultPaymentPossible = (): Codec<PreparePayResultPaymentPossible> =>
|
export const codecForPreparePayResultPaymentPossible =
|
||||||
buildCodecForObject<PreparePayResultPaymentPossible>()
|
(): Codec<PreparePayResultPaymentPossible> =>
|
||||||
.property("amountEffective", codecForAmountString())
|
buildCodecForObject<PreparePayResultPaymentPossible>()
|
||||||
.property("amountRaw", codecForAmountString())
|
.property("amountEffective", codecForAmountString())
|
||||||
.property("contractTerms", codecForContractTerms())
|
.property("amountRaw", codecForAmountString())
|
||||||
.property("proposalId", codecForString())
|
.property("contractTerms", codecForContractTerms())
|
||||||
.property("contractTermsHash", codecForString())
|
.property("proposalId", codecForString())
|
||||||
.property("noncePriv", codecForString())
|
.property("contractTermsHash", codecForString())
|
||||||
.property(
|
.property("noncePriv", codecForString())
|
||||||
"status",
|
.property(
|
||||||
codecForConstString(PreparePayResultType.PaymentPossible),
|
"status",
|
||||||
)
|
codecForConstString(PreparePayResultType.PaymentPossible),
|
||||||
.build("PreparePayResultPaymentPossible");
|
)
|
||||||
|
.build("PreparePayResultPaymentPossible");
|
||||||
|
|
||||||
export const codecForPreparePayResultInsufficientBalance = (): Codec<PreparePayResultInsufficientBalance> =>
|
export const codecForPreparePayResultInsufficientBalance =
|
||||||
buildCodecForObject<PreparePayResultInsufficientBalance>()
|
(): Codec<PreparePayResultInsufficientBalance> =>
|
||||||
.property("amountRaw", codecForAmountString())
|
buildCodecForObject<PreparePayResultInsufficientBalance>()
|
||||||
.property("contractTerms", codecForAny())
|
.property("amountRaw", codecForAmountString())
|
||||||
.property("proposalId", codecForString())
|
.property("contractTerms", codecForAny())
|
||||||
.property("noncePriv", codecForString())
|
.property("proposalId", codecForString())
|
||||||
.property(
|
.property("noncePriv", codecForString())
|
||||||
"status",
|
.property(
|
||||||
codecForConstString(PreparePayResultType.InsufficientBalance),
|
"status",
|
||||||
)
|
codecForConstString(PreparePayResultType.InsufficientBalance),
|
||||||
.build("PreparePayResultInsufficientBalance");
|
)
|
||||||
|
.build("PreparePayResultInsufficientBalance");
|
||||||
|
|
||||||
export const codecForPreparePayResultAlreadyConfirmed = (): Codec<PreparePayResultAlreadyConfirmed> =>
|
export const codecForPreparePayResultAlreadyConfirmed =
|
||||||
buildCodecForObject<PreparePayResultAlreadyConfirmed>()
|
(): Codec<PreparePayResultAlreadyConfirmed> =>
|
||||||
.property(
|
buildCodecForObject<PreparePayResultAlreadyConfirmed>()
|
||||||
"status",
|
.property(
|
||||||
codecForConstString(PreparePayResultType.AlreadyConfirmed),
|
"status",
|
||||||
)
|
codecForConstString(PreparePayResultType.AlreadyConfirmed),
|
||||||
.property("amountEffective", codecForAmountString())
|
)
|
||||||
.property("amountRaw", codecForAmountString())
|
.property("amountEffective", codecForAmountString())
|
||||||
.property("paid", codecForBoolean())
|
.property("amountRaw", codecForAmountString())
|
||||||
.property("contractTerms", codecForAny())
|
.property("paid", codecForBoolean())
|
||||||
.property("contractTermsHash", codecForString())
|
.property("contractTerms", codecForAny())
|
||||||
.property("proposalId", codecForString())
|
.property("contractTermsHash", codecForString())
|
||||||
.build("PreparePayResultAlreadyConfirmed");
|
.property("proposalId", codecForString())
|
||||||
|
.build("PreparePayResultAlreadyConfirmed");
|
||||||
|
|
||||||
export const codecForPreparePayResult = (): Codec<PreparePayResult> =>
|
export const codecForPreparePayResult = (): Codec<PreparePayResult> =>
|
||||||
buildCodecForUnion<PreparePayResult>()
|
buildCodecForUnion<PreparePayResult>()
|
||||||
@ -461,7 +466,7 @@ export interface PlanchetCreationResult {
|
|||||||
denomPub: DenominationPubKey;
|
denomPub: DenominationPubKey;
|
||||||
blindingKey: string;
|
blindingKey: string;
|
||||||
withdrawSig: string;
|
withdrawSig: string;
|
||||||
coinEv: string;
|
coinEv: CoinEnvelope;
|
||||||
coinValue: AmountJson;
|
coinValue: AmountJson;
|
||||||
coinEvHash: string;
|
coinEvHash: string;
|
||||||
}
|
}
|
||||||
@ -543,12 +548,13 @@ export interface ExchangeListItem {
|
|||||||
tos: ExchangeTos;
|
tos: ExchangeTos;
|
||||||
}
|
}
|
||||||
|
|
||||||
const codecForExchangeTos = (): Codec<ExchangeTos> => buildCodecForObject<ExchangeTos>()
|
const codecForExchangeTos = (): Codec<ExchangeTos> =>
|
||||||
.property("acceptedVersion", codecOptional(codecForString()))
|
buildCodecForObject<ExchangeTos>()
|
||||||
.property("currentVersion", codecOptional(codecForString()))
|
.property("acceptedVersion", codecOptional(codecForString()))
|
||||||
.property("contentType", codecOptional(codecForString()))
|
.property("currentVersion", codecOptional(codecForString()))
|
||||||
.property("content", codecOptional(codecForString()))
|
.property("contentType", codecOptional(codecForString()))
|
||||||
.build("ExchangeTos")
|
.property("content", codecOptional(codecForString()))
|
||||||
|
.build("ExchangeTos");
|
||||||
|
|
||||||
export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
|
export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
|
||||||
buildCodecForObject<ExchangeListItem>()
|
buildCodecForObject<ExchangeListItem>()
|
||||||
@ -670,10 +676,11 @@ export interface ForceExchangeUpdateRequest {
|
|||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForForceExchangeUpdateRequest = (): Codec<AddExchangeRequest> =>
|
export const codecForForceExchangeUpdateRequest =
|
||||||
buildCodecForObject<AddExchangeRequest>()
|
(): Codec<AddExchangeRequest> =>
|
||||||
.property("exchangeBaseUrl", codecForString())
|
buildCodecForObject<AddExchangeRequest>()
|
||||||
.build("AddExchangeRequest");
|
.property("exchangeBaseUrl", codecForString())
|
||||||
|
.build("AddExchangeRequest");
|
||||||
|
|
||||||
export interface GetExchangeTosRequest {
|
export interface GetExchangeTosRequest {
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
@ -691,11 +698,12 @@ export interface AcceptManualWithdrawalRequest {
|
|||||||
amount: string;
|
amount: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForAcceptManualWithdrawalRequet = (): Codec<AcceptManualWithdrawalRequest> =>
|
export const codecForAcceptManualWithdrawalRequet =
|
||||||
buildCodecForObject<AcceptManualWithdrawalRequest>()
|
(): Codec<AcceptManualWithdrawalRequest> =>
|
||||||
.property("exchangeBaseUrl", codecForString())
|
buildCodecForObject<AcceptManualWithdrawalRequest>()
|
||||||
.property("amount", codecForString())
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.build("AcceptManualWithdrawalRequest");
|
.property("amount", codecForString())
|
||||||
|
.build("AcceptManualWithdrawalRequest");
|
||||||
|
|
||||||
export interface GetWithdrawalDetailsForAmountRequest {
|
export interface GetWithdrawalDetailsForAmountRequest {
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
@ -707,28 +715,31 @@ export interface AcceptBankIntegratedWithdrawalRequest {
|
|||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec<AcceptBankIntegratedWithdrawalRequest> =>
|
export const codecForAcceptBankIntegratedWithdrawalRequest =
|
||||||
buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>()
|
(): Codec<AcceptBankIntegratedWithdrawalRequest> =>
|
||||||
.property("exchangeBaseUrl", codecForString())
|
buildCodecForObject<AcceptBankIntegratedWithdrawalRequest>()
|
||||||
.property("talerWithdrawUri", codecForString())
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.build("AcceptBankIntegratedWithdrawalRequest");
|
.property("talerWithdrawUri", codecForString())
|
||||||
|
.build("AcceptBankIntegratedWithdrawalRequest");
|
||||||
|
|
||||||
export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec<GetWithdrawalDetailsForAmountRequest> =>
|
export const codecForGetWithdrawalDetailsForAmountRequest =
|
||||||
buildCodecForObject<GetWithdrawalDetailsForAmountRequest>()
|
(): Codec<GetWithdrawalDetailsForAmountRequest> =>
|
||||||
.property("exchangeBaseUrl", codecForString())
|
buildCodecForObject<GetWithdrawalDetailsForAmountRequest>()
|
||||||
.property("amount", codecForString())
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.build("GetWithdrawalDetailsForAmountRequest");
|
.property("amount", codecForString())
|
||||||
|
.build("GetWithdrawalDetailsForAmountRequest");
|
||||||
|
|
||||||
export interface AcceptExchangeTosRequest {
|
export interface AcceptExchangeTosRequest {
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
etag: string;
|
etag: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForAcceptExchangeTosRequest = (): Codec<AcceptExchangeTosRequest> =>
|
export const codecForAcceptExchangeTosRequest =
|
||||||
buildCodecForObject<AcceptExchangeTosRequest>()
|
(): Codec<AcceptExchangeTosRequest> =>
|
||||||
.property("exchangeBaseUrl", codecForString())
|
buildCodecForObject<AcceptExchangeTosRequest>()
|
||||||
.property("etag", codecForString())
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.build("AcceptExchangeTosRequest");
|
.property("etag", codecForString())
|
||||||
|
.build("AcceptExchangeTosRequest");
|
||||||
|
|
||||||
export interface ApplyRefundRequest {
|
export interface ApplyRefundRequest {
|
||||||
talerRefundUri: string;
|
talerRefundUri: string;
|
||||||
@ -742,18 +753,20 @@ export const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> =>
|
|||||||
export interface GetWithdrawalDetailsForUriRequest {
|
export interface GetWithdrawalDetailsForUriRequest {
|
||||||
talerWithdrawUri: string;
|
talerWithdrawUri: string;
|
||||||
}
|
}
|
||||||
export const codecForGetWithdrawalDetailsForUri = (): Codec<GetWithdrawalDetailsForUriRequest> =>
|
export const codecForGetWithdrawalDetailsForUri =
|
||||||
buildCodecForObject<GetWithdrawalDetailsForUriRequest>()
|
(): Codec<GetWithdrawalDetailsForUriRequest> =>
|
||||||
.property("talerWithdrawUri", codecForString())
|
buildCodecForObject<GetWithdrawalDetailsForUriRequest>()
|
||||||
.build("GetWithdrawalDetailsForUriRequest");
|
.property("talerWithdrawUri", codecForString())
|
||||||
|
.build("GetWithdrawalDetailsForUriRequest");
|
||||||
|
|
||||||
export interface ListKnownBankAccountsRequest {
|
export interface ListKnownBankAccountsRequest {
|
||||||
currency?: string;
|
currency?: string;
|
||||||
}
|
}
|
||||||
export const codecForListKnownBankAccounts = (): Codec<ListKnownBankAccountsRequest> =>
|
export const codecForListKnownBankAccounts =
|
||||||
buildCodecForObject<ListKnownBankAccountsRequest>()
|
(): Codec<ListKnownBankAccountsRequest> =>
|
||||||
.property("currency", codecOptional(codecForString()))
|
buildCodecForObject<ListKnownBankAccountsRequest>()
|
||||||
.build("ListKnownBankAccountsRequest");
|
.property("currency", codecOptional(codecForString()))
|
||||||
|
.build("ListKnownBankAccountsRequest");
|
||||||
|
|
||||||
export interface GetExchangeWithdrawalInfo {
|
export interface GetExchangeWithdrawalInfo {
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
@ -761,15 +774,16 @@ export interface GetExchangeWithdrawalInfo {
|
|||||||
tosAcceptedFormat?: string[];
|
tosAcceptedFormat?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForGetExchangeWithdrawalInfo = (): Codec<GetExchangeWithdrawalInfo> =>
|
export const codecForGetExchangeWithdrawalInfo =
|
||||||
buildCodecForObject<GetExchangeWithdrawalInfo>()
|
(): Codec<GetExchangeWithdrawalInfo> =>
|
||||||
.property("exchangeBaseUrl", codecForString())
|
buildCodecForObject<GetExchangeWithdrawalInfo>()
|
||||||
.property("amount", codecForAmountJson())
|
.property("exchangeBaseUrl", codecForString())
|
||||||
.property(
|
.property("amount", codecForAmountJson())
|
||||||
"tosAcceptedFormat",
|
.property(
|
||||||
codecOptional(codecForList(codecForString())),
|
"tosAcceptedFormat",
|
||||||
)
|
codecOptional(codecForList(codecForString())),
|
||||||
.build("GetExchangeWithdrawalInfo");
|
)
|
||||||
|
.build("GetExchangeWithdrawalInfo");
|
||||||
|
|
||||||
export interface AbortProposalRequest {
|
export interface AbortProposalRequest {
|
||||||
proposalId: string;
|
proposalId: string;
|
||||||
@ -853,17 +867,17 @@ export interface RefreshPlanchetInfo {
|
|||||||
/**
|
/**
|
||||||
* Public key for the coin.
|
* Public key for the coin.
|
||||||
*/
|
*/
|
||||||
publicKey: string;
|
coinPub: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private key for the coin.
|
* Private key for the coin.
|
||||||
*/
|
*/
|
||||||
privateKey: string;
|
coinPriv: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blinded public key.
|
* Blinded public key.
|
||||||
*/
|
*/
|
||||||
coinEv: string;
|
coinEv: CoinEnvelope;
|
||||||
|
|
||||||
coinEvHash: string;
|
coinEvHash: string;
|
||||||
|
|
||||||
@ -896,12 +910,13 @@ export interface RecoveryLoadRequest {
|
|||||||
strategy?: RecoveryMergeStrategy;
|
strategy?: RecoveryMergeStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForWithdrawTestBalance = (): Codec<WithdrawTestBalanceRequest> =>
|
export const codecForWithdrawTestBalance =
|
||||||
buildCodecForObject<WithdrawTestBalanceRequest>()
|
(): Codec<WithdrawTestBalanceRequest> =>
|
||||||
.property("amount", codecForString())
|
buildCodecForObject<WithdrawTestBalanceRequest>()
|
||||||
.property("bankBaseUrl", codecForString())
|
.property("amount", codecForString())
|
||||||
.property("exchangeBaseUrl", codecForString())
|
.property("bankBaseUrl", codecForString())
|
||||||
.build("WithdrawTestBalanceRequest");
|
.property("exchangeBaseUrl", codecForString())
|
||||||
|
.build("WithdrawTestBalanceRequest");
|
||||||
|
|
||||||
export interface ApplyRefundResponse {
|
export interface ApplyRefundResponse {
|
||||||
contractTermsHash: string;
|
contractTermsHash: string;
|
||||||
@ -935,11 +950,12 @@ export interface SetCoinSuspendedRequest {
|
|||||||
suspended: boolean;
|
suspended: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForSetCoinSuspendedRequest = (): Codec<SetCoinSuspendedRequest> =>
|
export const codecForSetCoinSuspendedRequest =
|
||||||
buildCodecForObject<SetCoinSuspendedRequest>()
|
(): Codec<SetCoinSuspendedRequest> =>
|
||||||
.property("coinPub", codecForString())
|
buildCodecForObject<SetCoinSuspendedRequest>()
|
||||||
.property("suspended", codecForBoolean())
|
.property("coinPub", codecForString())
|
||||||
.build("SetCoinSuspendedRequest");
|
.property("suspended", codecForBoolean())
|
||||||
|
.build("SetCoinSuspendedRequest");
|
||||||
|
|
||||||
export interface ForceRefreshRequest {
|
export interface ForceRefreshRequest {
|
||||||
coinPubList: string[];
|
coinPubList: string[];
|
||||||
@ -972,10 +988,11 @@ export interface AbortPayWithRefundRequest {
|
|||||||
proposalId: string;
|
proposalId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForAbortPayWithRefundRequest = (): Codec<AbortPayWithRefundRequest> =>
|
export const codecForAbortPayWithRefundRequest =
|
||||||
buildCodecForObject<AbortPayWithRefundRequest>()
|
(): Codec<AbortPayWithRefundRequest> =>
|
||||||
.property("proposalId", codecForString())
|
buildCodecForObject<AbortPayWithRefundRequest>()
|
||||||
.build("AbortPayWithRefundRequest");
|
.property("proposalId", codecForString())
|
||||||
|
.build("AbortPayWithRefundRequest");
|
||||||
|
|
||||||
export interface GetFeeForDepositRequest {
|
export interface GetFeeForDepositRequest {
|
||||||
depositPaytoUri: string;
|
depositPaytoUri: string;
|
||||||
@ -987,18 +1004,18 @@ export interface CreateDepositGroupRequest {
|
|||||||
amount: AmountString;
|
amount: AmountString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const codecForGetFeeForDeposit = (): Codec<GetFeeForDepositRequest> =>
|
export const codecForGetFeeForDeposit = (): Codec<GetFeeForDepositRequest> =>
|
||||||
buildCodecForObject<GetFeeForDepositRequest>()
|
buildCodecForObject<GetFeeForDepositRequest>()
|
||||||
.property("amount", codecForAmountString())
|
.property("amount", codecForAmountString())
|
||||||
.property("depositPaytoUri", codecForString())
|
.property("depositPaytoUri", codecForString())
|
||||||
.build("GetFeeForDepositRequest");
|
.build("GetFeeForDepositRequest");
|
||||||
|
|
||||||
export const codecForCreateDepositGroupRequest = (): Codec<CreateDepositGroupRequest> =>
|
export const codecForCreateDepositGroupRequest =
|
||||||
buildCodecForObject<CreateDepositGroupRequest>()
|
(): Codec<CreateDepositGroupRequest> =>
|
||||||
.property("amount", codecForAmountString())
|
buildCodecForObject<CreateDepositGroupRequest>()
|
||||||
.property("depositPaytoUri", codecForString())
|
.property("amount", codecForAmountString())
|
||||||
.build("CreateDepositGroupRequest");
|
.property("depositPaytoUri", codecForString())
|
||||||
|
.build("CreateDepositGroupRequest");
|
||||||
|
|
||||||
export interface CreateDepositGroupResponse {
|
export interface CreateDepositGroupResponse {
|
||||||
depositGroupId: string;
|
depositGroupId: string;
|
||||||
@ -1015,10 +1032,11 @@ export interface TrackDepositGroupResponse {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForTrackDepositGroupRequest = (): Codec<TrackDepositGroupRequest> =>
|
export const codecForTrackDepositGroupRequest =
|
||||||
buildCodecForObject<TrackDepositGroupRequest>()
|
(): Codec<TrackDepositGroupRequest> =>
|
||||||
.property("depositGroupId", codecForAmountString())
|
buildCodecForObject<TrackDepositGroupRequest>()
|
||||||
.build("TrackDepositGroupRequest");
|
.property("depositGroupId", codecForAmountString())
|
||||||
|
.build("TrackDepositGroupRequest");
|
||||||
|
|
||||||
export interface WithdrawUriInfoResponse {
|
export interface WithdrawUriInfoResponse {
|
||||||
amount: AmountString;
|
amount: AmountString;
|
||||||
@ -1026,12 +1044,13 @@ export interface WithdrawUriInfoResponse {
|
|||||||
possibleExchanges: ExchangeListItem[];
|
possibleExchanges: ExchangeListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForWithdrawUriInfoResponse = (): Codec<WithdrawUriInfoResponse> =>
|
export const codecForWithdrawUriInfoResponse =
|
||||||
buildCodecForObject<WithdrawUriInfoResponse>()
|
(): Codec<WithdrawUriInfoResponse> =>
|
||||||
.property("amount", codecForAmountString())
|
buildCodecForObject<WithdrawUriInfoResponse>()
|
||||||
.property("defaultExchangeBaseUrl", codecOptional(codecForString()))
|
.property("amount", codecForAmountString())
|
||||||
.property("possibleExchanges", codecForList(codecForExchangeListItem()))
|
.property("defaultExchangeBaseUrl", codecOptional(codecForString()))
|
||||||
.build("WithdrawUriInfoResponse");
|
.property("possibleExchanges", codecForList(codecForExchangeListItem()))
|
||||||
|
.build("WithdrawUriInfoResponse");
|
||||||
|
|
||||||
export interface WalletCurrencyInfo {
|
export interface WalletCurrencyInfo {
|
||||||
trustedAuditors: {
|
trustedAuditors: {
|
||||||
@ -1054,15 +1073,17 @@ export interface RetryTransactionRequest {
|
|||||||
transactionId: string;
|
transactionId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForDeleteTransactionRequest = (): Codec<DeleteTransactionRequest> =>
|
export const codecForDeleteTransactionRequest =
|
||||||
buildCodecForObject<DeleteTransactionRequest>()
|
(): Codec<DeleteTransactionRequest> =>
|
||||||
.property("transactionId", codecForString())
|
buildCodecForObject<DeleteTransactionRequest>()
|
||||||
.build("DeleteTransactionRequest");
|
.property("transactionId", codecForString())
|
||||||
|
.build("DeleteTransactionRequest");
|
||||||
|
|
||||||
export const codecForRetryTransactionRequest = (): Codec<RetryTransactionRequest> =>
|
export const codecForRetryTransactionRequest =
|
||||||
buildCodecForObject<RetryTransactionRequest>()
|
(): Codec<RetryTransactionRequest> =>
|
||||||
.property("transactionId", codecForString())
|
buildCodecForObject<RetryTransactionRequest>()
|
||||||
.build("RetryTransactionRequest");
|
.property("transactionId", codecForString())
|
||||||
|
.build("RetryTransactionRequest");
|
||||||
|
|
||||||
export interface SetWalletDeviceIdRequest {
|
export interface SetWalletDeviceIdRequest {
|
||||||
/**
|
/**
|
||||||
@ -1071,10 +1092,11 @@ export interface SetWalletDeviceIdRequest {
|
|||||||
walletDeviceId: string;
|
walletDeviceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForSetWalletDeviceIdRequest = (): Codec<SetWalletDeviceIdRequest> =>
|
export const codecForSetWalletDeviceIdRequest =
|
||||||
buildCodecForObject<SetWalletDeviceIdRequest>()
|
(): Codec<SetWalletDeviceIdRequest> =>
|
||||||
.property("walletDeviceId", codecForString())
|
buildCodecForObject<SetWalletDeviceIdRequest>()
|
||||||
.build("SetWalletDeviceIdRequest");
|
.property("walletDeviceId", codecForString())
|
||||||
|
.build("SetWalletDeviceIdRequest");
|
||||||
|
|
||||||
export interface WithdrawFakebankRequest {
|
export interface WithdrawFakebankRequest {
|
||||||
amount: AmountString;
|
amount: AmountString;
|
||||||
@ -1082,12 +1104,13 @@ export interface WithdrawFakebankRequest {
|
|||||||
bank: string;
|
bank: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForWithdrawFakebankRequest = (): Codec<WithdrawFakebankRequest> =>
|
export const codecForWithdrawFakebankRequest =
|
||||||
buildCodecForObject<WithdrawFakebankRequest>()
|
(): Codec<WithdrawFakebankRequest> =>
|
||||||
.property("amount", codecForAmountString())
|
buildCodecForObject<WithdrawFakebankRequest>()
|
||||||
.property("bank", codecForString())
|
.property("amount", codecForAmountString())
|
||||||
.property("exchange", codecForString())
|
.property("bank", codecForString())
|
||||||
.build("WithdrawFakebankRequest");
|
.property("exchange", codecForString())
|
||||||
|
.build("WithdrawFakebankRequest");
|
||||||
|
|
||||||
export interface ImportDb {
|
export interface ImportDb {
|
||||||
dump: any;
|
dump: any;
|
||||||
@ -1095,4 +1118,4 @@ export interface ImportDb {
|
|||||||
export const codecForImportDbRequest = (): Codec<ImportDb> =>
|
export const codecForImportDbRequest = (): Codec<ImportDb> =>
|
||||||
buildCodecForObject<ImportDb>()
|
buildCodecForObject<ImportDb>()
|
||||||
.property("dump", codecForAny())
|
.property("dump", codecForAny())
|
||||||
.build("ImportDbRequest")
|
.build("ImportDbRequest");
|
||||||
|
@ -1525,7 +1525,7 @@ export class ExchangeService implements ExchangeServiceInterface {
|
|||||||
|
|
||||||
this.exchangeHttpProc = this.globalState.spawnService(
|
this.exchangeHttpProc = this.globalState.spawnService(
|
||||||
"taler-exchange-httpd",
|
"taler-exchange-httpd",
|
||||||
["-c", this.configFilename, ...this.timetravelArgArr],
|
["-LINFO", "-c", this.configFilename, ...this.timetravelArgArr],
|
||||||
`exchange-httpd-${this.name}`,
|
`exchange-httpd-${this.name}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -201,8 +201,8 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
|
|||||||
for (const da of denomPubs1) {
|
for (const da of denomPubs1) {
|
||||||
let found = false;
|
let found = false;
|
||||||
for (const db of denomPubs2) {
|
for (const db of denomPubs2) {
|
||||||
const d1 = DenominationPubKey.lift(da.denomPub);
|
const d1 = da.denomPub;
|
||||||
const d2 = DenominationPubKey.lift(db.denomPub);
|
const d2 = db.denomPub;
|
||||||
if (DenominationPubKey.cmp(d1, d2) === 0) {
|
if (DenominationPubKey.cmp(d1, d2) === 0) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
|
@ -30,8 +30,10 @@
|
|||||||
import {
|
import {
|
||||||
AmountJson,
|
AmountJson,
|
||||||
AmountString,
|
AmountString,
|
||||||
|
CoinEnvelope,
|
||||||
DenominationPubKey,
|
DenominationPubKey,
|
||||||
ExchangeProtocolVersion,
|
ExchangeProtocolVersion,
|
||||||
|
RefreshPlanchetInfo,
|
||||||
UnblindedSignature,
|
UnblindedSignature,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
@ -74,32 +76,7 @@ export interface DerivedRefreshSession {
|
|||||||
/**
|
/**
|
||||||
* Planchets for each cut-and-choose instance.
|
* Planchets for each cut-and-choose instance.
|
||||||
*/
|
*/
|
||||||
planchetsForGammas: {
|
planchetsForGammas: RefreshPlanchetInfo[][];
|
||||||
/**
|
|
||||||
* Public key for the coin.
|
|
||||||
*/
|
|
||||||
publicKey: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private key for the coin.
|
|
||||||
*/
|
|
||||||
privateKey: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blinded public key.
|
|
||||||
*/
|
|
||||||
coinEv: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash of the blinded public key.
|
|
||||||
*/
|
|
||||||
coinEvHash: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blinding key used.
|
|
||||||
*/
|
|
||||||
blindingKey: string;
|
|
||||||
}[][];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transfer keys, kappa of them.
|
* The transfer keys, kappa of them.
|
||||||
|
@ -28,6 +28,7 @@ import { CryptoWorker } from "./cryptoWorkerInterface.js";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CoinDepositPermission,
|
CoinDepositPermission,
|
||||||
|
CoinEnvelope,
|
||||||
RecoupRefreshRequest,
|
RecoupRefreshRequest,
|
||||||
RecoupRequest,
|
RecoupRequest,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
@ -452,7 +453,7 @@ export class CryptoApi {
|
|||||||
newDenomHash: string,
|
newDenomHash: string,
|
||||||
oldCoinPub: string,
|
oldCoinPub: string,
|
||||||
transferPub: string,
|
transferPub: string,
|
||||||
coinEv: string,
|
coinEv: CoinEnvelope,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return this.doRpc<string>(
|
return this.doRpc<string>(
|
||||||
"signCoinLink",
|
"signCoinLink",
|
||||||
|
@ -26,19 +26,49 @@
|
|||||||
|
|
||||||
// FIXME: Crypto should not use DB Types!
|
// FIXME: Crypto should not use DB Types!
|
||||||
import {
|
import {
|
||||||
AmountJson, Amounts, BenchmarkResult, buildSigPS,
|
AmountJson,
|
||||||
CoinDepositPermission, createEddsaKeyPair, createHashContext, decodeCrock,
|
Amounts,
|
||||||
DenomKeyType, DepositInfo, eddsaGetPublic, eddsaSign, eddsaVerify,
|
BenchmarkResult,
|
||||||
encodeCrock, ExchangeProtocolVersion,
|
buildSigPS,
|
||||||
FreshCoin, hash, hashDenomPub, kdf, keyExchangeEcdheEddsa,
|
CoinDepositPermission,
|
||||||
// Logger,
|
CoinEnvelope,
|
||||||
MakeSyncSignatureRequest, PlanchetCreationRequest, PlanchetCreationResult,
|
createEddsaKeyPair,
|
||||||
randomBytes, RecoupRefreshRequest,
|
createHashContext,
|
||||||
|
decodeCrock,
|
||||||
|
DenomKeyType,
|
||||||
|
DepositInfo,
|
||||||
|
eddsaGetPublic,
|
||||||
|
eddsaSign,
|
||||||
|
eddsaVerify,
|
||||||
|
encodeCrock,
|
||||||
|
ExchangeProtocolVersion,
|
||||||
|
FreshCoin,
|
||||||
|
hash,
|
||||||
|
HashCodeString,
|
||||||
|
hashCoinEv,
|
||||||
|
hashCoinEvInner,
|
||||||
|
hashDenomPub,
|
||||||
|
keyExchangeEcdheEddsa,
|
||||||
|
Logger,
|
||||||
|
MakeSyncSignatureRequest,
|
||||||
|
PlanchetCreationRequest,
|
||||||
|
PlanchetCreationResult,
|
||||||
|
randomBytes,
|
||||||
|
RecoupRefreshRequest,
|
||||||
RecoupRequest,
|
RecoupRequest,
|
||||||
RefreshPlanchetInfo, rsaBlind, rsaUnblind, rsaVerify, setupRefreshPlanchet,
|
RefreshPlanchetInfo,
|
||||||
|
rsaBlind,
|
||||||
|
rsaUnblind,
|
||||||
|
rsaVerify,
|
||||||
|
setupRefreshPlanchet,
|
||||||
setupRefreshTransferPub,
|
setupRefreshTransferPub,
|
||||||
setupTipPlanchet,
|
setupTipPlanchet,
|
||||||
setupWithdrawPlanchet, stringToBytes, TalerSignaturePurpose, Timestamp, timestampTruncateToSecond
|
setupWithdrawPlanchet,
|
||||||
|
stringToBytes,
|
||||||
|
TalerSignaturePurpose,
|
||||||
|
Timestamp,
|
||||||
|
timestampTruncateToSecond,
|
||||||
|
typedArrayConcat,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import bigint from "big-integer";
|
import bigint from "big-integer";
|
||||||
import { DenominationRecord, WireFee } from "../../db.js";
|
import { DenominationRecord, WireFee } from "../../db.js";
|
||||||
@ -50,10 +80,10 @@ import {
|
|||||||
DerivedTipPlanchet,
|
DerivedTipPlanchet,
|
||||||
DeriveRefreshSessionRequest,
|
DeriveRefreshSessionRequest,
|
||||||
DeriveTipRequest,
|
DeriveTipRequest,
|
||||||
SignTrackTransactionRequest
|
SignTrackTransactionRequest,
|
||||||
} from "../cryptoTypes.js";
|
} from "../cryptoTypes.js";
|
||||||
|
|
||||||
// const logger = new Logger("cryptoImplementation.ts");
|
const logger = new Logger("cryptoImplementation.ts");
|
||||||
|
|
||||||
function amountToBuffer(amount: AmountJson): Uint8Array {
|
function amountToBuffer(amount: AmountJson): Uint8Array {
|
||||||
const buffer = new ArrayBuffer(8 + 4 + 12);
|
const buffer = new ArrayBuffer(8 + 4 + 12);
|
||||||
@ -130,7 +160,7 @@ async function myEddsaSign(
|
|||||||
export class CryptoImplementation {
|
export class CryptoImplementation {
|
||||||
static enableTracing = false;
|
static enableTracing = false;
|
||||||
|
|
||||||
constructor(private primitiveWorker?: PrimitiveWorker) { }
|
constructor(private primitiveWorker?: PrimitiveWorker) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a pre-coin of the given denomination to be withdrawn from then given
|
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||||
@ -139,26 +169,26 @@ export class CryptoImplementation {
|
|||||||
async createPlanchet(
|
async createPlanchet(
|
||||||
req: PlanchetCreationRequest,
|
req: PlanchetCreationRequest,
|
||||||
): Promise<PlanchetCreationResult> {
|
): Promise<PlanchetCreationResult> {
|
||||||
if (
|
const denomPub = req.denomPub;
|
||||||
req.denomPub.cipher === DenomKeyType.Rsa ||
|
if (denomPub.cipher === DenomKeyType.Rsa) {
|
||||||
req.denomPub.cipher === DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
const reservePub = decodeCrock(req.reservePub);
|
const reservePub = decodeCrock(req.reservePub);
|
||||||
const denomPubRsa = decodeCrock(req.denomPub.rsa_public_key);
|
const denomPubRsa = decodeCrock(denomPub.rsa_public_key);
|
||||||
const derivedPlanchet = setupWithdrawPlanchet(
|
const derivedPlanchet = setupWithdrawPlanchet(
|
||||||
decodeCrock(req.secretSeed),
|
decodeCrock(req.secretSeed),
|
||||||
req.coinIndex,
|
req.coinIndex,
|
||||||
);
|
);
|
||||||
const coinPubHash = hash(derivedPlanchet.coinPub);
|
const coinPubHash = hash(derivedPlanchet.coinPub);
|
||||||
const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPubRsa);
|
const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPubRsa);
|
||||||
|
const coinEv: CoinEnvelope = {
|
||||||
|
cipher: DenomKeyType.Rsa,
|
||||||
|
rsa_blinded_planchet: encodeCrock(ev),
|
||||||
|
};
|
||||||
const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount;
|
const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount;
|
||||||
const denomPubHash = hashDenomPub(req.denomPub);
|
const denomPubHash = hashDenomPub(req.denomPub);
|
||||||
const evHash = hash(ev);
|
const evHash = hashCoinEv(coinEv, encodeCrock(denomPubHash));
|
||||||
|
|
||||||
const withdrawRequest = buildSigPS(
|
const withdrawRequest = buildSigPS(
|
||||||
TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW,
|
TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW,
|
||||||
)
|
)
|
||||||
.put(reservePub)
|
|
||||||
.put(amountToBuffer(amountWithFee))
|
.put(amountToBuffer(amountWithFee))
|
||||||
.put(denomPubHash)
|
.put(denomPubHash)
|
||||||
.put(evHash)
|
.put(evHash)
|
||||||
@ -171,14 +201,11 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
const planchet: PlanchetCreationResult = {
|
const planchet: PlanchetCreationResult = {
|
||||||
blindingKey: encodeCrock(derivedPlanchet.bks),
|
blindingKey: encodeCrock(derivedPlanchet.bks),
|
||||||
coinEv: encodeCrock(ev),
|
coinEv,
|
||||||
coinPriv: encodeCrock(derivedPlanchet.coinPriv),
|
coinPriv: encodeCrock(derivedPlanchet.coinPriv),
|
||||||
coinPub: encodeCrock(derivedPlanchet.coinPub),
|
coinPub: encodeCrock(derivedPlanchet.coinPub),
|
||||||
coinValue: req.value,
|
coinValue: req.value,
|
||||||
denomPub: {
|
denomPub,
|
||||||
cipher: req.denomPub.cipher,
|
|
||||||
rsa_public_key: encodeCrock(denomPubRsa),
|
|
||||||
},
|
|
||||||
denomPubHash: encodeCrock(denomPubHash),
|
denomPubHash: encodeCrock(denomPubHash),
|
||||||
reservePub: encodeCrock(reservePub),
|
reservePub: encodeCrock(reservePub),
|
||||||
withdrawSig: sigResult.sig,
|
withdrawSig: sigResult.sig,
|
||||||
@ -194,11 +221,8 @@ export class CryptoImplementation {
|
|||||||
* Create a planchet used for tipping, including the private keys.
|
* Create a planchet used for tipping, including the private keys.
|
||||||
*/
|
*/
|
||||||
createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet {
|
createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet {
|
||||||
if (
|
if (req.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||||
req.denomPub.cipher !== DenomKeyType.Rsa &&
|
throw Error(`unsupported cipher (${req.denomPub.cipher})`);
|
||||||
req.denomPub.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
throw Error("unsupported cipher");
|
|
||||||
}
|
}
|
||||||
const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex);
|
const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex);
|
||||||
const denomPub = decodeCrock(req.denomPub.rsa_public_key);
|
const denomPub = decodeCrock(req.denomPub.rsa_public_key);
|
||||||
@ -236,15 +260,7 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
const coinPriv = decodeCrock(req.coinPriv);
|
const coinPriv = decodeCrock(req.coinPriv);
|
||||||
const coinSig = eddsaSign(p, coinPriv);
|
const coinSig = eddsaSign(p, coinPriv);
|
||||||
if (req.denomPub.cipher === DenomKeyType.LegacyRsa) {
|
if (req.denomPub.cipher === DenomKeyType.Rsa) {
|
||||||
const paybackRequest: RecoupRequest = {
|
|
||||||
coin_blind_key_secret: req.blindingKey,
|
|
||||||
coin_sig: encodeCrock(coinSig),
|
|
||||||
denom_pub_hash: req.denomPubHash,
|
|
||||||
denom_sig: req.denomSig.rsa_signature,
|
|
||||||
};
|
|
||||||
return paybackRequest;
|
|
||||||
} else {
|
|
||||||
const paybackRequest: RecoupRequest = {
|
const paybackRequest: RecoupRequest = {
|
||||||
coin_blind_key_secret: req.blindingKey,
|
coin_blind_key_secret: req.blindingKey,
|
||||||
coin_sig: encodeCrock(coinSig),
|
coin_sig: encodeCrock(coinSig),
|
||||||
@ -252,6 +268,8 @@ export class CryptoImplementation {
|
|||||||
denom_sig: req.denomSig,
|
denom_sig: req.denomSig,
|
||||||
};
|
};
|
||||||
return paybackRequest;
|
return paybackRequest;
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,15 +286,7 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
const coinPriv = decodeCrock(req.coinPriv);
|
const coinPriv = decodeCrock(req.coinPriv);
|
||||||
const coinSig = eddsaSign(p, coinPriv);
|
const coinSig = eddsaSign(p, coinPriv);
|
||||||
if (req.denomPub.cipher === DenomKeyType.LegacyRsa) {
|
if (req.denomPub.cipher === DenomKeyType.Rsa) {
|
||||||
const recoupRequest: RecoupRefreshRequest = {
|
|
||||||
coin_blind_key_secret: req.blindingKey,
|
|
||||||
coin_sig: encodeCrock(coinSig),
|
|
||||||
denom_pub_hash: req.denomPubHash,
|
|
||||||
denom_sig: req.denomSig.rsa_signature,
|
|
||||||
};
|
|
||||||
return recoupRequest;
|
|
||||||
} else {
|
|
||||||
const recoupRequest: RecoupRefreshRequest = {
|
const recoupRequest: RecoupRefreshRequest = {
|
||||||
coin_blind_key_secret: req.blindingKey,
|
coin_blind_key_secret: req.blindingKey,
|
||||||
coin_sig: encodeCrock(coinSig),
|
coin_sig: encodeCrock(coinSig),
|
||||||
@ -284,6 +294,8 @@ export class CryptoImplementation {
|
|||||||
denom_sig: req.denomSig,
|
denom_sig: req.denomSig,
|
||||||
};
|
};
|
||||||
return recoupRequest;
|
return recoupRequest;
|
||||||
|
} else {
|
||||||
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,26 +376,11 @@ export class CryptoImplementation {
|
|||||||
sig: string,
|
sig: string,
|
||||||
masterPub: string,
|
masterPub: string,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (versionCurrent === ExchangeProtocolVersion.V12) {
|
const paytoHash = hash(stringToBytes(paytoUri + "\0"));
|
||||||
const paytoHash = hash(stringToBytes(paytoUri + "\0"));
|
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
|
||||||
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
|
.put(paytoHash)
|
||||||
.put(paytoHash)
|
.build();
|
||||||
.build();
|
return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));
|
||||||
return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));
|
|
||||||
} else if (versionCurrent === ExchangeProtocolVersion.V9) {
|
|
||||||
const h = kdf(
|
|
||||||
64,
|
|
||||||
stringToBytes("exchange-wire-signature"),
|
|
||||||
stringToBytes(paytoUri + "\0"),
|
|
||||||
new Uint8Array(0),
|
|
||||||
);
|
|
||||||
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
|
|
||||||
.put(h)
|
|
||||||
.build();
|
|
||||||
return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));
|
|
||||||
} else {
|
|
||||||
throw Error(`unsupported version (${versionCurrent})`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidContractTermsSignature(
|
isValidContractTermsSignature(
|
||||||
@ -444,10 +441,12 @@ export class CryptoImplementation {
|
|||||||
): Promise<CoinDepositPermission> {
|
): Promise<CoinDepositPermission> {
|
||||||
// FIXME: put extensions here if used
|
// FIXME: put extensions here if used
|
||||||
const hExt = new Uint8Array(64);
|
const hExt = new Uint8Array(64);
|
||||||
|
const hAgeCommitment = new Uint8Array(32);
|
||||||
let d: Uint8Array;
|
let d: Uint8Array;
|
||||||
if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
|
if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
|
||||||
d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
|
d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
|
||||||
.put(decodeCrock(depositInfo.contractTermsHash))
|
.put(decodeCrock(depositInfo.contractTermsHash))
|
||||||
|
.put(hAgeCommitment)
|
||||||
.put(hExt)
|
.put(hExt)
|
||||||
.put(decodeCrock(depositInfo.wireInfoHash))
|
.put(decodeCrock(depositInfo.wireInfoHash))
|
||||||
.put(decodeCrock(depositInfo.denomPubHash))
|
.put(decodeCrock(depositInfo.denomPubHash))
|
||||||
@ -457,18 +456,6 @@ export class CryptoImplementation {
|
|||||||
.put(amountToBuffer(depositInfo.feeDeposit))
|
.put(amountToBuffer(depositInfo.feeDeposit))
|
||||||
.put(decodeCrock(depositInfo.merchantPub))
|
.put(decodeCrock(depositInfo.merchantPub))
|
||||||
.build();
|
.build();
|
||||||
} else if (depositInfo.denomKeyType === DenomKeyType.LegacyRsa) {
|
|
||||||
d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
|
|
||||||
.put(decodeCrock(depositInfo.contractTermsHash))
|
|
||||||
.put(decodeCrock(depositInfo.wireInfoHash))
|
|
||||||
.put(decodeCrock(depositInfo.denomPubHash))
|
|
||||||
.put(timestampRoundedToBuffer(depositInfo.timestamp))
|
|
||||||
.put(timestampRoundedToBuffer(depositInfo.refundDeadline))
|
|
||||||
.put(amountToBuffer(depositInfo.spendAmount))
|
|
||||||
.put(amountToBuffer(depositInfo.feeDeposit))
|
|
||||||
.put(decodeCrock(depositInfo.merchantPub))
|
|
||||||
.put(decodeCrock(depositInfo.coinPub))
|
|
||||||
.build();
|
|
||||||
} else {
|
} else {
|
||||||
throw Error("unsupported exchange protocol version");
|
throw Error("unsupported exchange protocol version");
|
||||||
}
|
}
|
||||||
@ -490,18 +477,10 @@ export class CryptoImplementation {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
return s;
|
return s;
|
||||||
} else if (depositInfo.denomKeyType === DenomKeyType.LegacyRsa) {
|
|
||||||
const s: CoinDepositPermission = {
|
|
||||||
coin_pub: depositInfo.coinPub,
|
|
||||||
coin_sig: coinSigRes.sig,
|
|
||||||
contribution: Amounts.stringify(depositInfo.spendAmount),
|
|
||||||
h_denom: depositInfo.denomPubHash,
|
|
||||||
exchange_url: depositInfo.exchangeBaseUrl,
|
|
||||||
ub_sig: depositInfo.denomSig.rsa_signature,
|
|
||||||
};
|
|
||||||
return s;
|
|
||||||
} else {
|
} else {
|
||||||
throw Error("unsupported merchant protocol version");
|
throw Error(
|
||||||
|
`unsupported denomination cipher (${depositInfo.denomKeyType})`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,17 +530,18 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
for (const denomSel of newCoinDenoms) {
|
for (const denomSel of newCoinDenoms) {
|
||||||
for (let i = 0; i < denomSel.count; i++) {
|
for (let i = 0; i < denomSel.count; i++) {
|
||||||
if (denomSel.denomPub.cipher === DenomKeyType.LegacyRsa) {
|
if (denomSel.denomPub.cipher === DenomKeyType.Rsa) {
|
||||||
const r = decodeCrock(denomSel.denomPub.rsa_public_key);
|
const denomPubHash = hashDenomPub(denomSel.denomPub);
|
||||||
sessionHc.update(r);
|
sessionHc.update(denomPubHash);
|
||||||
} else {
|
} else {
|
||||||
sessionHc.update(hashDenomPub(denomSel.denomPub));
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionHc.update(decodeCrock(meltCoinPub));
|
sessionHc.update(decodeCrock(meltCoinPub));
|
||||||
sessionHc.update(amountToBuffer(valueWithFee));
|
sessionHc.update(amountToBuffer(valueWithFee));
|
||||||
|
|
||||||
for (let i = 0; i < kappa; i++) {
|
for (let i = 0; i < kappa; i++) {
|
||||||
const planchets: RefreshPlanchetInfo[] = [];
|
const planchets: RefreshPlanchetInfo[] = [];
|
||||||
for (let j = 0; j < newCoinDenoms.length; j++) {
|
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||||
@ -594,24 +574,29 @@ export class CryptoImplementation {
|
|||||||
coinPub = fresh.coinPub;
|
coinPub = fresh.coinPub;
|
||||||
blindingFactor = fresh.bks;
|
blindingFactor = fresh.bks;
|
||||||
}
|
}
|
||||||
const pubHash = hash(coinPub);
|
const coinPubHash = hash(coinPub);
|
||||||
if (
|
if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||||
denomSel.denomPub.cipher !== DenomKeyType.Rsa &&
|
|
||||||
denomSel.denomPub.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
throw Error("unsupported cipher, can't create refresh session");
|
throw Error("unsupported cipher, can't create refresh session");
|
||||||
}
|
}
|
||||||
const denomPub = decodeCrock(denomSel.denomPub.rsa_public_key);
|
const rsaDenomPub = decodeCrock(denomSel.denomPub.rsa_public_key);
|
||||||
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
const ev = rsaBlind(coinPubHash, blindingFactor, rsaDenomPub);
|
||||||
|
const coinEv: CoinEnvelope = {
|
||||||
|
cipher: DenomKeyType.Rsa,
|
||||||
|
rsa_blinded_planchet: encodeCrock(ev),
|
||||||
|
};
|
||||||
|
const coinEvHash = hashCoinEv(
|
||||||
|
coinEv,
|
||||||
|
encodeCrock(hashDenomPub(denomSel.denomPub)),
|
||||||
|
);
|
||||||
const planchet: RefreshPlanchetInfo = {
|
const planchet: RefreshPlanchetInfo = {
|
||||||
blindingKey: encodeCrock(blindingFactor),
|
blindingKey: encodeCrock(blindingFactor),
|
||||||
coinEv: encodeCrock(ev),
|
coinEv,
|
||||||
privateKey: encodeCrock(coinPriv),
|
coinPriv: encodeCrock(coinPriv),
|
||||||
publicKey: encodeCrock(coinPub),
|
coinPub: encodeCrock(coinPub),
|
||||||
coinEvHash: encodeCrock(hash(ev)),
|
coinEvHash: encodeCrock(coinEvHash),
|
||||||
};
|
};
|
||||||
planchets.push(planchet);
|
planchets.push(planchet);
|
||||||
sessionHc.update(ev);
|
hashCoinEvInner(coinEv, sessionHc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
planchetsForGammas.push(planchets);
|
planchetsForGammas.push(planchets);
|
||||||
@ -619,26 +604,15 @@ export class CryptoImplementation {
|
|||||||
|
|
||||||
const sessionHash = sessionHc.finish();
|
const sessionHash = sessionHc.finish();
|
||||||
let confirmData: Uint8Array;
|
let confirmData: Uint8Array;
|
||||||
if (req.exchangeProtocolVersion === ExchangeProtocolVersion.V9) {
|
// FIXME: fill in age commitment
|
||||||
confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT)
|
const hAgeCommitment = new Uint8Array(32);
|
||||||
.put(sessionHash)
|
confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT)
|
||||||
.put(decodeCrock(meltCoinDenomPubHash))
|
.put(sessionHash)
|
||||||
.put(amountToBuffer(valueWithFee))
|
.put(decodeCrock(meltCoinDenomPubHash))
|
||||||
.put(amountToBuffer(meltFee))
|
.put(hAgeCommitment)
|
||||||
.put(decodeCrock(meltCoinPub))
|
.put(amountToBuffer(valueWithFee))
|
||||||
.build();
|
.put(amountToBuffer(meltFee))
|
||||||
} else if (req.exchangeProtocolVersion === ExchangeProtocolVersion.V12) {
|
.build();
|
||||||
confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT)
|
|
||||||
.put(sessionHash)
|
|
||||||
.put(decodeCrock(meltCoinDenomPubHash))
|
|
||||||
.put(amountToBuffer(valueWithFee))
|
|
||||||
.put(amountToBuffer(meltFee))
|
|
||||||
.build();
|
|
||||||
} else {
|
|
||||||
throw Error(
|
|
||||||
`Exchange protocol version (${req.exchangeProtocolVersion}) not supported`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmSigResp = await myEddsaSign(this.primitiveWorker, {
|
const confirmSigResp = await myEddsaSign(this.primitiveWorker, {
|
||||||
msg: encodeCrock(confirmData),
|
msg: encodeCrock(confirmData),
|
||||||
@ -678,12 +652,15 @@ export class CryptoImplementation {
|
|||||||
newDenomHash: string,
|
newDenomHash: string,
|
||||||
oldCoinPub: string,
|
oldCoinPub: string,
|
||||||
transferPub: string,
|
transferPub: string,
|
||||||
coinEv: string,
|
coinEv: CoinEnvelope,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const coinEvHash = hash(decodeCrock(coinEv));
|
const coinEvHash = hashCoinEv(coinEv, newDenomHash);
|
||||||
|
// FIXME: fill in
|
||||||
|
const hAgeCommitment = new Uint8Array(32);
|
||||||
const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK)
|
const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK)
|
||||||
.put(decodeCrock(newDenomHash))
|
.put(decodeCrock(newDenomHash))
|
||||||
.put(decodeCrock(transferPub))
|
.put(decodeCrock(transferPub))
|
||||||
|
.put(hAgeCommitment)
|
||||||
.put(coinEvHash)
|
.put(coinEvHash)
|
||||||
.build();
|
.build();
|
||||||
const sig = await myEddsaSign(this.primitiveWorker, {
|
const sig = await myEddsaSign(this.primitiveWorker, {
|
||||||
|
@ -38,6 +38,7 @@ import {
|
|||||||
TalerErrorDetails,
|
TalerErrorDetails,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UnblindedSignature,
|
UnblindedSignature,
|
||||||
|
CoinEnvelope,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { RetryInfo } from "./util/retries.js";
|
import { RetryInfo } from "./util/retries.js";
|
||||||
import { PayCoinSelection } from "./util/coinSelection.js";
|
import { PayCoinSelection } from "./util/coinSelection.js";
|
||||||
@ -602,7 +603,7 @@ export interface PlanchetRecord {
|
|||||||
|
|
||||||
withdrawSig: string;
|
withdrawSig: string;
|
||||||
|
|
||||||
coinEv: string;
|
coinEv: CoinEnvelope;
|
||||||
|
|
||||||
coinEvHash: string;
|
coinEvHash: string;
|
||||||
|
|
||||||
@ -1154,7 +1155,6 @@ export interface WalletContractData {
|
|||||||
timestamp: Timestamp;
|
timestamp: Timestamp;
|
||||||
wireMethod: string;
|
wireMethod: string;
|
||||||
wireInfoHash: string;
|
wireInfoHash: string;
|
||||||
wireInfoLegacyHash?: string;
|
|
||||||
maxDepositFee: AmountJson;
|
maxDepositFee: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1294,9 +1294,9 @@ export const WALLET_BACKUP_STATE_KEY = "walletBackupState";
|
|||||||
*/
|
*/
|
||||||
export type ConfigRecord =
|
export type ConfigRecord =
|
||||||
| {
|
| {
|
||||||
key: typeof WALLET_BACKUP_STATE_KEY;
|
key: typeof WALLET_BACKUP_STATE_KEY;
|
||||||
value: WalletBackupConfState;
|
value: WalletBackupConfState;
|
||||||
}
|
}
|
||||||
| { key: "currencyDefaultsApplied"; value: boolean };
|
| { key: "currencyDefaultsApplied"; value: boolean };
|
||||||
|
|
||||||
export interface WalletBackupConfState {
|
export interface WalletBackupConfState {
|
||||||
@ -1392,9 +1392,9 @@ export interface WithdrawalGroupRecord {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* UID of the denomination selection.
|
* UID of the denomination selection.
|
||||||
*
|
*
|
||||||
* Used for merging backups.
|
* Used for merging backups.
|
||||||
*
|
*
|
||||||
* FIXME: Should this not also include a timestamp for more logical merging?
|
* FIXME: Should this not also include a timestamp for more logical merging?
|
||||||
*/
|
*/
|
||||||
denomSelUid: string;
|
denomSelUid: string;
|
||||||
@ -1480,17 +1480,17 @@ export enum BackupProviderStateTag {
|
|||||||
|
|
||||||
export type BackupProviderState =
|
export type BackupProviderState =
|
||||||
| {
|
| {
|
||||||
tag: BackupProviderStateTag.Provisional;
|
tag: BackupProviderStateTag.Provisional;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
tag: BackupProviderStateTag.Ready;
|
tag: BackupProviderStateTag.Ready;
|
||||||
nextBackupTimestamp: Timestamp;
|
nextBackupTimestamp: Timestamp;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
tag: BackupProviderStateTag.Retrying;
|
tag: BackupProviderStateTag.Retrying;
|
||||||
retryInfo: RetryInfo;
|
retryInfo: RetryInfo;
|
||||||
lastError?: TalerErrorDetails;
|
lastError?: TalerErrorDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BackupProviderTerms {
|
export interface BackupProviderTerms {
|
||||||
supportedProtocolVersion: string;
|
supportedProtocolVersion: string;
|
||||||
@ -1875,9 +1875,9 @@ export function exportDb(db: IDBDatabase): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseDump {
|
export interface DatabaseDump {
|
||||||
name: string,
|
name: string;
|
||||||
stores: { [s: string]: any },
|
stores: { [s: string]: any };
|
||||||
version: string,
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importDb(db: IDBDatabase, dump: DatabaseDump): Promise<any> {
|
export function importDb(db: IDBDatabase, dump: DatabaseDump): Promise<any> {
|
||||||
@ -1891,12 +1891,11 @@ export function importDb(db: IDBDatabase, dump: DatabaseDump): Promise<any> {
|
|||||||
const name = db.objectStoreNames[i];
|
const name = db.objectStoreNames[i];
|
||||||
const storeDump = dump.stores[name];
|
const storeDump = dump.stores[name];
|
||||||
if (!storeDump) continue;
|
if (!storeDump) continue;
|
||||||
Object.keys(storeDump).forEach(async key => {
|
Object.keys(storeDump).forEach(async (key) => {
|
||||||
const value = storeDump[key]
|
const value = storeDump[key];
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
tx.objectStore(name).put(value)
|
tx.objectStore(name).put(value);
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -337,8 +337,7 @@ export async function importBackup(
|
|||||||
|
|
||||||
for (const backupDenomination of backupExchangeDetails.denominations) {
|
for (const backupDenomination of backupExchangeDetails.denominations) {
|
||||||
if (
|
if (
|
||||||
backupDenomination.denom_pub.cipher !== DenomKeyType.Rsa &&
|
backupDenomination.denom_pub.cipher !== DenomKeyType.Rsa
|
||||||
backupDenomination.denom_pub.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
) {
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
|
@ -168,10 +168,7 @@ async function computeBackupCryptoData(
|
|||||||
};
|
};
|
||||||
for (const backupExchangeDetails of backupContent.exchange_details) {
|
for (const backupExchangeDetails of backupContent.exchange_details) {
|
||||||
for (const backupDenom of backupExchangeDetails.denominations) {
|
for (const backupDenom of backupExchangeDetails.denominations) {
|
||||||
if (
|
if (backupDenom.denom_pub.cipher !== DenomKeyType.Rsa) {
|
||||||
backupDenom.denom_pub.cipher !== DenomKeyType.Rsa &&
|
|
||||||
backupDenom.denom_pub.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
for (const backupCoin of backupDenom.coins) {
|
for (const backupCoin of backupDenom.coins) {
|
||||||
@ -192,18 +189,14 @@ async function computeBackupCryptoData(
|
|||||||
LibtoolVersion.compare(backupExchangeDetails.protocol_version, "9")
|
LibtoolVersion.compare(backupExchangeDetails.protocol_version, "9")
|
||||||
?.compatible
|
?.compatible
|
||||||
) {
|
) {
|
||||||
cryptoData.rsaDenomPubToHash[
|
cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] =
|
||||||
backupDenom.denom_pub.rsa_public_key
|
encodeCrock(hash(decodeCrock(backupDenom.denom_pub.rsa_public_key)));
|
||||||
] = encodeCrock(
|
|
||||||
hash(decodeCrock(backupDenom.denom_pub.rsa_public_key)),
|
|
||||||
);
|
|
||||||
} else if (
|
} else if (
|
||||||
LibtoolVersion.compare(backupExchangeDetails.protocol_version, "10")
|
LibtoolVersion.compare(backupExchangeDetails.protocol_version, "10")
|
||||||
?.compatible
|
?.compatible
|
||||||
) {
|
) {
|
||||||
cryptoData.rsaDenomPubToHash[
|
cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] =
|
||||||
backupDenom.denom_pub.rsa_public_key
|
encodeCrock(hashDenomPub(backupDenom.denom_pub));
|
||||||
] = encodeCrock(hashDenomPub(backupDenom.denom_pub));
|
|
||||||
} else {
|
} else {
|
||||||
throw Error("unsupported exchange protocol version");
|
throw Error("unsupported exchange protocol version");
|
||||||
}
|
}
|
||||||
@ -220,9 +213,8 @@ async function computeBackupCryptoData(
|
|||||||
);
|
);
|
||||||
const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(prop.nonce_priv)));
|
const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(prop.nonce_priv)));
|
||||||
cryptoData.proposalNoncePrivToPub[prop.nonce_priv] = noncePub;
|
cryptoData.proposalNoncePrivToPub[prop.nonce_priv] = noncePub;
|
||||||
cryptoData.proposalIdToContractTermsHash[
|
cryptoData.proposalIdToContractTermsHash[prop.proposal_id] =
|
||||||
prop.proposal_id
|
contractTermsHash;
|
||||||
] = contractTermsHash;
|
|
||||||
}
|
}
|
||||||
for (const purch of backupContent.purchases) {
|
for (const purch of backupContent.purchases) {
|
||||||
const contractTermsHash = await cryptoApi.hashString(
|
const contractTermsHash = await cryptoApi.hashString(
|
||||||
@ -230,9 +222,8 @@ async function computeBackupCryptoData(
|
|||||||
);
|
);
|
||||||
const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv)));
|
const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv)));
|
||||||
cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub;
|
cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub;
|
||||||
cryptoData.proposalIdToContractTermsHash[
|
cryptoData.proposalIdToContractTermsHash[purch.proposal_id] =
|
||||||
purch.proposal_id
|
contractTermsHash;
|
||||||
] = contractTermsHash;
|
|
||||||
}
|
}
|
||||||
return cryptoData;
|
return cryptoData;
|
||||||
}
|
}
|
||||||
@ -548,10 +539,11 @@ export interface RemoveBackupProviderRequest {
|
|||||||
provider: string;
|
provider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForRemoveBackupProvider = (): Codec<RemoveBackupProviderRequest> =>
|
export const codecForRemoveBackupProvider =
|
||||||
buildCodecForObject<RemoveBackupProviderRequest>()
|
(): Codec<RemoveBackupProviderRequest> =>
|
||||||
.property("provider", codecForString())
|
buildCodecForObject<RemoveBackupProviderRequest>()
|
||||||
.build("RemoveBackupProviderRequest");
|
.property("provider", codecForString())
|
||||||
|
.build("RemoveBackupProviderRequest");
|
||||||
|
|
||||||
export async function removeBackupProvider(
|
export async function removeBackupProvider(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
@ -619,12 +611,13 @@ interface SyncTermsOfServiceResponse {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const codecForSyncTermsOfServiceResponse = (): Codec<SyncTermsOfServiceResponse> =>
|
const codecForSyncTermsOfServiceResponse =
|
||||||
buildCodecForObject<SyncTermsOfServiceResponse>()
|
(): Codec<SyncTermsOfServiceResponse> =>
|
||||||
.property("storage_limit_in_megabytes", codecForNumber())
|
buildCodecForObject<SyncTermsOfServiceResponse>()
|
||||||
.property("annual_fee", codecForAmountString())
|
.property("storage_limit_in_megabytes", codecForNumber())
|
||||||
.property("version", codecForString())
|
.property("annual_fee", codecForAmountString())
|
||||||
.build("SyncTermsOfServiceResponse");
|
.property("version", codecForString())
|
||||||
|
.build("SyncTermsOfServiceResponse");
|
||||||
|
|
||||||
export interface AddBackupProviderRequest {
|
export interface AddBackupProviderRequest {
|
||||||
backupProviderBaseUrl: string;
|
backupProviderBaseUrl: string;
|
||||||
@ -637,12 +630,13 @@ export interface AddBackupProviderRequest {
|
|||||||
activate?: boolean;
|
activate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForAddBackupProviderRequest = (): Codec<AddBackupProviderRequest> =>
|
export const codecForAddBackupProviderRequest =
|
||||||
buildCodecForObject<AddBackupProviderRequest>()
|
(): Codec<AddBackupProviderRequest> =>
|
||||||
.property("backupProviderBaseUrl", codecForString())
|
buildCodecForObject<AddBackupProviderRequest>()
|
||||||
.property("name", codecForString())
|
.property("backupProviderBaseUrl", codecForString())
|
||||||
.property("activate", codecOptional(codecForBoolean()))
|
.property("name", codecForString())
|
||||||
.build("AddBackupProviderRequest");
|
.property("activate", codecOptional(codecForBoolean()))
|
||||||
|
.build("AddBackupProviderRequest");
|
||||||
|
|
||||||
export async function addBackupProvider(
|
export async function addBackupProvider(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
|
@ -27,7 +27,11 @@ import {
|
|||||||
CreateDepositGroupRequest,
|
CreateDepositGroupRequest,
|
||||||
CreateDepositGroupResponse,
|
CreateDepositGroupResponse,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
durationFromSpec, encodeCrock, GetFeeForDepositRequest, getRandomBytes, getTimestampNow,
|
durationFromSpec,
|
||||||
|
encodeCrock,
|
||||||
|
GetFeeForDepositRequest,
|
||||||
|
getRandomBytes,
|
||||||
|
getTimestampNow,
|
||||||
Logger,
|
Logger,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
parsePaytoUri,
|
parsePaytoUri,
|
||||||
@ -38,7 +42,7 @@ import {
|
|||||||
timestampTruncateToSecond,
|
timestampTruncateToSecond,
|
||||||
TrackDepositGroupRequest,
|
TrackDepositGroupRequest,
|
||||||
TrackDepositGroupResponse,
|
TrackDepositGroupResponse,
|
||||||
URL
|
URL,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { InternalWalletState } from "../common.js";
|
import { InternalWalletState } from "../common.js";
|
||||||
import { DepositGroupRecord, OperationStatus } from "../db.js";
|
import { DepositGroupRecord, OperationStatus } from "../db.js";
|
||||||
@ -54,7 +58,7 @@ import {
|
|||||||
getCandidatePayCoins,
|
getCandidatePayCoins,
|
||||||
getTotalPaymentCost,
|
getTotalPaymentCost,
|
||||||
hashWire,
|
hashWire,
|
||||||
hashWireLegacy
|
hashWireLegacy,
|
||||||
} from "./pay.js";
|
} from "./pay.js";
|
||||||
import { getTotalRefreshCost } from "./refresh.js";
|
import { getTotalRefreshCost } from "./refresh.js";
|
||||||
|
|
||||||
@ -199,47 +203,21 @@ async function processDepositGroupImpl(
|
|||||||
}
|
}
|
||||||
const perm = depositPermissions[i];
|
const perm = depositPermissions[i];
|
||||||
let requestBody: any;
|
let requestBody: any;
|
||||||
if (
|
logger.info("creating v10 deposit request");
|
||||||
typeof perm.ub_sig === "string" ||
|
requestBody = {
|
||||||
perm.ub_sig.cipher === DenomKeyType.LegacyRsa
|
contribution: Amounts.stringify(perm.contribution),
|
||||||
) {
|
merchant_payto_uri: depositGroup.wire.payto_uri,
|
||||||
// Legacy request
|
wire_salt: depositGroup.wire.salt,
|
||||||
logger.info("creating legacy deposit request");
|
h_contract_terms: depositGroup.contractTermsHash,
|
||||||
const wireHash = hashWireLegacy(
|
ub_sig: perm.ub_sig,
|
||||||
depositGroup.wire.payto_uri,
|
timestamp: depositGroup.contractTermsRaw.timestamp,
|
||||||
depositGroup.wire.salt,
|
wire_transfer_deadline:
|
||||||
);
|
depositGroup.contractTermsRaw.wire_transfer_deadline,
|
||||||
requestBody = {
|
refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
|
||||||
contribution: Amounts.stringify(perm.contribution),
|
coin_sig: perm.coin_sig,
|
||||||
wire: depositGroup.wire,
|
denom_pub_hash: perm.h_denom,
|
||||||
h_wire: wireHash,
|
merchant_pub: depositGroup.merchantPub,
|
||||||
h_contract_terms: depositGroup.contractTermsHash,
|
};
|
||||||
ub_sig: perm.ub_sig,
|
|
||||||
timestamp: depositGroup.contractTermsRaw.timestamp,
|
|
||||||
wire_transfer_deadline:
|
|
||||||
depositGroup.contractTermsRaw.wire_transfer_deadline,
|
|
||||||
refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
|
|
||||||
coin_sig: perm.coin_sig,
|
|
||||||
denom_pub_hash: perm.h_denom,
|
|
||||||
merchant_pub: depositGroup.merchantPub,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
logger.info("creating v10 deposit request");
|
|
||||||
requestBody = {
|
|
||||||
contribution: Amounts.stringify(perm.contribution),
|
|
||||||
merchant_payto_uri: depositGroup.wire.payto_uri,
|
|
||||||
wire_salt: depositGroup.wire.salt,
|
|
||||||
h_contract_terms: depositGroup.contractTermsHash,
|
|
||||||
ub_sig: perm.ub_sig,
|
|
||||||
timestamp: depositGroup.contractTermsRaw.timestamp,
|
|
||||||
wire_transfer_deadline:
|
|
||||||
depositGroup.contractTermsRaw.wire_transfer_deadline,
|
|
||||||
refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
|
|
||||||
coin_sig: perm.coin_sig,
|
|
||||||
denom_pub_hash: perm.h_denom,
|
|
||||||
merchant_pub: depositGroup.merchantPub,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
|
const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
|
||||||
logger.info(`depositing to ${url}`);
|
logger.info(`depositing to ${url}`);
|
||||||
const httpResp = await ws.http.postJson(url.href, requestBody);
|
const httpResp = await ws.http.postJson(url.href, requestBody);
|
||||||
|
@ -83,15 +83,7 @@ function denominationRecordFromKeys(
|
|||||||
denomIn: ExchangeDenomination,
|
denomIn: ExchangeDenomination,
|
||||||
): DenominationRecord {
|
): DenominationRecord {
|
||||||
let denomPub: DenominationPubKey;
|
let denomPub: DenominationPubKey;
|
||||||
// We support exchange protocol v9 and v10.
|
denomPub = denomIn.denom_pub;
|
||||||
if (typeof denomIn.denom_pub === "string") {
|
|
||||||
denomPub = {
|
|
||||||
cipher: DenomKeyType.LegacyRsa,
|
|
||||||
rsa_public_key: denomIn.denom_pub,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
denomPub = denomIn.denom_pub;
|
|
||||||
}
|
|
||||||
const denomPubHash = encodeCrock(hashDenomPub(denomPub));
|
const denomPubHash = encodeCrock(hashDenomPub(denomPub));
|
||||||
const d: DenominationRecord = {
|
const d: DenominationRecord = {
|
||||||
denomPub,
|
denomPub,
|
||||||
|
@ -606,7 +606,6 @@ export function extractContractData(
|
|||||||
timestamp: parsedContractTerms.timestamp,
|
timestamp: parsedContractTerms.timestamp,
|
||||||
wireMethod: parsedContractTerms.wire_method,
|
wireMethod: parsedContractTerms.wire_method,
|
||||||
wireInfoHash: parsedContractTerms.h_wire,
|
wireInfoHash: parsedContractTerms.h_wire,
|
||||||
wireInfoLegacyHash: parsedContractTerms.h_wire_legacy,
|
|
||||||
maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
|
maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
|
||||||
merchant: parsedContractTerms.merchant,
|
merchant: parsedContractTerms.merchant,
|
||||||
products: parsedContractTerms.products,
|
products: parsedContractTerms.products,
|
||||||
@ -1515,14 +1514,7 @@ export async function generateDepositPermissions(
|
|||||||
for (let i = 0; i < payCoinSel.coinPubs.length; i++) {
|
for (let i = 0; i < payCoinSel.coinPubs.length; i++) {
|
||||||
const { coin, denom } = coinWithDenom[i];
|
const { coin, denom } = coinWithDenom[i];
|
||||||
let wireInfoHash: string;
|
let wireInfoHash: string;
|
||||||
if (
|
wireInfoHash = contractData.wireInfoHash;
|
||||||
coin.denomPub.cipher === DenomKeyType.LegacyRsa &&
|
|
||||||
contractData.wireInfoLegacyHash
|
|
||||||
) {
|
|
||||||
wireInfoHash = contractData.wireInfoLegacyHash;
|
|
||||||
} else {
|
|
||||||
wireInfoHash = contractData.wireInfoHash;
|
|
||||||
}
|
|
||||||
const dp = await ws.cryptoApi.signDepositPermission({
|
const dp = await ws.cryptoApi.signDepositPermission({
|
||||||
coinPriv: coin.coinPriv,
|
coinPriv: coin.coinPriv,
|
||||||
coinPub: coin.coinPub,
|
coinPub: coin.coinPub,
|
||||||
|
@ -18,8 +18,10 @@ import {
|
|||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
ExchangeProtocolVersion,
|
ExchangeProtocolVersion,
|
||||||
|
ExchangeRefreshRevealRequest,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
HttpStatusCode,
|
HttpStatusCode,
|
||||||
|
j2s,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
@ -369,10 +371,6 @@ async function refreshMelt(
|
|||||||
|
|
||||||
let exchangeProtocolVersion: ExchangeProtocolVersion;
|
let exchangeProtocolVersion: ExchangeProtocolVersion;
|
||||||
switch (d.oldDenom.denomPub.cipher) {
|
switch (d.oldDenom.denomPub.cipher) {
|
||||||
case DenomKeyType.LegacyRsa: {
|
|
||||||
exchangeProtocolVersion = ExchangeProtocolVersion.V9;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DenomKeyType.Rsa: {
|
case DenomKeyType.Rsa: {
|
||||||
exchangeProtocolVersion = ExchangeProtocolVersion.V12;
|
exchangeProtocolVersion = ExchangeProtocolVersion.V12;
|
||||||
break;
|
break;
|
||||||
@ -397,16 +395,7 @@ async function refreshMelt(
|
|||||||
oldCoin.exchangeBaseUrl,
|
oldCoin.exchangeBaseUrl,
|
||||||
);
|
);
|
||||||
let meltReqBody: any;
|
let meltReqBody: any;
|
||||||
if (oldCoin.denomPub.cipher === DenomKeyType.LegacyRsa) {
|
if (oldCoin.denomPub.cipher === DenomKeyType.Rsa) {
|
||||||
meltReqBody = {
|
|
||||||
coin_pub: oldCoin.coinPub,
|
|
||||||
confirm_sig: derived.confirmSig,
|
|
||||||
denom_pub_hash: oldCoin.denomPubHash,
|
|
||||||
denom_sig: oldCoin.denomSig.rsa_signature,
|
|
||||||
rc: derived.hash,
|
|
||||||
value_with_fee: Amounts.stringify(derived.meltValueWithFee),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
meltReqBody = {
|
meltReqBody = {
|
||||||
coin_pub: oldCoin.coinPub,
|
coin_pub: oldCoin.coinPub,
|
||||||
confirm_sig: derived.confirmSig,
|
confirm_sig: derived.confirmSig,
|
||||||
@ -569,10 +558,6 @@ async function refreshReveal(
|
|||||||
|
|
||||||
let exchangeProtocolVersion: ExchangeProtocolVersion;
|
let exchangeProtocolVersion: ExchangeProtocolVersion;
|
||||||
switch (d.oldDenom.denomPub.cipher) {
|
switch (d.oldDenom.denomPub.cipher) {
|
||||||
case DenomKeyType.LegacyRsa: {
|
|
||||||
exchangeProtocolVersion = ExchangeProtocolVersion.V9;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DenomKeyType.Rsa: {
|
case DenomKeyType.Rsa: {
|
||||||
exchangeProtocolVersion = ExchangeProtocolVersion.V12;
|
exchangeProtocolVersion = ExchangeProtocolVersion.V12;
|
||||||
break;
|
break;
|
||||||
@ -600,7 +585,6 @@ async function refreshReveal(
|
|||||||
throw Error("refresh index error");
|
throw Error("refresh index error");
|
||||||
}
|
}
|
||||||
|
|
||||||
const evs = planchets.map((x: RefreshPlanchetInfo) => x.coinEv);
|
|
||||||
const newDenomsFlat: string[] = [];
|
const newDenomsFlat: string[] = [];
|
||||||
const linkSigs: string[] = [];
|
const linkSigs: string[] = [];
|
||||||
|
|
||||||
@ -620,10 +604,9 @@ async function refreshReveal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = {
|
const req: ExchangeRefreshRevealRequest = {
|
||||||
coin_evs: evs,
|
coin_evs: planchets.map((x) => x.coinEv),
|
||||||
new_denoms_h: newDenomsFlat,
|
new_denoms_h: newDenomsFlat,
|
||||||
rc: derived.hash,
|
|
||||||
transfer_privs: privs,
|
transfer_privs: privs,
|
||||||
transfer_pub: derived.transferPubs[norevealIndex],
|
transfer_pub: derived.transferPubs[norevealIndex],
|
||||||
link_sigs: linkSigs,
|
link_sigs: linkSigs,
|
||||||
@ -666,20 +649,14 @@ async function refreshReveal(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const pc = derived.planchetsForGammas[norevealIndex][newCoinIndex];
|
const pc = derived.planchetsForGammas[norevealIndex][newCoinIndex];
|
||||||
if (
|
if (denom.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||||
denom.denomPub.cipher !== DenomKeyType.Rsa &&
|
|
||||||
denom.denomPub.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
throw Error("cipher unsupported");
|
throw Error("cipher unsupported");
|
||||||
}
|
}
|
||||||
const evSig = reveal.ev_sigs[newCoinIndex].ev_sig;
|
const evSig = reveal.ev_sigs[newCoinIndex].ev_sig;
|
||||||
let rsaSig: string;
|
let rsaSig: string;
|
||||||
if (typeof evSig === "string") {
|
if (typeof evSig === "string") {
|
||||||
rsaSig = evSig;
|
rsaSig = evSig;
|
||||||
} else if (
|
} else if (evSig.cipher === DenomKeyType.Rsa) {
|
||||||
evSig.cipher === DenomKeyType.Rsa ||
|
|
||||||
evSig.cipher === DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
rsaSig = evSig.blinded_rsa_signature;
|
rsaSig = evSig.blinded_rsa_signature;
|
||||||
} else {
|
} else {
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
@ -691,8 +668,8 @@ async function refreshReveal(
|
|||||||
);
|
);
|
||||||
const coin: CoinRecord = {
|
const coin: CoinRecord = {
|
||||||
blindingKey: pc.blindingKey,
|
blindingKey: pc.blindingKey,
|
||||||
coinPriv: pc.privateKey,
|
coinPriv: pc.coinPriv,
|
||||||
coinPub: pc.publicKey,
|
coinPub: pc.coinPub,
|
||||||
currentAmount: denom.value,
|
currentAmount: denom.value,
|
||||||
denomPub: denom.denomPub,
|
denomPub: denom.denomPub,
|
||||||
denomPubHash: denom.denomPubHash,
|
denomPubHash: denom.denomPubHash,
|
||||||
@ -707,7 +684,7 @@ async function refreshReveal(
|
|||||||
oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
|
oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
|
||||||
},
|
},
|
||||||
suspended: false,
|
suspended: false,
|
||||||
coinEvHash: pc.coinEv,
|
coinEvHash: pc.coinEvHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
coins.push(coin);
|
coins.push(coin);
|
||||||
|
@ -306,37 +306,13 @@ async function processTipImpl(
|
|||||||
// FIXME: Maybe we want to signal to the caller that the transient error happened?
|
// FIXME: Maybe we want to signal to the caller that the transient error happened?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Do this earlier?
|
|
||||||
const merchantInfo = await ws.merchantOps.getMerchantInfo(
|
|
||||||
ws,
|
|
||||||
tipRecord.merchantBaseUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
let blindedSigs: BlindedDenominationSignature[] = [];
|
let blindedSigs: BlindedDenominationSignature[] = [];
|
||||||
|
|
||||||
if (merchantInfo.protocolVersionCurrent === MerchantProtocolVersion.V3) {
|
const response = await readSuccessResponseJsonOrThrow(
|
||||||
const response = await readSuccessResponseJsonOrThrow(
|
merchantResp,
|
||||||
merchantResp,
|
codecForMerchantTipResponseV2(),
|
||||||
codecForMerchantTipResponseV2(),
|
);
|
||||||
);
|
blindedSigs = response.blind_sigs.map((x) => x.blind_sig);
|
||||||
blindedSigs = response.blind_sigs.map((x) => x.blind_sig);
|
|
||||||
} else if (
|
|
||||||
merchantInfo.protocolVersionCurrent === MerchantProtocolVersion.V1
|
|
||||||
) {
|
|
||||||
const response = await readSuccessResponseJsonOrThrow(
|
|
||||||
merchantResp,
|
|
||||||
codecForMerchantTipResponseV1(),
|
|
||||||
);
|
|
||||||
blindedSigs = response.blind_sigs.map((x) => ({
|
|
||||||
cipher: DenomKeyType.Rsa,
|
|
||||||
blinded_rsa_signature: x.blind_sig,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
throw Error(
|
|
||||||
`unsupported merchant protocol version (${merchantInfo.protocolVersionCurrent})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blindedSigs.length !== planchets.length) {
|
if (blindedSigs.length !== planchets.length) {
|
||||||
throw Error("number of tip responses does not match requested planchets");
|
throw Error("number of tip responses does not match requested planchets");
|
||||||
@ -352,17 +328,11 @@ async function processTipImpl(
|
|||||||
const planchet = planchets[i];
|
const planchet = planchets[i];
|
||||||
checkLogicInvariant(!!planchet);
|
checkLogicInvariant(!!planchet);
|
||||||
|
|
||||||
if (
|
if (denom.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||||
denom.denomPub.cipher !== DenomKeyType.Rsa &&
|
|
||||||
denom.denomPub.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (blindedSig.cipher !== DenomKeyType.Rsa) {
|
||||||
blindedSig.cipher !== DenomKeyType.Rsa &&
|
|
||||||
blindedSig.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Amounts } from "@gnu-taler/taler-util";
|
import { Amounts, DenomKeyType } from "@gnu-taler/taler-util";
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
import { DenominationRecord, DenominationVerificationStatus } from "../db.js";
|
import { DenominationRecord, DenominationVerificationStatus } from "../db.js";
|
||||||
import { selectWithdrawalDenominations } from "./withdraw.js";
|
import { selectWithdrawalDenominations } from "./withdraw.js";
|
||||||
@ -29,7 +29,7 @@ test("withdrawal selection bug repro", (t) => {
|
|||||||
const denoms: DenominationRecord[] = [
|
const denoms: DenominationRecord[] = [
|
||||||
{
|
{
|
||||||
denomPub: {
|
denomPub: {
|
||||||
cipher: 1,
|
cipher: DenomKeyType.Rsa,
|
||||||
rsa_public_key:
|
rsa_public_key:
|
||||||
"040000XT67C8KBD6B75TTQ3SK8FWXMNQW4372T3BDDGPAMB9RFCA03638W8T3F71WFEFK9NP32VKYVNFXPYRWQ1N1HDKV5J0DFEKHBPJCYSWCBJDRNWD7G8BN8PT97FA9AMV75MYEK4X54D1HGJ207JSVJBGFCATSPNTEYNHEQF1F220W00TBZR1HNPDQFD56FG0DJQ9KGHM8EC33H6AY9YN9CNX5R3Z4TZ4Q23W47SBHB13H6W74FQJG1F50X38VRSC4SR8RWBAFB7S4K8D2H4NMRFSQT892A3T0BTBW7HM5C0H2CK6FRKG31F7W9WP1S29013K5CXYE55CT8TH6N8J9B780R42Y5S3ZB6J6E9H76XBPSGH4TGYSR2VZRB98J417KCQMZKX1BB67E7W5KVE37TC9SJ904002",
|
"040000XT67C8KBD6B75TTQ3SK8FWXMNQW4372T3BDDGPAMB9RFCA03638W8T3F71WFEFK9NP32VKYVNFXPYRWQ1N1HDKV5J0DFEKHBPJCYSWCBJDRNWD7G8BN8PT97FA9AMV75MYEK4X54D1HGJ207JSVJBGFCATSPNTEYNHEQF1F220W00TBZR1HNPDQFD56FG0DJQ9KGHM8EC33H6AY9YN9CNX5R3Z4TZ4Q23W47SBHB13H6W74FQJG1F50X38VRSC4SR8RWBAFB7S4K8D2H4NMRFSQT892A3T0BTBW7HM5C0H2CK6FRKG31F7W9WP1S29013K5CXYE55CT8TH6N8J9B780R42Y5S3ZB6J6E9H76XBPSGH4TGYSR2VZRB98J417KCQMZKX1BB67E7W5KVE37TC9SJ904002",
|
||||||
},
|
},
|
||||||
@ -83,7 +83,7 @@ test("withdrawal selection bug repro", (t) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
denomPub: {
|
denomPub: {
|
||||||
cipher: 1,
|
cipher: DenomKeyType.Rsa,
|
||||||
rsa_public_key:
|
rsa_public_key:
|
||||||
"040000Y63CF78QFPKRY77BRK9P557Q1GQWX3NCZ3HSYSK0Z7TT0KGRA7N4SKBKEHSTVHX1Z9DNXMJR4EXSY1TXCKV0GJ3T3YYC6Z0JNMJFVYQAV4FX5J90NZH1N33MZTV8HS9SMNAA9S6K73G4P99GYBB01B0P6M1KXZ5JRDR7VWBR3MEJHHGJ6QBMCJR3NWJRE3WJW9PRY8QPQ2S7KFWTWRESH2DBXCXWBD2SRN6P9YX8GRAEMFEGXC9V5GVJTEMH6ZDGNXFPWZE3JVJ2Q4N9GDYKBCHZCJ7M7M2RJ9ZV4Y64NAN9BT6XDC68215GKKRHTW1BBF1MYY6AR3JCTT9HYAM923RMVQR3TAEB7SDX8J76XRZWYH3AGJCZAQGMN5C8SSH9AHQ9RNQJQ15CN45R37X4YNFJV904002",
|
"040000Y63CF78QFPKRY77BRK9P557Q1GQWX3NCZ3HSYSK0Z7TT0KGRA7N4SKBKEHSTVHX1Z9DNXMJR4EXSY1TXCKV0GJ3T3YYC6Z0JNMJFVYQAV4FX5J90NZH1N33MZTV8HS9SMNAA9S6K73G4P99GYBB01B0P6M1KXZ5JRDR7VWBR3MEJHHGJ6QBMCJR3NWJRE3WJW9PRY8QPQ2S7KFWTWRESH2DBXCXWBD2SRN6P9YX8GRAEMFEGXC9V5GVJTEMH6ZDGNXFPWZE3JVJ2Q4N9GDYKBCHZCJ7M7M2RJ9ZV4Y64NAN9BT6XDC68215GKKRHTW1BBF1MYY6AR3JCTT9HYAM923RMVQR3TAEB7SDX8J76XRZWYH3AGJCZAQGMN5C8SSH9AHQ9RNQJQ15CN45R37X4YNFJV904002",
|
||||||
},
|
},
|
||||||
@ -138,7 +138,7 @@ test("withdrawal selection bug repro", (t) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
denomPub: {
|
denomPub: {
|
||||||
cipher: 1,
|
cipher: DenomKeyType.Rsa,
|
||||||
rsa_public_key:
|
rsa_public_key:
|
||||||
"040000YDESWC2B962DA4WK356SC50MA3N9KV0ZSGY3RC48JCTY258W909C7EEMT5BTC5KZ5T4CERCZ141P9QF87EK2BD1XEEM5GB07MB3H19WE4CQGAS8X84JBWN83PQGQXVMWE5HFA992KMGHC566GT9ZS2QPHZB6X89C4A80Z663PYAAPXP728VHAKATGNNBQ01ZZ2XD1CH9Y38YZBSPJ4K7GB2J76GBCYAVD9ENHDVWXJAXYRPBX4KSS5TXRR3K5NEN9ZV3AJD2V65K7ABRZDF5D5V1FJZZMNJ5XZ4FEREEKEBV9TDFPGJTKDEHEC60K3DN24DAATRESDJ1ZYYSYSRCAT4BT2B62ARGVMJTT5N2R126DRW9TGRWCW0ZAF2N2WET1H4NJEW77X0QT46Z5R3MZ0XPHD04002",
|
"040000YDESWC2B962DA4WK356SC50MA3N9KV0ZSGY3RC48JCTY258W909C7EEMT5BTC5KZ5T4CERCZ141P9QF87EK2BD1XEEM5GB07MB3H19WE4CQGAS8X84JBWN83PQGQXVMWE5HFA992KMGHC566GT9ZS2QPHZB6X89C4A80Z663PYAAPXP728VHAKATGNNBQ01ZZ2XD1CH9Y38YZBSPJ4K7GB2J76GBCYAVD9ENHDVWXJAXYRPBX4KSS5TXRR3K5NEN9ZV3AJD2V65K7ABRZDF5D5V1FJZZMNJ5XZ4FEREEKEBV9TDFPGJTKDEHEC60K3DN24DAATRESDJ1ZYYSYSRCAT4BT2B62ARGVMJTT5N2R126DRW9TGRWCW0ZAF2N2WET1H4NJEW77X0QT46Z5R3MZ0XPHD04002",
|
||||||
},
|
},
|
||||||
@ -192,7 +192,7 @@ test("withdrawal selection bug repro", (t) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
denomPub: {
|
denomPub: {
|
||||||
cipher: 1,
|
cipher: DenomKeyType.Rsa,
|
||||||
rsa_public_key:
|
rsa_public_key:
|
||||||
"040000YG3T1ADB8DVA6BD3EPV6ZHSHTDW35DEN4VH1AE6CSB7P1PSDTNTJG866PHF6QB1CCWYCVRGA0FVBJ9Q0G7KV7AD9010GDYBQH0NNPHW744MTNXVXWBGGGRGQGYK4DTYN1DSWQ1FZNDSZZPB5BEKG2PDJ93NX2JTN06Y8QMS2G734Z9XHC10EENBG2KVB7EJ3CM8PV1T32RC7AY62F3496E8D8KRHJQQTT67DSGMNKK86QXVDTYW677FG27DP20E8XY3M6FQD53NDJ1WWES91401MV1A3VXVPGC76GZVDD62W3WTJ1YMKHTTA3MRXX3VEAAH3XTKDN1ER7X6CZPMYTF8VK735VP2B2TZGTF28TTW4FZS32SBS64APCDF6SZQ427N5538TJC7SRE71YSP5ET8GS904002",
|
"040000YG3T1ADB8DVA6BD3EPV6ZHSHTDW35DEN4VH1AE6CSB7P1PSDTNTJG866PHF6QB1CCWYCVRGA0FVBJ9Q0G7KV7AD9010GDYBQH0NNPHW744MTNXVXWBGGGRGQGYK4DTYN1DSWQ1FZNDSZZPB5BEKG2PDJ93NX2JTN06Y8QMS2G734Z9XHC10EENBG2KVB7EJ3CM8PV1T32RC7AY62F3496E8D8KRHJQQTT67DSGMNKK86QXVDTYW677FG27DP20E8XY3M6FQD53NDJ1WWES91401MV1A3VXVPGC76GZVDD62W3WTJ1YMKHTTA3MRXX3VEAAH3XTKDN1ER7X6CZPMYTF8VK735VP2B2TZGTF28TTW4FZS32SBS64APCDF6SZQ427N5538TJC7SRE71YSP5ET8GS904002",
|
||||||
},
|
},
|
||||||
@ -247,7 +247,7 @@ test("withdrawal selection bug repro", (t) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
denomPub: {
|
denomPub: {
|
||||||
cipher: 1,
|
cipher: DenomKeyType.Rsa,
|
||||||
rsa_public_key:
|
rsa_public_key:
|
||||||
"040000ZC0G60E9QQ5PD81TSDWD9GV5Y6P8Z05NSPA696DP07NGQQVSRQXBA76Q6PRB0YFX295RG4MTQJXAZZ860ET307HSC2X37XAVGQXRVB8Q4F1V7NP5ZEVKTX75DZK1QRAVHEZGQYKSSH6DBCJNQF6V9WNQF3GEYVA4KCBHA7JF772KHXM9642C28Z0AS4XXXV2PABAN5C8CHYD5H7JDFNK3920W5Q69X0BS84XZ4RE2PW6HM1WZ6KGZ3MKWWWCPKQ1FSFABRBWKAB09PF563BEBXKY6M38QETPH5EDWGANHD0SC3QV0WXYVB7BNHNNQ0J5BNV56K563SYHM4E5ND260YRJSYA1GN5YSW2B1J5T1A1EBNYF2DN6JNJKWXWEQ42G5YS17ZSZ5EWDRA9QKV8EGTCNAD04002",
|
"040000ZC0G60E9QQ5PD81TSDWD9GV5Y6P8Z05NSPA696DP07NGQQVSRQXBA76Q6PRB0YFX295RG4MTQJXAZZ860ET307HSC2X37XAVGQXRVB8Q4F1V7NP5ZEVKTX75DZK1QRAVHEZGQYKSSH6DBCJNQF6V9WNQF3GEYVA4KCBHA7JF772KHXM9642C28Z0AS4XXXV2PABAN5C8CHYD5H7JDFNK3920W5Q69X0BS84XZ4RE2PW6HM1WZ6KGZ3MKWWWCPKQ1FSFABRBWKAB09PF563BEBXKY6M38QETPH5EDWGANHD0SC3QV0WXYVB7BNHNNQ0J5BNV56K563SYHM4E5ND260YRJSYA1GN5YSW2B1J5T1A1EBNYF2DN6JNJKWXWEQ42G5YS17ZSZ5EWDRA9QKV8EGTCNAD04002",
|
||||||
},
|
},
|
||||||
@ -301,7 +301,7 @@ test("withdrawal selection bug repro", (t) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
denomPub: {
|
denomPub: {
|
||||||
cipher: 1,
|
cipher: DenomKeyType.Rsa,
|
||||||
rsa_public_key:
|
rsa_public_key:
|
||||||
"040000ZSK2PMVY6E3NBQ52KXMW029M60F4BWYTDS0FZSD0PE53CNZ9H6TM3GQK1WRTEKQ5GRWJ1J9DY6Y42SP47QVT1XD1G0W05SQ5F3F7P5KSWR0FJBJ9NZBXQEVN8Q4JRC94X3JJ3XV3KBYTZ2HTDFV28C3H2SRR0XGNZB4FY85NDZF1G4AEYJJ9QB3C0V8H70YB8RV3FKTNH7XS4K4HFNZHJ5H9VMX5SM9Z2DX37HA5WFH0E2MJBVVF2BWWA5M0HPPSB365RAE2AMD42Q65A96WD80X27SB2ZNQZ8WX0K13FWF85GZ6YNYAJGE1KGN06JDEKE9QD68Z651D7XE8V6664TVVC8M68S7WD0DSXMJQKQ0BNJXNDE29Q7MRX6DA3RW0PZ44B3TKRK0294FPVZTNSTA6XF04002",
|
"040000ZSK2PMVY6E3NBQ52KXMW029M60F4BWYTDS0FZSD0PE53CNZ9H6TM3GQK1WRTEKQ5GRWJ1J9DY6Y42SP47QVT1XD1G0W05SQ5F3F7P5KSWR0FJBJ9NZBXQEVN8Q4JRC94X3JJ3XV3KBYTZ2HTDFV28C3H2SRR0XGNZB4FY85NDZF1G4AEYJJ9QB3C0V8H70YB8RV3FKTNH7XS4K4HFNZHJ5H9VMX5SM9Z2DX37HA5WFH0E2MJBVVF2BWWA5M0HPPSB365RAE2AMD42Q65A96WD80X27SB2ZNQZ8WX0K13FWF85GZ6YNYAJGE1KGN06JDEKE9QD68Z651D7XE8V6664TVVC8M68S7WD0DSXMJQKQ0BNJXNDE29Q7MRX6DA3RW0PZ44B3TKRK0294FPVZTNSTA6XF04002",
|
||||||
},
|
},
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
LibtoolVersion,
|
LibtoolVersion,
|
||||||
UnblindedSignature,
|
UnblindedSignature,
|
||||||
|
ExchangeWithdrawRequest,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
@ -497,9 +498,8 @@ async function processPlanchetExchangeRequest(
|
|||||||
`processing planchet #${coinIdx} in withdrawal ${withdrawalGroup.withdrawalGroupId}`,
|
`processing planchet #${coinIdx} in withdrawal ${withdrawalGroup.withdrawalGroupId}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const reqBody: any = {
|
const reqBody: ExchangeWithdrawRequest = {
|
||||||
denom_pub_hash: planchet.denomPubHash,
|
denom_pub_hash: planchet.denomPubHash,
|
||||||
reserve_pub: planchet.reservePub,
|
|
||||||
reserve_sig: planchet.withdrawSig,
|
reserve_sig: planchet.withdrawSig,
|
||||||
coin_ev: planchet.coinEv,
|
coin_ev: planchet.coinEv,
|
||||||
};
|
};
|
||||||
@ -580,28 +580,12 @@ async function processPlanchetVerifyAndStoreCoin(
|
|||||||
const { planchet, exchangeBaseUrl } = d;
|
const { planchet, exchangeBaseUrl } = d;
|
||||||
|
|
||||||
const planchetDenomPub = planchet.denomPub;
|
const planchetDenomPub = planchet.denomPub;
|
||||||
if (
|
if (planchetDenomPub.cipher !== DenomKeyType.Rsa) {
|
||||||
planchetDenomPub.cipher !== DenomKeyType.Rsa &&
|
|
||||||
planchetDenomPub.cipher !== DenomKeyType.LegacyRsa
|
|
||||||
) {
|
|
||||||
throw Error(`cipher (${planchetDenomPub.cipher}) not supported`);
|
throw Error(`cipher (${planchetDenomPub.cipher}) not supported`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let evSig = resp.ev_sig;
|
let evSig = resp.ev_sig;
|
||||||
if (typeof resp.ev_sig === "string") {
|
if (!(evSig.cipher === DenomKeyType.Rsa)) {
|
||||||
evSig = {
|
|
||||||
cipher: DenomKeyType.LegacyRsa,
|
|
||||||
blinded_rsa_signature: resp.ev_sig,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
evSig = resp.ev_sig;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
evSig.cipher === DenomKeyType.Rsa ||
|
|
||||||
evSig.cipher === DenomKeyType.LegacyRsa
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw Error("unsupported cipher");
|
throw Error("unsupported cipher");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,10 +623,7 @@ async function processPlanchetVerifyAndStoreCoin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let denomSig: UnblindedSignature;
|
let denomSig: UnblindedSignature;
|
||||||
if (
|
if (planchet.denomPub.cipher === DenomKeyType.Rsa) {
|
||||||
planchet.denomPub.cipher === DenomKeyType.LegacyRsa ||
|
|
||||||
planchet.denomPub.cipher === DenomKeyType.Rsa
|
|
||||||
) {
|
|
||||||
denomSig = {
|
denomSig = {
|
||||||
cipher: planchet.denomPub.cipher,
|
cipher: planchet.denomPub.cipher,
|
||||||
rsa_signature: denomSigRsa,
|
rsa_signature: denomSigRsa,
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
|
import { AmountJson, Amounts, DenomKeyType } from "@gnu-taler/taler-util";
|
||||||
import { AvailableCoinInfo, selectPayCoins } from "./coinSelection.js";
|
import { AvailableCoinInfo, selectPayCoins } from "./coinSelection.js";
|
||||||
|
|
||||||
function a(x: string): AmountJson {
|
function a(x: string): AmountJson {
|
||||||
@ -34,7 +34,7 @@ function fakeAci(current: string, feeDeposit: string): AvailableCoinInfo {
|
|||||||
availableAmount: a(current),
|
availableAmount: a(current),
|
||||||
coinPub: "foobar",
|
coinPub: "foobar",
|
||||||
denomPub: {
|
denomPub: {
|
||||||
cipher: 1,
|
cipher: DenomKeyType.Rsa,
|
||||||
rsa_public_key: "foobar",
|
rsa_public_key: "foobar",
|
||||||
},
|
},
|
||||||
feeDeposit: a(feeDeposit),
|
feeDeposit: a(feeDeposit),
|
||||||
@ -47,7 +47,7 @@ test("it should be able to pay if merchant takes the fees", (t) => {
|
|||||||
fakeAci("EUR:1.0", "EUR:0.1"),
|
fakeAci("EUR:1.0", "EUR:0.1"),
|
||||||
fakeAci("EUR:1.0", "EUR:0.0"),
|
fakeAci("EUR:1.0", "EUR:0.0"),
|
||||||
];
|
];
|
||||||
acis.forEach((x, i) => x.coinPub = String(i));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoins({
|
||||||
candidates: {
|
candidates: {
|
||||||
@ -75,7 +75,7 @@ test("it should take the last two coins if it pays less fees", (t) => {
|
|||||||
// Merchant covers the fee, this one shouldn't be used
|
// Merchant covers the fee, this one shouldn't be used
|
||||||
fakeAci("EUR:1.0", "EUR:0.0"),
|
fakeAci("EUR:1.0", "EUR:0.0"),
|
||||||
];
|
];
|
||||||
acis.forEach((x, i) => x.coinPub = String(i));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoins({
|
||||||
candidates: {
|
candidates: {
|
||||||
@ -102,8 +102,8 @@ test("it should take the last coins if the merchant doest not take all the fee",
|
|||||||
fakeAci("EUR:1.0", "EUR:0.5"),
|
fakeAci("EUR:1.0", "EUR:0.5"),
|
||||||
// this coin should be selected instead of previous one with fee
|
// this coin should be selected instead of previous one with fee
|
||||||
fakeAci("EUR:1.0", "EUR:0.0"),
|
fakeAci("EUR:1.0", "EUR:0.0"),
|
||||||
]
|
];
|
||||||
acis.forEach((x, i) => x.coinPub = String(i));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoins({
|
||||||
candidates: {
|
candidates: {
|
||||||
@ -221,7 +221,7 @@ test("it should use the coins that spent less relative fee", (t) => {
|
|||||||
fakeAci("EUR:0.05", "EUR:0.05"),
|
fakeAci("EUR:0.05", "EUR:0.05"),
|
||||||
fakeAci("EUR:0.05", "EUR:0.05"),
|
fakeAci("EUR:0.05", "EUR:0.05"),
|
||||||
];
|
];
|
||||||
acis.forEach((x, i) => x.coinPub = String(i));
|
acis.forEach((x, i) => (x.coinPub = String(i)));
|
||||||
|
|
||||||
const res = selectPayCoins({
|
const res = selectPayCoins({
|
||||||
candidates: {
|
candidates: {
|
||||||
|
Loading…
Reference in New Issue
Block a user