2c128eb1c0
- Implement BP+ verification using @noble/curves for Ed25519 ops - MSM 256 points in ~420ms - mobile-friendly, no WASM needed - Add verifyBulletproofPlus, verifyBulletproofPlusBatch, verifyRangeProof - Add generator initialization with caching (Gi, Hi points) - 24 new tests including performance benchmarks - Update exports in index.js and package.json - Bump version to 0.3.0
325 lines
12 KiB
TypeScript
325 lines
12 KiB
TypeScript
import { type CHash } from '../utils.ts';
|
|
import { type AffinePoint, type CurveLengths, type CurvePoint, type CurvePointCons } from './curve.ts';
|
|
import { type IField } from './modular.ts';
|
|
export type { AffinePoint };
|
|
type EndoBasis = [[bigint, bigint], [bigint, bigint]];
|
|
/**
|
|
* When Weierstrass curve has `a=0`, it becomes Koblitz curve.
|
|
* Koblitz curves allow using **efficiently-computable GLV endomorphism ψ**.
|
|
* Endomorphism uses 2x less RAM, speeds up precomputation by 2x and ECDH / key recovery by 20%.
|
|
* For precomputed wNAF it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
|
*
|
|
* Endomorphism consists of beta, lambda and splitScalar:
|
|
*
|
|
* 1. GLV endomorphism ψ transforms a point: `P = (x, y) ↦ ψ(P) = (β·x mod p, y)`
|
|
* 2. GLV scalar decomposition transforms a scalar: `k ≡ k₁ + k₂·λ (mod n)`
|
|
* 3. Then these are combined: `k·P = k₁·P + k₂·ψ(P)`
|
|
* 4. Two 128-bit point-by-scalar multiplications + one point addition is faster than
|
|
* one 256-bit multiplication.
|
|
*
|
|
* where
|
|
* * beta: β ∈ Fₚ with β³ = 1, β ≠ 1
|
|
* * lambda: λ ∈ Fₙ with λ³ = 1, λ ≠ 1
|
|
* * splitScalar decomposes k ↦ k₁, k₂, by using reduced basis vectors.
|
|
* Gauss lattice reduction calculates them from initial basis vectors `(n, 0), (-λ, 0)`
|
|
*
|
|
* Check out `test/misc/endomorphism.js` and
|
|
* [gist](https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066).
|
|
*/
|
|
export type EndomorphismOpts = {
|
|
beta: bigint;
|
|
basises?: EndoBasis;
|
|
splitScalar?: (k: bigint) => {
|
|
k1neg: boolean;
|
|
k1: bigint;
|
|
k2neg: boolean;
|
|
k2: bigint;
|
|
};
|
|
};
|
|
export type ScalarEndoParts = {
|
|
k1neg: boolean;
|
|
k1: bigint;
|
|
k2neg: boolean;
|
|
k2: bigint;
|
|
};
|
|
/**
|
|
* Splits scalar for GLV endomorphism.
|
|
*/
|
|
export declare function _splitEndoScalar(k: bigint, basis: EndoBasis, n: bigint): ScalarEndoParts;
|
|
/**
|
|
* 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/
|
|
*/
|
|
export type ECDSAExtraEntropy = boolean | Uint8Array;
|
|
/**
|
|
* - `compact` is the default format
|
|
* - `recovered` is the same as compact, but with an extra byte indicating recovery byte
|
|
* - `der` is ASN.1 DER encoding
|
|
*/
|
|
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;
|
|
};
|
|
/** Instance methods for 3D XYZ projective points. */
|
|
export interface WeierstrassPoint<T> extends CurvePoint<T, WeierstrassPoint<T>> {
|
|
/** projective X coordinate. Different from affine x. */
|
|
readonly X: T;
|
|
/** projective Y coordinate. Different from affine y. */
|
|
readonly Y: T;
|
|
/** projective z coordinate */
|
|
readonly Z: T;
|
|
/** affine x coordinate. Different from projective X. */
|
|
get x(): T;
|
|
/** affine y coordinate. Different from projective Y. */
|
|
get y(): T;
|
|
/** Encodes point using IEEE P1363 (DER) encoding. First byte is 2/3/4. Default = isCompressed. */
|
|
toBytes(isCompressed?: boolean): Uint8Array;
|
|
toHex(isCompressed?: boolean): string;
|
|
}
|
|
/** Static methods for 3D XYZ projective points. */
|
|
export interface WeierstrassPointCons<T> extends CurvePointCons<WeierstrassPoint<T>> {
|
|
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
new (X: T, Y: T, Z: T): WeierstrassPoint<T>;
|
|
CURVE(): WeierstrassOpts<T>;
|
|
}
|
|
/**
|
|
* Weierstrass curve options.
|
|
*
|
|
* * p: prime characteristic (order) of finite field, in which arithmetics is done
|
|
* * n: order of prime subgroup a.k.a total amount of valid curve points
|
|
* * h: cofactor, usually 1. h*n is group order; n is subgroup order
|
|
* * a: formula param, must be in field of p
|
|
* * b: formula param, must be in field of p
|
|
* * Gx: x coordinate of generator point a.k.a. base point
|
|
* * Gy: y coordinate of generator point
|
|
*/
|
|
export type WeierstrassOpts<T> = Readonly<{
|
|
p: bigint;
|
|
n: bigint;
|
|
h: bigint;
|
|
a: T;
|
|
b: T;
|
|
Gx: T;
|
|
Gy: T;
|
|
}>;
|
|
export type WeierstrassExtraOpts<T> = Partial<{
|
|
Fp: IField<T>;
|
|
Fn: IField<bigint>;
|
|
allowInfinityPoint: boolean;
|
|
endo: EndomorphismOpts;
|
|
isTorsionFree: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => boolean;
|
|
clearCofactor: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>) => WeierstrassPoint<T>;
|
|
fromBytes: (bytes: Uint8Array) => AffinePoint<T>;
|
|
toBytes: (c: WeierstrassPointCons<T>, point: WeierstrassPoint<T>, isCompressed: boolean) => Uint8Array;
|
|
}>;
|
|
/**
|
|
* Options for ECDSA signatures over a Weierstrass curve.
|
|
*
|
|
* * lowS: (default: true) whether produced / verified signatures occupy low half of ecdsaOpts.p. Prevents malleability.
|
|
* * hmac: (default: noble-hashes hmac) function, would be used to init hmac-drbg for k generation.
|
|
* * randomBytes: (default: webcrypto os-level CSPRNG) custom method for fetching secure randomness.
|
|
* * bits2int, bits2int_modN: used in sigs, sometimes overridden by curves
|
|
*/
|
|
export type ECDSAOpts = Partial<{
|
|
lowS: boolean;
|
|
hmac: (key: Uint8Array, message: Uint8Array) => Uint8Array;
|
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
|
bits2int: (bytes: Uint8Array) => bigint;
|
|
bits2int_modN: (bytes: Uint8Array) => bigint;
|
|
}>;
|
|
/**
|
|
* Elliptic Curve Diffie-Hellman interface.
|
|
* Provides keygen, secret-to-public conversion, calculating shared secrets.
|
|
*/
|
|
export interface ECDH {
|
|
keygen: (seed?: Uint8Array) => {
|
|
secretKey: Uint8Array;
|
|
publicKey: Uint8Array;
|
|
};
|
|
getPublicKey: (secretKey: Uint8Array, isCompressed?: boolean) => Uint8Array;
|
|
getSharedSecret: (secretKeyA: Uint8Array, publicKeyB: Uint8Array, isCompressed?: boolean) => Uint8Array;
|
|
Point: WeierstrassPointCons<bigint>;
|
|
utils: {
|
|
isValidSecretKey: (secretKey: Uint8Array) => boolean;
|
|
isValidPublicKey: (publicKey: Uint8Array, isCompressed?: boolean) => boolean;
|
|
randomSecretKey: (seed?: Uint8Array) => Uint8Array;
|
|
};
|
|
lengths: CurveLengths;
|
|
}
|
|
/**
|
|
* ECDSA interface.
|
|
* Only supported for prime fields, not Fp2 (extension fields).
|
|
*/
|
|
export interface ECDSA extends ECDH {
|
|
sign: (message: Uint8Array, secretKey: Uint8Array, opts?: ECDSASignOpts) => Uint8Array;
|
|
verify: (signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array, opts?: ECDSAVerifyOpts) => boolean;
|
|
recoverPublicKey(signature: Uint8Array, message: Uint8Array, opts?: ECDSARecoverOpts): Uint8Array;
|
|
Signature: ECDSASignatureCons;
|
|
}
|
|
export declare class DERErr extends Error {
|
|
constructor(m?: string);
|
|
}
|
|
export type IDER = {
|
|
Err: typeof DERErr;
|
|
_tlv: {
|
|
encode: (tag: number, data: string) => string;
|
|
decode(tag: number, data: Uint8Array): {
|
|
v: Uint8Array;
|
|
l: Uint8Array;
|
|
};
|
|
};
|
|
_int: {
|
|
encode(num: bigint): string;
|
|
decode(data: Uint8Array): bigint;
|
|
};
|
|
toSig(hex: string | Uint8Array): {
|
|
r: bigint;
|
|
s: bigint;
|
|
};
|
|
hexFromSig(sig: {
|
|
r: bigint;
|
|
s: bigint;
|
|
}): string;
|
|
};
|
|
/**
|
|
* ASN.1 DER encoding utilities. ASN is very complex & fragile. Format:
|
|
*
|
|
* [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S]
|
|
*
|
|
* Docs: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/, https://luca.ntop.org/Teaching/Appunti/asn1.html
|
|
*/
|
|
export declare const DER: IDER;
|
|
/**
|
|
* Creates weierstrass Point constructor, based on specified curve options.
|
|
*
|
|
* See {@link WeierstrassOpts}.
|
|
*
|
|
* @example
|
|
```js
|
|
const opts = {
|
|
p: 0xfffffffffffffffffffffffffffffffeffffac73n,
|
|
n: 0x100000000000000000001b8fa16dfab9aca16b6b3n,
|
|
h: 1n,
|
|
a: 0n,
|
|
b: 7n,
|
|
Gx: 0x3b4c382ce37aa192a4019e763036f4f5dd4d7ebbn,
|
|
Gy: 0x938cf935318fdced6bc28286531733c3f03c4feen,
|
|
};
|
|
const secp160k1_Point = weierstrass(opts);
|
|
```
|
|
*/
|
|
export declare function weierstrass<T>(params: WeierstrassOpts<T>, extraOpts?: WeierstrassExtraOpts<T>): WeierstrassPointCons<T>;
|
|
/** Methods of ECDSA signature instance. */
|
|
export interface ECDSASignature {
|
|
readonly r: bigint;
|
|
readonly s: bigint;
|
|
readonly recovery?: number;
|
|
addRecoveryBit(recovery: number): ECDSASignature & {
|
|
readonly recovery: number;
|
|
};
|
|
hasHighS(): boolean;
|
|
recoverPublicKey(messageHash: Uint8Array): WeierstrassPoint<bigint>;
|
|
toBytes(format?: string): Uint8Array;
|
|
toHex(format?: string): string;
|
|
}
|
|
/** Methods of ECDSA signature constructor. */
|
|
export type ECDSASignatureCons = {
|
|
new (r: bigint, s: bigint, recovery?: number): ECDSASignature;
|
|
fromBytes(bytes: Uint8Array, format?: ECDSASignatureFormat): ECDSASignature;
|
|
fromHex(hex: string, format?: ECDSASignatureFormat): ECDSASignature;
|
|
};
|
|
/**
|
|
* Implementation of the Shallue and van de Woestijne method for any weierstrass curve.
|
|
* TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular.
|
|
* b = True and y = sqrt(u / v) if (u / v) is square in F, and
|
|
* b = False and y = sqrt(Z * (u / v)) otherwise.
|
|
* @param Fp
|
|
* @param Z
|
|
* @returns
|
|
*/
|
|
export declare function SWUFpSqrtRatio<T>(Fp: IField<T>, Z: T): (u: T, v: T) => {
|
|
isValid: boolean;
|
|
value: T;
|
|
};
|
|
/**
|
|
* Simplified Shallue-van de Woestijne-Ulas Method
|
|
* https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2
|
|
*/
|
|
export declare function mapToCurveSimpleSWU<T>(Fp: IField<T>, opts: {
|
|
A: T;
|
|
B: T;
|
|
Z: T;
|
|
}): (u: T) => {
|
|
x: T;
|
|
y: T;
|
|
};
|
|
/**
|
|
* Sometimes users only need getPublicKey, getSharedSecret, and secret key handling.
|
|
* This helper ensures no signature functionality is present. Less code, smaller bundle size.
|
|
*/
|
|
export declare function ecdh(Point: WeierstrassPointCons<bigint>, ecdhOpts?: {
|
|
randomBytes?: (bytesLength?: number) => Uint8Array;
|
|
}): ECDH;
|
|
/**
|
|
* Creates ECDSA signing interface for given elliptic curve `Point` and `hash` function.
|
|
*
|
|
* @param Point created using {@link weierstrass} function
|
|
* @param hash used for 1) message prehash-ing 2) k generation in `sign`, using hmac_drbg(hash)
|
|
* @param ecdsaOpts rarely needed, see {@link ECDSAOpts}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* const p256_Point = weierstrass(...);
|
|
* const p256_sha256 = ecdsa(p256_Point, sha256);
|
|
* const p256_sha224 = ecdsa(p256_Point, sha224);
|
|
* const p256_sha224_r = ecdsa(p256_Point, sha224, { randomBytes: (length) => { ... } });
|
|
* ```
|
|
*/
|
|
export declare function ecdsa(Point: WeierstrassPointCons<bigint>, hash: CHash, ecdsaOpts?: ECDSAOpts): ECDSA;
|
|
//# sourceMappingURL=weierstrass.d.ts.map
|