taler-typescript-core

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

commit 33b1ea8d0e5159d4d9b2ffac016d72e2daa417fc
parent 8b89769ed6216a24590eaa0482abd0652cab68f2
Author: Florian Dold <dold@taler.net>
Date:   Thu, 20 Aug 2026 19:06:48 +0200

wallet-core: test private-data log redaction

Diffstat:
Mpackages/taler-wallet-core/src/donau.test.ts | 101++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mpackages/taler-wallet-core/src/pay-peer-push-debit.test.ts | 53++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 152 insertions(+), 2 deletions(-)

diff --git a/packages/taler-wallet-core/src/donau.test.ts b/packages/taler-wallet-core/src/donau.test.ts @@ -13,11 +13,17 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts } from "@gnu-taler/taler-util"; +import { + Amounts, + encodeCrock, + setGlobalLogLevelFromString, +} from "@gnu-taler/taler-util"; +import { HeadersImpl, HttpRequestLibrary } from "@gnu-taler/taler-util/http"; import assert from "node:assert"; import { test } from "node:test"; import { CandidateDonationUnit, + generateDonauPlanchets, selectDonationUnitsExact, } from "./donau.js"; @@ -41,3 +47,96 @@ test("donation units must represent the requested amount exactly", () => { [two, two], ); }); + +test("Donau planchet generation does not log taxpayer or blinding secrets", async () => { + const taxId = "DE-SECRET-TAXPAYER-ID"; + const taxIdHash = encodeCrock(new Uint8Array(64).fill(1)); + const taxIdSalt = encodeCrock(new Uint8Array(32).fill(2)); + const purchase = { + choiceIndex: 0, + donauAmount: "EUR:1", + donauBaseUrl: "https://donau.example/", + donauOutputIndex: 0, + donauTaxId: taxId, + donauTaxIdHash: taxIdHash, + donauTaxIdSalt: taxIdSalt, + donauYear: 2026, + proposalId: "proposal", + }; + const http: HttpRequestLibrary = { + async fetch(url, opt) { + const body = { + base_url: "https://donau.example/", + currency: "EUR", + donation_units: [ + { + donation_unit_pub: { + cipher: "RSA", + rsa_public_key: "040000W1", + }, + lost: false, + value: "EUR:1", + year: 2026, + }, + ], + signkeys: [], + version: "0:0:0", + }; + return { + requestMethod: opt?.method ?? "GET", + requestUrl: url, + status: 200, + headers: new HeadersImpl(), + async bytes() { + return new TextEncoder().encode(JSON.stringify(body)); + }, + async json() { + return body; + }, + async text() { + return JSON.stringify(body); + }, + }; + }, + }; + let blindingKey: string | undefined; + const wex = { + http, + cryptoApi: { + async rsaBlind(req: { bks: string }) { + blindingKey = req.bks; + return { blinded: "blinded-identifier" }; + }, + }, + async runWalletDbTx(fn: (tx: any) => Promise<unknown>) { + return await fn({ + async countDonationPlanchetsByProposal() { + return 0; + }, + async getPurchase() { + return purchase; + }, + async upsertDonationPlanchet() {}, + }); + }, + }; + + const originalWrite = process.stderr.write; + let output = ""; + process.stderr.write = ((chunk: Uint8Array | string) => { + output += chunk.toString(); + return true; + }) as typeof process.stderr.write; + setGlobalLogLevelFromString("trace"); + try { + await generateDonauPlanchets(wex as any, purchase.proposalId); + } finally { + process.stderr.write = originalWrite; + setGlobalLogLevelFromString("info"); + } + + assert.ok(blindingKey); + for (const secret of [taxId, taxIdHash, taxIdSalt, blindingKey]) { + assert.ok(!output.includes(secret), secret); + } +}); diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.test.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.test.ts @@ -1,7 +1,13 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { ScopeType, TalerProtocolTimestamp } from "@gnu-taler/taler-util"; import { + ScopeType, + TalerProtocolTimestamp, + setGlobalLogLevelFromString, +} from "@gnu-taler/taler-util"; +import { PeerPushDebitStatus } from "./db-common.js"; +import { + PeerPushDebitTransactionContext, decodePeerPushDebitQuote, encodePeerPushDebitQuote, } from "./pay-peer-push-debit.js"; @@ -50,3 +56,48 @@ test("peer push debit quote rejects malformed and unknown versions", () => { ), ); }); + +test("peer push debit metadata logging excludes private capabilities", async () => { + const privateValues = [ + "contract-private-capability", + "merge-private-capability", + "purse-private-capability", + ]; + const rec = { + amount: "CHF:1", + contractPriv: privateValues[0], + exchangeBaseUrl: "https://exchange.example/", + mergePriv: privateValues[1], + pursePriv: privateValues[2], + status: PeerPushDebitStatus.PendingReady, + timestampCreated: 1, + }; + const tx = { + async getPeerPushDebit() { + return rec; + }, + async upsertTransactionMeta() {}, + }; + const ctx = new PeerPushDebitTransactionContext( + undefined as any, + "purse-public-key", + ); + const originalWrite = process.stderr.write; + let output = ""; + process.stderr.write = ((chunk: Uint8Array | string) => { + output += chunk.toString(); + return true; + }) as typeof process.stderr.write; + setGlobalLogLevelFromString("info"); + try { + await ctx.updateTransactionMeta(tx as any); + } finally { + process.stderr.write = originalWrite; + setGlobalLogLevelFromString("info"); + } + + assert.match(output, /purse-public-key/); + for (const privateValue of privateValues) { + assert.ok(!output.includes(privateValue), privateValue); + } +});