@bsv/sdk 1.9.29 → 1.9.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/ECDSA.js +22 -23
- package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
- package/dist/cjs/src/primitives/Point.js +61 -4
- package/dist/cjs/src/primitives/Point.js.map +1 -1
- package/dist/cjs/src/primitives/PrivateKey.js +2 -2
- package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
- package/dist/cjs/src/primitives/PublicKey.js +1 -1
- package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/ECDSA.js +22 -23
- package/dist/esm/src/primitives/ECDSA.js.map +1 -1
- package/dist/esm/src/primitives/Point.js +61 -4
- package/dist/esm/src/primitives/Point.js.map +1 -1
- package/dist/esm/src/primitives/PrivateKey.js +2 -2
- package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
- package/dist/esm/src/primitives/PublicKey.js +1 -1
- package/dist/esm/src/primitives/PublicKey.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
- package/dist/types/src/primitives/Point.d.ts +1 -0
- package/dist/types/src/primitives/Point.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/dist/umd/bundle.js.map +1 -1
- package/package.json +1 -1
- package/src/primitives/ECDSA.ts +25 -23
- package/src/primitives/Point.ts +75 -3
- package/src/primitives/PrivateKey.ts +2 -2
- package/src/primitives/PublicKey.ts +1 -1
- package/src/primitives/__tests/ECDSA.test.ts +12 -0
- package/src/primitives/__tests/Point.test.ts +60 -0
package/package.json
CHANGED
package/src/primitives/ECDSA.ts
CHANGED
|
@@ -39,6 +39,15 @@ function truncateToN (
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function bnToBigInt (bn: BigNumber): bigint {
|
|
43
|
+
const bytes = bn.toArray('be')
|
|
44
|
+
let x = 0n
|
|
45
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
46
|
+
x = (x << 8n) | BigInt(bytes[i])
|
|
47
|
+
}
|
|
48
|
+
return x
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
const curve = new Curve()
|
|
43
52
|
const bytes = curve.n.byteLength()
|
|
44
53
|
const ns1 = curve.n.subn(1)
|
|
@@ -65,18 +74,15 @@ export const sign = (
|
|
|
65
74
|
forceLowS: boolean = false,
|
|
66
75
|
customK?: BigNumber | ((iter: number) => BigNumber)
|
|
67
76
|
): Signature => {
|
|
68
|
-
// —— prepare inputs ────────────────────────────────────────────────────────
|
|
69
77
|
msg = truncateToN(msg)
|
|
70
|
-
const msgBig =
|
|
71
|
-
const keyBig =
|
|
78
|
+
const msgBig = bnToBigInt(msg)
|
|
79
|
+
const keyBig = bnToBigInt(key)
|
|
72
80
|
|
|
73
|
-
// DRBG seeding identical to previous implementation
|
|
74
81
|
const bkey = key.toArray('be', bytes)
|
|
75
82
|
const nonce = msg.toArray('be', bytes)
|
|
76
83
|
const drbg = new DRBG(bkey, nonce)
|
|
77
84
|
|
|
78
85
|
for (let iter = 0; ; iter++) {
|
|
79
|
-
// —— k generation & basic validity checks ───────────────────────────────
|
|
80
86
|
let kBN =
|
|
81
87
|
typeof customK === 'function'
|
|
82
88
|
? customK(iter)
|
|
@@ -84,31 +90,29 @@ export const sign = (
|
|
|
84
90
|
? customK
|
|
85
91
|
: new BigNumber(drbg.generate(bytes), 16)
|
|
86
92
|
|
|
87
|
-
if (kBN == null)
|
|
93
|
+
if (kBN == null) {
|
|
94
|
+
throw new Error('k is undefined')
|
|
95
|
+
}
|
|
96
|
+
|
|
88
97
|
kBN = truncateToN(kBN, true)
|
|
89
98
|
|
|
90
99
|
if (kBN.cmpn(1) < 0 || kBN.cmp(ns1) > 0) {
|
|
91
100
|
if (BigNumber.isBN(customK)) {
|
|
92
|
-
throw new Error('Invalid fixed custom K value (must be >1 and <N
|
|
101
|
+
throw new Error('Invalid fixed custom K value (must be >1 and <N-1)')
|
|
93
102
|
}
|
|
94
103
|
continue
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
const
|
|
106
|
+
const R = curve.g.mulCT(kBN)
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
const R = scalarMultiplyWNAF(kBig, { x: GX_BIGINT, y: GY_BIGINT })
|
|
101
|
-
if (R.Z === 0n) { // point at infinity – should never happen for valid k
|
|
108
|
+
if (R.isInfinity()) {
|
|
102
109
|
if (BigNumber.isBN(customK)) {
|
|
103
110
|
throw new Error('Invalid fixed custom K value (k·G at infinity)')
|
|
104
111
|
}
|
|
105
112
|
continue
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
|
|
109
|
-
const zInv = biModInv(R.Z)
|
|
110
|
-
const zInv2 = biModMul(zInv, zInv)
|
|
111
|
-
const xAff = biModMul(R.X, zInv2)
|
|
115
|
+
const xAff = BigInt('0x' + R.getX().toString(16))
|
|
112
116
|
const rBig = modN(xAff)
|
|
113
117
|
|
|
114
118
|
if (rBig === 0n) {
|
|
@@ -118,7 +122,7 @@ export const sign = (
|
|
|
118
122
|
continue
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
const kBig = BigInt('0x' + kBN.toString(16))
|
|
122
126
|
const kInv = modInvN(kBig)
|
|
123
127
|
const rTimesKey = modMulN(rBig, keyBig)
|
|
124
128
|
const sum = modN(msgBig + rTimesKey)
|
|
@@ -131,12 +135,10 @@ export const sign = (
|
|
|
131
135
|
continue
|
|
132
136
|
}
|
|
133
137
|
|
|
134
|
-
// low‑S mitigation (BIP‑62/BIP‑340 style)
|
|
135
138
|
if (forceLowS && sBig > halfN) {
|
|
136
139
|
sBig = N_BIGINT - sBig
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
// —— convert back to BigNumber & return ─────────────────────────────────
|
|
140
142
|
const r = new BigNumber(rBig.toString(16), 16)
|
|
141
143
|
const s = new BigNumber(sBig.toString(16), 16)
|
|
142
144
|
return new Signature(r, s)
|
|
@@ -163,18 +165,18 @@ export const sign = (
|
|
|
163
165
|
*/
|
|
164
166
|
export const verify = (msg: BigNumber, sig: Signature, key: Point): boolean => {
|
|
165
167
|
// Convert inputs to BigInt
|
|
166
|
-
const hash =
|
|
168
|
+
const hash = bnToBigInt(msg)
|
|
167
169
|
if ((key.x == null) || (key.y == null)) {
|
|
168
170
|
throw new Error('Invalid public key: missing coordinates.')
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
const publicKey = {
|
|
172
|
-
x:
|
|
173
|
-
y:
|
|
174
|
+
x: bnToBigInt(key.x),
|
|
175
|
+
y: bnToBigInt(key.y)
|
|
174
176
|
}
|
|
175
177
|
const signature = {
|
|
176
|
-
r:
|
|
177
|
-
s:
|
|
178
|
+
r: bnToBigInt(sig.r),
|
|
179
|
+
s: bnToBigInt(sig.s)
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
const { r, s } = signature
|
package/src/primitives/Point.ts
CHANGED
|
@@ -3,6 +3,21 @@ import JPoint from './JacobianPoint.js'
|
|
|
3
3
|
import BigNumber from './BigNumber.js'
|
|
4
4
|
import { toArray, toHex } from './utils.js'
|
|
5
5
|
|
|
6
|
+
function ctSwap (
|
|
7
|
+
swap: bigint,
|
|
8
|
+
a: JacobianPointBI,
|
|
9
|
+
b: JacobianPointBI
|
|
10
|
+
): void {
|
|
11
|
+
const mask = -swap
|
|
12
|
+
const swapX = (a.X ^ b.X) & mask
|
|
13
|
+
const swapY = (a.Y ^ b.Y) & mask
|
|
14
|
+
const swapZ = (a.Z ^ b.Z) & mask
|
|
15
|
+
|
|
16
|
+
a.X ^= swapX; b.X ^= swapX
|
|
17
|
+
a.Y ^= swapY; b.Y ^= swapY
|
|
18
|
+
a.Z ^= swapZ; b.Z ^= swapZ
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
// -----------------------------------------------------------------------------
|
|
7
22
|
// BigInt helpers & constants (secp256k1) – hoisted so we don't recreate them on
|
|
8
23
|
// every Point.mul() call.
|
|
@@ -102,6 +117,10 @@ export const jpDouble = (P: JacobianPointBI): JacobianPointBI => {
|
|
|
102
117
|
return { X: X3, Y: Y3, Z: Z3 }
|
|
103
118
|
}
|
|
104
119
|
|
|
120
|
+
// NOTE:
|
|
121
|
+
// jpAdd contains conditional branches.
|
|
122
|
+
// In mulCT, jpAdd and jpDouble are executed in a fixed pattern
|
|
123
|
+
// independent of scalar bits, satisfying TOB-4 constant-time requirements.
|
|
105
124
|
export const jpAdd = (P: JacobianPointBI, Q: JacobianPointBI): JacobianPointBI => {
|
|
106
125
|
if (P.Z === BI_ZERO) return Q
|
|
107
126
|
if (Q.Z === BI_ZERO) return P
|
|
@@ -734,13 +753,17 @@ export default class Point extends BasePoint {
|
|
|
734
753
|
return this
|
|
735
754
|
}
|
|
736
755
|
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
|
|
756
|
+
const isNeg = k.isNeg()
|
|
757
|
+
const kAbs = isNeg ? k.neg() : k
|
|
758
|
+
let kBig = BigInt('0x' + kAbs.toString(16))
|
|
759
|
+
|
|
740
760
|
kBig = biMod(kBig)
|
|
741
761
|
if (kBig === BI_ZERO) {
|
|
742
762
|
return new Point(null, null)
|
|
743
763
|
}
|
|
764
|
+
if (kBig === BI_ZERO) {
|
|
765
|
+
return new Point(null, null)
|
|
766
|
+
}
|
|
744
767
|
|
|
745
768
|
if (this.x === null || this.y === null) {
|
|
746
769
|
throw new Error('Point coordinates cannot be null')
|
|
@@ -774,6 +797,55 @@ export default class Point extends BasePoint {
|
|
|
774
797
|
return result
|
|
775
798
|
}
|
|
776
799
|
|
|
800
|
+
mulCT (k: BigNumber | number | number[] | string): Point {
|
|
801
|
+
if (!BigNumber.isBN(k)) {
|
|
802
|
+
k = new BigNumber(k as any, 16)
|
|
803
|
+
}
|
|
804
|
+
k = k as BigNumber
|
|
805
|
+
|
|
806
|
+
if (this.inf) return new Point(null, null)
|
|
807
|
+
|
|
808
|
+
// ✅ SAFE sign handling (this is the fix)
|
|
809
|
+
const isNeg = k.isNeg()
|
|
810
|
+
const kAbs = isNeg ? k.neg() : k
|
|
811
|
+
let kBig = BigInt('0x' + kAbs.toString(16))
|
|
812
|
+
|
|
813
|
+
kBig = biMod(kBig)
|
|
814
|
+
if (kBig === 0n) return new Point(null, null)
|
|
815
|
+
|
|
816
|
+
const Px =
|
|
817
|
+
this === this.curve.g
|
|
818
|
+
? GX_BIGINT
|
|
819
|
+
: BigInt('0x' + this.getX().toString(16))
|
|
820
|
+
|
|
821
|
+
const Py =
|
|
822
|
+
this === this.curve.g
|
|
823
|
+
? GY_BIGINT
|
|
824
|
+
: BigInt('0x' + this.getY().toString(16))
|
|
825
|
+
|
|
826
|
+
let R0: JacobianPointBI = { X: 0n, Y: 1n, Z: 0n }
|
|
827
|
+
let R1: JacobianPointBI = { X: Px, Y: Py, Z: 1n }
|
|
828
|
+
|
|
829
|
+
const bits = kBig.toString(2)
|
|
830
|
+
for (let i = 0; i < bits.length; i++) {
|
|
831
|
+
const bit = bits[i] === '1' ? 1n : 0n
|
|
832
|
+
ctSwap(bit, R0, R1)
|
|
833
|
+
R1 = jpAdd(R0, R1)
|
|
834
|
+
R0 = jpDouble(R0)
|
|
835
|
+
ctSwap(bit, R0, R1)
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (R0.Z === 0n) return new Point(null, null)
|
|
839
|
+
|
|
840
|
+
const zInv = biModInv(R0.Z)
|
|
841
|
+
const zInv2 = biModMul(zInv, zInv)
|
|
842
|
+
const x = biModMul(R0.X, zInv2)
|
|
843
|
+
const y = biModMul(R0.Y, biModMul(zInv2, zInv))
|
|
844
|
+
|
|
845
|
+
const result = new Point(x.toString(16), y.toString(16))
|
|
846
|
+
return isNeg ? result.neg() : result
|
|
847
|
+
}
|
|
848
|
+
|
|
777
849
|
/**
|
|
778
850
|
* Performs a multiplication and addition operation in a single step.
|
|
779
851
|
* Multiplies this Point by k1, adds the resulting Point to the result of p2 multiplied by k2.
|
|
@@ -260,7 +260,7 @@ export default class PrivateKey extends BigNumber {
|
|
|
260
260
|
*/
|
|
261
261
|
toPublicKey (): PublicKey {
|
|
262
262
|
const c = new Curve()
|
|
263
|
-
const p = c.g.
|
|
263
|
+
const p = c.g.mulCT(this)
|
|
264
264
|
return new PublicKey(p.x, p.y)
|
|
265
265
|
}
|
|
266
266
|
|
|
@@ -352,7 +352,7 @@ export default class PrivateKey extends BigNumber {
|
|
|
352
352
|
if (!key.validate()) {
|
|
353
353
|
throw new Error('Public key not valid for ECDH secret derivation')
|
|
354
354
|
}
|
|
355
|
-
return key.
|
|
355
|
+
return key.mulCT(this)
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
/**
|
|
@@ -117,4 +117,16 @@ describe('ECDSA', () => {
|
|
|
117
117
|
ECDSA.verify(msg, signature, infinityPub)
|
|
118
118
|
).toThrow()
|
|
119
119
|
})
|
|
120
|
+
|
|
121
|
+
it('sign/verify works with large private key (mulCT stress)', () => {
|
|
122
|
+
const bigKey = new BigNumber(
|
|
123
|
+
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f',
|
|
124
|
+
16
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const sig = ECDSA.sign(msg, bigKey)
|
|
128
|
+
const pub = curve.g.mul(bigKey)
|
|
129
|
+
|
|
130
|
+
expect(ECDSA.verify(msg, sig, pub)).toBe(true)
|
|
131
|
+
})
|
|
120
132
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Point from '../../primitives/Point'
|
|
2
|
+
import BigNumber from '../../primitives/BigNumber'
|
|
2
3
|
|
|
3
4
|
describe('Point.fromJSON / fromDER / fromX curve validation (TOB-24)', () => {
|
|
4
5
|
it('rejects clearly off-curve coordinates', () => {
|
|
@@ -50,3 +51,62 @@ describe('Point.fromJSON / fromDER / fromX curve validation (TOB-24)', () => {
|
|
|
50
51
|
expect(() => Point.fromX(badX, true)).toThrow(/Invalid point/)
|
|
51
52
|
})
|
|
52
53
|
})
|
|
54
|
+
|
|
55
|
+
describe('Point.mulCT (constant-time scalar multiplication)', () => {
|
|
56
|
+
const G = Point.fromString(
|
|
57
|
+
'0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
it('returns point at infinity for scalar = 0', () => {
|
|
61
|
+
const r = G.mulCT(0)
|
|
62
|
+
expect(r.isInfinity()).toBe(true)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('matches regular mul for small scalar', () => {
|
|
66
|
+
const k = 5
|
|
67
|
+
const r1 = G.mul(k)
|
|
68
|
+
const r2 = G.mulCT(k)
|
|
69
|
+
|
|
70
|
+
expect(r2.eq(r1)).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('matches regular mul for large scalar', () => {
|
|
74
|
+
const k =
|
|
75
|
+
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'
|
|
76
|
+
|
|
77
|
+
const r1 = G.mul(k)
|
|
78
|
+
const r2 = G.mulCT(k)
|
|
79
|
+
|
|
80
|
+
expect(r2.eq(r1)).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('works with non-generator base point', () => {
|
|
84
|
+
const base = G.mul(3)
|
|
85
|
+
const k = 11
|
|
86
|
+
|
|
87
|
+
const r1 = base.mul(k)
|
|
88
|
+
const r2 = base.mulCT(k)
|
|
89
|
+
|
|
90
|
+
expect(r2.eq(r1)).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('handles alternating bit patterns (ctSwap exercised)', () => {
|
|
94
|
+
// 101010... pattern forces both swap paths
|
|
95
|
+
const k = BigInt(
|
|
96
|
+
'0b101010101010101010101010101010101010101010101010101010101010101'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const r1 = G.mul(k.toString(10))
|
|
100
|
+
const r2 = G.mulCT(k.toString(10))
|
|
101
|
+
|
|
102
|
+
expect(r2.eq(r1)).toBe(true)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('handles negative scalars correctly', () => {
|
|
106
|
+
const k = new BigNumber('123456', 16)
|
|
107
|
+
const r1 = G.mul(k.neg())
|
|
108
|
+
const r2 = G.mulCT(k.neg())
|
|
109
|
+
expect(r2.eq(r1)).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|