aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-09-06 16:30:55 +0200
committerFlorian Dold <florian@dold.me>2024-09-06 16:30:55 +0200
commit726df07be957dbc053ff633242ca3c4bd752e8fd (patch)
tree3b543a131f2c78438bb6d2735a1feeff90bbca90
parent5402b00b796011005f52e200c856832f225ffaf9 (diff)
downloadwallet-core-726df07be957dbc053ff633242ca3c4bd752e8fd.tar.xz
wallet-core: attempt to implement KYC auth, test still failing
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts4
-rw-r--r--packages/taler-util/src/types-taler-exchange.ts9
-rw-r--r--packages/taler-util/src/types-taler-wallet-transactions.ts1
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts12
-rw-r--r--packages/taler-wallet-core/src/db.ts3
-rw-r--r--packages/taler-wallet-core/src/deposits.ts328
-rw-r--r--packages/taler-wallet-core/src/pay-merchant.ts4
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts8
16 files changed, 299 insertions, 102 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts
index 12e62607b..320db6912 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts
@@ -29,7 +29,7 @@ import {
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -57,7 +57,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts
index 4dc072882..741025b83 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts
@@ -24,7 +24,7 @@ import {
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -52,7 +52,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts
index 2ee58c7d8..08e5f1091 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts
@@ -24,7 +24,7 @@ import {
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -52,7 +52,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts b/packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts
index 5ff260a1d..da1ba6d8d 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts
@@ -26,7 +26,7 @@ import {
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -50,7 +50,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts
index 5d4211c9f..357332deb 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts
@@ -32,7 +32,7 @@ import {
import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -57,7 +57,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts b/packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts
index 7af36502a..97e6a9121 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts
@@ -33,7 +33,7 @@ import {
import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -62,7 +62,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts b/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts
index 2205ee687..0496a51cb 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts
@@ -30,7 +30,7 @@ import {
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -59,7 +59,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts b/packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts
index ed880251d..465279c28 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts
@@ -29,7 +29,7 @@ import {
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -58,7 +58,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts
index 27073ab2a..7861fae97 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts
@@ -28,7 +28,7 @@ import {
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
- EddsaKeypairStrings,
+ EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
@@ -52,7 +52,7 @@ interface KycTestEnv {
exchangeBankAccount: HarnessExchangeBankAccount;
walletClient: WalletClient;
walletService: WalletService;
- amlKeypair: EddsaKeypairStrings;
+ amlKeypair: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts
index bc8165991..2bff19063 100644
--- a/packages/taler-util/src/types-taler-exchange.ts
+++ b/packages/taler-util/src/types-taler-exchange.ts
@@ -1617,6 +1617,14 @@ export interface LegitimizationNeededResponse {
// should use the number to check for the account's AML/KYC status
// using the /kyc-check/$REQUIREMENT_ROW endpoint.
requirement_row: Integer | undefined;
+
+ // True if the operation was denied because the
+ // KYC auth key does not match the merchant public
+ // key. In this case, a KYC auth wire transfer
+ // with the merchant public key must be performed
+ // first.
+ // Since exchange protocol **v21**.
+ bad_kyc_auth?: boolean;
}
export interface AccountKycStatus {
@@ -2423,6 +2431,7 @@ export const codecForLegitimizationNeededResponse =
.property("h_payto", codecForString())
.property("account_pub", codecOptional(codecForString()))
.property("requirement_row", codecOptional(codecForNumber()))
+ .property("bad_kyc_auth", codecOptional(codecForBoolean()))
.build("TalerExchangeApi.LegitimizationNeededResponse");
export const codecForAccountKycStatus = (): Codec<AccountKycStatus> =>
diff --git a/packages/taler-util/src/types-taler-wallet-transactions.ts b/packages/taler-util/src/types-taler-wallet-transactions.ts
index 675878e0c..8d01e7fdf 100644
--- a/packages/taler-util/src/types-taler-wallet-transactions.ts
+++ b/packages/taler-util/src/types-taler-wallet-transactions.ts
@@ -130,6 +130,7 @@ export enum TransactionMinorState {
MergeKycRequired = "merge-kyc",
BalanceKycRequired = "balance-kyc",
BalanceKycInit = "balance-kyc-init",
+ KycAuthRequired = "kyc-auth",
Track = "track",
SubmitPayment = "submit-payment",
RebindSession = "rebind-session",
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index ce6193b83..6280795e8 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -170,7 +170,7 @@ export interface TalerCryptoInterface {
req: ContractTermsValidationRequest,
): Promise<ValidationResult>;
- createEddsaKeypair(req: {}): Promise<EddsaKeypairStrings>;
+ createEddsaKeypair(req: {}): Promise<EddsaKeyPairStrings>;
eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
@@ -333,12 +333,12 @@ export const nullCrypto: TalerCryptoInterface = {
): Promise<ValidationResult> {
throw new Error("Function not implemented.");
},
- createEddsaKeypair: function (req: unknown): Promise<EddsaKeypairStrings> {
+ createEddsaKeypair: function (req: unknown): Promise<EddsaKeyPairStrings> {
throw new Error("Function not implemented.");
},
eddsaGetPublic: function (
req: EddsaGetPublicRequest,
- ): Promise<EddsaKeypairStrings> {
+ ): Promise<EddsaKeyPairStrings> {
throw new Error("Function not implemented.");
},
unblindDenominationSignature: function (
@@ -602,7 +602,7 @@ export interface WireAccountValidationRequest {
creditRestrictions?: any[];
}
-export interface EddsaKeypairStrings {
+export interface EddsaKeyPairStrings {
priv: string;
pub: string;
}
@@ -1085,7 +1085,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
*/
async createEddsaKeypair(
tci: TalerCryptoInterfaceR,
- ): Promise<EddsaKeypairStrings> {
+ ): Promise<EddsaKeyPairStrings> {
const eddsaPriv = encodeCrock(getRandomBytes(32));
const eddsaPubRes = await tci.eddsaGetPublic(tci, {
priv: eddsaPriv,
@@ -1099,7 +1099,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
async eddsaGetPublic(
tci: TalerCryptoInterfaceR,
req: EddsaGetPublicRequest,
- ): Promise<EddsaKeypairStrings> {
+ ): Promise<EddsaKeyPairStrings> {
return {
priv: req.priv,
pub: encodeCrock(eddsaGetPublic(decodeCrock(req.priv))),
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index ba33c5555..a42548bb3 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1736,6 +1736,9 @@ export enum DepositOperationStatus {
PendingDepositKyc = 0x0100_0003,
SuspendedDepositKyc = 0x0110_0003,
+ PendingDepositKycAuth = 0x0100_0005,
+ SuspendedDepositKycAuth = 0x0110_0005,
+
Aborting = 0x0103_0000,
SuspendedAborting = 0x0113_0000,
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
index 371d7cf70..3bac319cd 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -111,7 +111,7 @@ import {
getExchangeWireFee,
getScopeForAllExchanges,
} from "./exchanges.js";
-import { EddsaKeypairStrings } from "./index.js";
+import { EddsaKeyPairStrings } from "./index.js";
import {
extractContractData,
generateDepositPermissions,
@@ -301,6 +301,7 @@ export class DepositTransactionContext implements TransactionContext {
case DepositOperationStatus.SuspendedDeposit:
case DepositOperationStatus.SuspendedDepositKyc:
case DepositOperationStatus.SuspendedTrack:
+ case DepositOperationStatus.SuspendedDepositKycAuth:
break;
case DepositOperationStatus.PendingDepositKyc:
newOpStatus = DepositOperationStatus.SuspendedDepositKyc;
@@ -317,6 +318,9 @@ export class DepositTransactionContext implements TransactionContext {
case DepositOperationStatus.Aborting:
newOpStatus = DepositOperationStatus.SuspendedAborting;
break;
+ case DepositOperationStatus.PendingDepositKycAuth:
+ newOpStatus = DepositOperationStatus.SuspendedDepositKycAuth;
+ break;
default:
assertUnreachable(dg.operationStatus);
}
@@ -398,6 +402,7 @@ export class DepositTransactionContext implements TransactionContext {
case DepositOperationStatus.PendingDeposit:
case DepositOperationStatus.PendingDepositKyc:
case DepositOperationStatus.PendingTrack:
+ case DepositOperationStatus.PendingDepositKycAuth:
break;
case DepositOperationStatus.SuspendedDepositKyc:
newOpStatus = DepositOperationStatus.PendingDepositKyc;
@@ -414,6 +419,9 @@ export class DepositTransactionContext implements TransactionContext {
case DepositOperationStatus.SuspendedTrack:
newOpStatus = DepositOperationStatus.PendingTrack;
break;
+ case DepositOperationStatus.SuspendedDepositKycAuth:
+ newOpStatus = DepositOperationStatus.PendingDepositKycAuth;
+ break;
default:
assertUnreachable(dg.operationStatus);
}
@@ -539,6 +547,18 @@ export function computeDepositTransactionStatus(
// We lie to the UI by hiding the specific KYC state.
minor: TransactionMinorState.KycRequired,
};
+ case DepositOperationStatus.PendingDepositKycAuth:
+ return {
+ major: TransactionMajorState.Pending,
+ // We lie to the UI by hiding the specific KYC state.
+ minor: TransactionMinorState.KycAuthRequired,
+ };
+ case DepositOperationStatus.SuspendedDepositKycAuth:
+ return {
+ major: TransactionMajorState.Suspended,
+ // We lie to the UI by hiding the specific KYC state.
+ minor: TransactionMinorState.KycAuthRequired,
+ };
default:
assertUnreachable(dg.operationStatus);
}
@@ -594,6 +614,10 @@ export function computeDepositTransactionActions(
return [TransactionAction.Resume, TransactionAction.Abort];
case DepositOperationStatus.SuspendedDepositKyc:
return [TransactionAction.Suspend, TransactionAction.Abort];
+ case DepositOperationStatus.PendingDepositKycAuth:
+ return [TransactionAction.Suspend, TransactionAction.Abort];
+ case DepositOperationStatus.SuspendedDepositKycAuth:
+ return [TransactionAction.Resume, TransactionAction.Abort];
default:
assertUnreachable(dg.operationStatus);
}
@@ -831,10 +855,7 @@ async function processDepositGroupPendingKyc(
depositGroup: DepositGroupRecord,
): Promise<TaskRunResult> {
const { depositGroupId } = depositGroup;
- const transactionId = constructTransactionIdentifier({
- tag: TransactionType.Deposit,
- depositGroupId,
- });
+ const ctx = new DepositTransactionContext(wex, depositGroupId);
const kycInfo = depositGroup.kycInfo;
@@ -842,17 +863,9 @@ async function processDepositGroupPendingKyc(
throw Error("invalid DB state, in pending(kyc), but no kycInfo present");
}
- const lastReserveKeypair = await getLastWithdrawalKeyPair(
- wex,
- kycInfo.exchangeBaseUrl,
- );
- if (!lastReserveKeypair) {
- // Need to do a KYC transfer
- throw Error("not supported yet");
- }
const sigResp = await wex.cryptoApi.signWalletKycAuth({
- accountPriv: lastReserveKeypair.priv,
- accountPub: lastReserveKeypair.pub,
+ accountPriv: depositGroup.merchantPriv,
+ accountPub: depositGroup.merchantPub,
});
const url = new URL(
@@ -876,8 +889,6 @@ async function processDepositGroupPendingKyc(
},
);
- const ctx = new DepositTransactionContext(wex, depositGroupId);
-
if (
kycStatusRes.status === HttpStatusCode.Ok ||
kycStatusRes.status === HttpStatusCode.NoContent
@@ -906,15 +917,113 @@ async function processDepositGroupPendingKyc(
return { oldTxState, newTxState };
},
);
- notifyTransition(wex, transactionId, transitionInfo);
+ notifyTransition(wex, ctx.transactionId, transitionInfo);
} else if (kycStatusRes.status === HttpStatusCode.Accepted) {
logger.info("kyc still pending");
} else {
- throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`);
+ throwUnexpectedRequestError(
+ kycStatusRes,
+ await readTalerErrorResponse(kycStatusRes),
+ );
}
return TaskRunResult.backoff();
}
+async function processDepositGroupPendingKycAuth(
+ wex: WalletExecutionContext,
+ depositGroup: DepositGroupRecord,
+): Promise<TaskRunResult> {
+ const { depositGroupId } = depositGroup;
+ const ctx = new DepositTransactionContext(wex, depositGroupId);
+
+ const kycInfo = depositGroup.kycInfo;
+
+ if (!kycInfo) {
+ throw Error(
+ "invalid DB state, in pending(kyc-auth), but no kycInfo present",
+ );
+ }
+
+ const sigResp = await wex.cryptoApi.signWalletKycAuth({
+ accountPriv: depositGroup.merchantPriv,
+ accountPub: depositGroup.merchantPub,
+ });
+
+ const url = new URL(
+ `kyc-check/${kycInfo.paytoHash}`,
+ kycInfo.exchangeBaseUrl,
+ );
+
+ url.searchParams.set("await_auth", "YES");
+
+ const kycStatusRes = await wex.ws.runLongpollQueueing(
+ wex,
+ url.hostname,
+ async (timeoutMs) => {
+ url.searchParams.set("timeout_ms", `${timeoutMs}`);
+ logger.info(`kyc url ${url.href}`);
+ return await wex.http.fetch(url.href, {
+ method: "GET",
+ cancellationToken: wex.cancellationToken,
+ headers: {
+ ["Account-Owner-Signature"]: sigResp.sig,
+ },
+ });
+ },
+ );
+
+ logger.info(
+ `kyc-check for auth longpoll result status: ${kycStatusRes.status}`,
+ );
+
+ switch (kycStatusRes.status) {
+ case HttpStatusCode.Ok:
+ return await transitionKycAuthSuccess(ctx);
+ case HttpStatusCode.NoContent:
+ return await transitionKycAuthSuccess(ctx);
+ case HttpStatusCode.Accepted:
+ logger.info("kyc still pending");
+ return TaskRunResult.longpollReturnedPending();
+ default:
+ throwUnexpectedRequestError(
+ kycStatusRes,
+ await readTalerErrorResponse(kycStatusRes),
+ );
+ }
+}
+
+async function transitionKycAuthSuccess(
+ ctx: DepositTransactionContext,
+): Promise<TaskRunResult> {
+ const transitionInfo = await ctx.wex.db.runReadWriteTx(
+ { storeNames: ["depositGroups", "transactionsMeta"] },
+ async (tx) => {
+ const newDg = await tx.depositGroups.get(ctx.depositGroupId);
+ if (!newDg) {
+ return;
+ }
+ const oldTxState = computeDepositTransactionStatus(newDg);
+ switch (newDg.operationStatus) {
+ case DepositOperationStatus.PendingDepositKycAuth:
+ newDg.operationStatus = DepositOperationStatus.PendingDeposit;
+ break;
+ default:
+ return;
+ }
+ await tx.depositGroups.put(newDg);
+ await ctx.updateTransactionMeta(tx);
+ const newTxState = computeDepositTransactionStatus(newDg);
+ return { oldTxState, newTxState };
+ },
+ );
+ notifyTransition(ctx.wex, ctx.transactionId, transitionInfo);
+ if (transitionInfo) {
+ return TaskRunResult.progress();
+ } else {
+ return TaskRunResult.backoff();
+ }
+}
+
/**
* Finds the reserve key pair of the most recent withdrawal
* with the given exchange.
@@ -923,9 +1032,9 @@ async function processDepositGroupPendingKyc(
async function getLastWithdrawalKeyPair(
wex: WalletExecutionContext,
exchangeBaseUrl: string,
-): Promise<EddsaKeypairStrings | undefined> {
+): Promise<EddsaKeyPairStrings | undefined> {
let candidateTimestamp: AbsoluteTime | undefined = undefined;
- let candidateRes: EddsaKeypairStrings | undefined = undefined;
+ let candidateRes: EddsaKeyPairStrings | undefined = undefined;
await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
const withdrawalRecs =
await tx.withdrawalGroups.indexes.byExchangeBaseUrl.getAll(
@@ -966,14 +1075,9 @@ async function transitionToKycRequired(
const ctx = new DepositTransactionContext(wex, depositGroupId);
- const lastReserveKeypair = await getLastWithdrawalKeyPair(wex, exchangeUrl);
- if (!lastReserveKeypair) {
- // Need to do a KYC transfer
- throw Error("not supported yet");
- }
const sigResp = await wex.cryptoApi.signWalletKycAuth({
- accountPriv: lastReserveKeypair.priv,
- accountPub: lastReserveKeypair.pub,
+ accountPriv: depositGroup.merchantPriv,
+ accountPub: depositGroup.merchantPub,
});
const url = new URL(`kyc-check/${kycPaytoHash}`, exchangeUrl);
@@ -1032,6 +1136,48 @@ async function transitionToKycRequired(
}
}
+async function transitionToKycAuthRequired(
+ wex: WalletExecutionContext,
+ depositGroup: DepositGroupRecord,
+ kycPaytoHash: string,
+ exchangeUrl: string,
+): Promise<TaskRunResult> {
+ const { depositGroupId } = depositGroup;
+
+ const ctx = new DepositTransactionContext(wex, depositGroupId);
+
+ const transitionInfo = await wex.db.runReadWriteTx(
+ { storeNames: ["depositGroups", "transactionsMeta"] },
+ async (tx) => {
+ const dg = await tx.depositGroups.get(depositGroupId);
+ if (!dg) {
+ return undefined;
+ }
+ const oldTxState = computeDepositTransactionStatus(dg);
+ switch (dg.operationStatus) {
+ case DepositOperationStatus.PendingTrack:
+ throw Error("not yet supported");
+ break;
+ case DepositOperationStatus.PendingDeposit:
+ dg.operationStatus = DepositOperationStatus.PendingDepositKycAuth;
+ break;
+ default:
+ return;
+ }
+ dg.kycInfo = {
+ exchangeBaseUrl: exchangeUrl,
+ paytoHash: kycPaytoHash,
+ };
+ await tx.depositGroups.put(dg);
+ await ctx.updateTransactionMeta(tx);
+ const newTxState = computeDepositTransactionStatus(dg);
+ return { oldTxState, newTxState };
+ },
+ );
+ notifyTransition(wex, ctx.transactionId, transitionInfo);
+ return TaskRunResult.progress();
+}
+
async function processDepositGroupPendingTrack(
wex: WalletExecutionContext,
depositGroup: DepositGroupRecord,
@@ -1399,12 +1545,21 @@ async function processDepositGroupPendingDeposit(
logger.info(
`kyc legitimization needed response: ${j2s(kycLegiNeededResp)}`,
);
- return transitionToKycRequired(
- wex,
- depositGroup,
- kycLegiNeededResp.h_payto,
- exchangeBaseUrl,
- );
+ if (kycLegiNeededResp.bad_kyc_auth) {
+ return transitionToKycAuthRequired(
+ wex,
+ depositGroup,
+ kycLegiNeededResp.h_payto,
+ exchangeBaseUrl,
+ );
+ } else {
+ return transitionToKycRequired(
+ wex,
+ depositGroup,
+ kycLegiNeededResp.h_payto,
+ exchangeBaseUrl,
+ );
+ }
}
}
@@ -1488,6 +1643,8 @@ export async function processDepositGroup(
return processDepositGroupPendingDeposit(wex, depositGroup);
case DepositOperationStatus.Aborting:
return processDepositGroupAborting(wex, depositGroup);
+ case DepositOperationStatus.PendingDepositKycAuth:
+ return processDepositGroupPendingKycAuth(wex, depositGroup);
}
return TaskRunResult.finished();
@@ -1544,8 +1701,9 @@ async function trackDeposit(
return { type: "wired", ...wired };
}
default: {
- throw Error(
- `unexpected response from track-transaction (${httpResp.status})`,
+ throwUnexpectedRequestError(
+ httpResp,
+ await readTalerErrorResponse(httpResp),
);
}
}
@@ -1700,8 +1858,8 @@ export async function createDepositGroup(
wex: WalletExecutionContext,
req: CreateDepositGroupRequest,
): Promise<CreateDepositGroupResponse> {
- const p = parsePaytoUri(req.depositPaytoUri);
- if (!p) {
+ const depositPayto = parsePaytoUri(req.depositPaytoUri);
+ if (!depositPayto) {
throw Error("invalid payto URI");
}
@@ -1732,15 +1890,68 @@ export async function createDepositGroup(
AbsoluteTime.addDuration(now, Duration.fromSpec({ minutes: 5 })),
);
const nowRounded = AbsoluteTime.toProtocolTimestamp(now);
+
+ const payCoinSel = await selectPayCoins(wex, {
+ restrictExchanges: {
+ auditors: [],
+ exchanges: exchangeInfos.map((x) => ({
+ exchangeBaseUrl: x.url,
+ exchangePub: x.master_pub,
+ })),
+ },
+ restrictWireMethod: depositPayto.targetType,
+ contractTermsAmount: amount,
+ depositFeeLimit: amount,
+ prevPayCoins: [],
+ });
+
+ let coins: SelectedProspectiveCoin[] | undefined = undefined;
+
+ switch (payCoinSel.type) {
+ case "success":
+ coins = payCoinSel.coinSel.coins;
+ break;
+ case "failure":
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ {
+ insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
+ },
+ );
+ case "prospective":
+ coins = payCoinSel.result.prospectiveCoins;
+ break;
+ default:
+ assertUnreachable(payCoinSel);
+ }
+
+ // Heuristic for the merchant key pair: If there's an exchange where we made
+ // a withdrawal from, use that key pair, so the user doesn't have to do
+ // a KYC transfer to establish a kyc accout key pair.
+ // FIXME: Extend the heuristic to use the last used merchant key pair?
+ let merchantPair: EddsaKeyPairStrings | undefined = undefined;
+ if (coins.length > 0) {
+ const res = await getLastWithdrawalKeyPair(wex, coins[0].exchangeBaseUrl);
+ if (res) {
+ logger.info(
+ `reusing reserve pub ${res.pub} from last withdrawal to ${coins[0].exchangeBaseUrl}`,
+ );
+ merchantPair = res;
+ }
+ }
+ if (!merchantPair) {
+ logger.info(`creating new merchant key pair for deposit`);
+ merchantPair = await wex.cryptoApi.createEddsaKeypair({});
+ }
+
const noncePair = await wex.cryptoApi.createEddsaKeypair({});
- const merchantPair = await wex.cryptoApi.createEddsaKeypair({});
const wireSalt = encodeCrock(getRandomBytes(16));
const wireHash = hashWire(req.depositPaytoUri, wireSalt);
const contractTerms: MerchantContractTerms = {
exchanges: exchangeInfos,
amount: req.amount,
max_fee: Amounts.stringify(amount),
- wire_method: p.targetType,
+ wire_method: depositPayto.targetType,
timestamp: nowRounded,
merchant_base_url: "",
summary: "",
@@ -1768,37 +1979,6 @@ export async function createDepositGroup(
"",
);
- const payCoinSel = await selectPayCoins(wex, {
- restrictExchanges: {
- auditors: [],
- exchanges: contractData.allowedExchanges,
- },
- restrictWireMethod: contractData.wireMethod,
- contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
- depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
- prevPayCoins: [],
- });
-
- let coins: SelectedProspectiveCoin[] | undefined = undefined;
-
- switch (payCoinSel.type) {
- case "success":
- coins = payCoinSel.coinSel.coins;
- break;
- case "failure":
- throw TalerError.fromDetail(
- TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
- {
- insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
- },
- );
- case "prospective":
- coins = payCoinSel.result.prospectiveCoins;
- break;
- default:
- assertUnreachable(payCoinSel);
- }
-
const totalDepositCost = await getTotalPaymentCost(wex, currency, coins);
let depositGroupId: string;
@@ -1830,7 +2010,11 @@ export async function createDepositGroup(
}
const counterpartyEffectiveDepositAmount =
- await getCounterpartyEffectiveDepositAmount(wex, p.targetType, coins);
+ await getCounterpartyEffectiveDepositAmount(
+ wex,
+ depositPayto.targetType,
+ coins,
+ );
const depositGroup: DepositGroupRecord = {
contractTermsHash,
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
index 12cfedc9e..8744fc5d1 100644
--- a/packages/taler-wallet-core/src/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -123,7 +123,7 @@ import {
TransactionContext,
TransitionResultType,
} from "./common.js";
-import { EddsaKeypairStrings } from "./crypto/cryptoImplementation.js";
+import { EddsaKeyPairStrings } from "./crypto/cryptoImplementation.js";
import {
CoinRecord,
DbCoinSelection,
@@ -1196,7 +1196,7 @@ async function createOrReusePurchase(
return oldProposal.proposalId;
}
- let noncePair: EddsaKeypairStrings;
+ let noncePair: EddsaKeyPairStrings;
let shared = false;
if (noncePriv) {
shared = true;
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
index 1ed600265..6c9bcac63 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -129,7 +129,7 @@ import {
requireExchangeTosAcceptedOrThrow,
runWithClientCancellation,
} from "./common.js";
-import { EddsaKeypairStrings } from "./crypto/cryptoImplementation.js";
+import { EddsaKeyPairStrings } from "./crypto/cryptoImplementation.js";
import {
CoinRecord,
CoinSourceType,
@@ -3060,7 +3060,7 @@ export async function internalPrepareCreateWithdrawalGroup(
exchangeBaseUrl: string | undefined;
forcedWithdrawalGroupId?: string;
forcedDenomSel?: ForcedDenomSel;
- reserveKeyPair?: EddsaKeypairStrings;
+ reserveKeyPair?: EddsaKeyPairStrings;
restrictAge?: number;
wgInfo: WgInfo;
},
@@ -3256,7 +3256,7 @@ export async function internalCreateWithdrawalGroup(
amount?: AmountJson;
forcedWithdrawalGroupId?: string;
forcedDenomSel?: ForcedDenomSel;
- reserveKeyPair?: EddsaKeypairStrings;
+ reserveKeyPair?: EddsaKeyPairStrings;
restrictAge?: number;
wgInfo: WgInfo;
},
@@ -3846,7 +3846,7 @@ export async function createManualWithdrawal(
);
}
- let reserveKeyPair: EddsaKeypairStrings;
+ let reserveKeyPair: EddsaKeyPairStrings;
if (req.forceReservePriv) {
const pubResp = await wex.cryptoApi.eddsaGetPublic({
priv: req.forceReservePriv,