// ASCII characters
static C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const
- static _ch (ch: number): number | undefined {
- if (ch >= this.C._0 && ch <= this.C._9) return ch - this.C._0 // '2' => 50-48
- if (ch >= this.C.A && ch <= this.C.F) return ch - (this.C.A - 10) // 'B' => 66-(65-10)
- if (ch >= this.C.a && ch <= this.C.f) return ch - (this.C.a - 10) // 'b' => 98-(97-10)
+ static _char (char: number): number | undefined {
+ if (char >= this.C._0 && char <= this.C._9) return char - this.C._0 // '2' => 50-48
+ if (char >= this.C.A && char <= this.C.F) return char - (this.C.A - 10) // 'B' => 66-(65-10)
+ if (char >= this.C.a && char <= this.C.f) return char - (this.C.a - 10) // 'b' => 98-(97-10)
return
}
const array = new Uint8Array(al)
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
// treat each char as ASCII
- const n1 = this._ch(hex.charCodeAt(hi)) // parse first char, multiply it by 16
- const n2 = this._ch(hex.charCodeAt(hi + 1)) // parse second char
+ const n1 = this._char(hex.charCodeAt(hi)) // parse first char, multiply it by 16
+ const n2 = this._char(hex.charCodeAt(hi + 1)) // parse second char
if (n1 === undefined || n2 === undefined) return this.err(e)
array[ai] = n1 * 16 + n2 // example: 'A9' => 10*16 + 9
}
return r
}
- /** WebCrypto OS-level CSPRNG (random number generator). Will throw when not available. */
- static randomBytes = (len: number = this.L): Bytes => {
- return crypto.getRandomValues(new Uint8Array(len))
- }
-
- static arange (n: bigint, min: bigint, max: bigint, msg = 'bad number: out of range'): bigint {
+ static bigintInRange (n: bigint, min: bigint, max: bigint, msg?: string): bigint {
return typeof n === 'bigint' && min <= n && n < max
? n
- : this.err(msg)
+ : this.err(msg ?? 'bigint out of range')
}
/** modular division */
/** secp256k1 formula. Koblitz curves are subclass of weierstrass curves with a=0, making it x³+b */
static koblitz = (x: bigint) => this.M(this.M(x * x) * x + this._b)
/** assert is element of field mod P (incl. 0) */
- static FpIsValid = (n: bigint) => this.arange(n, 0n, this.P)
+ static FpIsValid = (n: bigint) => this.bigintInRange(n, 0n, this.P)
/** assert is element of field mod P (excl. 0) */
- static FpIsValidNot0 = (n: bigint) => this.arange(n, 1n, this.P)
+ static FpIsValidNot0 = (n: bigint) => this.bigintInRange(n, 1n, this.P)
/** assert is element of field mod N (excl. 0) */
- static FnIsValidNot0 = (n: bigint) => this.arange(n, 1n, this.N)
+ static FnIsValidNot0 = (n: bigint) => this.bigintInRange(n, 1n, this.N)
static isEven = (y: bigint) => (y & 1n) === 0n
/** create Uint8Array of byte n */
static u8of = (n: number): Bytes => Uint8Array.of(n)
/** Number to 32b. Must be 0 <= num < 2²⁵⁶. validate, pad, to bytes. */
static bigintTo32Bytes (num: bigint): Bytes {
return this.hexToBytes(this
- .arange(num, 0n, 2n ** 256n) // secp256k1 is weierstrass curve. Equation is x³ + ax + b.
+ .bigintInRange(num, 0n, 2n ** 256n) // secp256k1 is weierstrass curve. Equation is x³ + ax + b.
.toString(16)
.padStart(this.L2, '0')
)
/** Normalize private key to scalar (bigint). Verifies scalar is in range 1<s<N */
static secretKeyToScalar = (secretKey: Bytes): bigint => {
const num = this.bytesToBigint(this.abytes(secretKey, this.L, 'secret key'))
- return this.arange(num, 1n, this.N, 'invalid secret key: outside of range')
+ return this.bigintInRange(num, 1n, this.N, 'invalid secret key: outside of range')
}
/** For Signature malleability, validates sig.s is bigger than N/2. */
static highS = (n: bigint): boolean => n > this.N >> 1n
static signatureFromBytes (b: Bytes, format: ECDSASignatureFormat = this.SIG_COMPACT): Signature {
this.assertSigLength(b, format)
- let rec: number | undefined
+ let recoveryBit: number | undefined
if (format === this.SIG_RECOVERED) {
- rec = b[0]
+ recoveryBit = b[0]
b = b.subarray(1)
}
const r = this.sliceBytesNumBE(b, 0, this.L)
const s = this.sliceBytesNumBE(b, this.L, this.L2)
- return this.Signature(r, s, rec)
+ return this.Signature(r, s, recoveryBit)
}
/**
extraEntropy: false,
}
- static _sha = 'SHA-256'
static hashes = {
hmacSha256Async: async (key: Bytes, message: Bytes): Promise<Bytes> => {
const name = 'HMAC'
- const k = await crypto.subtle.importKey('raw', key, { name, hash: { name: this._sha } }, false, ['sign'])
+ const k = await crypto.subtle.importKey('raw', key, { name, hash: { name: 'SHA-256' } }, false, ['sign'])
return new Uint8Array(await crypto.subtle.sign(name, k, message))
},
hmacSha256: undefined as undefined | ((key: Bytes, message: Bytes) => Bytes),
- sha256Async: async (msg: Bytes): Promise<Bytes> => new Uint8Array(await crypto.subtle.digest(this._sha, msg)),
+ sha256Async: async (msg: Bytes): Promise<Bytes> => new Uint8Array(await crypto.subtle.digest('SHA-256', msg)),
sha256: undefined as undefined | ((message: Bytes) => Bytes),
}
- static prepMsg = (msg: Bytes, opts: ECDSARecoverOpts, async_: boolean): Bytes | Promise<Bytes> => {
+ static prepMsg (msg: Bytes, opts: ECDSARecoverOpts, async_: boolean): Bytes | Promise<Bytes> {
this.abytes(msg, undefined, 'message')
if (!opts.prehash) return msg
return async_ ? this.hashes.sha256Async(msg) : this.callHash('sha256')(msg)
if (extraEntropy != null && extraEntropy !== false) {
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k')
// gen random bytes OR pass as-is
- const e = extraEntropy === true ? this.randomBytes(this.L) : extraEntropy
+ const e = extraEntropy === true ? crypto.getRandomValues(new Uint8Array(this.L)) : extraEntropy
seedArgs.push(this.abytes(e, undefined, 'extraEntropy')) // check for being bytes
}
const seed = this.concatBytes(...seedArgs)
// FIPS 186 B.4.1 compliant key generation produces private keys
// with modulo bias being neglible. takes >N+16 bytes, returns (hash mod n-1)+1
- static randomSecretKey = (seed = this.randomBytes(this.lengths.seed)) => {
+ static randomSecretKey (seed?: Bytes) {
+ seed ??= crypto.getRandomValues(new Uint8Array(this.lengths.seed))
this.abytes(seed)
if (seed.length < this.lengths.seed || seed.length > 1024) this.err('expected 40-1024b')
const num = this.M(this.bytesToBigint(seed), this.N - 1n)
numberToBytesBE: this.bigintTo32Bytes as (n: bigint) => Bytes,
mod: this.M as (a: bigint, md?: bigint) => bigint,
invert: this.invert as (num: bigint, md?: bigint) => bigint, // math utilities
- randomBytes: this.randomBytes as (len?: number) => Bytes,
secretKeyToScalar: this.secretKeyToScalar as typeof this.secretKeyToScalar,
abytes: this.abytes as typeof this.abytes,
}
* Creates Schnorr signature as per BIP340. Verifies itself before returning anything.
* auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous.
*/
- static signSchnorr = (message: Bytes, secretKey: Bytes, auxRand: Bytes = this.randomBytes(this.L)): Bytes => {
+ static signSchnorr = (message: Bytes, secretKey: Bytes, auxRand?: Bytes): Bytes => {
+ auxRand ??= crypto.getRandomValues(new Uint8Array(this.L))
const { m, px, d, a } = this.prepSigSchnorr(message, secretKey, auxRand)
const aux = this.taggedHash(this.T_AUX, a)
// Let t be the byte-wise xor of bytes(d) and hash/aux(a)
return sig
}
- static signSchnorrAsync = async (
- message: Bytes,
- secretKey: Bytes,
- auxRand: Bytes = this.randomBytes(this.L)
- ): Promise<Bytes> => {
+ static async signSchnorrAsync (message: Bytes, secretKey: Bytes, auxRand?: Bytes): Promise<Bytes> {
+ auxRand ??= crypto.getRandomValues(new Uint8Array(this.L))
const { m, px, d, a } = this.prepSigSchnorr(message, secretKey, auxRand)
const aux = await this.taggedHashAsync(this.T_AUX, a)
// Let t be the byte-wise xor of bytes(d) and hash/aux(a)
return res instanceof Promise ? res.then(later) : later(res)
}
- static _verifSchnorr = (
+ static _verifSchnorr (
signature: Bytes,
message: Bytes,
publicKey: Bytes,
challengeFn: (...args: Bytes[]) => bigint | Promise<bigint>
- ): boolean | Promise<boolean> => {
+ ): boolean | Promise<boolean> {
const sig = this.abytes(signature, this.L2, 'signature')
const msg = this.abytes(message, undefined, 'message')
const pub = this.abytes(publicKey, this.L, 'publicKey')
const px = this.bigintTo32Bytes(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.
- this.arange(r, 1n, this.P)
+ this.bigintInRange(r, 1n, this.P)
const s = this.sliceBytesNumBE(sig, this.L, this.L2) // Let s = int(sig[32:64]); fail if s ≥ n.
- this.arange(s, 1n, this.N)
+ this.bigintInRange(s, 1n, this.N)
const i = this.concatBytes(this.bigintTo32Bytes(r), px, msg)
// int(challenge(bytes(r)||bytes(P)||m))%n
return this.callSyncAsyncFn(challengeFn(i), (e) => {
static scalarBits = 256
static pwindows = Math.ceil(this.scalarBits / this.W) + 1 // 33 for W=8, NOT 32 - see wNAF loop
static pwindowSize = 2 ** (this.W - 1) // 128 for W=8
- static precompute = () => {
+ static precompute () {
const points: Point[] = []
let p = this.G
let b = p
}
static Gpows: Point[] | undefined = undefined // precomputes for base point G
// const-time negate
- static ctneg = (cnd: boolean, p: Point) => {
+ static ctneg (cnd: boolean, p: Point) {
const n = p.negate()
return cnd ? n : p
}
wbits -= maxNum
n += 1n
}
- const off = w * this.pwindowSize
- const offF = off // offsets, evaluate both
- const offP = off + Math.abs(wbits) - 1
+ const offset = w * this.pwindowSize
+ const offsetF = offset // offsets, evaluate both
+ const offsetP = offset + Math.abs(wbits) - 1
const isEven = w % 2 !== 0 // conditions, evaluate both
const isNeg = wbits < 0
if (wbits === 0) {
// off == I: can't add it. Adding random offF instead.
- f = f.add(this.ctneg(isEven, comp[offF])) // bits are 0: add garbage to fake point
+ f = f.add(this.ctneg(isEven, comp[offsetF])) // bits are 0: add garbage to fake point
} else {
- p = p.add(this.ctneg(isNeg, comp[offP])) // bits are 1: add to result point
+ p = p.add(this.ctneg(isNeg, comp[offsetP])) // bits are 1: add to result point
}
}
if (n !== 0n) this.err('invalid wnaf')