@bsv/sdk
Version:
BSV Blockchain Software Development Kit
459 lines (412 loc) • 13.5 kB
text/typescript
import BasePoint from './BasePoint.js'
import BigNumber from './BigNumber.js'
import Point from './Point.js'
/**
* The `JacobianPoint` class extends the `BasePoint` class for handling Jacobian coordinates on an Elliptic Curve.
* This class defines the properties and the methods needed to work with points in Jacobian coordinates.
*
* The Jacobian coordinates represent a point (x, y, z) on an Elliptic Curve such that the usual (x, y) coordinates are given by (x/z^2, y/z^3).
*
* @property x - The `x` coordinate of the point in the Jacobian form.
* @property y - The `y` coordinate of the point in the Jacobian form.
* @property z - The `z` coordinate of the point in the Jacobian form.
* @property zOne - Flag that indicates if the `z` coordinate is one.
*
* @example
* const pointJ = new JacobianPoint('3', '4', '1');
*/
export default class JacobianPoint extends BasePoint {
x: BigNumber
y: BigNumber
z: BigNumber
zOne: boolean
/**
* Constructs a new `JacobianPoint` instance.
*
* @param x - If `null`, the x-coordinate will default to the curve's defined 'one' constant.
* If `x` is not a BigNumber, `x` will be converted to a `BigNumber` assuming it is a hex string.
*
* @param y - If `null`, the y-coordinate will default to the curve's defined 'one' constant.
* If `y` is not a BigNumber, `y` will be converted to a `BigNumber` assuming it is a hex string.
*
* @param z - If `null`, the z-coordinate will default to 0.
* If `z` is not a BigNumber, `z` will be converted to a `BigNumber` assuming it is a hex string.
*
* @example
* const pointJ1 = new JacobianPoint(null, null, null); // creates point at infinity
* const pointJ2 = new JacobianPoint('3', '4', '1'); // creates point (3, 4, 1)
*/
constructor (
x: string | BigNumber | null,
y: string | BigNumber | null,
z: string | BigNumber | null
) {
super('jacobian')
if (x === null && y === null && z === null) {
this.x = this.curve.one
this.y = this.curve.one
this.z = new BigNumber(0)
} else {
if (!BigNumber.isBN(x)) {
x = new BigNumber(x as string, 16)
}
this.x = x as BigNumber
if (!BigNumber.isBN(y)) {
y = new BigNumber(y as string, 16)
}
this.y = y as BigNumber
if (!BigNumber.isBN(z)) {
z = new BigNumber(z as string, 16)
}
this.z = z as BigNumber
}
if (this.x.red == null) {
this.x = this.x.toRed(this.curve.red)
}
if (this.y.red == null) {
this.y = this.y.toRed(this.curve.red)
}
if (this.z.red == null) {
this.z = this.z.toRed(this.curve.red)
}
this.zOne = this.z === this.curve.one
}
/**
* Converts the `JacobianPoint` object instance to standard affine `Point` format and returns `Point` type.
*
* @returns The `Point`(affine) object representing the same point as the original `JacobianPoint`.
*
* If the initial `JacobianPoint` represents point at infinity, an instance of `Point` at infinity is returned.
*
* @example
* const pointJ = new JacobianPoint('3', '4', '1');
* const pointP = pointJ.toP(); // The point in affine coordinates.
*/
toP (): Point {
if (this.isInfinity()) {
return new Point(null, null)
}
const zinv = this.z.redInvm()
const zinv2 = zinv.redSqr()
const ax = this.x.redMul(zinv2)
const ay = this.y.redMul(zinv2).redMul(zinv)
return new Point(ax, ay)
}
/**
* Negation operation. It returns the additive inverse of the Jacobian point.
*
* @method neg
* @returns Returns a new Jacobian point as the result of the negation.
*
* @example
* const jp = new JacobianPoint(x, y, z)
* const result = jp.neg()
*/
neg (): JacobianPoint {
return new JacobianPoint(this.x, this.y.redNeg(), this.z)
}
/**
* Addition operation in the Jacobian coordinates. It takes a Jacobian point as an argument
* and returns a new Jacobian point as a result of the addition. In the special cases,
* when either one of the points is the point at infinity, it will return the other point.
*
* @method add
* @param p - The Jacobian point to be added.
* @returns Returns a new Jacobian point as the result of the addition.
*
* @example
* const p1 = new JacobianPoint(x1, y1, z1)
* const p2 = new JacobianPoint(x2, y2, z2)
* const result = p1.add(p2)
*/
add (p: JacobianPoint): JacobianPoint {
// O + P = P
if (this.isInfinity()) {
return p
}
// P + O = P
if (p.isInfinity()) {
return this
}
// 12M + 4S + 7A
const pz2 = p.z.redSqr()
const z2 = this.z.redSqr()
const u1 = this.x.redMul(pz2)
const u2 = p.x.redMul(z2)
const s1 = this.y.redMul(pz2.redMul(p.z))
const s2 = p.y.redMul(z2.redMul(this.z))
const h = u1.redSub(u2)
const r = s1.redSub(s2)
if (h.cmpn(0) === 0) {
if (r.cmpn(0) !== 0) {
return new JacobianPoint(null, null, null)
} else {
return this.dbl()
}
}
const h2 = h.redSqr()
const h3 = h2.redMul(h)
const v = u1.redMul(h2)
const nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v)
const ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3))
const nz = this.z.redMul(p.z).redMul(h)
return new JacobianPoint(nx, ny, nz)
}
/**
* Mixed addition operation. This function combines the standard point addition with
* the transformation from the affine to Jacobian coordinates. It first converts
* the affine point to Jacobian, and then preforms the addition.
*
* @method mixedAdd
* @param p - The affine point to be added.
* @returns Returns the result of the mixed addition as a new Jacobian point.
*
* @example
* const jp = new JacobianPoint(x1, y1, z1)
* const ap = new Point(x2, y2)
* const result = jp.mixedAdd(ap)
*/
mixedAdd (p: Point): JacobianPoint {
// O + P = P
if (this.isInfinity()) {
return p.toJ()
}
// P + O = P
if (p.isInfinity()) {
return this
}
// Ensure x and y are not null
if (p.x === null || p.y === null) {
throw new Error('Point coordinates cannot be null')
}
// 8M + 3S + 7A
const z2 = this.z.redSqr()
const u1 = this.x
const u2 = p.x.redMul(z2)
const s1 = this.y
const s2 = p.y.redMul(z2).redMul(this.z)
const h = u1.redSub(u2)
const r = s1.redSub(s2)
if (h.cmpn(0) === 0) {
if (r.cmpn(0) !== 0) {
return new JacobianPoint(null, null, null)
} else {
return this.dbl()
}
}
const h2 = h.redSqr()
const h3 = h2.redMul(h)
const v = u1.redMul(h2)
const nx = r.redSqr().redIAdd(h3).redISub(v).redISub(v)
const ny = r.redMul(v.redISub(nx)).redISub(s1.redMul(h3))
const nz = this.z.redMul(h)
return new JacobianPoint(nx, ny, nz)
}
/**
* Multiple doubling operation. It doubles the Jacobian point as many times as the pow parameter specifies. If pow is 0 or the point is the point at infinity, it will return the point itself.
*
* @method dblp
* @param pow - The number of times the point should be doubled.
* @returns Returns a new Jacobian point as the result of multiple doublings.
*
* @example
* const jp = new JacobianPoint(x, y, z)
* const result = jp.dblp(3)
*/
dblp (pow: number): JacobianPoint {
if (pow === 0) {
return this
}
if (this.isInfinity()) {
return this
}
if (typeof pow === 'undefined') {
return this.dbl()
}
/* eslint-disable @typescript-eslint/no-this-alias */
let r = this as JacobianPoint
for (let i = 0; i < pow; i++) {
r = r.dbl()
}
return r
}
/**
* Point doubling operation in the Jacobian coordinates. A special case is when the point is the point at infinity, in this case, this function will return the point itself.
*
* @method dbl
* @returns Returns a new Jacobian point as the result of the doubling.
*
* @example
* const jp = new JacobianPoint(x, y, z)
* const result = jp.dbl()
*/
dbl (): JacobianPoint {
if (this.isInfinity()) {
return this
}
let nx
let ny
let nz
// Z = 1
if (this.zOne) {
// hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html
// #doubling-mdbl-2007-bl
// 1M + 5S + 14A
// XX = X1^2
const xx = this.x.redSqr()
// YY = Y1^2
const yy = this.y.redSqr()
// YYYY = YY^2
const yyyy = yy.redSqr()
// S = 2 * ((X1 + YY)^2 - XX - YYYY)
let s = this.x.redAdd(yy).redSqr().redISub(xx).redISub(yyyy)
s = s.redIAdd(s)
// M = 3 * XX + a; a = 0
const m = xx.redAdd(xx).redIAdd(xx)
// T = M ^ 2 - 2*S
const t = m.redSqr().redISub(s).redISub(s)
// 8 * YYYY
let yyyy8 = yyyy.redIAdd(yyyy)
yyyy8 = yyyy8.redIAdd(yyyy8)
yyyy8 = yyyy8.redIAdd(yyyy8)
// X3 = T
nx = t
// Y3 = M * (S - T) - 8 * YYYY
ny = m.redMul(s.redISub(t)).redISub(yyyy8)
// Z3 = 2*Y1
nz = this.y.redAdd(this.y)
} else {
// hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html
// #doubling-dbl-2009-l
// 2M + 5S + 13A
// A = X1^2
const a = this.x.redSqr()
// B = Y1^2
const b = this.y.redSqr()
// C = B^2
const c = b.redSqr()
// D = 2 * ((X1 + B)^2 - A - C)
let d = this.x.redAdd(b).redSqr().redISub(a).redISub(c)
d = d.redIAdd(d)
// E = 3 * A
const e = a.redAdd(a).redIAdd(a)
// F = E^2
const f = e.redSqr()
// 8 * C
let c8 = c.redIAdd(c)
c8 = c8.redIAdd(c8)
c8 = c8.redIAdd(c8)
// X3 = F - 2 * D
nx = f.redISub(d).redISub(d)
// Y3 = E * (D - X3) - 8 * C
ny = e.redMul(d.redISub(nx)).redISub(c8)
// Z3 = 2 * Y1 * Z1
nz = this.y.redMul(this.z)
nz = nz.redIAdd(nz)
}
return new JacobianPoint(nx, ny, nz)
}
/**
* Equality check operation. It checks whether the affine or Jacobian point is equal to this Jacobian point.
*
* @method eq
* @param p - The affine or Jacobian point to compare with.
* @returns Returns true if the points are equal, otherwise returns false.
*
* @example
* const jp1 = new JacobianPoint(x1, y1, z1)
* const jp2 = new JacobianPoint(x2, y2, z2)
* const areEqual = jp1.eq(jp2)
*/
eq (p: Point | JacobianPoint): boolean {
if (p.type === 'affine') {
return this.eq((p as Point).toJ())
}
if (this === p) {
return true
}
// x1 * z2^2 == x2 * z1^2
const z2 = this.z.redSqr()
p = p as JacobianPoint
const pz2 = p.z.redSqr()
if (this.x.redMul(pz2).redISub(p.x.redMul(z2)).cmpn(0) !== 0) {
return false
}
// y1 * z2^3 == y2 * z1^3
const z3 = z2.redMul(this.z)
const pz3 = pz2.redMul(p.z)
return this.y.redMul(pz3).redISub(p.y.redMul(z3)).cmpn(0) === 0
}
/**
* Equality check operation in relation to an x coordinate of a point in projective coordinates.
* It checks whether the x coordinate of the Jacobian point is equal to the provided x coordinate
* of a point in projective coordinates.
*
* @method eqXToP
* @param x - The x coordinate of a point in projective coordinates.
* @returns Returns true if the x coordinates are equal, otherwise returns false.
*
* @example
* const jp = new JacobianPoint(x1, y1, z1)
* const isXEqual = jp.eqXToP(x2)
*/
eqXToP (x: BigNumber): boolean {
const zs = this.z.redSqr()
const rx = x.toRed(this.curve?.red).redMul(zs)
if (this.x.cmp(rx) === 0) {
return true
}
const xc = x.clone()
if (this.curve === null || (this.curve.redN == null)) {
throw new Error('Curve or redN is not initialized.')
}
const t = this.curve.redN.redMul(zs)
while (xc.cmp(this.curve.p) < 0) {
xc.iadd(this.curve.n)
if (xc.cmp(this.curve.p) >= 0) {
return false
}
rx.redIAdd(t)
if (this.x.cmp(rx) === 0) {
return true
}
}
// ✅ Ensure function always returns a boolean
return false
}
/**
* Returns the string representation of the JacobianPoint instance.
* @method inspect
* @returns Returns the string description of the JacobianPoint. If the JacobianPoint represents a point at infinity, the return value of this function is '<EC JPoint Infinity>'. For a normal point, it returns the string description format as '<EC JPoint x: x-coordinate y: y-coordinate z: z-coordinate>'.
*
* @example
* const point = new JacobianPoint('5', '6', '1');
* console.log(point.inspect()); // Output: '<EC JPoint x: 5 y: 6 z: 1>'
*/
inspect (): string {
if (this.isInfinity()) {
return '<EC JPoint Infinity>'
}
return (
'<EC JPoint x: ' +
this.x.toString(16, 2) +
' y: ' +
this.y.toString(16, 2) +
' z: ' +
this.z.toString(16, 2) +
'>'
)
}
/**
* Checks whether the JacobianPoint instance represents a point at infinity.
* @method isInfinity
* @returns Returns true if the JacobianPoint's z-coordinate equals to zero (which represents the point at infinity in Jacobian coordinates). Returns false otherwise.
*
* @example
* const point = new JacobianPoint('5', '6', '0');
* console.log(point.isInfinity()); // Output: true
*/
isInfinity (): boolean {
return this.z.cmpn(0) === 0
}
}