taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit be373f904cbbd83e309181b2ec1379926da40e01
parent cb66e904aa520cc71da99ba0aa2906a935151a9f
Author: Florian Dold <dold@taler.net>
Date:   Thu, 20 Aug 2026 19:06:43 +0200

wallet-core: select slate outputs from the payment choice

Diffstat:
Mpackages/taler-wallet-core/src/crypto/cryptoImplementation.test.ts | 46+++++++++++++++++++++++++++++++++++++++++++++-
Mpackages/taler-wallet-core/src/crypto/cryptoImplementation.ts | 55+++++++++++++++++++++++++++++++------------------------
2 files changed, 76 insertions(+), 25 deletions(-)

diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.test.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.test.ts @@ -22,12 +22,15 @@ import { createEddsaKeyPair, decodeCrock, durationRoundedToBuffer, + DenomKeyType, eddsaSign, encodeCrock, EddsaPublicKeyString, EddsaSignatureString, ExchangeSignKeyJson, GlobalFees, + MerchantContractOutputType, + MerchantContractTermsV1, TalerProtocolDuration, TalerProtocolTimestamp, TalerSignaturePurpose, @@ -35,7 +38,10 @@ import { } from "@gnu-taler/taler-util"; import assert from "node:assert"; import { test } from "node:test"; -import { nativeCryptoR } from "./cryptoImplementation.js"; +import { + collectSlateTokenEnvelopes, + nativeCryptoR, +} from "./cryptoImplementation.js"; const master = createEddsaKeyPair(); const masterPub = encodeCrock(master.eddsaPub); @@ -77,6 +83,44 @@ const validOver = { key: encodeCrock(signKey.eddsaPub), }; +test("slate creation reads outputs from the selected choice", () => { + const contractTerms = { + choices: [ + { + outputs: [ + { + type: MerchantContractOutputType.Token, + token_family_slug: "first", + }, + ], + }, + { + outputs: [ + { + type: MerchantContractOutputType.Token, + token_family_slug: "second", + }, + ], + }, + ], + token_families: { + first: { + keys: [{ cipher: DenomKeyType.Rsa, rsa_pub: "first-key" }], + }, + second: { + keys: [{ cipher: DenomKeyType.Rsa, rsa_pub: "second-key" }], + }, + }, + } as unknown as MerchantContractTermsV1; + + assert.deepStrictEqual(collectSlateTokenEnvelopes(contractTerms, 1), [ + { + cipher: DenomKeyType.Rsa, + rsa_blinded_planchet: "second-key", + }, + ]); +}); + test("a correctly signed exchange signing key is accepted", async () => { const res = await nativeCryptoR.isValidSignKey(nativeCryptoR, { masterPub, diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -83,6 +83,7 @@ import { Logger, MakeSyncSignatureRequest, MerchantContractOutputType, + MerchantContractTermsV1, PayWalletData, PlanchetCreationRequest, PlanchetUnblindInfo, @@ -828,6 +829,32 @@ export interface DenominationValidationRequest { masterSig: string; } +export function collectSlateTokenEnvelopes( + contractTerms: MerchantContractTermsV1, + choiceIndex: number, +): TokenEnvelope[] { + const choice = contractTerms.choices[choiceIndex]; + const tokenEvs: TokenEnvelope[] = []; + for (const output of choice.outputs) { + if (output.type !== MerchantContractOutputType.Token) { + continue; + } + const family = contractTerms.token_families[output.token_family_slug]; + tokenEvs.push( + ...family.keys.map((key) => { + if (key.cipher !== DenomKeyType.Rsa) { + throw Error(`unsupported cipher (${key.cipher})`); + } + return { + cipher: DenomKeyType.Rsa, + rsa_blinded_planchet: key.rsa_pub, + } as TokenEnvelope; + }), + ); + } + return tokenEvs; +} + export interface PaymentSignatureValidationRequest { sig: string; contractHash: string; @@ -1233,30 +1260,10 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { const tokenIssuePubHash = hashTokenIssuePub(req.tokenIssuePub); const evHash = hashTokenEv(tokenEv, encodeCrock(tokenIssuePubHash)); - const choice = req.contractTerms.choices[req.outputIndex]; - const tokenEvs: TokenEnvelope[] = []; - for (const output of choice.outputs) { - if (output.type !== MerchantContractOutputType.Token) { - continue; - } - - const slug = output.token_family_slug; - const family = req.contractTerms.token_families[slug]; - tokenEvs.push( - ...family.keys.map((key) => { - let tokenEv: TokenEnvelope; - if (key.cipher === DenomKeyType.Rsa) { - tokenEv = { - cipher: DenomKeyType.Rsa, - rsa_blinded_planchet: key.rsa_pub, - }; - } else { - throw Error(`unsupported cipher (${req.tokenIssuePub.cipher})`); - } - return tokenEv; - }), - ); - } + const tokenEvs = collectSlateTokenEnvelopes( + req.contractTerms, + req.choiceIndex, + ); // wallet data object with envelopes const tokenWalletData: PayWalletData = {