diff options
author | Florian Dold <florian@dold.me> | 2024-09-06 16:30:55 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-09-06 16:30:55 +0200 |
commit | 726df07be957dbc053ff633242ca3c4bd752e8fd (patch) | |
tree | 3b543a131f2c78438bb6d2735a1feeff90bbca90 /packages | |
parent | 5402b00b796011005f52e200c856832f225ffaf9 (diff) |
wallet-core: attempt to implement KYC auth, test still failing
Diffstat (limited to 'packages')
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, |