Skip to content

Commit c69a552

Browse files
committed
Migrate bech32 to @scure/base
1 parent 6c84837 commit c69a552

File tree

7 files changed

+71
-14
lines changed

7 files changed

+71
-14
lines changed

.pnp.cjs

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:839fa0b7046eb80672306ed8e74f72ba5c18035789016ff04549eb8598b953a8
3+
size 102321

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ and this project adheres to
2525

2626
([#1819])
2727

28+
- Replace bech32 implementation by @scure/base. This changes a bunch of error
29+
messages but is otherwise not breaking user code. ([#1825])
30+
2831
[#1819]: https://github.com/cosmos/cosmjs/pull/1819
32+
[#1825]: https://github.com/cosmos/cosmjs/pull/1825
2933

3034
## [0.36.1] - 2025-10-02
3135

packages/encoding/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.cjs"
3939
},
4040
"dependencies": {
41+
"@scure/base": "^2.0.0",
4142
"base64-js": "^1.3.0",
4243
"bech32": "^1.1.4",
4344
"readonly-date": "^1.0.0"

packages/encoding/src/bech32.spec.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe("bech32", () => {
1616
});
1717

1818
it("works for very long prefixes", () => {
19-
expect(() => toBech32("p".repeat(70), new Uint8Array(20))).toThrowError(/exceeds length limit/i);
19+
expect(() => toBech32("p".repeat(70), new Uint8Array(20))).toThrowError(/length 109 exceeds limit 90/i);
2020
});
2121

2222
// See https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Bech32
@@ -26,7 +26,7 @@ describe("bech32", () => {
2626
});
2727

2828
it("throws if result exceeds 90 characters", () => {
29-
expect(() => toBech32("eth", new Uint8Array(51))).toThrowError(/exceeds length limit/i);
29+
expect(() => toBech32("eth", new Uint8Array(51))).toThrowError(/length 92 exceeds limit 90/i);
3030
});
3131

3232
it("works if a limit parameter is provided", () => {
@@ -40,7 +40,7 @@ describe("bech32", () => {
4040

4141
it("throws if result exceeds the provided limit parameter", () => {
4242
const limit = 10;
43-
expect(() => toBech32("eth", ethAddressRaw, limit)).toThrowError(/exceeds length limit/i);
43+
expect(() => toBech32("eth", ethAddressRaw, limit)).toThrowError(/length 42 exceeds limit 10/i);
4444
});
4545
});
4646

@@ -77,16 +77,33 @@ describe("bech32", () => {
7777
"cosmospub1ytql0csgqvfzd666axrjzqmn5q2ucztcyxw8hvlzen94ay05tegaerkug5pn3xn8wqdymt598ufzd666axrjzqsxllmwacap3f6xyc4x30jl8ecrcs2tze3zzgxkmthcsqxnqxhwwgfzd666axrjzqs2rlu3wz5gnslgpprszjr8r65n0d6y39q657th77eyvengtk3z0y6h2pnk",
7878
90,
7979
),
80-
).toThrowError(/exceeds length limit/i);
80+
).toThrowError(/invalid string length/i);
81+
});
82+
83+
it("throws for missing separator", () => {
84+
expect(() => fromBech32("nooneinhere")).toThrowError(/No bech32 separator found/i);
85+
});
86+
87+
it("throws for invalid checksum", () => {
88+
const corrupted = "eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0dxxxxxx";
89+
expect(() => fromBech32(corrupted)).toThrowError(/invalid checksum/i);
8190
});
8291

8392
it("throws for mixed case addresses", () => {
8493
// "Decoders MUST NOT accept strings where some characters are uppercase and some are lowercase (such strings are referred to as mixed case strings)."
8594
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
86-
expect(() => fromBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
87-
expect(() => fromBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
88-
expect(() => fromBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
89-
expect(() => fromBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
95+
expect(() => fromBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
96+
/must be lowercase or uppercase/i,
97+
);
98+
expect(() => fromBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
99+
/must be lowercase or uppercase/i,
100+
);
101+
expect(() => fromBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
102+
/must be lowercase or uppercase/i,
103+
);
104+
expect(() => fromBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
105+
/must be lowercase or uppercase/i,
106+
);
90107
});
91108
});
92109

@@ -103,10 +120,18 @@ describe("bech32", () => {
103120
it("throws for mixed case addresses", () => {
104121
// "Decoders MUST NOT accept strings where some characters are uppercase and some are lowercase (such strings are referred to as mixed case strings)."
105122
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
106-
expect(() => normalizeBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
107-
expect(() => normalizeBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
108-
expect(() => normalizeBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
109-
expect(() => normalizeBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
123+
expect(() => normalizeBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
124+
/must be lowercase or uppercase/i,
125+
);
126+
expect(() => normalizeBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
127+
/must be lowercase or uppercase/i,
128+
);
129+
expect(() => normalizeBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
130+
/must be lowercase or uppercase/i,
131+
);
132+
expect(() => normalizeBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
133+
/must be lowercase or uppercase/i,
134+
);
110135
});
111136
});
112137
});

packages/encoding/src/bech32.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
import * as bech32 from "bech32";
1+
import { bech32 } from "@scure/base";
22

33
export function toBech32(prefix: string, data: Uint8Array, limit?: number): string {
44
const address = bech32.encode(prefix, bech32.toWords(data), limit);
55
return address;
66
}
77

8+
function hasBech32Separator(input: string): input is `${string}1${string}` {
9+
return input.indexOf("1") !== -1;
10+
}
11+
812
export function fromBech32(
913
address: string,
1014
limit = Infinity,
1115
): { readonly prefix: string; readonly data: Uint8Array } {
16+
// This extra check can be removed once
17+
// https://github.com/paulmillr/scure-base/pull/45 is merged and published.
18+
if (!hasBech32Separator(address)) throw new Error(`No bech32 separator found`);
19+
1220
const decodedAddress = bech32.decode(address, limit);
1321
return {
1422
prefix: decodedAddress.prefix,

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ __metadata:
357357
resolution: "@cosmjs/encoding@workspace:packages/encoding"
358358
dependencies:
359359
"@istanbuljs/nyc-config-typescript": "npm:^1.0.1"
360+
"@scure/base": "npm:^2.0.0"
360361
"@types/base64-js": "npm:^1.2.5"
361362
"@types/jasmine": "npm:^4"
362363
"@types/karma-firefox-launcher": "npm:^2"
@@ -1329,6 +1330,13 @@ __metadata:
13291330
languageName: node
13301331
linkType: hard
13311332

1333+
"@scure/base@npm:^2.0.0":
1334+
version: 2.0.0
1335+
resolution: "@scure/base@npm:2.0.0"
1336+
checksum: 10c0/7d999c7bebf053bb49cb706fdc6c5366737cff0f7f7518f52d32d7f7ad7b898904f03673648a2af5c4f22396f5c05f1d8bddbf010d6595052d07ba8163d506ad
1337+
languageName: node
1338+
linkType: hard
1339+
13321340
"@shikijs/engine-oniguruma@npm:^3.6.0":
13331341
version: 3.6.0
13341342
resolution: "@shikijs/engine-oniguruma@npm:3.6.0"

0 commit comments

Comments
 (0)