hash-to-curve: add xmd/xof support

This commit is contained in:
Paul Miller 2022-12-31 06:47:26 +00:00
parent 65d7256b9e
commit 5d42549acc
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
8 changed files with 92 additions and 13 deletions

@ -96,6 +96,10 @@ export const CURVES = {
old_secp.recoverPublicKey(msg, new old_secp.Signature(sig.r, sig.s), sig.recovery),
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg),
},
hashToCurve: {
samples: 500,
noble: () => secp256k1.Point.hashToCurve('abcd'),
},
},
ed25519: {
data: () => {
@ -124,6 +128,10 @@ export const CURVES = {
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
},
hashToCurve: {
samples: 500,
noble: () => ed25519.Point.hashToCurve('abcd'),
},
},
ed448: {
data: () => {
@ -145,6 +153,10 @@ export const CURVES = {
samples: 500,
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub),
},
hashToCurve: {
samples: 500,
noble: () => ed448.Point.hashToCurve('abcd'),
},
},
nist: {
data: () => {
@ -168,6 +180,12 @@ export const CURVES = {
P384: ({ p384: { sig, msg, pub } }) => P384.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: {
data: () => {

@ -17,10 +17,11 @@ export type htfOpts = {
k: number;
// option to use a message that has already been processed by
// expand_message_xmd
expand: boolean;
expand?: 'xmd' | 'xof';
// 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;
};
@ -29,7 +30,8 @@ export function validateHTFOpts(opts: htfOpts) {
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.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))
throw new Error('Invalid htf/hash function');
}
@ -101,6 +103,32 @@ export function expand_message_xmd(
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
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3
// 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 DST = stringToBytes(options.DST);
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);
} 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);
for (let i = 0; i < count; i++) {

@ -12,7 +12,7 @@ export type CHash = {
(message: Uint8Array | string): Uint8Array;
blockLen: 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

@ -926,12 +926,12 @@ const htfDefaults = {
k: 128,
// option to use a message that has already been processed by
// expand_message_xmd
expand: true,
expand: '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
hash: sha256,
};
} as const;
// Encoding utils
// Point on G1 curve: (x, y)

@ -37,7 +37,7 @@ export const P256 = createCurve(
p: Fp.ORDER,
m: 1,
k: 128,
expand: true,
expand: 'xmd',
hash: sha256,
},
} as const,

@ -41,7 +41,7 @@ export const P384 = createCurve({
p: Fp.ORDER,
m: 1,
k: 192,
expand: true,
expand: 'xmd',
hash: sha384,
},
} as const,

@ -54,7 +54,7 @@ export const P521 = createCurve({
p: Fp.ORDER,
m: 1,
k: 256,
expand: true,
expand: 'xmd',
hash: sha512,
},
} as const, sha512);

@ -4,16 +4,27 @@ import { bytesToHex } from '@noble/hashes/utils';
// Generic tests for all curves in package
import { sha256 } from '@noble/hashes/sha256';
import { sha512 } from '@noble/hashes/sha512';
import { shake128, shake256 } from '@noble/hashes/sha3';
import { secp256r1 } from '../lib/esm/p256.js';
import { secp384r1 } from '../lib/esm/p384.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 { 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_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' };
// 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
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' };
@ -58,6 +69,26 @@ testExpandXMD(sha256, xmd_sha256_38);
testExpandXMD(sha256, xmd_sha256_256);
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) {
// bls-G2 support
if (s.includes(',')) {
@ -97,8 +128,8 @@ testCurve(secp521r1, p521_ro, p521_nu);
testCurve(bls12_381.G1, g1_ro, g1_nu);
testCurve(bls12_381.G2, g2_ro, g2_nu);
testCurve(secp256k1, secp256k1_ro, secp256k1_nu);
//testCurve(ed25519, ed25519_ro, ed25519_nu);
//testCurve(ed448, ed448_ro, ed448_nu);
testCurve(ed25519, ed25519_ro, ed25519_nu);
testCurve(ed448, ed448_ro, ed448_nu);
// ESM is broken.
import url from 'url';