diff --git a/.pnp.cjs b/.pnp.cjs index 57288f5008..0fba2543f2 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -495,7 +495,7 @@ const RAW_RUNTIME_STATE = ],\ [\ "@scure/base",\ - "npm:1.2.6"\ + "npm:2.0.0"\ ],\ [\ "@shikijs/engine-oniguruma",\ @@ -1013,10 +1013,6 @@ const RAW_RUNTIME_STATE = "base64id",\ "npm:2.0.0"\ ],\ - [\ - "bech32",\ - "npm:1.1.4"\ - ],\ [\ "binary-extensions",\ "npm:2.2.0"\ @@ -3541,6 +3537,7 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@cosmjs/encoding", "workspace:packages/encoding"],\ ["@istanbuljs/nyc-config-typescript", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:1.0.1"],\ + ["@scure/base", "npm:2.0.0"],\ ["@types/base64-js", "npm:1.3.0"],\ ["@types/jasmine", "npm:4.6.1"],\ ["@types/karma-firefox-launcher", "npm:2.1.0"],\ @@ -3548,7 +3545,6 @@ const RAW_RUNTIME_STATE = ["@types/karma-jasmine-html-reporter", "npm:1.5.1"],\ ["@types/node", "npm:22.10.6"],\ ["base64-js", "npm:1.5.1"],\ - ["bech32", "npm:1.1.4"],\ ["glob", "npm:11.0.3"],\ ["jasmine", "npm:4.6.0"],\ ["jasmine-spec-reporter", "npm:6.0.0"],\ @@ -4673,6 +4669,13 @@ const RAW_RUNTIME_STATE = ["@scure/base", "npm:1.2.6"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/@scure-base-npm-2.0.0-97a775035d-7d999c7beb.zip/node_modules/@scure/base/",\ + "packageDependencies": [\ + ["@scure/base", "npm:2.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@shikijs/engine-oniguruma", [\ @@ -6925,15 +6928,6 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ - ["bech32", [\ - ["npm:1.1.4", {\ - "packageLocation": "./.yarn/cache/bech32-npm-1.1.4-87b69922f7-5f62ca47b8.zip/node_modules/bech32/",\ - "packageDependencies": [\ - ["bech32", "npm:1.1.4"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["binary-extensions", [\ ["npm:2.2.0", {\ "packageLocation": "./.yarn/cache/binary-extensions-npm-2.2.0-180c33fec7-d73d8b8972.zip/node_modules/binary-extensions/",\ diff --git a/.yarn/cache/@scure-base-npm-2.0.0-97a775035d-7d999c7beb.zip b/.yarn/cache/@scure-base-npm-2.0.0-97a775035d-7d999c7beb.zip new file mode 100644 index 0000000000..562bd25f68 --- /dev/null +++ b/.yarn/cache/@scure-base-npm-2.0.0-97a775035d-7d999c7beb.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:839fa0b7046eb80672306ed8e74f72ba5c18035789016ff04549eb8598b953a8 +size 102321 diff --git a/.yarn/cache/bech32-npm-1.1.4-87b69922f7-5f62ca47b8.zip b/.yarn/cache/bech32-npm-1.1.4-87b69922f7-5f62ca47b8.zip deleted file mode 100644 index 93982d08da..0000000000 --- a/.yarn/cache/bech32-npm-1.1.4-87b69922f7-5f62ca47b8.zip +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba17f12156fbe643245a6006f3b599555284562e31235a33383e5854fac95571 -size 10896 diff --git a/CHANGELOG.md b/CHANGELOG.md index fd4281bcf6..07f5377d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/encoding/package.json b/packages/encoding/package.json index 3a778ad7f8..efb5130311 100644 --- a/packages/encoding/package.json +++ b/packages/encoding/package.json @@ -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": { diff --git a/packages/encoding/src/bech32.spec.ts b/packages/encoding/src/bech32.spec.ts index 1806ddc410..f0fef06984 100644 --- a/packages/encoding/src/bech32.spec.ts +++ b/packages/encoding/src/bech32.spec.ts @@ -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 @@ -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", () => { @@ -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); }); }); @@ -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, + ); }); }); @@ -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, + ); }); }); }); diff --git a/packages/encoding/src/bech32.ts b/packages/encoding/src/bech32.ts index 7af6c97378..45389a771b 100644 --- a/packages/encoding/src/bech32.ts +++ b/packages/encoding/src/bech32.ts @@ -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, diff --git a/yarn.lock b/yarn.lock index 98d6935cd1..cbabedae93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -357,6 +357,7 @@ __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" @@ -364,7 +365,6 @@ __metadata: "@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" @@ -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" @@ -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"