commit 7739f02be1cfe8bb15bcc4df8b39ac0776b00f00
parent 0dd975e825daf88440824d2452f85c95daf72c57
Author: Florian Dold <dold@taler.net>
Date: Tue, 21 Jul 2026 10:54:06 +0200
util: test segwit address decoding against BIP-173 and BIP-350
Diffstat:
1 file changed, 76 insertions(+), 0 deletions(-)
diff --git a/packages/taler-util/src/segwit_addr.test.ts b/packages/taler-util/src/segwit_addr.test.ts
@@ -0,0 +1,76 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ 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 assert from "node:assert";
+import { test } from "node:test";
+import { BitcoinBech32 } from "./bech32.js";
+import { Result } from "./result.js";
+import { BitcoinSewgit } from "./segwit_addr.js";
+
+// Version 0 witness programs use bech32, version 1 and up use bech32m.
+// Test vectors are from BIP-173 and BIP-350.
+const BECH32_V0 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
+const BECH32M_V1 =
+ "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0";
+const BECH32M_V16 = "BC1SW50QGDZ25J";
+// Same programs, but checksummed with the other constant.
+const V0_WITH_BECH32M = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh";
+const V16_WITH_BECH32 = "BC1SW50QA3JX3S";
+
+test("segwit address decoding accepts bech32 and bech32m", (t) => {
+ const v0 = Result.unpack(BitcoinSewgit.decode(BECH32_V0));
+ assert.strictEqual(v0.version, 0);
+ assert.strictEqual(v0.program.length, 20);
+
+ const v1 = Result.unpack(BitcoinSewgit.decode(BECH32M_V1));
+ assert.strictEqual(v1.version, 1);
+ assert.strictEqual(v1.program.length, 32);
+
+ const v16 = Result.unpack(BitcoinSewgit.decode(BECH32M_V16));
+ assert.strictEqual(v16.version, 16);
+});
+
+test("segwit address decoding rejects the wrong checksum constant", (t) => {
+ assert.strictEqual(BitcoinSewgit.decode(V0_WITH_BECH32M).tag, "error");
+ assert.strictEqual(BitcoinSewgit.decode(V16_WITH_BECH32).tag, "error");
+});
+
+test("segwit address decoding honours an explicitly given encoding", (t) => {
+ assert.strictEqual(
+ BitcoinSewgit.decode(BECH32_V0, BitcoinBech32.Encodings.BECH32).tag,
+ "ok",
+ );
+ assert.strictEqual(
+ BitcoinSewgit.decode(BECH32M_V1, BitcoinBech32.Encodings.BECH32M).tag,
+ "ok",
+ );
+ assert.strictEqual(
+ BitcoinSewgit.decode(BECH32_V0, BitcoinBech32.Encodings.BECH32M).tag,
+ "error",
+ );
+ assert.strictEqual(
+ BitcoinSewgit.decode(BECH32M_V1, BitcoinBech32.Encodings.BECH32).tag,
+ "error",
+ );
+});
+
+test("bech32 decoding verifies the checksum without an expected encoding", (t) => {
+ const corrupted =
+ BECH32_V0.slice(0, -1) + (BECH32_V0.endsWith("4") ? "5" : "4");
+ assert.strictEqual(BitcoinBech32.decode(corrupted).tag, "error");
+ assert.strictEqual(BitcoinBech32.decode(BECH32_V0).tag, "ok");
+ assert.strictEqual(BitcoinBech32.decode(BECH32M_V1).tag, "ok");
+});