forked from tornado-packages/noble-curves
hash-to-curve: speed-up os2ip, change code a bit
This commit is contained in:
parent
0163b63532
commit
6bc4b35cf4
10
README.md
10
README.md
@ -692,6 +692,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,11 +1,10 @@
|
|||||||
/*! 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';
|
||||||
|
|
||||||
export type Opts = {
|
export type Opts = {
|
||||||
DST: string; // DST: a domain separation tag, defined in section 2.2.5
|
DST: string | Uint8Array; // 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
|
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
|
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
|
k: number; // k: the target security level for the suite in bits, defined in section 5.1
|
||||||
@ -17,17 +16,16 @@ export type Opts = {
|
|||||||
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 +66,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));
|
||||||
@ -118,32 +115,40 @@ export function expand_message_xof(
|
|||||||
/**
|
/**
|
||||||
* 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
|
||||||
* 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
|
||||||
|
* As for options:
|
||||||
|
* * `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
|
||||||
* @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 +189,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 +198,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;
|
||||||
|
Loading…
Reference in New Issue
Block a user