Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .yarn/cache/@scure-base-npm-2.0.0-97a775035d-7d999c7beb.zip
Git LFS file not shown
3 changes: 0 additions & 3 deletions .yarn/cache/bech32-npm-1.1.4-87b69922f7-5f62ca47b8.zip

This file was deleted.

4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ and this project adheres to

([#1819])

- Replace bech32 implementation by @scure/base. This changes a bunch of error
messages but is otherwise not breaking user code. ([#1825])

[#1819]: https://github.com/cosmos/cosmjs/pull/1819
[#1825]: https://github.com/cosmos/cosmjs/pull/1825

## [0.36.1] - 2025-10-02

Expand Down
2 changes: 1 addition & 1 deletion packages/encoding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.cjs"
},
"dependencies": {
"@scure/base": "^2.0.0",
"base64-js": "^1.3.0",
"bech32": "^1.1.4",
"readonly-date": "^1.0.0"
},
"devDependencies": {
Expand Down
49 changes: 37 additions & 12 deletions packages/encoding/src/bech32.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("bech32", () => {
});

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

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

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

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

it("throws if result exceeds the provided limit parameter", () => {
const limit = 10;
expect(() => toBech32("eth", ethAddressRaw, limit)).toThrowError(/exceeds length limit/i);
expect(() => toBech32("eth", ethAddressRaw, limit)).toThrowError(/length 42 exceeds limit 10/i);
});
});

Expand Down Expand Up @@ -77,16 +77,33 @@ describe("bech32", () => {
"cosmospub1ytql0csgqvfzd666axrjzqmn5q2ucztcyxw8hvlzen94ay05tegaerkug5pn3xn8wqdymt598ufzd666axrjzqsxllmwacap3f6xyc4x30jl8ecrcs2tze3zzgxkmthcsqxnqxhwwgfzd666axrjzqs2rlu3wz5gnslgpprszjr8r65n0d6y39q657th77eyvengtk3z0y6h2pnk",
90,
),
).toThrowError(/exceeds length limit/i);
).toThrowError(/invalid string length/i);
});

it("throws for missing separator", () => {
expect(() => fromBech32("nooneinhere")).toThrowError(/No bech32 separator found/i);
});

it("throws for invalid checksum", () => {
const corrupted = "eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0dxxxxxx";
expect(() => fromBech32(corrupted)).toThrowError(/invalid checksum/i);
});

it("throws for mixed case addresses", () => {
// "Decoders MUST NOT accept strings where some characters are uppercase and some are lowercase (such strings are referred to as mixed case strings)."
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
expect(() => fromBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => fromBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => fromBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => fromBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => fromBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
expect(() => fromBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
expect(() => fromBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
expect(() => fromBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
});
});

Expand All @@ -103,10 +120,18 @@ describe("bech32", () => {
it("throws for mixed case addresses", () => {
// "Decoders MUST NOT accept strings where some characters are uppercase and some are lowercase (such strings are referred to as mixed case strings)."
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
expect(() => normalizeBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => normalizeBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => normalizeBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => normalizeBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(/Mixed-case/i);
expect(() => normalizeBech32("Eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
expect(() => normalizeBech32("eTh1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
expect(() => normalizeBech32("ETH1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
expect(() => normalizeBech32("eth1n48g2mjh9Ezz7zjtya37wtgg5r5emr0drkwlgw")).toThrowError(
/must be lowercase or uppercase/i,
);
});
});
});
10 changes: 9 additions & 1 deletion packages/encoding/src/bech32.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import * as bech32 from "bech32";
import { bech32 } from "@scure/base";

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

function hasBech32Separator(input: string): input is `${string}1${string}` {
return input.indexOf("1") !== -1;
}

export function fromBech32(
address: string,
limit = Infinity,
): { readonly prefix: string; readonly data: Uint8Array } {
// This extra check can be removed once
// https://github.com/paulmillr/scure-base/pull/45 is merged and published.
if (!hasBech32Separator(address)) throw new Error(`No bech32 separator found`);

const decodedAddress = bech32.decode(address, limit);
return {
prefix: decodedAddress.prefix,
Expand Down
16 changes: 8 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -357,14 +357,14 @@ __metadata:
resolution: "@cosmjs/encoding@workspace:packages/encoding"
dependencies:
"@istanbuljs/nyc-config-typescript": "npm:^1.0.1"
"@scure/base": "npm:^2.0.0"
"@types/base64-js": "npm:^1.2.5"
"@types/jasmine": "npm:^4"
"@types/karma-firefox-launcher": "npm:^2"
"@types/karma-jasmine": "npm:^4"
"@types/karma-jasmine-html-reporter": "npm:^1"
"@types/node": "npm:*"
base64-js: "npm:^1.3.0"
bech32: "npm:^1.1.4"
glob: "npm:^11"
jasmine: "npm:^4"
jasmine-spec-reporter: "npm:^6"
Expand Down Expand Up @@ -1329,6 +1329,13 @@ __metadata:
languageName: node
linkType: hard

"@scure/base@npm:^2.0.0":
version: 2.0.0
resolution: "@scure/base@npm:2.0.0"
checksum: 10c0/7d999c7bebf053bb49cb706fdc6c5366737cff0f7f7518f52d32d7f7ad7b898904f03673648a2af5c4f22396f5c05f1d8bddbf010d6595052d07ba8163d506ad
languageName: node
linkType: hard

"@shikijs/engine-oniguruma@npm:^3.6.0":
version: 3.6.0
resolution: "@shikijs/engine-oniguruma@npm:3.6.0"
Expand Down Expand Up @@ -2555,13 +2562,6 @@ __metadata:
languageName: node
linkType: hard

"bech32@npm:^1.1.4":
version: 1.1.4
resolution: "bech32@npm:1.1.4"
checksum: 10c0/5f62ca47b8df99ace9c0e0d8deb36a919d91bf40066700aaa9920a45f86bb10eb56d537d559416fd8703aa0fb60dddb642e58f049701e7291df678b2033e5ee5
languageName: node
linkType: hard

"binary-extensions@npm:^2.0.0":
version: 2.2.0
resolution: "binary-extensions@npm:2.2.0"
Expand Down
Loading