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:
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) }),
+ );
+});