forked from tornado-packages/noble-curves
hash-to-curve: add xmd/xof support
This commit is contained in:
parent
65d7256b9e
commit
5d42549acc
@ -96,6 +96,10 @@ export const CURVES = {
|
|||||||
old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery),
|
old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery),
|
||||||
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg),
|
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg),
|
||||||
},
|
},
|
||||||
|
hashToCurve: {
|
||||||
|
samples: 500,
|
||||||
|
noble: () => secp256k1.Point.hashToCurve('abcd'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ed25519: {
|
ed25519: {
|
||||||
data: () => {
|
data: () => {
|
||||||
@ -124,6 +128,10 @@ export const CURVES = {
|
|||||||
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
|
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
|
||||||
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
|
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
|
||||||
},
|
},
|
||||||
|
hashToCurve: {
|
||||||
|
samples: 500,
|
||||||
|
noble: () => ed25519.Point.hashToCurve('abcd'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ed448: {
|
ed448: {
|
||||||
data: () => {
|
data: () => {
|
||||||
@ -145,6 +153,10 @@ export const CURVES = {
|
|||||||
samples: 500,
|
samples: 500,
|
||||||
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub),
|
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub),
|
||||||
},
|
},
|
||||||
|
hashToCurve: {
|
||||||
|
samples: 500,
|
||||||
|
noble: () => ed448.Point.hashToCurve('abcd'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
nist: {
|
nist: {
|
||||||
data: () => {
|
data: () => {
|
||||||
@ -168,6 +180,12 @@ export const CURVES = {
|
|||||||
P384: ({ p384: { sig, msg, pub } }) => P384.verify(sig, msg, pub),
|
P384: ({ p384: { sig, msg, pub } }) => P384.verify(sig, msg, pub),
|
||||||
P521: ({ p521: { sig, msg, pub } }) => P521.verify(sig, msg, pub),
|
P521: ({ p521: { sig, msg, pub } }) => P521.verify(sig, msg, pub),
|
||||||
},
|
},
|
||||||
|
hashToCurve: {
|
||||||
|
samples: 500,
|
||||||
|
P256: () => P256.Point.hashToCurve('abcd'),
|
||||||
|
P384: () => P384.Point.hashToCurve('abcd'),
|
||||||
|
P521: () => P521.Point.hashToCurve('abcd'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
stark: {
|
stark: {
|
||||||
data: () => {
|
data: () => {
|
||||||
|
@ -17,10 +17,11 @@ export type htfOpts = {
|
|||||||
k: number;
|
k: number;
|
||||||
// option to use a message that has already been processed by
|
// option to use a message that has already been processed by
|
||||||
// expand_message_xmd
|
// expand_message_xmd
|
||||||
expand: boolean;
|
expand?: 'xmd' | 'xof';
|
||||||
// Hash functions for: expand_message_xmd is appropriate for use with a
|
// 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.
|
// 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
|
// 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -29,7 +30,8 @@ export function validateHTFOpts(opts: htfOpts) {
|
|||||||
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
|
if (typeof opts.p !== 'bigint') throw new Error('Invalid htf/p');
|
||||||
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
|
if (typeof opts.m !== 'number') throw new Error('Invalid htf/m');
|
||||||
if (typeof opts.k !== 'number') throw new Error('Invalid htf/k');
|
if (typeof opts.k !== 'number') throw new Error('Invalid htf/k');
|
||||||
if (typeof opts.expand !== 'boolean') throw new Error('Invalid htf/expand');
|
if (opts.expand !== 'xmd' && opts.expand !== 'xof' && opts.expand !== undefined)
|
||||||
|
throw new Error('Invalid htf/expand');
|
||||||
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
||||||
throw new Error('Invalid htf/hash function');
|
throw new Error('Invalid htf/hash function');
|
||||||
}
|
}
|
||||||
@ -101,6 +103,32 @@ export function expand_message_xmd(
|
|||||||
return pseudo_random_bytes.slice(0, lenInBytes);
|
return pseudo_random_bytes.slice(0, lenInBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function expand_message_xof(
|
||||||
|
msg: Uint8Array,
|
||||||
|
DST: Uint8Array,
|
||||||
|
lenInBytes: number,
|
||||||
|
k: number,
|
||||||
|
H: CHash
|
||||||
|
) {
|
||||||
|
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.3
|
||||||
|
// DST = H('H2C-OVERSIZE-DST-' || a_very_long_DST, Math.ceil((lenInBytes * k) / 8));
|
||||||
|
if (DST.length > 255) {
|
||||||
|
const dkLen = Math.ceil((2 * k) / 8);
|
||||||
|
DST = H.create({ dkLen }).update(stringToBytes('H2C-OVERSIZE-DST-')).update(DST).digest();
|
||||||
|
}
|
||||||
|
if (lenInBytes > 65535 || DST.length > 255)
|
||||||
|
throw new Error('expand_message_xof: invalid lenInBytes');
|
||||||
|
return (
|
||||||
|
H.create({ dkLen: lenInBytes })
|
||||||
|
.update(msg)
|
||||||
|
.update(i2osp(lenInBytes, 2))
|
||||||
|
// 2. DST_prime = DST || I2OSP(len(DST), 1)
|
||||||
|
.update(DST)
|
||||||
|
.update(i2osp(DST.length, 1))
|
||||||
|
.digest()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
// Inputs:
|
// Inputs:
|
||||||
@ -116,8 +144,10 @@ export function hash_to_field(msg: Uint8Array, count: number, options: htfOpts):
|
|||||||
const len_in_bytes = count * options.m * L;
|
const len_in_bytes = count * options.m * L;
|
||||||
const DST = stringToBytes(options.DST);
|
const DST = stringToBytes(options.DST);
|
||||||
let pseudo_random_bytes = msg;
|
let pseudo_random_bytes = msg;
|
||||||
if (options.expand) {
|
if (options.expand === 'xmd') {
|
||||||
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash);
|
pseudo_random_bytes = expand_message_xmd(msg, DST, len_in_bytes, options.hash);
|
||||||
|
} else if (options.expand === 'xof') {
|
||||||
|
pseudo_random_bytes = expand_message_xof(msg, DST, len_in_bytes, options.k, options.hash);
|
||||||
}
|
}
|
||||||
const u = new Array(count);
|
const u = new Array(count);
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
|
@ -12,7 +12,7 @@ export type CHash = {
|
|||||||
(message: Uint8Array | string): Uint8Array;
|
(message: Uint8Array | string): Uint8Array;
|
||||||
blockLen: number;
|
blockLen: number;
|
||||||
outputLen: number;
|
outputLen: number;
|
||||||
create(): any;
|
create(opts?: { dkLen?: number }): any; // For shake
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
|
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
|
||||||
|
@ -926,12 +926,12 @@ const htfDefaults = {
|
|||||||
k: 128,
|
k: 128,
|
||||||
// option to use a message that has already been processed by
|
// option to use a message that has already been processed by
|
||||||
// expand_message_xmd
|
// expand_message_xmd
|
||||||
expand: true,
|
expand: 'xmd',
|
||||||
// Hash functions for: expand_message_xmd is appropriate for use with a
|
// 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.
|
// 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
|
// BBS+ uses blake2: https://github.com/hyperledger/aries-framework-go/issues/2247
|
||||||
hash: sha256,
|
hash: sha256,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
// Encoding utils
|
// Encoding utils
|
||||||
// Point on G1 curve: (x, y)
|
// Point on G1 curve: (x, y)
|
||||||
|
@ -37,7 +37,7 @@ export const P256 = createCurve(
|
|||||||
p: Fp.ORDER,
|
p: Fp.ORDER,
|
||||||
m: 1,
|
m: 1,
|
||||||
k: 128,
|
k: 128,
|
||||||
expand: true,
|
expand: 'xmd',
|
||||||
hash: sha256,
|
hash: sha256,
|
||||||
},
|
},
|
||||||
} as const,
|
} as const,
|
||||||
|
@ -41,7 +41,7 @@ export const P384 = createCurve({
|
|||||||
p: Fp.ORDER,
|
p: Fp.ORDER,
|
||||||
m: 1,
|
m: 1,
|
||||||
k: 192,
|
k: 192,
|
||||||
expand: true,
|
expand: 'xmd',
|
||||||
hash: sha384,
|
hash: sha384,
|
||||||
},
|
},
|
||||||
} as const,
|
} as const,
|
||||||
|
@ -54,7 +54,7 @@ export const P521 = createCurve({
|
|||||||
p: Fp.ORDER,
|
p: Fp.ORDER,
|
||||||
m: 1,
|
m: 1,
|
||||||
k: 256,
|
k: 256,
|
||||||
expand: true,
|
expand: 'xmd',
|
||||||
hash: sha512,
|
hash: sha512,
|
||||||
},
|
},
|
||||||
} as const, sha512);
|
} as const, sha512);
|
||||||
|
@ -4,16 +4,27 @@ import { bytesToHex } from '@noble/hashes/utils';
|
|||||||
// Generic tests for all curves in package
|
// Generic tests for all curves in package
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
import { shake128, shake256 } from '@noble/hashes/sha3';
|
||||||
import { secp256r1 } from '../lib/esm/p256.js';
|
import { secp256r1 } from '../lib/esm/p256.js';
|
||||||
import { secp384r1 } from '../lib/esm/p384.js';
|
import { secp384r1 } from '../lib/esm/p384.js';
|
||||||
import { secp521r1 } from '../lib/esm/p521.js';
|
import { secp521r1 } from '../lib/esm/p521.js';
|
||||||
|
import { ed25519 } from '../lib/esm/ed25519.js';
|
||||||
|
import { ed448 } from '../lib/esm/ed448.js';
|
||||||
import { secp256k1 } from '../lib/esm/secp256k1.js';
|
import { secp256k1 } from '../lib/esm/secp256k1.js';
|
||||||
import { bls12_381 } from '../lib/esm/bls12-381.js';
|
import { bls12_381 } from '../lib/esm/bls12-381.js';
|
||||||
import { stringToBytes, expand_message_xmd } from '../lib/esm/abstract/hash-to-curve.js';
|
import {
|
||||||
|
stringToBytes,
|
||||||
|
expand_message_xmd,
|
||||||
|
expand_message_xof,
|
||||||
|
} from '../lib/esm/abstract/hash-to-curve.js';
|
||||||
|
// XMD
|
||||||
import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' };
|
import { default as xmd_sha256_38 } from './hash-to-curve/expand_message_xmd_SHA256_38.json' assert { type: 'json' };
|
||||||
import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };
|
import { default as xmd_sha256_256 } from './hash-to-curve/expand_message_xmd_SHA256_256.json' assert { type: 'json' };
|
||||||
import { default as xmd_sha512_38 } from './hash-to-curve/expand_message_xmd_SHA512_38.json' assert { type: 'json' };
|
import { default as xmd_sha512_38 } from './hash-to-curve/expand_message_xmd_SHA512_38.json' assert { type: 'json' };
|
||||||
|
// XOF
|
||||||
|
import { default as xof_shake128_36 } from './hash-to-curve/expand_message_xof_SHAKE128_36.json' assert { type: 'json' };
|
||||||
|
import { default as xof_shake128_256 } from './hash-to-curve/expand_message_xof_SHAKE128_256.json' assert { type: 'json' };
|
||||||
|
import { default as xof_shake256_36 } from './hash-to-curve/expand_message_xof_SHAKE256_36.json' assert { type: 'json' };
|
||||||
// P256
|
// P256
|
||||||
import { default as p256_ro } from './hash-to-curve/P256_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
|
import { default as p256_ro } from './hash-to-curve/P256_XMD:SHA-256_SSWU_RO_.json' assert { type: 'json' };
|
||||||
import { default as p256_nu } from './hash-to-curve/P256_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
|
import { default as p256_nu } from './hash-to-curve/P256_XMD:SHA-256_SSWU_NU_.json' assert { type: 'json' };
|
||||||
@ -58,6 +69,26 @@ testExpandXMD(sha256, xmd_sha256_38);
|
|||||||
testExpandXMD(sha256, xmd_sha256_256);
|
testExpandXMD(sha256, xmd_sha256_256);
|
||||||
testExpandXMD(sha512, xmd_sha512_38);
|
testExpandXMD(sha512, xmd_sha512_38);
|
||||||
|
|
||||||
|
function testExpandXOF(hash, vectors) {
|
||||||
|
for (let i = 0; i < vectors.tests.length; i++) {
|
||||||
|
const t = vectors.tests[i];
|
||||||
|
should(`expand_message_xof/${vectors.hash}/${vectors.DST.length}/${i}`, () => {
|
||||||
|
const p = expand_message_xof(
|
||||||
|
stringToBytes(t.msg),
|
||||||
|
stringToBytes(vectors.DST),
|
||||||
|
+t.len_in_bytes,
|
||||||
|
vectors.k,
|
||||||
|
hash
|
||||||
|
);
|
||||||
|
deepStrictEqual(bytesToHex(p), t.uniform_bytes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testExpandXOF(shake128, xof_shake128_36);
|
||||||
|
testExpandXOF(shake128, xof_shake128_256);
|
||||||
|
testExpandXOF(shake256, xof_shake256_36);
|
||||||
|
|
||||||
function stringToFp(s) {
|
function stringToFp(s) {
|
||||||
// bls-G2 support
|
// bls-G2 support
|
||||||
if (s.includes(',')) {
|
if (s.includes(',')) {
|
||||||
@ -97,8 +128,8 @@ testCurve(secp521r1, p521_ro, p521_nu);
|
|||||||
testCurve(bls12_381.G1, g1_ro, g1_nu);
|
testCurve(bls12_381.G1, g1_ro, g1_nu);
|
||||||
testCurve(bls12_381.G2, g2_ro, g2_nu);
|
testCurve(bls12_381.G2, g2_ro, g2_nu);
|
||||||
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
|
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
|
||||||
//testCurve(ed25519, ed25519_ro, ed25519_nu);
|
testCurve(ed25519, ed25519_ro, ed25519_nu);
|
||||||
//testCurve(ed448, ed448_ro, ed448_nu);
|
testCurve(ed448, ed448_ro, ed448_nu);
|
||||||
|
|
||||||
// ESM is broken.
|
// ESM is broken.
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
|
Loading…
Reference in New Issue
Block a user