static L: 32 = 32 // field / group byte length
static C: 33 = 33 // byte length of SEC1 compressed form of coordinate pair
+ /** Generator / base point */
+ static G: Point = this.Point(this.Gx, this.Gy, 1n)
+ /** Identity / zero point */
+ static I: Point = this.Point(0n, 1n, 0n)
+
// ## Helpers
// ----------
static err (message = ''): never {
return value
}
- static hexToBytes (hex: string): Bytes {
- if (!/[0-9A-Fa-f]+/.test(hex ?? '')) return this.err('hex invalid')
- if (hex.length % 2) hex = `0${hex}`
- const intArray = hex.match(/.{2}/g)?.map(v => parseInt(v, 16))
- return new Uint8Array(intArray ?? [])
- }
-
- // prettier-ignore
- static concatBytes (...arrs: Bytes[]): Bytes {
- const r = new Uint8Array(arrs.reduce((sum, a) => sum + this.abytes(a).length, 0)) // create u8a of summed length
- let pad = 0 // walk through each array,
- arrs.forEach(a => { r.set(a, pad); pad += a.length }) // ensure they have proper type
- return r
- }
-
static bigintInRange (n: bigint, min: bigint, max: bigint, msg?: string): bigint {
return typeof n === 'bigint' && min <= n && n < max
? n
/** modular division */
static M (a: bigint): bigint {
- const r = a % this.P
- return r >= 0n ? r : this.P + r
+ const p = this.P, r = a % p
+ return r >= 0n ? r : p + r
}
/** Modular inversion using eucledian GCD (non-CT). No negative exponent for now. */
/** secp256k1 formula. Koblitz curves are subclass of weierstrass curves with a=0, making it x³+b */
static koblitz = (x: bigint): bigint => this.M(this.M(x * x) * x + this.b)
static isEven = (y: bigint): boolean => (y & 1n) === 0n
- static getPrefix = (y: bigint) => Uint8Array.of(this.isEven(y) ? 0x02 : 0x03)
/** lift_x from BIP340 calculates square root. Validates x, then validates root*root. */
static lift_x (x: bigint): bigint {
// Let c = x³ + 7 mod p. Fail if x ≥ p. (also fail if x < 1)
static Add (p: Point, q: Point): Point {
const { X: X1, Y: Y1, Z: Z1 } = p
const { X: X2, Y: Y2, Z: Z2 } = q
- const P = this.P
const a = this.a
const b3 = this.M(this.b * 3n)
let X3 = 0n, Y3 = 0n, Z3 = 0n
/** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */
static Point (X: bigint, Y: bigint, Z: bigint): Point {
- const secp256k1 = this
return Object.freeze({
X: this.bigintInRange(X, 0n, this.P),
Y: this.bigintInRange(Y, 1n, this.P), // Y can't be 0 in Projective
})
}
- /** Convert Uint8Array or hex string to Point. */
- static pointFromBytes (bytes: Bytes): Point {
- this.abytes(bytes)
- let p: Point | undefined = undefined
- const length = bytes.length
- const head = bytes[0]
- const tail = bytes.subarray(1)
- const x = this.bytesToBigint(tail.subarray(0, this.L))
- // No actual validation is done here: use .assertValidity()
- if (length === this.C && (head === 0x02 || head === 0x03)) {
- // Equation is y² == x³ + ax + b. We calculate y from x.
- // y = √y²; there are two solutions: y, -y. Determine proper solution based on prefix
- let y = this.lift_x(x)
- const evenY = this.isEven(y)
- const evenH = this.isEven(BigInt(head))
- if (evenH !== evenY) y = this.M(-y)
- p = this.Point(x, y, 1n)
- }
- // Validate point
- return this.isValidPoint(p)
- }
-
- /** Converts point to 33/65-byte Uint8Array. */
- static pointToBytes (p: Point): Bytes {
- const { x, y } = this.Affine(this.isValidPoint(p))
- const x32b = this.bigintTo32Bytes(x)
- return this.concatBytes(this.getPrefix(y), x32b)
- }
-
- /** Generator / base point */
- static G: Point = this.Point(this.Gx, this.Gy, 1n)
- /** Identity / zero point */
- static I: Point = this.Point(0n, 1n, 0n)
-
static bytesToBigint (b: Bytes): bigint {
let int = BigInt(b[0]), len = b.length
for (let i = 1; i < len; i++) {
return this.bigintInRange(num, 1n, this.N, 'invalid secret key: outside of range')
}
- /** Creates 33/65-byte public key from 32-byte private key. */
+ /** Derives a compressed 33-byte public key from a 32-byte private key. */
static getPublicKey (sk: Bytes): Bytes {
- const pk = this.pointToBytes(this.wNAF(this.secretKeyToScalar(sk)).p)
+ const p = this.wNAF(this.secretKeyToScalar(sk)).p
+ let { x, y } = this.Affine(this.isValidPoint(p))
+ const len = this.C
+ const pk = new Uint8Array(len)
+ for (let i = len - 1; i >= 1; i--) {
+ pk[i] = Number(x & 0xffn)
+ x >>= 8n
+ }
+ pk[0] = this.isEven(y) ? 0x02 : 0x03
return this.isValidPublicKey(pk) ? pk : this.err('derived invalid public key from secret key')
}
+ /** Converts point to 33-byte Uint8Array and checks validity. */
static isValidPublicKey (pk: Bytes): boolean {
+ if (pk.length !== this.C) return false
try {
- const l = pk.length
- if (l !== this.C) return false
- return !!this.pointFromBytes(pk)
+ this.abytes(pk)
+ let p: Point | undefined = undefined
+ const length = pk.length
+ const head = pk[0]
+ const tail = pk.subarray(1)
+ const x = this.bytesToBigint(tail.subarray(0, this.L))
+ // No actual validation is done here until calling isValidPoint()
+ if (length === this.C && (head === 0x02 || head === 0x03)) {
+ // Equation is y² == x³ + ax + b. We calculate y from x.
+ // y = √y²; there are two solutions: y, -y. Determine proper solution based on prefix
+ let y = this.lift_x(x)
+ const evenY = this.isEven(y)
+ const evenH = this.isEven(BigInt(head))
+ if (evenH !== evenY) y = this.M(-y)
+ p = this.Point(x, y, 1n)
+ }
+ // Validate point
+ return !!this.isValidPoint(p)
} catch (error) {
return false
}