--- /dev/null
+/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
+
+import { Point, Signature, hashes, signAsync, verifyAsync, recoverPublicKey, recoverPublicKeyAsync, getSharedSecret, getPublicKey, etc, utils } from "@noble/secp256k1"
+import { randomBytes, sign, verify } from "crypto"
+import { format } from "path"
+import { _verify } from "../wallet/verify"
+
+/**
+ * 5KB JS implementation of secp256k1 ECDSA / Schnorr signatures & ECDH.
+ * Compliant with RFC6979 & BIP340.
+ * @module
+ */
+
+/**
+ * Fork rereleased under GPLv3.
+ * Functionality wrapped in a class for embedding into a Web Worker.
+ */
+
+/** Alias to Uint8Array. */
+export type Bytes = Uint8Array
+/** Signature instance, which allows recovering pubkey from it. */
+export type RecoveredSignature = Secp256k1['Signature'] & { recovery: number }
+/** Weierstrass elliptic curve options. */
+export type WeierstrassOpts<T> = Readonly<{
+ p: bigint
+ n: bigint
+ h: bigint
+ a: T
+ b: T
+ Gx: T
+ Gy: T
+}>
+declare const globalThis: Record<string, any> | undefined // Typescript symbol present in browsers
+/** Point in 2d xy affine coordinates. */
+export type AffinePoint = {
+ x: bigint
+ y: bigint
+}
+export type ECDSAExtraEntropy = boolean | Bytes
+/**
+ * - `compact` is the default format
+ * - `recovered` is the same as compact, but with an extra byte indicating recovery byte
+ * - `der` is not supported; and provided for consistency.
+ * Switch to noble-curves if you need der.
+ */
+export type ECDSASignatureFormat = 'compact' | 'recovered' | 'der'
+/**
+ * - `prehash`: (default: true) indicates whether to do sha256(message).
+ * When a custom hash is used, it must be set to `false`.
+ */
+export type ECDSARecoverOpts = {
+ prehash?: boolean
+}
+/**
+ * - `prehash`: (default: true) indicates whether to do sha256(message).
+ * When a custom hash is used, it must be set to `false`.
+ * - `lowS`: (default: true) prohibits signatures which have (sig.s >= CURVE.n/2n).
+ * Compatible with BTC/ETH. Setting `lowS: false` allows to create malleable signatures,
+ * which is default openssl behavior.
+ * Non-malleable signatures can still be successfully verified in openssl.
+ * - `format`: (default: 'compact') 'compact' or 'recovered' with recovery byte
+ */
+export type ECDSAVerifyOpts = {
+ prehash?: boolean
+ lowS?: boolean
+ format?: ECDSASignatureFormat
+}
+/**
+ * - `prehash`: (default: true) indicates whether to do sha256(message).
+ * When a custom hash is used, it must be set to `false`.
+ * - `lowS`: (default: true) prohibits signatures which have (sig.s >= CURVE.n/2n).
+ * Compatible with BTC/ETH. Setting `lowS: false` allows to create malleable signatures,
+ * which is default openssl behavior.
+ * Non-malleable signatures can still be successfully verified in openssl.
+ * - `format`: (default: 'compact') 'compact' or 'recovered' with recovery byte
+ * - `extraEntropy`: (default: false) creates sigs with increased security, see {@link ECDSAExtraEntropy}
+ */
+export type ECDSASignOpts = {
+ prehash?: boolean
+ lowS?: boolean
+ format?: ECDSASignatureFormat
+ extraEntropy?: ECDSAExtraEntropy
+}
+type Pred<T> = (v: Bytes) => T | undefined
+
+export class Secp256k1 {
+ /**
+ * Curve params. secp256k1 is short weierstrass / koblitz curve. Equation is y² == x³ + ax + b.
+ * * P = `2n**256n-2n**32n-2n**977n` // field over which calculations are done
+ * * N = `2n**256n - 0x14551231950b75fc4402da1732fc9bebfn` // group order, amount of curve points
+ * * h = `1n` // cofactor
+ * * a = `0n` // equation param
+ * * b = `7n` // equation param
+ * * Gx, Gy are coordinates of Generator / base point
+ */
+ static P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn
+ static N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n
+ static h = 1n
+ static a = 0n
+ static _b = 7n
+ static Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n
+ static Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n
+
+ static secp256k1_CURVE: WeierstrassOpts<bigint> = {
+ p: this.P,
+ n: this.N,
+ h: 1n,
+ a: 0n,
+ b: this._b,
+ Gx: this.Gx,
+ Gy: this.Gy,
+ }
+
+ static L = 32 // field / group byte length
+ static L2 = 64
+ static lengths = {
+ publicKey: this.L + 1,
+ publicKeyUncompressed: this.L2 + 1,
+ signature: this.L2,
+ seed: this.L + this.L / 2,
+ }
+
+ // Helpers and Precomputes sections are reused between libraries
+
+ // ## Helpers
+ // ----------
+ static captureTrace = (...args: Parameters<typeof Error.captureStackTrace>): void => {
+ if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {
+ Error.captureStackTrace(...args)
+ }
+ }
+ static err = (message = ''): never => {
+ const e = new Error(message)
+ this.captureTrace(e, this.err)
+ throw e
+ }
+ static isBig = (n: unknown): n is bigint => typeof n === 'bigint' // is big integer
+ static isStr = (s: unknown): s is string => typeof s === 'string' // is string
+ static isBytes = (a: unknown): a is Uint8Array =>
+ a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array')
+ /** Asserts something is Uint8Array. */
+ static abytes = (value: Bytes, length?: number, title: string = ''): Bytes => {
+ const bytes = this.isBytes(value)
+ const len = value?.length
+ const needsLen = length !== undefined
+ if (!bytes || (needsLen && len !== length)) {
+ const prefix = title && `"${title}" `
+ const ofLen = needsLen ? ` of length ${length}` : ''
+ const got = bytes ? `length=${len}` : `type=${typeof value}`
+ this.err(prefix + 'expected Uint8Array' + ofLen + ', got ' + got)
+ }
+ return value
+ }
+ /** create Uint8Array */
+ static u8n = (len: number): Bytes => new Uint8Array(len)
+ static padh = (n: number | bigint, pad: number) => n.toString(16).padStart(pad, '0')
+ static bytesToHex = (b: Bytes): string =>
+ Array.from(this.abytes(b))
+ .map((e) => this.padh(e, 2))
+ .join('')
+ static C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const // ASCII characters
+ 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)
+ return
+ }
+ static hexToBytes = (hex: string): Bytes => {
+ const e = 'hex invalid'
+ if (!this.isStr(hex)) return this.err(e)
+ const hl = hex.length
+ const al = hl / 2
+ if (hl % 2) return this.err(e)
+ const array = this.u8n(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
+ if (n1 === undefined || n2 === undefined) return this.err(e)
+ array[ai] = n1 * 16 + n2 // example: 'A9' => 10*16 + 9
+ }
+ return array
+ }
+ static cr = () => globalThis?.crypto // WebCrypto is available in all modern environments
+ static subtle = () => this.cr()?.subtle ?? this.err('crypto.subtle must be defined, consider polyfill')
+ // prettier-ignore
+ static concatBytes = (...arrs: Bytes[]): Bytes => {
+ const r = this.u8n(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
+ }
+ /** WebCrypto OS-level CSPRNG (random number generator). Will throw when not available. */
+ static randomBytes = (len: number = this.L): Bytes => {
+ const c = this.cr()
+ return c.getRandomValues(this.u8n(len))
+ }
+ static big = BigInt
+ static arange = (n: bigint, min: bigint, max: bigint, msg = 'bad number: out of range'): bigint =>
+ this.isBig(n) && min <= n && n < max ? n : this.err(msg)
+ /** modular division */
+ static M = (a: bigint, b: bigint = this.P) => {
+ const r = a % b
+ return r >= 0n ? r : b + r
+ }
+ static modN = (a: bigint) => this.M(a, this.N)
+ /** Modular inversion using eucledian GCD (non-CT). No negative exponent for now. */
+ // prettier-ignore
+ static invert = (num: bigint, md: bigint): bigint => {
+ if (num === 0n || md <= 0n) this.err('no inverse n=' + num + ' mod=' + md)
+ let a = this.M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n
+ while (a !== 0n) {
+ const q = b / a, r = b % a
+ const m = x - u * q, n = y - v * q
+ b = a, a = r, x = u, y = v, u = m, v = n
+ }
+ return b === 1n ? this.M(x, md) : this.err('no inverse') // b is gcd at this point
+ }
+ static callHash = (name: string) => {
+ // @ts-ignore
+ const fn = hashes[name]
+ if (typeof fn !== 'function') this.err('hashes.' + name + ' not set')
+ return fn
+ }
+ static hash = (msg: Bytes): Bytes => this.callHash('sha256')(msg)
+ static apoint = (p: unknown) => (p instanceof this.Point ? p : this.err('Point expected'))
+ // ## End of Helpers
+ // -----------------
+
+ /** 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)
+ /** assert is element of field mod P (excl. 0) */
+ static FpIsValidNot0 = (n: bigint) => this.arange(n, 1n, this.P)
+ /** assert is element of field mod N (excl. 0) */
+ static FnIsValidNot0 = (n: bigint) => this.arange(n, 1n, this.N)
+ static isEven = (y: bigint) => (y & 1n) === 0n
+ /** create Uint8Array of byte n */
+ static u8of = (n: number): Bytes => Uint8Array.of(n)
+ static getPrefix = (y: bigint) => this.u8of(this.isEven(y) ? 0x02 : 0x03)
+ /** lift_x from BIP340 calculates square root. Validates x, then validates root*root. */
+ static lift_x = (x: bigint) => {
+ // Let c = x³ + 7 mod p. Fail if x ≥ p. (also fail if x < 1)
+ const c = this.koblitz(this.FpIsValidNot0(x))
+ // c = √y
+ // y = c^((p+1)/4) mod p
+ // This formula works for fields p = 3 mod 4 -- a special, fast case.
+ // Paper: "Square Roots from 1;24,51,10 to Dan Shanks".
+ let r = 1n
+ for (let num = c, e = (this.P + 1n) / 4n; e > 0n; e >>= 1n) {
+ // powMod: modular exponentiation.
+ if (e & 1n) r = (r * num) % this.P // Uses exponentiation by squaring.
+ num = (num * num) % this.P // Not constant-time.
+ }
+ return this.M(r * r) === c ? r : this.err('sqrt invalid') // check if result is valid
+ }
+
+ /** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */
+ static Point = class {
+ static BASE: Secp256k1['Point']
+ static ZERO: Secp256k1['Point']
+ 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)
+ }
+ static CURVE (): WeierstrassOpts<bigint> {
+ return Secp256k1.secp256k1_CURVE
+ }
+ /** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */
+ static fromAffine (ap: AffinePoint): Point {
+ const { x, y } = ap
+ return x === 0n && y === 0n ? Secp256k1.I : new Secp256k1.Point(x, y, 1n)
+ }
+ /** Convert Uint8Array or hex string to Point. */
+ static fromBytes (bytes: Bytes): Point {
+ Secp256k1.abytes(bytes)
+ const { publicKey: comp, publicKeyUncompressed: uncomp } = Secp256k1.lengths // e.g. for 32-byte: 33, 65
+ let p: Point | undefined = undefined
+ const length = bytes.length
+ const head = bytes[0]
+ const tail = bytes.subarray(1)
+ const x = Secp256k1.sliceBytesNumBE(tail, 0, Secp256k1.L)
+ // No actual validation is done here: use .assertValidity()
+ if (length === comp && (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 = Secp256k1.lift_x(x)
+ const evenY = Secp256k1.isEven(y)
+ const evenH = Secp256k1.isEven(Secp256k1.big(head))
+ if (evenH !== evenY) y = Secp256k1.M(-y)
+ p = new Point(x, y, 1n)
+ }
+ // Uncompressed 65-byte point, 0x04 prefix
+ if (length === uncomp && head === 0x04) p = new Point(x, Secp256k1.sliceBytesNumBE(tail, Secp256k1.L, Secp256k1.L2), 1n)
+ // Validate point
+ return p ? p.assertValidity() : Secp256k1.err('bad point: not on curve')
+ }
+ static fromHex (hex: string): Point {
+ return Secp256k1.Point.fromBytes(Secp256k1.hexToBytes(hex))
+ }
+ 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(I)
+ }
+ /** Flip point over y coordinate. */
+ negate (): Point {
+ return new 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 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 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(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))
+ }
+ }
+ /** Generator / base point */
+ static G: Point = new Point(this.Gx, this.Gy, 1n)
+ /** Identity / zero point */
+ static I: Point = new Point(0n, 1n, 0n)
+ // Static aliases
+ static {
+ this.Point.BASE = this.G
+ this.Point.ZERO = this.I
+ }
+ /** `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()
+ }
+ static bytesToNumBE = (b: Bytes): bigint => this.big('0x' + (this.bytesToHex(b) || '0'))
+ static sliceBytesNumBE = (b: Bytes, from: number, to: number) => this.bytesToNumBE(b.subarray(from, to))
+ static B256 = 2n ** 256n // secp256k1 is weierstrass curve. Equation is x³ + ax + b.
+ /** Number to 32b. Must be 0 <= num < B256. validate, pad, to bytes. */
+ static numTo32b = (num: bigint): Bytes => this.hexToBytes(this.padh(this.arange(num, 0n, this.B256), this.L2))
+ /** Normalize private key to scalar (bigint). Verifies scalar is in range 1<s<N */
+ static secretKeyToScalar = (secretKey: Bytes): bigint => {
+ const num = this.bytesToNumBE(this.abytes(secretKey, this.L, 'secret key'))
+ return this.arange(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
+ /** Creates 33/65-byte public key from 32-byte private key. */
+ static getPublicKey = (privKey: Bytes, isCompressed = true): Bytes => {
+ return this.G.multiply(this.secretKeyToScalar(privKey)).toBytes(isCompressed)
+ }
+
+ static isValidSecretKey = (secretKey: Bytes): boolean => {
+ try {
+ return !!this.secretKeyToScalar(secretKey)
+ } catch (error) {
+ return false
+ }
+ }
+ static isValidPublicKey = (publicKey: Bytes, isCompressed?: boolean): boolean => {
+ const { publicKey: comp, publicKeyUncompressed } = this.lengths
+ try {
+ const l = publicKey.length
+ if (isCompressed === true && l !== comp) return false
+ if (isCompressed === false && l !== publicKeyUncompressed) return false
+ return !!this.Point.fromBytes(publicKey)
+ } catch (error) {
+ return false
+ }
+ }
+
+ static assertRecoveryBit = (recovery?: number) => {
+ if (![0, 1, 2, 3].includes(recovery!)) this.err('recovery id must be valid and present')
+ }
+ static assertSigFormat = (format?: ECDSASignatureFormat) => {
+ if (format != null && !ALL_SIG.includes(format))
+ err(`Signature format must be one of: ${ALL_SIG.join(', ')}`)
+ if (format === SIG_DER) this.err('Signature format "der" is not supported: switch to noble-curves')
+ }
+ static assertSigLength = (sig: Bytes, format: ECDSASignatureFormat = SIG_COMPACT) => {
+ this.assertSigFormat(format)
+ const SL = this.lengths.signature
+ const RL = SL + 1
+ let msg = `Signature format "${format}" expects Uint8Array with length `
+ if (format === SIG_COMPACT && sig.length !== SL) this.err(msg + SL)
+ if (format === SIG_RECOVERED && sig.length !== RL) this.err(msg + RL)
+ }
+ /** ECDSA Signature class. Supports only compact 64-byte representation, not DER. */
+ static Signature = class {
+ readonly r: bigint
+ readonly s: bigint
+ readonly recovery?: number
+ constructor (r: bigint, s: bigint, recovery?: number) {
+ this.r = Secp256k1.FnIsValidNot0(r) // 1 <= r < N
+ this.s = Secp256k1.FnIsValidNot0(s) // 1 <= s < N
+ if (recovery != null) this.recovery = recovery
+ Object.freeze(this)
+ }
+ static fromBytes (b: Bytes, format: ECDSASignatureFormat = SIG_COMPACT): Secp256k1['Signature'] {
+ Secp256k1.assertSigLength(b, format)
+ let rec: number | undefined
+ if (format === SIG_RECOVERED) {
+ rec = b[0]
+ b = b.subarray(1)
+ }
+ const r = Secp256k1.sliceBytesNumBE(b, 0, Secp256k1.L)
+ const s = Secp256k1.sliceBytesNumBE(b, Secp256k1.L, Secp256k1.L2)
+ return new Signature(r, s, rec)
+ }
+ addRecoveryBit (bit: number): RecoveredSignature {
+ return new Signature(this.r, this.s, bit) as RecoveredSignature
+ }
+ hasHighS (): boolean {
+ return Secp256k1.highS(this.s)
+ }
+ toBytes (format: ECDSASignatureFormat = SIG_COMPACT): Bytes {
+ const { r, s, recovery } = this
+ const res = Secp256k1.concatBytes(Secp256k1.numTo32b(r), Secp256k1.numTo32b(s))
+ if (format === SIG_RECOVERED) {
+ Secp256k1.assertRecoveryBit(recovery)
+ return Secp256k1.concatBytes(Uint8Array.of(recovery!), res)
+ }
+ return res
+ }
+ }
+
+ /**
+ * RFC6979: ensure ECDSA msg is X bytes, convert to BigInt.
+ * RFC suggests optional truncating via bits2octets.
+ * FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits,
+ * which matches bits2int. bits2int can produce res>N.
+ */
+ static bits2int = (bytes: Bytes): bigint => {
+ const delta = bytes.length * 8 - 256
+ if (delta > 1024) this.err('msg invalid') // our CUSTOM check, "just-in-case": prohibit long inputs
+ const num = this.bytesToNumBE(bytes)
+ return delta > 0 ? num >> this.big(delta) : num
+ }
+ /** int2octets can't be used; pads small msgs with 0: BAD for truncation as per RFC vectors */
+ static bits2int_modN = (bytes: Bytes): bigint => this.modN(this.bits2int(this.abytes(bytes)))
+ /**
+ * Option to enable hedged signatures with improved security.
+ *
+ * * Randomly generated k is bad, because broken CSPRNG would leak private keys.
+ * * Deterministic k (RFC6979) is better; but is suspectible to fault attacks.
+ *
+ * We allow using technique described in RFC6979 3.6: additional k', a.k.a. adding randomness
+ * to deterministic sig. If CSPRNG is broken & randomness is weak, it would STILL be as secure
+ * as ordinary sig without ExtraEntropy.
+ *
+ * * `true` means "fetch data, from CSPRNG, incorporate it into k generation"
+ * * `false` means "disable extra entropy, use purely deterministic k"
+ * * `Uint8Array` passed means "incorporate following data into k generation"
+ *
+ * https://paulmillr.com/posts/deterministic-signatures/
+ */
+ // todo: better name
+ static SIG_COMPACT: ECDSASignatureFormat = 'compact'
+ static SIG_RECOVERED = 'recovered'
+ static SIG_DER = 'der'
+ static ALL_SIG = [this.SIG_COMPACT, this.SIG_RECOVERED, this.SIG_DER] as const
+
+ static defaultSignOpts: ECDSASignOpts = {
+ lowS: true,
+ prehash: true,
+ format: this.SIG_COMPACT,
+ extraEntropy: false,
+ }
+
+ static _sha = 'SHA-256'
+ static hashes = {
+ hmacSha256Async: async (key: Bytes, message: Bytes): Promise<Bytes> => {
+ const s = this.subtle()
+ const name = 'HMAC'
+ const k = await s.importKey('raw', key, { name, hash: { name: this._sha } }, false, ['sign'])
+ return this.u8n(await s.sign(name, k, message))
+ },
+ hmacSha256: undefined as undefined | ((key: Bytes, message: Bytes) => Bytes),
+ sha256Async: async (msg: Bytes): Promise<Bytes> => this.u8n(await sthis.ubtle().digest(this._sha, msg)),
+ sha256: undefined as undefined | ((message: Bytes) => 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)
+ }
+
+ static NULL = this.u8n(0)
+ static byte0 = this.u8of(0x00)
+ static byte1 = this.u8of(0x01)
+ static _maxDrbgIters = 1000
+ static _drbgErr = 'drbg: tried max amount of iterations'
+ // HMAC-DRBG from NIST 800-90. Minimal, non-full-spec - used for RFC6979 signatures.
+ static hmacDrbg = (seed: Bytes, pred: Pred<Bytes>): Bytes => {
+ let v = u8n(L) // Steps B, C of RFC6979 3.2: set hashLen
+ let k = u8n(L) // In our case, it's always equal to L
+ let i = 0 // Iterations counter, will throw when over max
+ static reset = () => {
+ v.fill(1)
+ k.fill(0)
+ }
+ // h = hmac(K || V || ...)
+ static h = (...b: Bytes[]) => callHash('hmacSha256')(k, concatBytes(v, ...b))
+ static reseed = (seed = NULL) => {
+ // HMAC-DRBG reseed() function. Steps D-G
+ k = h(byte0, seed) // k = hmac(k || v || 0x00 || seed)
+ v = h() // v = hmac(k || v)
+ if (seed.length === 0) return
+ k = h(byte1, seed) // k = hmac(k || v || 0x01 || seed)
+ v = h() // v = hmac(k || v)
+ }
+ // HMAC-DRBG generate() function
+ static gen = () => {
+ if (i++ >= _maxDrbgIters) err(_drbgErr)
+ v = h() // v = hmac(k || v)
+ return v // this diverges from noble-curves: we don't allow arbitrary output len!
+ }
+ reset ()
+ reseed (seed) // Steps D-G
+ let res: Bytes | undefined = undefined // Step H: grind until k is in [1..n-1]
+while (!(res = pred(gen()))) reseed() // test predicate until it returns ok
+reset()
+return res!
+}
+
+// Identical to hmacDrbg, but async: uses built-in WebCrypto
+static hmacDrbgAsync = async (seed: Bytes, pred: Pred<Bytes>): Promise<Bytes> => {
+ let v = u8n(L) // Steps B, C of RFC6979 3.2: set hashLen
+ let k = u8n(L) // In our case, it's always equal to L
+ let i = 0 // Iterations counter, will throw when over max
+ static reset = () => {
+ v.fill(1)
+ k.fill(0)
+ }
+ // h = hmac(K || V || ...)
+ static h = (...b: Bytes[]) => hashes.hmacSha256Async(k, concatBytes(v, ...b))
+ static reseed = async (seed = NULL) => {
+ // HMAC-DRBG reseed() function. Steps D-G
+ k = await h(byte0, seed) // k = hmac(K || V || 0x00 || seed)
+ v = await h() // v = hmac(K || V)
+ if (seed.length === 0) return
+ k = await h(byte1, seed) // k = hmac(K || V || 0x01 || seed)
+ v = await h() // v = hmac(K || V)
+ }
+ // HMAC-DRBG generate() function
+ static gen = async () => {
+ if (i++ >= _maxDrbgIters) err(_drbgErr)
+ v = await h() // v = hmac(K || V)
+ return v // this diverges from noble-curves: we don't allow arbitrary output len!
+ }
+ reset()
+ await reseed(seed) // Steps D-G
+ let res: Bytes | undefined = undefined // Step H: grind until k is in [1..n-1]
+ while (!(res = pred(await gen()))) await reseed() // test predicate until it returns ok
+ reset()
+ return res!
+}
+
+// RFC6979 signature generation, preparation step.
+// Follows [SEC1](https://secg.org/sec1-v2.pdf) 4.1.2 & RFC6979.
+static _sign = <T> (
+ messageHash: Bytes,
+ secretKey: Bytes,
+ opts: ECDSASignOpts,
+ hmacDrbg: (seed: Bytes, pred: Pred<Bytes>) => T
+): T => {
+ let { lowS, extraEntropy } = opts // generates low-s sigs by default
+ // RFC6979 3.2: we skip step A
+ static int2octets = numTo32b // int to octets
+ static h1i = bits2int_modN(messageHash) // msg bigint
+ static h1o = int2octets(h1i) // msg octets
+ static d = secretKeyToScalar(secretKey) // validate private key, convert to bigint
+ static seedArgs = [int2octets(d), h1o] // Step D of RFC6979 3.2
+ /** RFC6979 3.6: additional k' (optional). See {@link ECDSAExtraEntropy}. */
+ 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 ? randomBytes(L) : extraEntropy
+ seedArgs.push(abytes(e, undefined, 'extraEntropy')) // check for being bytes
+ }
+ static seed = concatBytes(...seedArgs)
+ static m = h1i // convert msg to bigint
+ // Converts signature params into point w r/s, checks result for validity.
+ // To transform k => Signature:
+ // q = k⋅G
+ // r = q.x mod n
+ // s = k^-1(m + rd) mod n
+ // Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q−1] according to
+ // https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it:
+ // a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT
+ static k2sig = (kBytes: Bytes): Bytes | undefined => {
+ // RFC 6979 Section 3.2, step 3: k = bits2int(T)
+ // Important: all mod() calls here must be done over N
+ const k = bits2int(kBytes)
+ if (!(1n <= k && k < N)) return // Valid scalars (including k) must be in 1..N-1
+ const ik = invert(k, N) // k^-1 mod n
+ const q = G.multiply(k).toAffine() // q = k⋅G
+ const r = modN(q.x) // r = q.x mod n
+ if (r === 0n) return
+ const s = modN(ik * modN(m + r * d)) // s = k^-1(m + rd) mod n
+ if (s === 0n) return
+ let recovery = (q.x === r ? 0 : 2) | Number(q.y & 1n) // recovery bit (2 or 3, when q.x > n)
+ let normS = s // normalized S
+ if (lowS && highS(s)) {
+ // if lowS was passed, ensure s is always
+ normS = modN(-s) // in the bottom half of CURVE.n
+ recovery ^= 1
+ }
+ const sig = new Signature(r, normS, recovery) as RecoveredSignature // use normS, not s
+ return sig.toBytes(opts.format)
+ }
+ return hmacDrbg(seed, k2sig)
+}
+
+// Follows [SEC1](https://secg.org/sec1-v2.pdf) 4.1.4.
+static _verify = (sig: Bytes, messageHash: Bytes, publicKey: Bytes, opts: ECDSAVerifyOpts = {}) => {
+ static { lowS, format } = opts
+ if (sig instanceof Signature) err('Signature must be in Uint8Array, use .toBytes()')
+ assertSigLength(sig, format)
+ abytes(publicKey, undefined, 'publicKey')
+ try {
+ const { r, s } = Signature.fromBytes(sig, format)
+ const h = bits2int_modN(messageHash) // Truncate hash
+ const P = Point.fromBytes(publicKey) // Validate public key
+ if (lowS && highS(s)) return false // lowS bans sig.s >= CURVE.n/2
+ const is = invert(s, N) // s^-1
+ const u1 = modN(h * is) // u1 = hs^-1 mod n
+ const u2 = modN(r * is) // u2 = rs^-1 mod n
+ const R = doubleScalarMulUns(P, u1, u2).toAffine() // R = u1⋅G + u2⋅P
+ // Stop if R is identity / zero point. Check is done inside `doubleScalarMulUns`
+ const v = modN(R.x) // R.x must be in N's field, not P's
+ return v === r // mod(R.x, n) == r
+ } catch (error) {
+ return false
+ }
+}
+
+static setDefaults = (opts: ECDSASignOpts): Required<ECDSASignOpts> => {
+ static res: ECDSASignOpts = {}
+ Object.keys(defaultSignOpts).forEach((k: string) => {
+ // @ts-ignore
+ res[k] = opts[k] ?? defaultSignOpts[k]
+ })
+ return res as Required<ECDSASignOpts>
+}
+
+/**
+ * Sign a message using secp256k1. Sync: uses `hashes.sha256` and `hashes.hmacSha256`.
+ * Prehashes message with sha256, disable using `prehash: false`.
+ * @param opts - see {@link ECDSASignOpts} for details. Enabling {@link ECDSAExtraEntropy} will improve security.
+ * @example
+ * ```js
+ * const msg = new TextEncoder().encode('hello noble');
+ * sign(msg, secretKey);
+ * sign(keccak256(msg), secretKey, { prehash: false });
+ * sign(msg, secretKey, { extraEntropy: true });
+ * sign(msg, secretKey, { format: 'recovered' });
+ * ```
+ */
+static sign = (message: Bytes, secretKey: Bytes, opts: ECDSASignOpts = {}): Bytes => {
+ opts = setDefaults(opts)
+ message = prepMsg(message, opts, false) as Bytes
+ return _sign(message, secretKey, opts, hmacDrbg)
+}
+
+/**
+ * Sign a message using secp256k1. Async: uses built-in WebCrypto hashes.
+ * Prehashes message with sha256, disable using `prehash: false`.
+ * @param opts - see {@link ECDSASignOpts} for details. Enabling {@link ECDSAExtraEntropy} will improve security.
+ * @example
+ * ```js
+ * const msg = new TextEncoder().encode('hello noble');
+ * await signAsync(msg, secretKey);
+ * await signAsync(keccak256(msg), secretKey, { prehash: false });
+ * await signAsync(msg, secretKey, { extraEntropy: true });
+ * await signAsync(msg, secretKey, { format: 'recovered' });
+ * ```
+ */
+static signAsync = async (
+ message: Bytes,
+ secretKey: Bytes,
+ opts: ECDSASignOpts = {}
+): Promise<Bytes> => {
+ opts = setDefaults(opts)
+ message = await prepMsg(message, opts, true)
+ return _sign(message, secretKey, opts, hmacDrbgAsync)
+}
+
+/**
+ * Verify a signature using secp256k1. Sync: uses `hashes.sha256` and `hashes.hmacSha256`.
+ * @param signature - default is 64-byte 'compact' format, also see {@link ECDSASignatureFormat}
+ * @param message - message which was signed. Keep in mind `prehash` from opts.
+ * @param publicKey - public key which
+ * @param opts - see {@link ECDSAVerifyOpts} for details.
+ * @example
+ * ```js
+ * const msg = new TextEncoder().encode('hello noble');
+ * verify(sig, msg, publicKey);
+ * verify(sig, keccak256(msg), publicKey, { prehash: false });
+ * verify(sig, msg, publicKey, { lowS: false });
+ * verify(sigr, msg, publicKey, { format: 'recovered' });
+ * ```
+ */
+static verify = (
+ signature: Bytes,
+ message: Bytes,
+ publicKey: Bytes,
+ opts: ECDSAVerifyOpts = {}
+): boolean => {
+ opts = setDefaults(opts)
+ message = prepMsg(message, opts, false) as Bytes
+ return _verify(signature, message, publicKey, opts)
+}
+
+/**
+ * Verify a signature using secp256k1. Async: uses built-in WebCrypto hashes.
+ * @param signature - default is 64-byte 'compact' format, also see {@link ECDSASignatureFormat}
+ * @param message - message which was signed. Keep in mind `prehash` from opts.
+ * @param publicKey - public key which
+ * @param opts - see {@link ECDSAVerifyOpts} for details.
+ * @example
+ * ```js
+ * const msg = new TextEncoder().encode('hello noble');
+ * verify(sig, msg, publicKey);
+ * verify(sig, keccak256(msg), publicKey, { prehash: false });
+ * verify(sig, msg, publicKey, { lowS: false });
+ * verify(sigr, msg, publicKey, { format: 'recovered' });
+ * ```
+ */
+static verifyAsync = async (
+ sig: Bytes,
+ message: Bytes,
+ publicKey: Bytes,
+ opts: ECDSAVerifyOpts = {}
+): Promise<boolean> => {
+ opts = setDefaults(opts)
+ message = await prepMsg(message, opts, true)
+ return _verify(sig, message, publicKey, opts)
+}
+
+static _recover = (signature: Bytes, messageHash: Bytes) => {
+ static sig = Signature.fromBytes(signature, 'recovered')
+ static { r, s, recovery } = sig
+ // 0 or 1 recovery id determines sign of "y" coordinate.
+ // 2 or 3 means q.x was >N.
+ assertRecoveryBit(recovery)
+ static h = bits2int_modN(abytes(messageHash, L)) // Truncate hash
+ static radj = recovery === 2 || recovery === 3 ? r + N : r
+ FpIsValidNot0(radj) // ensure q.x is still a field element
+ static head = getPrefix(big(recovery!)) // head is 0x02 or 0x03
+ static Rb = concatBytes(head, numTo32b(radj)) // concat head + r
+ static R = Point.fromBytes(Rb)
+ static ir = invert(radj, N) // r^-1
+ static u1 = modN(-h * ir) // -hr^-1
+ static u2 = modN(s * ir) // sr^-1
+ static point = doubleScalarMulUns(R, u1, u2) // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1)
+ return point.toBytes()
+}
+
+/**
+ * ECDSA public key recovery. Requires msg hash and recovery id.
+ * Follows [SEC1](https://secg.org/sec1-v2.pdf) 4.1.6.
+ */
+static recoverPublicKey = (signature: Bytes, message: Bytes, opts: ECDSARecoverOpts = {}): Bytes => {
+ message = prepMsg(message, setDefaults(opts), false) as Bytes
+ return _recover(signature, message)
+}
+
+static recoverPublicKeyAsync = async (
+ signature: Bytes,
+ message: Bytes,
+ opts: ECDSARecoverOpts = {}
+): Promise<Bytes> => {
+ message = await prepMsg(message, setDefaults(opts), true)
+ return _recover(signature, message)
+}
+
+/**
+ * Elliptic Curve Diffie-Hellman (ECDH) on secp256k1.
+ * Result is **NOT hashed**. Use hash or KDF on it if you need.
+ * @param isCompressed 33-byte (true) or 65-byte (false) output
+ * @returns public key C
+ */
+static getSharedSecret = (secretKeyA: Bytes, publicKeyB: Bytes, isCompressed = true): Bytes => {
+ return Point.fromBytes(publicKeyB).multiply(secretKeyToScalar(secretKeyA)).toBytes(isCompressed)
+}
+
+// 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 = randomBytes(lengths.seed)) => {
+ abytes(seed)
+ if (seed.length < lengths.seed || seed.length > 1024) err('expected 40-1024b')
+ static num = M(bytesToNumBE(seed), N - 1n)
+ return numTo32b(num + 1n)
+}
+
+type KeysSecPub = { secretKey: Bytes; publicKey: Bytes }
+type KeygenFn = (seed?: Bytes) => KeysSecPub
+static createKeygen = (getPublicKey: (secretKey: Bytes) => Bytes) => (seed?: Bytes): KeysSecPub => {
+ static secretKey = randomSecretKey(seed)
+ return { secretKey, publicKey: getPublicKey(secretKey) }
+}
+static keygen: KeygenFn = createKeygen(getPublicKey)
+
+/** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */
+static etc = {
+ hexToBytes: hexToBytes as (hex: string) => Bytes,
+ bytesToHex: bytesToHex as (bytes: Bytes) => string,
+ concatBytes: concatBytes as (...arrs: Bytes[]) => Bytes,
+ bytesToNumberBE: bytesToNumBE as (a: Bytes) => bigint,
+ numberToBytesBE: numTo32b as (n: bigint) => Bytes,
+ mod: M as (a: bigint, md?: bigint) => bigint,
+ invert: invert as (num: bigint, md?: bigint) => bigint, // math utilities
+ randomBytes: randomBytes as (len?: number) => Bytes,
+ secretKeyToScalar: secretKeyToScalar as typeof secretKeyToScalar,
+ abytes: abytes as typeof abytes,
+}
+
+/** Curve-specific utilities for private keys. */
+static utils = {
+ isValidSecretKey: isValidSecretKey as typeof isValidSecretKey,
+ isValidPublicKey: isValidPublicKey as typeof isValidPublicKey,
+ randomSecretKey: randomSecretKey as () => Bytes,
+}
+
+// Schnorr signatures are superior to ECDSA from above. Below is Schnorr-specific BIP0340 code.
+// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
+static getTag = (tag: string) => Uint8Array.from('BIP0340/' + tag, (c) => c.charCodeAt(0))
+static T_AUX = 'aux'
+static T_NONCE = 'nonce'
+static T_CHALLENGE = 'challenge'
+static taggedHash = (tag: string, ...messages: Bytes[]): Bytes => {
+ static fn = callHash('sha256')
+ static tagH = fn(getTag(tag))
+ return fn(concatBytes(tagH, tagH, ...messages))
+}
+static taggedHashAsync = async (tag: string, ...messages: Bytes[]): Promise<Bytes> => {
+ static fn = hashes.sha256Async
+ static tagH = await fn(getTag(tag))
+ return await fn(concatBytes(tagH, tagH, ...messages))
+}
+
+// ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03
+// Calculate point, scalar and bytes
+static extpubSchnorr = (priv: Bytes) => {
+ static d_ = secretKeyToScalar(priv)
+ static p = G.multiply(d_) // P = d'⋅G; 0 < d' < n check is done inside
+ static { x, y } = p.assertValidity().toAffine() // validate Point is not at infinity
+ static d = isEven(y) ? d_ : modN(-d_)
+ static px = numTo32b(x)
+ return { d, px }
+}
+
+static bytesModN = (bytes: Bytes) => modN(bytesToNumBE(bytes))
+static challenge = (...args: Bytes[]): bigint => bytesModN(taggedHash(T_CHALLENGE, ...args))
+static challengeAsync = async (...args: Bytes[]): Promise<bigint> =>
+ bytesModN(await taggedHashAsync(T_CHALLENGE, ...args))
+
+/**
+ * Schnorr public key is just `x` coordinate of Point as per BIP340.
+ */
+static pubSchnorr = (secretKey: Bytes): Bytes => {
+ return extpubSchnorr(secretKey).px // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G)
+}
+
+static keygenSchnorr: KeygenFn = createKeygen(pubSchnorr)
+
+// Common preparation fn for both sync and async signing
+static prepSigSchnorr = (message: Bytes, secretKey: Bytes, auxRand: Bytes) => {
+ static { px, d } = extpubSchnorr(secretKey)
+ return { m: abytes(message), px, d, a: abytes(auxRand, L) }
+}
+
+static extractK = (rand: Bytes) => {
+ static k_ = bytesModN(rand) // Let k' = int(rand) mod n
+ if (k_ === 0n) err('sign failed: k is zero') // Fail if k' = 0.
+ static { px, d } = extpubSchnorr(numTo32b(k_)) // Let R = k'⋅G.
+ return { rx: px, k: d }
+}
+
+// Common signature creation helper
+static createSigSchnorr = (k: bigint, px: Bytes, e: bigint, d: bigint): Bytes => {
+ return concatBytes(px, numTo32b(modN(k + e * d)))
+}
+
+static E_INVSIG = 'invalid signature produced'
+/**
+ * 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 = randomBytes(L)): Bytes => {
+ static { m, px, d, a } = prepSigSchnorr(message, secretKey, auxRand)
+ static aux = taggedHash(T_AUX, a)
+ // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
+ static t = numTo32b(d ^ bytesToNumBE(aux))
+ // Let rand = hash/nonce(t || bytes(P) || m)
+ static rand = taggedHash(T_NONCE, t, px, m)
+ static { rx, k } = extractK(rand)
+ // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
+ static e = challenge(rx, px, m)
+ static sig = createSigSchnorr(k, rx, e, d)
+ // If Verify(bytes(P), m, sig) (see below) returns failure, abort
+ if (!verifySchnorr(sig, m, px)) err(E_INVSIG)
+ return sig
+}
+
+static signSchnorrAsync = async (
+ message: Bytes,
+ secretKey: Bytes,
+ auxRand: Bytes = randomBytes(L)
+): Promise<Bytes> => {
+ static { m, px, d, a } = prepSigSchnorr(message, secretKey, auxRand)
+ static aux = await taggedHashAsync(T_AUX, a)
+ // Let t be the byte-wise xor of bytes(d) and hash/aux(a)
+ static t = numTo32b(d ^ bytesToNumBE(aux))
+ // Let rand = hash/nonce(t || bytes(P) || m)
+ static rand = await taggedHashAsync(T_NONCE, t, px, m)
+ static { rx, k } = extractK(rand)
+ // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n.
+ static e = await challengeAsync(rx, px, m)
+ static sig = createSigSchnorr(k, rx, e, d)
+ // If Verify(bytes(P), m, sig) (see below) returns failure, abort
+ if (!(await verifySchnorrAsync(sig, m, px))) err(E_INVSIG)
+ return sig
+}
+
+// const finishVerif = (P_: Point, r: bigint, s: bigint, e: bigint) => {};
+
+type MaybePromise<T> = T | Promise<T>
+static callSyncAsyncFn = <T, O> (res: MaybePromise<T>, later: (res2: T) => O) => {
+ return res instanceof Promise ? res.then(later) : later(res)
+}
+
+static _verifSchnorr = (
+ signature: Bytes,
+ message: Bytes,
+ publicKey: Bytes,
+ challengeFn: (...args: Bytes[]) => bigint | Promise<bigint>
+): boolean | Promise<boolean> => {
+ static sig = abytes(signature, L2, 'signature')
+ static msg = abytes(message, undefined, 'message')
+ static pub = abytes(publicKey, L, 'publicKey')
+ try {
+ // lift_x from BIP340. Convert 32-byte x coordinate to elliptic curve point.
+ // Fail if x ≥ p. Let c = x³ + 7 mod p.
+ const x = bytesToNumBE(pub)
+ const y = lift_x(x) // Let y = c^(p+1)/4 mod p.
+ const y_ = isEven(y) ? y : 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 Point(x, y_, 1n).assertValidity()
+ const px = numTo32b(P_.toAffine().x)
+ // P = lift_x(int(pk)); fail if that fails
+ const r = sliceBytesNumBE(sig, 0, L) // Let r = int(sig[0:32]); fail if r ≥ p.
+ arange(r, 1n, P)
+ const s = sliceBytesNumBE(sig, L, L2) // Let s = int(sig[32:64]); fail if s ≥ n.
+ arange(s, 1n, N)
+ const i = concatBytes(numTo32b(r), px, msg)
+ // int(challenge(bytes(r)||bytes(P)||m))%n
+ return callSyncAsyncFn(challengeFn(i), (e) => {
+ const { x, y } = doubleScalarMulUns(P_, s, modN(-e)).toAffine() // R = s⋅G - e⋅P
+ if (!isEven(y) || x !== r) return false // -eP == (n-e)P
+ return true // Fail if is_infinite(R) / not has_even_y(R) / x(R) ≠ r.
+ })
+ } catch (error) {
+ return false
+ }
+}
+
+/**
+ * Verifies Schnorr signature.
+ * Will swallow errors & return false except for initial type validation of arguments.
+ */
+static verifySchnorr = (s: Bytes, m: Bytes, p: Bytes): boolean =>
+ _verifSchnorr(s, m, p, challenge) as boolean
+static verifySchnorrAsync = async (s: Bytes, m: Bytes, p: Bytes): Promise<boolean> =>
+ _verifSchnorr(s, m, p, challengeAsync) as Promise<boolean>
+
+static schnorr: {
+ keygen: typeof keygenSchnorr,
+ getPublicKey: typeof pubSchnorr
+ sign: typeof signSchnorr
+ verify: typeof verifySchnorr
+ signAsync: typeof signSchnorrAsync,
+ verifyAsync: typeof verifySchnorrAsync
+} = {
+ keygen: keygenSchnorr,
+ getPublicKey: pubSchnorr,
+ sign: signSchnorr,
+ verify: verifySchnorr,
+ signAsync: signSchnorrAsync,
+ verifyAsync: verifySchnorrAsync,
+}
+
+// ## Precomputes
+// --------------
+
+static W = 8 // W is window size
+static scalarBits = 256
+static pwindows = Math.ceil(scalarBits / W) + 1 // 33 for W=8, NOT 32 - see wNAF loop
+static pwindowSize = 2 ** (W - 1) // 128 for W=8
+static precompute = () => {
+ static points: Point[] = []
+ let p = G
+ let b = p
+ for (let w = 0; w < pwindows; w++) {
+ b = p
+ points.push(b)
+ for (let i = 1; i < pwindowSize; i++) {
+ b = b.add(p)
+ points.push(b)
+ } // i=1, bc we skip 0
+ p = b.double()
+ }
+ return points
+}
+let Gpows: Point[] | undefined = undefined // precomputes for base point G
+// const-time negate
+static ctneg = (cnd: boolean, p: Point) => {
+ static n = p.negate()
+ return cnd ? n : p
+}
+
+/**
+ * Precomputes give 12x faster getPublicKey(), 10x sign(), 2x verify() by
+ * caching multiples of G (base point). Cache is stored in 32MB of RAM.
+ * Any time `G.multiply` is done, precomputes are used.
+ * Not used for getSharedSecret, which instead multiplies random pubkey `P.multiply`.
+ *
+ * w-ary non-adjacent form (wNAF) precomputation method is 10% slower than windowed method,
+ * but takes 2x less RAM. RAM reduction is possible by utilizing `.subtract`.
+ *
+ * !! Precomputes can be disabled by commenting-out call of the wNAF() inside Point#multiply().
+ */
+static wNAF = (n: bigint): { p: Point; f: Point } => {
+ static comp = Gpows || (Gpows = precompute())
+ let p = I
+ let f = G // f must be G, or could become I in the end
+ static pow_2_w = 2 ** W // 256 for W=8
+ static maxNum = pow_2_w // 256 for W=8
+ static mask = big(pow_2_w - 1) // 255 for W=8 == mask 0b11111111
+ static shiftBy = big(W) // 8 for W=8
+ for (let w = 0; w < pwindows; w++) {
+ let wbits = Number(n & mask) // extract W bits.
+ n >>= shiftBy // shift number by W bits.
+ // We use negative indexes to reduce size of precomputed table by 2x.
+ // Instead of needing precomputes 0..256, we only calculate them for 0..128.
+ // If an index > 128 is found, we do (256-index) - where 256 is next window.
+ // Naive: index +127 => 127, +224 => 224
+ // Optimized: index +127 => 127, +224 => 256-32
+ if (wbits > pwindowSize) {
+ wbits -= maxNum
+ n += 1n
+ }
+ const off = w * pwindowSize
+ const offF = off // offsets, evaluate both
+ const offP = off + 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(ctneg(isEven, comp[offF])) // bits are 0: add garbage to fake point
+ } else {
+ p = p.add(ctneg(isNeg, comp[offP])) // bits are 1: add to result point
+ }
+ }
+ if (n !== 0n) err('invalid wnaf')
+ return { p, f } // return both real and fake points for JIT
+}
+}
+
+// !! Remove the export below to easily use in REPL / browser console
+// export {
+// etc,
+// getPublicKey, getSharedSecret,
+// hash, hashes,
+// keygen,
+// Point, recoverPublicKey, recoverPublicKeyAsync, schnorr, sign, signAsync,
+
+// Signature, utils, verify, verifyAsync
+// }
await Promise.all([\r
suite('Import wallets', async () => {\r
\r
- await test('nano.org BIP-44 test vector mnemonic', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
-\r
- assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
- assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
- assert.ok(account instanceof Account)\r
- assert.equal(account.publicKey, NANO_TEST_VECTORS.PUBLIC_0)\r
- assert.equal(account.address, NANO_TEST_VECTORS.ADDRESS_0)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('nano.org BIP-44 test vector seed with no mnemonic', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
-\r
- assert.ok('mnemonic' in wallet)\r
- assert.ok('seed' in wallet)\r
- assert.ok(account instanceof Account)\r
- assert.ok(wallet.mnemonic === undefined)\r
- assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
- assert.equal(account.publicKey, NANO_TEST_VECTORS.PUBLIC_0)\r
- assert.equal(account.address, NANO_TEST_VECTORS.ADDRESS_0)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('Trezor-derived BIP-44 entropy for 12-word mnemonic', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_0)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
-\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_0))\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_0))\r
- assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_0)\r
- assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_0)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('Trezor-derived BIP-44 entropy for 15-word mnemonic', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_1)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
-\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_1))\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_1))\r
- assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_1)\r
- assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_1)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('Trezor-derived BIP-44 entropy for 18-word mnemonic', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_2)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
-\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_2))\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_2))\r
- assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_2)\r
- assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_2)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('Trezor-derived BIP-44 entropy for 21-word mnemonic', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_3)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
-\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_3))\r
- assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_3))\r
- assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_3)\r
- assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_3)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('BIP-44 zero-string entropy', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_0, TREZOR_TEST_VECTORS.PASSWORD)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const accounts = await wallet.accounts(0, 3)\r
-\r
- assert.ok('mnemonic' in wallet)\r
- assert.ok('seed' in wallet)\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.SEED_0))\r
- assert.equal(accounts.size, 4)\r
-\r
- for (let i = 0; i < accounts.size; i++) {\r
- const account = accounts.get(i)\r
- assert.exists(account)\r
- assert.exists(account.address)\r
- assert.exists(account.publicKey)\r
- }\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('BLAKE2b zero-string seed', async () => {\r
- const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_0)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const accounts = await wallet.accounts(0, 3)\r
-\r
- assert.ok('mnemonic' in wallet)\r
- assert.ok('seed' in wallet)\r
- assert.ok(wallet.mnemonic === undefined)\r
- assert.ok(wallet.seed === undefined)\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_0))\r
- assert.equal(accounts.size, 4)\r
-\r
- for (let i = 0; i < accounts.size; i++) {\r
- const account = accounts.get(i)\r
- assert.exists(account)\r
- assert.exists(account.address)\r
- assert.exists(account.publicKey)\r
- }\r
+ // await test('nano.org BIP-44 test vector mnemonic', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+\r
+ // assert.ok(await wallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+ // assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+ // assert.ok(account instanceof Account)\r
+ // assert.equal(account.publicKey, NANO_TEST_VECTORS.PUBLIC_0)\r
+ // assert.equal(account.address, NANO_TEST_VECTORS.ADDRESS_0)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('nano.org BIP-44 test vector seed with no mnemonic', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+\r
+ // assert.ok('mnemonic' in wallet)\r
+ // assert.ok('seed' in wallet)\r
+ // assert.ok(account instanceof Account)\r
+ // assert.ok(wallet.mnemonic === undefined)\r
+ // assert.ok(await wallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+ // assert.equal(account.publicKey, NANO_TEST_VECTORS.PUBLIC_0)\r
+ // assert.equal(account.address, NANO_TEST_VECTORS.ADDRESS_0)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('Trezor-derived BIP-44 entropy for 12-word mnemonic', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_0)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_0))\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_0))\r
+ // assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_0)\r
+ // assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_0)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('Trezor-derived BIP-44 entropy for 15-word mnemonic', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_1)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_1))\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_1))\r
+ // assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_1)\r
+ // assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_1)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('Trezor-derived BIP-44 entropy for 18-word mnemonic', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_2)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_2))\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_2))\r
+ // assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_2)\r
+ // assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_2)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('Trezor-derived BIP-44 entropy for 21-word mnemonic', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.MNEMONIC_3)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.MNEMONIC_3))\r
+ // assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.SEED_3))\r
+ // assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.PUBLIC_3)\r
+ // assert.equal(account.address, CUSTOM_TEST_VECTORS.ADDRESS_3)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('BIP-44 zero-string entropy', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_0, TREZOR_TEST_VECTORS.PASSWORD)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const accounts = await wallet.accounts(0, 3)\r
+\r
+ // assert.ok('mnemonic' in wallet)\r
+ // assert.ok('seed' in wallet)\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.SEED_0))\r
+ // assert.equal(accounts.size, 4)\r
+\r
+ // for (let i = 0; i < accounts.size; i++) {\r
+ // const account = accounts.get(i)\r
+ // assert.exists(account)\r
+ // assert.exists(account.address)\r
+ // assert.exists(account.publicKey)\r
+ // }\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('BLAKE2b zero-string seed', async () => {\r
+ // const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_0)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const accounts = await wallet.accounts(0, 3)\r
+\r
+ // assert.ok('mnemonic' in wallet)\r
+ // assert.ok('seed' in wallet)\r
+ // assert.ok(wallet.mnemonic === undefined)\r
+ // assert.ok(wallet.seed === undefined)\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_0))\r
+ // assert.equal(accounts.size, 4)\r
+\r
+ // for (let i = 0; i < accounts.size; i++) {\r
+ // const account = accounts.get(i)\r
+ // assert.exists(account)\r
+ // assert.exists(account.address)\r
+ // assert.exists(account.publicKey)\r
+ // }\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('Trezor-derived BLAKE2b test vectors verified with third-party libraries', async () => {\r
+ // const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_1)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const accounts = await wallet.accounts(0, 1)\r
+\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_1))\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_1))\r
+\r
+ // const account0 = accounts.get(0)\r
+ // assert.exists(account0)\r
+ // assert.ok(account0 instanceof Account)\r
+ // assert.equal(account0.publicKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PUBLIC_0)\r
+ // assert.equal(account0.address, TREZOR_TEST_VECTORS.BLAKE2B_1_ADDRESS_0)\r
+\r
+ // const account1 = accounts.get(1)\r
+ // assert.exists(account1)\r
+ // assert.ok(account1 instanceof Account)\r
+ // assert.equal(account1.publicKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PUBLIC_1)\r
+ // assert.equal(account1.address, TREZOR_TEST_VECTORS.BLAKE2B_1_ADDRESS_1)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('BLAKE2b seed creates identical wallet as its derived mnemonic', async () => {\r
+ // const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_2)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const walletAccount = await wallet.account()\r
+\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_2))\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_2))\r
+\r
+ // const imported = await Wallet.load('BLAKE2b', TREZOR_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_2)\r
+ // await imported.unlock(TREZOR_TEST_VECTORS.PASSWORD)\r
+ // const importedAccount = await imported.account()\r
+\r
+ // assert.ok(await imported.verify(TREZOR_TEST_VECTORS.MNEMONIC_2))\r
+ // assert.ok(await imported.verify(TREZOR_TEST_VECTORS.ENTROPY_2))\r
+ // assert.equal(importedAccount.publicKey, walletAccount.publicKey)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // await assert.resolves(imported.destroy())\r
+ // })\r
+\r
+ // await test('BLAKE2b mnemonic for maximum seed value', async () => {\r
+ // const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_3)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+\r
+ // assert.ok(account instanceof Account)\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_3))\r
+ // assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_3))\r
+ // assert.equal(account.publicKey, TREZOR_TEST_VECTORS.BLAKE2B_3_PUBLIC_0)\r
+ // assert.equal(account.address, TREZOR_TEST_VECTORS.BLAKE2B_3_ADDRESS_0)\r
+\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
+\r
+ // await test('Reject invalid seed', async () => {\r
+ // await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C797'))\r
+ // await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C79701'))\r
+ // await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0.replaceAll(/./g, 'x')))\r
+ // })\r
+\r
+ // await test('Reject invalid length seed', async () => {\r
+ // await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED + 'f'),\r
+ // `Expected a ${NANO_TEST_VECTORS.BIP39_SEED.length}-character seed, but received ${NANO_TEST_VECTORS.BIP39_SEED.length + 1}-character string.`)\r
+ // await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED.slice(0, -1)),\r
+ // `Expected a ${NANO_TEST_VECTORS.BIP39_SEED.length}-character seed, but received ${NANO_TEST_VECTORS.BIP39_SEED.length - 1}-character string.`)\r
+ // })\r
+\r
+ // await test('Reject seed containing non-hex characters', async () => {\r
+ // await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.SEED_0.replace(/./, 'g')),\r
+ // 'Seed contains invalid hexadecimal characters.')\r
+ // await assert.rejects(Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_1.replace(/./, 'g')),\r
+ // 'Seed contains invalid hexadecimal characters.')\r
+ // })\r
+\r
+ // await test('Import BIP-44 wallet from storage using a wallet-generated ID', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
+ // const restored = await Wallet.restore(wallet.id)\r
+\r
+ // assert.ok('mnemonic' in restored)\r
+ // assert.ok('seed' in restored)\r
+ // assert.ok(restored.mnemonic === undefined)\r
+ // assert.ok(restored.seed === undefined)\r
+ // await assert.rejects(restored.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+ // await assert.rejects(restored.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+\r
+ // await restored.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+\r
+ // assert.ok(await restored.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+ // assert.ok(await restored.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+\r
+ // await assert.resolves(restored.destroy())\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('Trezor-derived BLAKE2b test vectors verified with third-party libraries', async () => {\r
- const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_1)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const accounts = await wallet.accounts(0, 1)\r
-\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_1))\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_1))\r
-\r
- const account0 = accounts.get(0)\r
- assert.exists(account0)\r
- assert.ok(account0 instanceof Account)\r
- assert.equal(account0.publicKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PUBLIC_0)\r
- assert.equal(account0.address, TREZOR_TEST_VECTORS.BLAKE2B_1_ADDRESS_0)\r
-\r
- const account1 = accounts.get(1)\r
- assert.exists(account1)\r
- assert.ok(account1 instanceof Account)\r
- assert.equal(account1.publicKey, TREZOR_TEST_VECTORS.BLAKE2B_1_PUBLIC_1)\r
- assert.equal(account1.address, TREZOR_TEST_VECTORS.BLAKE2B_1_ADDRESS_1)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('BLAKE2b seed creates identical wallet as its derived mnemonic', async () => {\r
- const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_2)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const walletAccount = await wallet.account()\r
-\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_2))\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_2))\r
-\r
- const imported = await Wallet.load('BLAKE2b', TREZOR_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_2)\r
- await imported.unlock(TREZOR_TEST_VECTORS.PASSWORD)\r
- const importedAccount = await imported.account()\r
-\r
- assert.ok(await imported.verify(TREZOR_TEST_VECTORS.MNEMONIC_2))\r
- assert.ok(await imported.verify(TREZOR_TEST_VECTORS.ENTROPY_2))\r
- assert.equal(importedAccount.publicKey, walletAccount.publicKey)\r
-\r
- await assert.resolves(wallet.destroy())\r
- await assert.resolves(imported.destroy())\r
- })\r
-\r
- await test('BLAKE2b mnemonic for maximum seed value', async () => {\r
- const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.MNEMONIC_3)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
-\r
- assert.ok(account instanceof Account)\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.MNEMONIC_3))\r
- assert.ok(await wallet.verify(TREZOR_TEST_VECTORS.ENTROPY_3))\r
- assert.equal(account.publicKey, TREZOR_TEST_VECTORS.BLAKE2B_3_PUBLIC_0)\r
- assert.equal(account.address, TREZOR_TEST_VECTORS.BLAKE2B_3_ADDRESS_0)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
+ // await test('Import BLAKE2B wallet from storage using a wallet-generated ID', async () => {\r
+ // const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0)\r
+ // const restored = await Wallet.restore(wallet.id)\r
+\r
+ // assert.ok('mnemonic' in restored)\r
+ // assert.ok('seed' in restored)\r
+ // assert.ok(restored.mnemonic === undefined)\r
+ // assert.ok(restored.seed === undefined)\r
+ // await assert.rejects(restored.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
+ // await assert.rejects(restored.verify(TREZOR_TEST_VECTORS.ENTROPY_0))\r
\r
- await test('Reject invalid seed', async () => {\r
- await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C797'))\r
- await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, '6CAF5A42BB8074314AAE20295975ECE663BE7AAD945A73613D193B0CC41C79701'))\r
- await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0.replaceAll(/./g, 'x')))\r
- })\r
-\r
- await test('Reject invalid length seed', async () => {\r
- await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED + 'f'),\r
- `Expected a ${NANO_TEST_VECTORS.BIP39_SEED.length}-character seed, but received ${NANO_TEST_VECTORS.BIP39_SEED.length + 1}-character string.`)\r
- await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED.slice(0, -1)),\r
- `Expected a ${NANO_TEST_VECTORS.BIP39_SEED.length}-character seed, but received ${NANO_TEST_VECTORS.BIP39_SEED.length - 1}-character string.`)\r
- })\r
-\r
- await test('Reject seed containing non-hex characters', async () => {\r
- await assert.rejects(Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.SEED_0.replace(/./, 'g')),\r
- 'Seed contains invalid hexadecimal characters.')\r
- await assert.rejects(Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_1.replace(/./, 'g')),\r
- 'Seed contains invalid hexadecimal characters.')\r
- })\r
-\r
- await test('Import BIP-44 wallet from storage using a wallet-generated ID', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
- const restored = await Wallet.restore(wallet.id)\r
-\r
- assert.ok('mnemonic' in restored)\r
- assert.ok('seed' in restored)\r
- assert.ok(restored.mnemonic === undefined)\r
- assert.ok(restored.seed === undefined)\r
- await assert.rejects(restored.verify(NANO_TEST_VECTORS.MNEMONIC))\r
- await assert.rejects(restored.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
-\r
- await restored.unlock(NANO_TEST_VECTORS.PASSWORD)\r
-\r
- assert.ok(await restored.verify(NANO_TEST_VECTORS.MNEMONIC))\r
- assert.ok(await restored.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
-\r
- await assert.resolves(restored.destroy())\r
- await assert.resolves(wallet.destroy())\r
- })\r
-\r
- await test('Import BLAKE2B wallet from storage using a wallet-generated ID', async () => {\r
- const wallet = await Wallet.load('BLAKE2b', NANO_TEST_VECTORS.PASSWORD, TREZOR_TEST_VECTORS.ENTROPY_0)\r
- const restored = await Wallet.restore(wallet.id)\r
+ // await restored.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+\r
+ // assert.ok(await restored.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
+ // assert.ok(await restored.verify(TREZOR_TEST_VECTORS.ENTROPY_0))\r
\r
- assert.ok('mnemonic' in restored)\r
- assert.ok('seed' in restored)\r
- assert.ok(restored.mnemonic === undefined)\r
- assert.ok(restored.seed === undefined)\r
- await assert.rejects(restored.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
- await assert.rejects(restored.verify(TREZOR_TEST_VECTORS.ENTROPY_0))\r
+ // await assert.resolves(restored.destroy())\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
\r
- await restored.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // await test('export wallet IDs from storage and reimport them', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
+ // const backups = await Wallet.backup()\r
\r
- assert.ok(await restored.verify(TREZOR_TEST_VECTORS.MNEMONIC_0))\r
- assert.ok(await restored.verify(TREZOR_TEST_VECTORS.ENTROPY_0))\r
+ // assert.ok(backups.some(record => record.id === wallet.id))\r
\r
- await assert.resolves(restored.destroy())\r
- await assert.resolves(wallet.destroy())\r
- })\r
+ // const restored = await Wallet.restore(wallet.id)\r
\r
- await test('export wallet IDs from storage and reimport them', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
- const backups = await Wallet.backup()\r
+ // assert.ok('mnemonic' in restored)\r
+ // assert.ok('seed' in restored)\r
+ // assert.ok(restored.mnemonic === undefined)\r
+ // assert.ok(restored.seed === undefined)\r
\r
- assert.ok(backups.some(record => record.id === wallet.id))\r
+ // await restored.unlock(NANO_TEST_VECTORS.PASSWORD)\r
\r
- const restored = await Wallet.restore(wallet.id)\r
+ // assert.ok(await restored.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+ // assert.ok(await restored.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
\r
- assert.ok('mnemonic' in restored)\r
- assert.ok('seed' in restored)\r
- assert.ok(restored.mnemonic === undefined)\r
- assert.ok(restored.seed === undefined)\r
+ // await assert.resolves(wallet.destroy())\r
+ // await assert.resolves(restored.destroy())\r
+ // })\r
\r
- await restored.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // await test('restore wallets without referencing IDs', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
+ // const backups = await Wallet.backup()\r
\r
- assert.ok(await restored.verify(NANO_TEST_VECTORS.MNEMONIC))\r
- assert.ok(await restored.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
+ // assert.ok(backups.some(record => record.id === wallet.id))\r
\r
- await assert.resolves(wallet.destroy())\r
- await assert.resolves(restored.destroy())\r
- })\r
+ // const restored = await Wallet.restore()\r
+ // const restoredWallet = restored.find(restoredWallet => restoredWallet.id === wallet.id)\r
\r
- await test('restore wallets without referencing IDs', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
- const backups = await Wallet.backup()\r
+ // assert.exists(restoredWallet)\r
+ // assert.ok('mnemonic' in restoredWallet)\r
+ // assert.ok('seed' in restoredWallet)\r
+ // assert.ok(restoredWallet.mnemonic === undefined)\r
+ // assert.ok(restoredWallet.seed === undefined)\r
\r
- assert.ok(backups.some(record => record.id === wallet.id))\r
+ // await restoredWallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
\r
- const restored = await Wallet.restore()\r
- const restoredWallet = restored.find(restoredWallet => restoredWallet.id === wallet.id)\r
+ // assert.ok(await restoredWallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
+ // assert.ok(await restoredWallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
\r
- assert.exists(restoredWallet)\r
- assert.ok('mnemonic' in restoredWallet)\r
- assert.ok('seed' in restoredWallet)\r
- assert.ok(restoredWallet.mnemonic === undefined)\r
- assert.ok(restoredWallet.seed === undefined)\r
+ // for (const restoredWallet of restored) {\r
+ // await assert.resolves(restoredWallet.destroy())\r
+ // }\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
\r
- await restoredWallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // await test('load account from legacy address', async () => {\r
+ // const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
+ // await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
+ // const account = await wallet.account()\r
+ // const legacy = Account.load(NANO_TEST_VECTORS.ADDRESS_0.replace('nano_', 'xrb_'))\r
\r
- assert.ok(await restoredWallet.verify(NANO_TEST_VECTORS.MNEMONIC))\r
- assert.ok(await restoredWallet.verify(NANO_TEST_VECTORS.BIP39_SEED))\r
-\r
- for (const restoredWallet of restored) {\r
- await assert.resolves(restoredWallet.destroy())\r
- }\r
- await assert.resolves(wallet.destroy())\r
- })\r
+ // assert.equal(account.address, legacy.address)\r
+ // assert.equal(NANO_TEST_VECTORS.ADDRESS_0, legacy.address)\r
+ // assert.equal(NANO_TEST_VECTORS.PUBLIC_0, legacy.publicKey)\r
\r
- await test('load account from legacy address', async () => {\r
- const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.MNEMONIC, NANO_TEST_VECTORS.PASSWORD)\r
- await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
- const account = await wallet.account()\r
- const legacy = Account.load(NANO_TEST_VECTORS.ADDRESS_0.replace('nano_', 'xrb_'))\r
-\r
- assert.equal(account.address, legacy.address)\r
- assert.equal(NANO_TEST_VECTORS.ADDRESS_0, legacy.address)\r
- assert.equal(NANO_TEST_VECTORS.PUBLIC_0, legacy.publicKey)\r
-\r
- await assert.resolves(wallet.destroy())\r
- })\r
+ // await assert.resolves(wallet.destroy())\r
+ // })\r
\r
await test('nano.org Exodus test vector mnemonic', async () => {\r
- const wallet = await Wallet.load('Exodus', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.SHORT_MNEMONIC)\r
+ const wallet = await Wallet.load('Exodus', NANO_TEST_VECTORS.PASSWORD, CUSTOM_TEST_VECTORS.EXODUS.MNEMONIC)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
\r
- assert.ok(await wallet.verify(NANO_TEST_VECTORS.SHORT_MNEMONIC))\r
- assert.ok(await wallet.verify(NANO_TEST_VECTORS.SHORT_BIP39_SEED))\r
+ assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.EXODUS.MNEMONIC))\r
+ assert.ok(await wallet.verify(CUSTOM_TEST_VECTORS.EXODUS.BIP39_SEED))\r
assert.ok(account instanceof Account)\r
- assert.equal(account.publicKey, NANO_TEST_VECTORS.SHORT_PUBLIC_0)\r
- assert.equal(account.address, NANO_TEST_VECTORS.SHORT_ADDRESS_0)\r
+ assert.equal(account.publicKey, CUSTOM_TEST_VECTORS.EXODUS.PUBLIC_0)\r
+ assert.equal(account.address, CUSTOM_TEST_VECTORS.EXODUS.ADDRESS_0)\r
\r
await assert.resolves(wallet.destroy())\r
})\r