taler-typescript-core

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

commit 36233a934b60f007af34d7f80a3452c12a95a584
parent fc42bc395dee4b34283f7c6b2c12889f66580de0
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 02:15:25 +0200

util: test fixed-length Crockford decoding and AML h_payto validation

Diffstat:
Mpackages/taler-util/src/taler-crypto.test.ts | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+), 0 deletions(-)

diff --git a/packages/taler-util/src/taler-crypto.test.ts b/packages/taler-util/src/taler-crypto.test.ts @@ -55,6 +55,9 @@ import { hpkeOpenOneshot, AgeRestriction, } from "./taler-crypto.js"; +import * as talerCrypto from "./taler-crypto.js"; +import { signAmlDecision } from "./taler-signatures.js"; +import { TalerProtocolTimestamp } from "./time.js"; import { sha512 } from "./kdf.js"; import * as nacl from "./nacl-fast.js"; import { initNodePrng } from "./prng-node.js"; @@ -644,3 +647,58 @@ test("rfc9180 HPKE DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305 tes const m_cand = hpkeOpenOneshot(skRm, info, aad, ct_cand); assert.deepStrictEqual(m_cand, pt); }); + +test("decodeCrockFixed rejects wrong-length encodings", (t) => { + // GNUnet's string_to_data decodes into a buffer of fixed size and rejects any + // input that does not encode exactly that many bytes. Resolved dynamically so + // the file compiles before the helper exists. + const decodeCrockFixed = (talerCrypto as any).decodeCrockFixed as ( + s: string, + n: number, + ) => Uint8Array; + assert.strictEqual(typeof decodeCrockFixed, "function"); + + const bytes = new Uint8Array(32); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = (i * 7 + 1) & 0xff; + } + const enc = encodeCrock(bytes); + assert.strictEqual(enc.length, 52); + + // The canonical encoding round-trips. + assert.deepStrictEqual(decodeCrockFixed(enc, 32), bytes); + + // Truncated and over-long encodings are rejected instead of returning a + // buffer of the wrong length. + assert.throws(() => decodeCrockFixed(enc.slice(0, 51), 32)); + assert.throws(() => decodeCrockFixed(enc + "0", 32)); + + // The same string decoded against a different expected size is rejected. + assert.throws(() => decodeCrockFixed(enc, 31)); + assert.throws(() => decodeCrockFixed(enc, 33)); +}); + +test("signAmlDecision rejects a malformed h_payto", (t) => { + const priv = new Uint8Array(32); + const hPayto = encodeCrock(new Uint8Array(64)); + const decision: any = { + keep_investigating: false, + decision_time: TalerProtocolTimestamp.fromSeconds(1000), + attributes_expiration: TalerProtocolTimestamp.fromSeconds(2000), + h_payto: hPayto, + justification: "test", + properties: {}, + new_rules: {}, + new_measures: null, + attributes: null, + }; + + // A well-formed 64-byte h_payto signs. + assert.ok(signAmlDecision(priv, decision) instanceof Uint8Array); + + // A truncated h_payto would silently produce a wrong-length signed blob, so + // it must be rejected outright. + assert.throws(() => + signAmlDecision(priv, { ...decision, h_payto: hPayto.slice(0, -1) }), + ); +});