3 Commits
0.7.2 ... 0.7.3

Author SHA1 Message Date
Paul Miller
b8b2e91f74 Release 0.7.3. 2023-02-26 19:05:53 +01:00
Paul Miller
9ee694ae23 docs updates 2023-02-26 19:05:40 +01:00
Paul Miller
6bc4b35cf4 hash-to-curve: speed-up os2ip, change code a bit 2023-02-26 18:55:30 +01:00
5 changed files with 66 additions and 49 deletions

View File

@@ -5,7 +5,7 @@ Audited & minimal JS implementation of elliptic curve cryptography.
- **noble** family, zero dependencies
- Short Weierstrass, Edwards, Montgomery curves
- 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
- 🧜‍♂️ [Poseidon](https://www.poseidon-hash.info) ZK-friendly hash
- 🏎 [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
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:
@@ -481,7 +481,6 @@ hashToCurve('0102abcd');
console.log(hashToCurve(randomBytes()));
console.log(encodeToCurve(randomBytes()));
import { bls12_381 } from '@noble/curves/bls12-381';
bls12_381.G1.hashToCurve(randomBytes(), { DST: 'another' });
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.
Hash must conform to `CHash` interface (see [weierstrass section](#abstractweierstrass-short-weierstrass-curve)).
```ts
function expand_message_xmd(
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)
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
_ `options` `{DST: string, p: bigint, m: number, k: number, expand: 'xmd' | 'xof', hash: H}`
_ Returns `[u_0, ..., u_(count - 1)]`, a list of field elements.
- `msg` a byte string containing the message to hash
- `count` the number of elements of F to output
- `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
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
@@ -586,7 +592,6 @@ const derived = hkdf(sha256, someKey, undefined, 'application', 40); // 40 bytes
const validPrivateKey = mod.hashToPrivateScalar(derived, p256.CURVE.n);
```
### abstract/utils: General utilities
```ts
@@ -692,6 +697,16 @@ aggregatePublicKeys/128 x 7 ops/sec @ 125ms/op
aggregateSignatures/8 x 45 ops/sec @ 22ms/op
aggregateSignatures/32 x 11 ops/sec @ 84ms/op
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

View File

@@ -11,6 +11,7 @@ import { hashToCurve as P521 } from '../p521.js';
import { hashToCurve as ed25519 } from '../ed25519.js';
import { hashToCurve as ed448 } from '../ed448.js';
import { utf8ToBytes } from '../abstract/utils.js';
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
run(async () => {
const rand = randomBytes(40);

View File

@@ -1,6 +1,6 @@
{
"name": "@noble/curves",
"version": "0.7.2",
"version": "0.7.3",
"description": "Minimal, auditable JS implementation of elliptic curve cryptography",
"files": [
"abstract",

View File

@@ -1,33 +1,35 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
import type { Group, GroupConstructor, AffinePoint } from './curve.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 = {
DST: string; // DST: a domain separation tag, defined in section 2.2.5
encodeDST: string;
p: bigint; // characteristic of F, where F is a finite field of characteristic p and order q = p^m
m: number; // extension degree of F, m >= 1
k: number; // k: the target security level for the suite in bits, defined in section 5.1
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
DST: string | Uint8Array;
p: bigint;
m: number;
k: number;
expand?: 'xmd' | 'xof';
hash: CHash;
};
// Octet Stream to Integer (bytesToNumberBE)
function os2ip(bytes: Uint8Array): bigint {
let result = 0n;
for (let i = 0; i < bytes.length; i++) {
result <<= 8n;
result += BigInt(bytes[i]);
}
return result;
function validateDST(dst: string | Uint8Array): Uint8Array {
if (dst instanceof Uint8Array) return dst;
if (typeof dst === 'string') return utf8ToBytes(dst);
throw new Error('DST must be Uint8Array or string');
}
// 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 {
if (value < 0 || value >= 1 << (8 * length)) {
throw new Error(`bad I2OSP call: value=${value} length=${length}`);
@@ -68,13 +70,12 @@ export function expand_message_xmd(
isNum(lenInBytes);
// 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));
const b_in_bytes = H.outputLen;
const r_in_bytes = H.blockLen;
const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H;
const ell = Math.ceil(lenInBytes / b_in_bytes);
if (ell > 255) throw new Error('Invalid xmd length');
const DST_prime = concatBytes(DST, i2osp(DST.length, 1));
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_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));
@@ -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
* @param msg a byte string containing the message to hash
* @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.
*/
export function hash_to_field(msg: Uint8Array, count: number, options: Opts): bigint[][] {
const { p, k, m, hash, expand, DST: _DST } = options;
isBytes(msg);
isNum(count);
if (typeof _DST !== 'string') throw new Error('DST must be valid');
const DST = validateDST(_DST);
const log2p = p.toString(2).length;
const L = Math.ceil((log2p + k) / 8); // section 5.1 of ietf draft link above
const len_in_bytes = count * m * L;
const DST = utf8ToBytes(_DST);
const pseudo_random_bytes =
expand === 'xmd'
? expand_message_xmd(msg, DST, len_in_bytes, hash)
: expand === 'xof'
? expand_message_xof(msg, DST, len_in_bytes, k, hash)
: msg;
let prb; // pseudo_random_bytes
if (expand === 'xmd') {
prb = expand_message_xmd(msg, DST, len_in_bytes, hash);
} else if (expand === 'xof') {
prb = expand_message_xof(msg, DST, len_in_bytes, k, hash);
} else if (expand === undefined) {
prb = msg;
} else {
throw new Error('expand must be "xmd", "xof" or undefined');
}
const u = new Array(count);
for (let i = 0; i < count; i++) {
const e = new Array(m);
for (let j = 0; j < m; j++) {
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);
}
u[i] = e;
@@ -184,7 +188,7 @@ export type htfBasicOpts = { DST: string };
export function createHasher<T>(
Point: H2CPointConstructor<T>,
mapToCurve: MapToCurve<T>,
def: Opts
def: Opts & { encodeDST?: string }
) {
validateObject(def, {
DST: 'string',
@@ -193,10 +197,7 @@ export function createHasher<T>(
k: 'isSafeInteger',
hash: 'hash',
});
if (def.expand !== 'xmd' && def.expand !== 'xof' && def.expand !== undefined)
throw new Error('Invalid htf/expand');
if (typeof mapToCurve !== 'function')
throw new Error('hashToCurve: mapToCurve() has not been defined');
if (typeof mapToCurve !== 'function') throw new Error('mapToCurve() must be defined');
return {
// Encodes byte string to elliptic curve
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3

View File

@@ -857,7 +857,7 @@ describe('bls12-381/basic', () => {
const options = {
p: bls.CURVE.r,
m: 1,
expand: false,
expand: undefined,
};
for (let vector of SCALAR_VECTORS) {
const [okmAscii, expectedHex] = vector;