Skip to content

Commit 4ba51cf

Browse files
authored
Merge pull request #1731 from cosmos/decimal-simon
Replace bn.js with bigint
2 parents 7b37cb9 + f274774 commit 4ba51cf

File tree

10 files changed

+75
-118
lines changed

10 files changed

+75
-118
lines changed

.pnp.cjs

Lines changed: 0 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.yarn/cache/@types-bn.js-npm-5.1.0-4a0335ff4f-04c6705445.zip

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

.yarn/cache/bn.js-npm-5.2.0-11748c0b07-67e17b1934.zip

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

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ and this project adheres to
2424
and `SigningStargateClient.createWithSigner` non-async. ([#1597])
2525
- @cosmjs/cosmwasm: Make constructor functions `{Signing,}CosmWasmClient.create`
2626
and `SigningCosmWasmClient.createWithSigner` non-async. ([#1597])
27+
- replace bn.js dependency with bigint ([#1720])
2728

2829
[#1597]: https://github.com/cosmos/cosmjs/pull/1597
30+
[#1720]: https://github.com/cosmos/cosmjs/pull/1720
2931

3032
## [0.34.0] - 2025-07-11
3133

packages/crypto/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,10 @@
4444
"@cosmjs/utils": "workspace:^",
4545
"@noble/curves": "^1.9.2",
4646
"@noble/hashes": "^1",
47-
"bn.js": "^5.2.0",
4847
"libsodium-wrappers-sumo": "^0.7.11"
4948
},
5049
"devDependencies": {
5150
"@istanbuljs/nyc-config-typescript": "^1.0.1",
52-
"@types/bn.js": "^5",
5351
"@types/jasmine": "^4",
5452
"@types/karma-firefox-launcher": "^2",
5553
"@types/karma-jasmine": "^4",

packages/crypto/src/slip10.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { toAscii, toHex } from "@cosmjs/encoding";
1+
import { fromHex, toAscii, toHex } from "@cosmjs/encoding";
22
import { Uint32, Uint53 } from "@cosmjs/math";
3+
import { assert } from "@cosmjs/utils";
34
import { secp256k1 } from "@noble/curves/secp256k1";
4-
// eslint-disable-next-line @typescript-eslint/naming-convention
5-
import BN from "bn.js";
65

76
import { Hmac } from "./hmac";
87
import { Sha512 } from "./sha";
@@ -26,6 +25,14 @@ function bytesToUnsignedBigInt(a: Uint8Array): bigint {
2625
return BigInt("0x" + toHex(a));
2726
}
2827

28+
function intTo32be(n: bigint): Uint8Array {
29+
assert(n >= 0n);
30+
assert(n < 2n ** (32n * 8n));
31+
// 32 bytes is 64 hexadecimal characters
32+
const hex = n.toString(16).padStart(64, "0");
33+
return fromHex(hex);
34+
}
35+
2936
/**
3037
* Reverse mapping of Slip10Curve
3138
*/
@@ -175,8 +182,8 @@ export class Slip10 {
175182

176183
// step 5
177184
const n = this.n(curve);
178-
const returnChildKeyAsNumber = new BN(il).add(new BN(parentPrivkey)).mod(n);
179-
const returnChildKey = Uint8Array.from(returnChildKeyAsNumber.toArray("be", 32));
185+
const returnChildKeyAsNumber = (bytesToUnsignedBigInt(il) + bytesToUnsignedBigInt(parentPrivkey)) % n;
186+
const returnChildKey = intTo32be(returnChildKeyAsNumber);
180187

181188
// step 6
182189
if (this.isGteN(curve, il) || this.isZero(returnChildKey)) {
@@ -198,14 +205,14 @@ export class Slip10 {
198205
}
199206

200207
private static isGteN(curve: Slip10Curve, privkey: Uint8Array): boolean {
201-
const keyAsNumber = new BN(privkey);
202-
return keyAsNumber.gte(this.n(curve));
208+
const keyAsNumber = bytesToUnsignedBigInt(privkey);
209+
return keyAsNumber >= this.n(curve);
203210
}
204211

205-
private static n(curve: Slip10Curve): BN {
212+
private static n(curve: Slip10Curve): bigint {
206213
switch (curve) {
207214
case Slip10Curve.Secp256k1:
208-
return new BN("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
215+
return 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
209216
default:
210217
throw new Error("curve not supported");
211218
}

packages/math/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,8 @@
3737
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
3838
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
3939
},
40-
"dependencies": {
41-
"bn.js": "^5.2.0"
42-
},
4340
"devDependencies": {
4441
"@istanbuljs/nyc-config-typescript": "^1.0.1",
45-
"@types/bn.js": "^5",
4642
"@types/jasmine": "^4",
4743
"@types/karma-firefox-launcher": "^2",
4844
"@types/karma-jasmine": "^4",

packages/math/src/decimal.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// eslint-disable-next-line @typescript-eslint/naming-convention
2-
import BN from "bn.js";
3-
41
import { Uint32, Uint53, Uint64 } from "./integers";
52

63
// Too large values lead to massive memory usage. Limit to something sensible.
@@ -94,7 +91,10 @@ export class Decimal {
9491

9592
public static compare(a: Decimal, b: Decimal): number {
9693
if (a.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match");
97-
return a.data.atomics.cmp(new BN(b.atomics));
94+
const difference = a.data.atomics - b.data.atomics;
95+
if (difference < 0n) return -1;
96+
if (difference > 0n) return 1;
97+
return 0;
9898
}
9999

100100
public get atomics(): string {
@@ -106,7 +106,7 @@ export class Decimal {
106106
}
107107

108108
private readonly data: {
109-
readonly atomics: BN;
109+
readonly atomics: bigint;
110110
readonly fractionalDigits: number;
111111
};
112112

@@ -118,7 +118,7 @@ export class Decimal {
118118
}
119119

120120
this.data = {
121-
atomics: new BN(atomics),
121+
atomics: BigInt(atomics),
122122
fractionalDigits: fractionalDigits,
123123
};
124124
}
@@ -130,36 +130,36 @@ export class Decimal {
130130

131131
/** Returns the greatest decimal <= this which has no fractional part (rounding down) */
132132
public floor(): Decimal {
133-
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
134-
const whole = this.data.atomics.div(factor);
135-
const fractional = this.data.atomics.mod(factor);
133+
const factor = 10n ** BigInt(this.data.fractionalDigits);
134+
const whole = this.data.atomics / factor;
135+
const fractional = this.data.atomics % factor;
136136

137-
if (fractional.isZero()) {
137+
if (fractional === 0n) {
138138
return this.clone();
139139
} else {
140-
return Decimal.fromAtomics(whole.mul(factor).toString(), this.fractionalDigits);
140+
return Decimal.fromAtomics((whole * factor).toString(), this.fractionalDigits);
141141
}
142142
}
143143

144144
/** Returns the smallest decimal >= this which has no fractional part (rounding up) */
145145
public ceil(): Decimal {
146-
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
147-
const whole = this.data.atomics.div(factor);
148-
const fractional = this.data.atomics.mod(factor);
146+
const factor = 10n ** BigInt(this.data.fractionalDigits);
147+
const whole = this.data.atomics / factor;
148+
const fractional = this.data.atomics % factor;
149149

150-
if (fractional.isZero()) {
150+
if (fractional === 0n) {
151151
return this.clone();
152152
} else {
153-
return Decimal.fromAtomics(whole.addn(1).mul(factor).toString(), this.fractionalDigits);
153+
return Decimal.fromAtomics(((whole + 1n) * factor).toString(), this.fractionalDigits);
154154
}
155155
}
156156

157157
public toString(): string {
158-
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
159-
const whole = this.data.atomics.div(factor);
160-
const fractional = this.data.atomics.mod(factor);
158+
const factor = 10n ** BigInt(this.data.fractionalDigits);
159+
const whole = this.data.atomics / factor;
160+
const fractional = this.data.atomics % factor;
161161

162-
if (fractional.isZero()) {
162+
if (fractional === 0n) {
163163
return whole.toString();
164164
} else {
165165
const fullFractionalPart = fractional.toString().padStart(this.data.fractionalDigits, "0");
@@ -185,7 +185,7 @@ export class Decimal {
185185
*/
186186
public plus(b: Decimal): Decimal {
187187
if (this.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match");
188-
const sum = this.data.atomics.add(new BN(b.atomics));
188+
const sum = this.data.atomics + b.data.atomics;
189189
return new Decimal(sum.toString(), this.fractionalDigits);
190190
}
191191

@@ -197,8 +197,8 @@ export class Decimal {
197197
*/
198198
public minus(b: Decimal): Decimal {
199199
if (this.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match");
200-
const difference = this.data.atomics.sub(new BN(b.atomics));
201-
if (difference.ltn(0)) throw new Error("Difference must not be negative");
200+
const difference = this.data.atomics - b.data.atomics;
201+
if (difference < 0n) throw new Error("Difference must not be negative");
202202
return new Decimal(difference.toString(), this.fractionalDigits);
203203
}
204204

@@ -208,7 +208,7 @@ export class Decimal {
208208
* We only allow multiplication by unsigned integers to avoid rounding errors.
209209
*/
210210
public multiply(b: Uint32 | Uint53 | Uint64): Decimal {
211-
const product = this.data.atomics.mul(new BN(b.toString()));
211+
const product = this.data.atomics * b.toBigInt();
212212
return new Decimal(product.toString(), this.fractionalDigits);
213213
}
214214

0 commit comments

Comments
 (0)