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), 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';