Skip to content

Commit e9a0ede

Browse files
authored
Merge pull request #1825 from cosmos/implement-bech32-scrure-base
Migrate bech32 implementation to @scure/base
2 parents 6c84837 + 91ebf3e commit e9a0ede

File tree

8 files changed

+71
-40
lines changed

8 files changed

+71
-40
lines changed

.pnp.cjs

Lines changed: 9 additions & 15 deletions
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

.yarn/cache/bech32-npm-1.1.4-87b69922f7-5f62ca47b8.zip

Lines changed: 0 additions & 3 deletions
This file was deleted.

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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
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",
42-
"bech32": "^1.1.4",
4343
"readonly-date": "^1.0.0"
4444
},
4545
"devDependencies": {

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 & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,14 +357,14 @@ __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"
363364
"@types/karma-jasmine": "npm:^4"
364365
"@types/karma-jasmine-html-reporter": "npm:^1"
365366
"@types/node": "npm:*"
366367
base64-js: "npm:^1.3.0"
367-
bech32: "npm:^1.1.4"
368368
glob: "npm:^11"
369369
jasmine: "npm:^4"
370370
jasmine-spec-reporter: "npm:^6"
@@ -1329,6 +1329,13 @@ __metadata:
13291329
languageName: node
13301330
linkType: hard
13311331

1332+
"@scure/base@npm:^2.0.0":
1333+
version: 2.0.0
1334+
resolution: "@scure/base@npm:2.0.0"
1335+
checksum: 10c0/7d999c7bebf053bb49cb706fdc6c5366737cff0f7f7518f52d32d7f7ad7b898904f03673648a2af5c4f22396f5c05f1d8bddbf010d6595052d07ba8163d506ad
1336+
languageName: node
1337+
linkType: hard
1338+
13321339
"@shikijs/engine-oniguruma@npm:^3.6.0":
13331340
version: 3.6.0
13341341
resolution: "@shikijs/engine-oniguruma@npm:3.6.0"
@@ -2555,13 +2562,6 @@ __metadata:
25552562
languageName: node
25562563
linkType: hard
25572564

2558-
"bech32@npm:^1.1.4":
2559-
version: 1.1.4
2560-
resolution: "bech32@npm:1.1.4"
2561-
checksum: 10c0/5f62ca47b8df99ace9c0e0d8deb36a919d91bf40066700aaa9920a45f86bb10eb56d537d559416fd8703aa0fb60dddb642e58f049701e7291df678b2033e5ee5
2562-
languageName: node
2563-
linkType: hard
2564-
25652565
"binary-extensions@npm:^2.0.0":
25662566
version: 2.2.0
25672567
resolution: "binary-extensions@npm:2.2.0"

0 commit comments

Comments
 (0)