* Functionality wrapped in a class for embedding into a Web Worker.
*/
-type Point = InstanceType<typeof Secp256k1.Point>
+type Point = {
+ X: bigint
+ Y: bigint
+ Z: bigint
+ get x (): bigint
+ get y (): bigint
+ equals: (other: Point) => boolean
+ is0: () => boolean
+ negate: () => Point
+ double: () => Point
+ add: (other: Point) => Point
+ subtract: (other: Point) => Point
+ multiply: (n: bigint, safe?: boolean) => Point
+ multiplyUnsafe: (scalar: bigint) => Point
+ toAffine: () => AffinePoint
+ assertValidity: () => Point
+ toBytes: (isCompressed?: boolean) => Bytes
+ toHex: (isCompressed?: boolean) => string
+}
type Signature = ReturnType<typeof Secp256k1.Signature>
/** Alias to Uint8Array. */
}
/** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */
- static Point = class {
- readonly X: bigint
- readonly Y: bigint
- readonly Z: bigint
- constructor (X: bigint, Y: bigint, Z: bigint) {
- this.X = Secp256k1.FpIsValid(X)
- this.Y = Secp256k1.FpIsValidNot0(Y) // Y can't be 0 in Projective
- this.Z = Secp256k1.FpIsValid(Z)
- Object.freeze(this)
- }
- get x (): bigint {
- return this.toAffine().x
- }
- get y (): bigint {
- return this.toAffine().y
- }
- /** Equality check: compare points P&Q. */
- equals (other: Point): boolean {
- const { X: X1, Y: Y1, Z: Z1 } = this
- const { X: X2, Y: Y2, Z: Z2 } = Secp256k1.apoint(other) // checks class equality
- const X1Z2 = Secp256k1.M(X1 * Z2)
- const X2Z1 = Secp256k1.M(X2 * Z1)
- const Y1Z2 = Secp256k1.M(Y1 * Z2)
- const Y2Z1 = Secp256k1.M(Y2 * Z1)
- return X1Z2 === X2Z1 && Y1Z2 === Y2Z1
- }
- is0 (): boolean {
- return this.equals(Secp256k1.I)
- }
- /** Flip point over y coordinate. */
- negate (): Point {
- return new (this.constructor as typeof Secp256k1.Point)(this.X, Secp256k1.M(-this.Y), this.Z)
- }
- /** Point doubling: P+P, complete formula. */
- double (): Point {
- return this.add(this)
- }
- /**
- * Point addition: P+Q, complete, exception-free formula
- * (Renes-Costello-Batina, algo 1 of [2015/1060](https://eprint.iacr.org/2015/1060)).
- * Cost: `12M + 0S + 3*a + 3*b3 + 23add`.
- */
- // prettier-ignore
- add (other: Point): Point {
- const { X: X1, Y: Y1, Z: Z1 } = this
- const { X: X2, Y: Y2, Z: Z2 } = Secp256k1.apoint(other)
- const a = 0n
- const b = Secp256k1._b
- let X3 = 0n, Y3 = 0n, Z3 = 0n
- const b3 = Secp256k1.M(b * 3n)
- let t0 = Secp256k1.M(X1 * X2), t1 = Secp256k1.M(Y1 * Y2), t2 = Secp256k1.M(Z1 * Z2), t3 = Secp256k1.M(X1 + Y1) // step 1
- let t4 = Secp256k1.M(X2 + Y2) // step 5
- t3 = Secp256k1.M(t3 * t4); t4 = Secp256k1.M(t0 + t1); t3 = Secp256k1.M(t3 - t4); t4 = Secp256k1.M(X1 + Z1)
- let t5 = Secp256k1.M(X2 + Z2) // step 10
- t4 = Secp256k1.M(t4 * t5); t5 = Secp256k1.M(t0 + t2); t4 = Secp256k1.M(t4 - t5); t5 = Secp256k1.M(Y1 + Z1)
- X3 = Secp256k1.M(Y2 + Z2) // step 15
- t5 = Secp256k1.M(t5 * X3); X3 = Secp256k1.M(t1 + t2); t5 = Secp256k1.M(t5 - X3); Z3 = Secp256k1.M(a * t4)
- X3 = Secp256k1.M(b3 * t2) // step 20
- Z3 = Secp256k1.M(X3 + Z3); X3 = Secp256k1.M(t1 - Z3); Z3 = Secp256k1.M(t1 + Z3); Y3 = Secp256k1.M(X3 * Z3)
- t1 = Secp256k1.M(t0 + t0) // step 25
- t1 = Secp256k1.M(t1 + t0); t2 = Secp256k1.M(a * t2); t4 = Secp256k1.M(b3 * t4); t1 = Secp256k1.M(t1 + t2)
- t2 = Secp256k1.M(t0 - t2) // step 30
- t2 = Secp256k1.M(a * t2); t4 = Secp256k1.M(t4 + t2); t0 = Secp256k1.M(t1 * t4); Y3 = Secp256k1.M(Y3 + t0)
- t0 = Secp256k1.M(t5 * t4) // step 35
- X3 = Secp256k1.M(t3 * X3); X3 = Secp256k1.M(X3 - t0); t0 = Secp256k1.M(t3 * t1); Z3 = Secp256k1.M(t5 * Z3)
- Z3 = Secp256k1.M(Z3 + t0) // step 40
- return new Secp256k1.Point(X3, Y3, Z3)
- }
- subtract (other: Point): Point {
- return this.add(Secp256k1.apoint(other).negate())
- }
- /**
- * Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
- * Uses {@link wNAF} for base point.
- * Uses fake point to mitigate side-channel leakage.
- * @param n scalar by which point is multiplied
- * @param safe safe mode guards against timing attacks; unsafe mode is faster
- */
- multiply (n: bigint, safe = true): Point {
- if (!safe && n === 0n) return Secp256k1.I
- Secp256k1.FnIsValidNot0(n)
- if (n === 1n) return this
- if (this.equals(Secp256k1.G)) return Secp256k1.wNAF(n).p
- // init result point & fake point
- let p = Secp256k1.I
- let f = Secp256k1.G
- for (let d: Point = this; n > 0n; d = d.double(), n >>= 1n) {
- // if bit is present, add to point
- // if not present, add to fake, for timing safety
- if (n & 1n) p = p.add(d)
- else if (safe) f = f.add(d)
+ static Point = (X: bigint, Y: bigint, Z: bigint): Point => {
+ const secp256k1 = this
+ return Object.freeze({
+ X: this.FpIsValid(X),
+ Y: this.FpIsValidNot0(Y), // Y can't be 0 in Projective
+ Z: this.FpIsValid(Z),
+ get x (): bigint {
+ return this.toAffine().x
+ },
+ get y (): bigint {
+ return this.toAffine().y
+ },
+ /** Equality check: compare points P&Q. */
+ equals (other: Point): boolean {
+ const { X: X1, Y: Y1, Z: Z1 } = this
+ const { X: X2, Y: Y2, Z: Z2 } = other
+ const X1Z2 = secp256k1.M(X1 * Z2)
+ const X2Z1 = secp256k1.M(X2 * Z1)
+ const Y1Z2 = secp256k1.M(Y1 * Z2)
+ const Y2Z1 = secp256k1.M(Y2 * Z1)
+ return X1Z2 === X2Z1 && Y1Z2 === Y2Z1
+ },
+ is0 (): boolean {
+ return this.equals(secp256k1.I)
+ },
+ /** Flip point over y coordinate. */
+ negate: (): Point => {
+ return this.Point(X, this.M(-Y), Z)
+ },
+ /** Point doubling: P+P, complete formula. */
+ double (): Point {
+ return this.add(this)
+ },
+ /**
+ * Point addition: P+Q, complete, exception-free formula
+ * (Renes-Costello-Batina, algo 1 of [2015/1060](https://eprint.iacr.org/2015/1060)).
+ * Cost: `12M + 0S + 3*a + 3*b3 + 23add`.
+ */
+ // prettier-ignore
+ add: (other: Point): Point => {
+ const { X: X1, Y: Y1, Z: Z1 } = { X, Y, Z }
+ const { X: X2, Y: Y2, Z: Z2 } = other
+ const a = 0n
+ const b = this._b
+ let X3 = 0n, Y3 = 0n, Z3 = 0n
+ const b3 = this.M(b * 3n)
+ let t0 = this.M(X1 * X2), t1 = this.M(Y1 * Y2), t2 = this.M(Z1 * Z2), t3 = this.M(X1 + Y1) // step 1
+ let t4 = this.M(X2 + Y2) // step 5
+ t3 = this.M(t3 * t4); t4 = this.M(t0 + t1); t3 = this.M(t3 - t4); t4 = this.M(X1 + Z1)
+ let t5 = this.M(X2 + Z2) // step 10
+ t4 = this.M(t4 * t5); t5 = this.M(t0 + t2); t4 = this.M(t4 - t5); t5 = this.M(Y1 + Z1)
+ X3 = this.M(Y2 + Z2) // step 15
+ t5 = this.M(t5 * X3); X3 = this.M(t1 + t2); t5 = this.M(t5 - X3); Z3 = this.M(a * t4)
+ X3 = this.M(b3 * t2) // step 20
+ Z3 = this.M(X3 + Z3); X3 = this.M(t1 - Z3); Z3 = this.M(t1 + Z3); Y3 = this.M(X3 * Z3)
+ t1 = this.M(t0 + t0) // step 25
+ t1 = this.M(t1 + t0); t2 = this.M(a * t2); t4 = this.M(b3 * t4); t1 = this.M(t1 + t2)
+ t2 = this.M(t0 - t2) // step 30
+ t2 = this.M(a * t2); t4 = this.M(t4 + t2); t0 = this.M(t1 * t4); Y3 = this.M(Y3 + t0)
+ t0 = this.M(t5 * t4) // step 35
+ X3 = this.M(t3 * X3); X3 = this.M(X3 - t0); t0 = this.M(t3 * t1); Z3 = this.M(t5 * Z3)
+ Z3 = this.M(Z3 + t0) // step 40
+ return this.Point(X3, Y3, Z3)
+ },
+ subtract (other: Point): Point {
+ return this.add(other.negate())
+ },
+ /**
+ * Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
+ * Uses {@link wNAF} for base point.
+ * Uses fake point to mitigate side-channel leakage.
+ * @param n scalar by which point is multiplied
+ * @param safe safe mode guards against timing attacks; unsafe mode is faster
+ */
+ multiply (n: bigint, safe = true): Point {
+ if (!safe && n === 0n) return secp256k1.I
+ secp256k1.FnIsValidNot0(n)
+ if (n === 1n) return this
+ if (this.equals(secp256k1.G)) return secp256k1.wNAF(n).p
+ // init result point & fake point
+ let p = secp256k1.I
+ let f = secp256k1.G
+ for (let d: Point = this; n > 0n; d = d.double(), n >>= 1n) {
+ // if bit is present, add to point
+ // if not present, add to fake, for timing safety
+ if (n & 1n) p = p.add(d)
+ else if (safe) f = f.add(d)
+ }
+ return p
+ },
+ multiplyUnsafe (scalar: bigint): Point {
+ return this.multiply(scalar, false)
+ },
+ /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
+ toAffine (): AffinePoint {
+ const { X: x, Y: y, Z: z } = this
+ // fast-paths for ZERO point OR Z=1
+ if (this.equals(secp256k1.I)) return { x: 0n, y: 0n }
+ if (z === 1n) return { x, y }
+ const iz = secp256k1.invert(z, secp256k1.P)
+ // (Z * Z^-1) must be 1, otherwise bad math
+ if (secp256k1.M(z * iz) !== 1n) secp256k1.err('inverse invalid')
+ // x = X*Z^-1; y = Y*Z^-1
+ return { x: secp256k1.M(x * iz), y: secp256k1.M(y * iz) }
+ },
+ /** Checks if the point is valid and on-curve. */
+ assertValidity (): Point {
+ const { x, y } = this.toAffine() // convert to 2d xy affine point.
+ secp256k1.FpIsValidNot0(x) // must be in range 1 <= x,y < P
+ secp256k1.FpIsValidNot0(y)
+ // y² == x³ + ax + b, equation sides must be equal
+ return secp256k1.M(y * y) === secp256k1.koblitz(x) ? this : secp256k1.err('bad point: not on curve')
+ },
+ /** Converts point to 33/65-byte Uint8Array. */
+ toBytes (isCompressed = true): Bytes {
+ const { x, y } = this.assertValidity().toAffine()
+ const x32b = secp256k1.numTo32b(x)
+ if (isCompressed) return secp256k1.concatBytes(secp256k1.getPrefix(y), x32b)
+ return secp256k1.concatBytes(secp256k1.u8of(0x04), x32b, secp256k1.numTo32b(y))
+ },
+ toHex (isCompressed?: boolean): string {
+ return secp256k1.bytesToHex(this.toBytes(isCompressed))
}
- return p
- }
- multiplyUnsafe (scalar: bigint): Point {
- return this.multiply(scalar, false)
- }
- /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
- toAffine (): AffinePoint {
- const { X: x, Y: y, Z: z } = this
- // fast-paths for ZERO point OR Z=1
- if (this.equals(Secp256k1.I)) return { x: 0n, y: 0n }
- if (z === 1n) return { x, y }
- const iz = Secp256k1.invert(z, Secp256k1.P)
- // (Z * Z^-1) must be 1, otherwise bad math
- if (Secp256k1.M(z * iz) !== 1n) Secp256k1.err('inverse invalid')
- // x = X*Z^-1; y = Y*Z^-1
- return { x: Secp256k1.M(x * iz), y: Secp256k1.M(y * iz) }
- }
- /** Checks if the point is valid and on-curve. */
- assertValidity (): Point {
- const { x, y } = this.toAffine() // convert to 2d xy affine point.
- Secp256k1.FpIsValidNot0(x) // must be in range 1 <= x,y < P
- Secp256k1.FpIsValidNot0(y)
- // y² == x³ + ax + b, equation sides must be equal
- return Secp256k1.M(y * y) === Secp256k1.koblitz(x) ? this : Secp256k1.err('bad point: not on curve')
- }
- /** Converts point to 33/65-byte Uint8Array. */
- toBytes (isCompressed = true): Bytes {
- const { x, y } = this.assertValidity().toAffine()
- const x32b = Secp256k1.numTo32b(x)
- if (isCompressed) return Secp256k1.concatBytes(Secp256k1.getPrefix(y), x32b)
- return Secp256k1.concatBytes(Secp256k1.u8of(0x04), x32b, Secp256k1.numTo32b(y))
- }
-
- toHex (isCompressed?: boolean): string {
- return Secp256k1.bytesToHex(this.toBytes(isCompressed))
- }
+ })
}
/** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */
static pointFromAffine (ap: AffinePoint): Point {
const { x, y } = ap
- return x === 0n && y === 0n ? this.I : new this.Point(x, y, 1n)
+ return x === 0n && y === 0n ? this.I : this.Point(x, y, 1n)
}
/** Convert Uint8Array or hex string to Point. */
static pointFromBytes (bytes: Bytes): Point {
const evenY = this.isEven(y)
const evenH = this.isEven(this.big(head))
if (evenH !== evenY) y = this.M(-y)
- p = new this.Point(x, y, 1n)
+ p = this.Point(x, y, 1n)
}
// Uncompressed 65-byte point, 0x04 prefix
- if (length === uncomp && head === 0x04) p = new this.Point(x, this.sliceBytesNumBE(tail, this.L, this.L2), 1n)
+ if (length === uncomp && head === 0x04) p = this.Point(x, this.sliceBytesNumBE(tail, this.L, this.L2), 1n)
// Validate point
return p ? p.assertValidity() : this.err('bad point: not on curve')
}
}
/** Generator / base point */
- static G: Point = new this.Point(this.Gx, this.Gy, 1n)
+ static G: Point = this.Point(this.Gx, this.Gy, 1n)
/** Identity / zero point */
- static I: Point = new this.Point(0n, 1n, 0n)
+ static I: Point = this.Point(0n, 1n, 0n)
/** `Q = u1⋅G + u2⋅R`. Verifies Q is not ZERO. Unsafe: non-CT. */
static doubleScalarMulUns = (R: Point, u1: bigint, u2: bigint): Point => {
return this.G.multiply(u1, false).add(R.multiply(u2, false)).assertValidity()
const y_ = this.isEven(y) ? y : this.M(-y)
// Return the unique point P such that x(P) = x and
// y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise.
- const P_ = new this.Point(x, y_, 1n).assertValidity()
+ const P_ = this.Point(x, y_, 1n).assertValidity()
const px = this.numTo32b(P_.toAffine().x)
// P = lift_x(int(pk)); fail if that fails
const r = this.sliceBytesNumBE(sig, 0, this.L) // Let r = int(sig[0:32]); fail if r ≥ p.