Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8b2e91f74 | ||
|
|
9ee694ae23 | ||
|
|
6bc4b35cf4 |
33
README.md
33
README.md
@@ -5,7 +5,7 @@ Audited & minimal JS implementation of elliptic curve cryptography.
|
|||||||
- **noble** family, zero dependencies
|
- **noble** family, zero dependencies
|
||||||
- Short Weierstrass, Edwards, Montgomery curves
|
- Short Weierstrass, Edwards, Montgomery curves
|
||||||
- ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
|
- ECDSA, EdDSA, Schnorr, BLS signature schemes, ECDH key agreement
|
||||||
- #️⃣ [hash to curve](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve/)
|
- #️⃣ [hash to curve](#abstracthash-to-curve-hashing-strings-to-curve-points)
|
||||||
for encoding or hashing an arbitrary string to an elliptic curve point
|
for encoding or hashing an arbitrary string to an elliptic curve point
|
||||||
- 🧜♂️ [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
|
- 🧜♂️ [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
|
||||||
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
|
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
|
||||||
@@ -470,7 +470,7 @@ const x25519 = montgomery({
|
|||||||
|
|
||||||
### abstract/hash-to-curve: Hashing strings to curve points
|
### abstract/hash-to-curve: Hashing strings to curve points
|
||||||
|
|
||||||
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v11](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11).
|
The module allows to hash arbitrary strings to elliptic curve points. Implements [hash-to-curve v16](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16).
|
||||||
|
|
||||||
Every curve has exported `hashToCurve` and `encodeToCurve` methods:
|
Every curve has exported `hashToCurve` and `encodeToCurve` methods:
|
||||||
|
|
||||||
@@ -481,7 +481,6 @@ hashToCurve('0102abcd');
|
|||||||
console.log(hashToCurve(randomBytes()));
|
console.log(hashToCurve(randomBytes()));
|
||||||
console.log(encodeToCurve(randomBytes()));
|
console.log(encodeToCurve(randomBytes()));
|
||||||
|
|
||||||
|
|
||||||
import { bls12_381 } from '@noble/curves/bls12-381';
|
import { bls12_381 } from '@noble/curves/bls12-381';
|
||||||
bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
|
bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
|
||||||
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
|
bls12_381.G2.hashToCurve(randomBytes(), { DST: 'custom' });
|
||||||
@@ -491,6 +490,8 @@ If you need low-level methods from spec:
|
|||||||
|
|
||||||
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
`expand_message_xmd` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.4.1) produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
||||||
|
|
||||||
|
Hash must conform to `CHash` interface (see [weierstrass section](#abstractweierstrass-short-weierstrass-curve)).
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function expand_message_xmd(
|
function expand_message_xmd(
|
||||||
msg: Uint8Array,
|
msg: Uint8Array,
|
||||||
@@ -509,13 +510,18 @@ function expand_message_xof(
|
|||||||
|
|
||||||
`hash_to_field(msg, count, options)` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
|
`hash_to_field(msg, count, options)` [(spec)](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3)
|
||||||
hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
|
hashes arbitrary-length byte strings to a list of one or more elements of a finite field F.
|
||||||
_ `msg` a byte string containing the message to hash
|
|
||||||
_ `count` the number of elements of F to output
|
- `msg` a byte string containing the message to hash
|
||||||
_ `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
|
- `count` the number of elements of F to output
|
||||||
_ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
|
- `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`.
|
||||||
|
- `p` is field prime, m=field extension (1 for prime fields)
|
||||||
|
- `k` is security target in bits (e.g. 128).
|
||||||
|
- `expand` should be `xmd` for SHA2, SHA3, BLAKE; `xof` for SHAKE, BLAKE-XOF
|
||||||
|
- `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
|
||||||
|
- Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function hash_to_field(msg: Uint8Array, count: number, options: htfOpts): bigint[][];
|
function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][];
|
||||||
```
|
```
|
||||||
|
|
||||||
### abstract/poseidon: Poseidon hash
|
### abstract/poseidon: Poseidon hash
|
||||||
@@ -586,7 +592,6 @@ const derived = hkdf(sha256, someKey, undefined, 'application', 40); // 40 bytes
|
|||||||
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
|
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### abstract/utils: General utilities
|
### abstract/utils: General utilities
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -692,6 +697,16 @@ aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op
|
|||||||
aggregateSignatures/8 x 45 ops/sec @ 22ms/op
|
aggregateSignatures/8 x 45 ops/sec @ 22ms/op
|
||||||
aggregateSignatures/32 x 11 ops/sec @ 84ms/op
|
aggregateSignatures/32 x 11 ops/sec @ 84ms/op
|
||||||
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp
|
aggregateSignatures/128 x 3 ops/sec @ 332ms/opp
|
||||||
|
|
||||||
|
hash-to-curve
|
||||||
|
hash_to_field x 850,340 ops/sec @ 1μs/op
|
||||||
|
hashToCurve
|
||||||
|
├─secp256k1 x 1,850 ops/sec @ 540μs/op
|
||||||
|
├─P256 x 3,352 ops/sec @ 298μs/op
|
||||||
|
├─P384 x 1,367 ops/sec @ 731μs/op
|
||||||
|
├─P521 x 691 ops/sec @ 1ms/op
|
||||||
|
├─ed25519 x 2,492 ops/sec @ 401μs/op
|
||||||
|
└─ed448 x 1,045 ops/sec @ 956μs/op
|
||||||
```
|
```
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { hashToCurve as P521 } from '../p521.js';
|
|||||||
import { hashToCurve as ed25519 } from '../ed25519.js';
|
import { hashToCurve as ed25519 } from '../ed25519.js';
|
||||||
import { hashToCurve as ed448 } from '../ed448.js';
|
import { hashToCurve as ed448 } from '../ed448.js';
|
||||||
import { utf8ToBytes } from '../abstract/utils.js';
|
import { utf8ToBytes } from '../abstract/utils.js';
|
||||||
|
|
||||||
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
||||||
run(async () => {
|
run(async () => {
|
||||||
const rand = randomBytes(40);
|
const rand = randomBytes(40);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "0.7.2",
|
"version": "0.7.3",
|
||||||
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
|
||||||
"files": [
|
"files": [
|
||||||
"abstract",
|
"abstract",
|
||||||
|
|||||||
@@ -1,33 +1,35 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import type { Group, GroupConstructor, AffinePoint } from './curve.js';
|
import type { Group, GroupConstructor, AffinePoint } from './curve.js';
|
||||||
import { mod, Field } from './modular.js';
|
import { mod, Field } from './modular.js';
|
||||||
import { CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
|
import { bytesToNumberBE, CHash, concatBytes, utf8ToBytes, validateObject } from './utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * `DST` is a domain separation tag, defined in section 2.2.5
|
||||||
|
* * `p` characteristic of F, where F is a finite field of characteristic p and order q = p^m
|
||||||
|
* * `m` is extension degree (1 for prime fields)
|
||||||
|
* * `k` is the target security target in bits (e.g. 128), from section 5.1
|
||||||
|
* * `expand` is `xmd` (SHA2, SHA3, BLAKE) or `xof` (SHAKE, BLAKE-XOF)
|
||||||
|
* * `hash` conforming to `utils.CHash` interface, with `outputLen` / `blockLen` props
|
||||||
|
*/
|
||||||
export type Opts = {
|
export type Opts = {
|
||||||
DST: string; // DST: a domain separation tag, defined in section 2.2.5
|
DST: string | Uint8Array;
|
||||||
encodeDST: string;
|
p: bigint;
|
||||||
p: bigint; // characteristic of F, where F is a finite field of characteristic p and order q = p^m
|
m: number;
|
||||||
m: number; // extension degree of F, m >= 1
|
k: number;
|
||||||
k: number; // k: the target security level for the suite in bits, defined in section 5.1
|
expand?: 'xmd' | 'xof';
|
||||||
expand?: 'xmd' | 'xof'; // use a message that has already been processed by expand_message_xmd
|
|
||||||
// Hash functions for: expand_message_xmd is appropriate for use with a
|
|
||||||
// wide range of hash functions, including SHA-2, SHA-3, BLAKE2, and others.
|
|
||||||
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
|
||||||
// TODO: verify that hash is shake if expand==='xof' via types
|
|
||||||
hash: CHash;
|
hash: CHash;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Octet Stream to Integer (bytesToNumberBE)
|
function validateDST(dst: string | Uint8Array): Uint8Array {
|
||||||
function os2ip(bytes: Uint8Array): bigint {
|
if (dst instanceof Uint8Array) return dst;
|
||||||
let result = 0n;
|
if (typeof dst === 'string') return utf8ToBytes(dst);
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
throw new Error('DST must be Uint8Array or string');
|
||||||
result <<= 8n;
|
|
||||||
result += BigInt(bytes[i]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Integer to Octet Stream
|
// Octet Stream to Integer. "spec" implementation of os2ip is 2.5x slower vs bytesToNumberBE.
|
||||||
|
const os2ip = bytesToNumberBE;
|
||||||
|
|
||||||
|
// Integer to Octet Stream (numberToBytesBE)
|
||||||
function i2osp(value: number, length: number): Uint8Array {
|
function i2osp(value: number, length: number): Uint8Array {
|
||||||
if (value < 0 || value >= 1 << (8 * length)) {
|
if (value < 0 || value >= 1 << (8 * length)) {
|
||||||
throw new Error(`bad I2OSP call: value=${value} length=${length}`);
|
throw new Error(`bad I2OSP call: value=${value} length=${length}`);
|
||||||
@@ -68,13 +70,12 @@ export function expand_message_xmd(
|
|||||||
isNum(lenInBytes);
|
isNum(lenInBytes);
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
||||||
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
|
if (DST.length > 255) DST = H(concatBytes(utf8ToBytes('H2C-OVERSIZE-DST-'), DST));
|
||||||
const b_in_bytes = H.outputLen;
|
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
|
||||||
const r_in_bytes = H.blockLen;
|
|
||||||
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
const ell = Math.ceil(lenInBytes / b_in_bytes);
|
||||||
if (ell > 255) throw new Error('Invalid xmd length');
|
if (ell > 255) throw new Error('Invalid xmd length');
|
||||||
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
|
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
|
||||||
const Z_pad = i2osp(0, r_in_bytes);
|
const Z_pad = i2osp(0, r_in_bytes);
|
||||||
const l_i_b_str = i2osp(lenInBytes, 2);
|
const l_i_b_str = i2osp(lenInBytes, 2); // len_in_bytes_str
|
||||||
const b = new Array<Uint8Array>(ell);
|
const b = new Array<Uint8Array>(ell);
|
||||||
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
|
const b_0 = H(concatBytes(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime));
|
||||||
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
|
b[0] = H(concatBytes(b_0, i2osp(1, 1), DST_prime));
|
||||||
@@ -120,30 +121,33 @@ export function expand_message_xof(
|
|||||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
|
||||||
* @param msg a byte string containing the message to hash
|
* @param msg a byte string containing the message to hash
|
||||||
* @param count the number of elements of F to output
|
* @param count the number of elements of F to output
|
||||||
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
|
* @param options `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`, see above
|
||||||
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
|
* @returns [u_0, ..., u_(count - 1)], a list of field elements.
|
||||||
*/
|
*/
|
||||||
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
|
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
|
||||||
const { p, k, m, hash, expand, DST: _DST } = options;
|
const { p, k, m, hash, expand, DST: _DST } = options;
|
||||||
isBytes(msg);
|
isBytes(msg);
|
||||||
isNum(count);
|
isNum(count);
|
||||||
if (typeof _DST !== 'string') throw new Error('DST must be valid');
|
const DST = validateDST(_DST);
|
||||||
const log2p = p.toString(2).length;
|
const log2p = p.toString(2).length;
|
||||||
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
|
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
|
||||||
const len_in_bytes = count * m * L;
|
const len_in_bytes = count * m * L;
|
||||||
const DST = utf8ToBytes(_DST);
|
let prb; // pseudo_random_bytes
|
||||||
const pseudo_random_bytes =
|
if (expand === 'xmd') {
|
||||||
expand === 'xmd'
|
prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
|
||||||
? expand_message_xmd(msg, DST, len_in_bytes, hash)
|
} else if (expand === 'xof') {
|
||||||
: expand === 'xof'
|
prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
|
||||||
? expand_message_xof(msg, DST, len_in_bytes, k, hash)
|
} else if (expand === undefined) {
|
||||||
: msg;
|
prb = msg;
|
||||||
|
} else {
|
||||||
|
throw new Error('expand must be "xmd", "xof" or undefined');
|
||||||
|
}
|
||||||
const u = new Array(count);
|
const u = new Array(count);
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const e = new Array(m);
|
const e = new Array(m);
|
||||||
for (let j = 0; j < m; j++) {
|
for (let j = 0; j < m; j++) {
|
||||||
const elm_offset = L * (j + i * m);
|
const elm_offset = L * (j + i * m);
|
||||||
const tv = pseudo_random_bytes.subarray(elm_offset, elm_offset + L);
|
const tv = prb.subarray(elm_offset, elm_offset + L);
|
||||||
e[j] = mod(os2ip(tv), p);
|
e[j] = mod(os2ip(tv), p);
|
||||||
}
|
}
|
||||||
u[i] = e;
|
u[i] = e;
|
||||||
@@ -184,7 +188,7 @@ export type htfBasicOpts = { DST: string };
|
|||||||
export function createHasher<T>(
|
export function createHasher<T>(
|
||||||
Point: H2CPointConstructor<T>,
|
Point: H2CPointConstructor<T>,
|
||||||
mapToCurve: MapToCurve<T>,
|
mapToCurve: MapToCurve<T>,
|
||||||
def: Opts
|
def: Opts & { encodeDST?: string }
|
||||||
) {
|
) {
|
||||||
validateObject(def, {
|
validateObject(def, {
|
||||||
DST: 'string',
|
DST: 'string',
|
||||||
@@ -193,10 +197,7 @@ export function createHasher<T>(
|
|||||||
k: 'isSafeInteger',
|
k: 'isSafeInteger',
|
||||||
hash: 'hash',
|
hash: 'hash',
|
||||||
});
|
});
|
||||||
if (def.expand !== 'xmd' && def.expand !== 'xof' && def.expand !== undefined)
|
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
|
||||||
throw new Error('Invalid htf/expand');
|
|
||||||
if (typeof mapToCurve !== 'function')
|
|
||||||
throw new Error('hashToCurve: mapToCurve() has not been defined');
|
|
||||||
return {
|
return {
|
||||||
// Encodes byte string to elliptic curve
|
// Encodes byte string to elliptic curve
|
||||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3
|
||||||
|
|||||||
@@ -857,7 +857,7 @@ describe('bls12-381/basic', () => {
|
|||||||
const options = {
|
const options = {
|
||||||
p: bls.CURVE.r,
|
p: bls.CURVE.r,
|
||||||
m: 1,
|
m: 1,
|
||||||
expand: false,
|
expand: undefined,
|
||||||
};
|
};
|
||||||
for (let vector of SCALAR_VECTORS) {
|
for (let vector of SCALAR_VECTORS) {
|
||||||
const [okmAscii, expectedHex] = vector;
|
const [okmAscii, expectedHex] = vector;
|
||||||
|
|||||||
Reference in New Issue
Block a user