edwards: change API. Add options.strict, context. Add edwardsToMontgomery
This commit is contained in:
parent
65c0dc6c59
commit
113d906233
@ -18,7 +18,7 @@ export type CurveType = BasicCurve<bigint> & {
|
||||
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array; // clears bits to get valid field elemtn
|
||||
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array; // Used for hashing
|
||||
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint }; // Ratio √(u/v)
|
||||
preHash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
|
||||
prehash?: FHash; // RFC 8032 pre-hashing of messages to sign() / verify()
|
||||
mapToCurve?: (scalar: bigint[]) => AffinePoint<bigint>; // for hash-to-curve standard
|
||||
};
|
||||
|
||||
@ -90,7 +90,15 @@ export type CurveFn = {
|
||||
// It is not generic twisted curve for now, but ed25519/ed448 generic implementation
|
||||
export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
||||
const { Fp, n: CURVE_ORDER, preHash, hash: cHash, randomBytes, nByteLength, h: cofactor } = CURVE;
|
||||
const {
|
||||
Fp,
|
||||
n: CURVE_ORDER,
|
||||
prehash: preHash,
|
||||
hash: cHash,
|
||||
randomBytes,
|
||||
nByteLength,
|
||||
h: cofactor,
|
||||
} = CURVE;
|
||||
const MASK = _2n ** BigInt(nByteLength * 8);
|
||||
const modP = Fp.create; // Function overrides
|
||||
|
||||
@ -423,29 +431,30 @@ export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||
}
|
||||
|
||||
/** Signs message with privateKey. RFC8032 5.1.6 */
|
||||
function sign(msg: Hex, privKey: Hex, context?: Hex): Uint8Array {
|
||||
function sign(msg: Hex, privKey: Hex, options: { context?: Hex } = {}): Uint8Array {
|
||||
msg = ensureBytes('message', msg);
|
||||
if (preHash) msg = preHash(msg); // for ed25519ph etc.
|
||||
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey);
|
||||
const r = hashDomainToScalar(context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
|
||||
const r = hashDomainToScalar(options.context, prefix, msg); // r = dom2(F, C) || prefix || PH(M)
|
||||
const R = G.multiply(r).toRawBytes(); // R = rG
|
||||
const k = hashDomainToScalar(context, R, pointBytes, msg); // R || A || PH(M)
|
||||
const k = hashDomainToScalar(options.context, R, pointBytes, msg); // R || A || PH(M)
|
||||
const s = modN(r + k * scalar); // S = (r + k * s) mod L
|
||||
assertGE0(s); // 0 <= s < l
|
||||
const res = ut.concatBytes(R, ut.numberToBytesLE(s, Fp.BYTES));
|
||||
return ensureBytes('result', res, nByteLength * 2); // 64-byte signature
|
||||
}
|
||||
|
||||
function verify(sig: Hex, msg: Hex, publicKey: Hex, context?: Hex): boolean {
|
||||
const verifyOpts: { context?: Hex; strict?: boolean } = { strict: false };
|
||||
function verify(sig: Hex, msg: Hex, publicKey: Hex, options = verifyOpts): boolean {
|
||||
const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7.
|
||||
sig = ensureBytes('signature', sig, 2 * len); // An extended group equation is checked.
|
||||
msg = ensureBytes('message', msg); // ZIP215 compliant, which means not fully RFC8032 compliant.
|
||||
if (preHash) msg = preHash(msg); // for ed25519ph, etc
|
||||
const A = Point.fromHex(publicKey, false); // Check for s bounds, hex validity
|
||||
const R = Point.fromHex(sig.slice(0, len), false); // 0 <= R < 2^256: ZIP215 R can be >= P
|
||||
const R = Point.fromHex(sig.slice(0, len), options.strict); // R <P (RFC8032) or <2^256 (ZIP215)
|
||||
const s = ut.bytesToNumberLE(sig.slice(len, 2 * len));
|
||||
const SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside
|
||||
const k = hashDomainToScalar(context, R.toRawBytes(), A.toRawBytes(), msg);
|
||||
const k = hashDomainToScalar(options.context, R.toRawBytes(), A.toRawBytes(), msg);
|
||||
const RkA = R.add(A.multiplyUnsafe(k));
|
||||
// [8][S]B = [8]R + [8][k]A'
|
||||
return RkA.subtract(SB).clearCofactor().equals(Point.ZERO);
|
||||
|
@ -95,7 +95,7 @@ export const ED25519_TORSION_SUBGROUP = [
|
||||
|
||||
const Fp = Field(ED25519_P, undefined, true);
|
||||
|
||||
const ED25519_DEF = {
|
||||
const ed25519Defaults = {
|
||||
// Param: a
|
||||
a: BigInt(-1),
|
||||
// Equal to -121665/121666 over finite field.
|
||||
@ -120,7 +120,7 @@ const ED25519_DEF = {
|
||||
uvRatio,
|
||||
} as const;
|
||||
|
||||
export const ed25519 = twistedEdwards(ED25519_DEF);
|
||||
export const ed25519 = twistedEdwards(ed25519Defaults);
|
||||
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||
if (ctx.length > 255) throw new Error('Context is too big');
|
||||
return concatBytes(
|
||||
@ -130,11 +130,11 @@ function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||
data
|
||||
);
|
||||
}
|
||||
export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain });
|
||||
export const ed25519ctx = twistedEdwards({ ...ed25519Defaults, domain: ed25519_domain });
|
||||
export const ed25519ph = twistedEdwards({
|
||||
...ED25519_DEF,
|
||||
...ed25519Defaults,
|
||||
domain: ed25519_domain,
|
||||
preHash: sha512,
|
||||
prehash: sha512,
|
||||
});
|
||||
|
||||
export const x25519 = montgomery({
|
||||
@ -153,6 +153,20 @@ export const x25519 = montgomery({
|
||||
randomBytes,
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts ed25519 public key to x25519 public key. Uses formula:
|
||||
* * `(u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)`
|
||||
* * `(x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))`
|
||||
* @example
|
||||
* const aPub = ed25519.getPublicKey(utils.randomPrivateKey());
|
||||
* x25519.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
|
||||
*/
|
||||
export function edwardsToMontgomery(edwardsPub: Hex): Uint8Array {
|
||||
const { y } = ed25519.ExtendedPoint.fromHex(edwardsPub);
|
||||
const _1n = BigInt(1);
|
||||
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
|
||||
}
|
||||
|
||||
// Hash To Curve Elligator2 Map (NOTE: different from ristretto255 elligator)
|
||||
// NOTE: very important part is usage of FpSqrtEven for ELL2_C1_EDWARDS, since
|
||||
// SageMath returns different root first and everything falls apart
|
||||
|
30
src/ed448.ts
30
src/ed448.ts
@ -120,7 +120,7 @@ const ED448_DEF = {
|
||||
|
||||
export const ed448 = twistedEdwards(ED448_DEF);
|
||||
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
||||
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
|
||||
export const ed448ph = twistedEdwards({ ...ED448_DEF, prehash: shake256_64 });
|
||||
|
||||
export const x448 = montgomery({
|
||||
a: BigInt(156326),
|
||||
@ -136,22 +136,22 @@ export const x448 = montgomery({
|
||||
},
|
||||
adjustScalarBytes,
|
||||
randomBytes,
|
||||
// The 4-isogeny maps between the Montgomery curve and this Edwards
|
||||
// curve are:
|
||||
// (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3)
|
||||
// (x, y) = (4*v*(u^2 - 1)/(u^4 - 2*u^2 + 4*v^2 + 1),
|
||||
// -(u^5 - 2*u^3 - 4*u*v^2 + u)/
|
||||
// (u^5 - 2*u^2*v^2 - 2*u^3 - 2*v^2 + u))
|
||||
// xyToU: (p: PointType) => {
|
||||
// const P = ed448P;
|
||||
// const { x, y } = p;
|
||||
// if (x === _0n) throw new Error(`Point with x=0 doesn't have mapping`);
|
||||
// const invX = invert(x * x, P); // x^2
|
||||
// const u = mod(y * y * invX, P); // (y^2/x^2)
|
||||
// return numberToBytesLE(u, 56);
|
||||
// },
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts edwards448 public key to x448 public key. Uses formula:
|
||||
* * `(u, v) = ((y-1)/(y+1), sqrt(156324)*u/x)`
|
||||
* * `(x, y) = (sqrt(156324)*u/v, (1+u)/(1-u))`
|
||||
* @example
|
||||
* const aPub = ed448.getPublicKey(utils.randomPrivateKey());
|
||||
* x448.getSharedSecret(edwardsToMontgomery(aPub), edwardsToMontgomery(someonesPub))
|
||||
*/
|
||||
export function edwardsToMontgomery(edwardsPub: string | Uint8Array): Uint8Array {
|
||||
const { y } = ed448.ExtendedPoint.fromHex(edwardsPub);
|
||||
const _1n = BigInt(1);
|
||||
return Fp.toBytes(Fp.create((y - _1n) * Fp.inv(y + _1n)));
|
||||
}
|
||||
|
||||
// Hash To Curve Elligator2 Map
|
||||
const ELL2_C1 = (Fp.ORDER - BigInt(3)) / BigInt(4); // 1. c1 = (q - 3) / 4 # Integer arithmetic
|
||||
const ELL2_J = BigInt(156326);
|
||||
|
@ -62,8 +62,14 @@ describe('RFC8032ctx', () => {
|
||||
const v = VECTORS_RFC8032_CTX[i];
|
||||
should(`${i}`, () => {
|
||||
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
|
||||
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature);
|
||||
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true);
|
||||
deepStrictEqual(
|
||||
hex(ed25519ctx.sign(v.message, v.secretKey, { context: v.context })),
|
||||
v.signature
|
||||
);
|
||||
deepStrictEqual(
|
||||
ed25519ctx.verify(v.signature, v.message, v.publicKey, { context: v.context }),
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -93,14 +99,7 @@ describe('RFC8032ph', () => {
|
||||
});
|
||||
|
||||
// x25519
|
||||
should('X25519 base point', () => {
|
||||
const { y } = ed25519ph.ExtendedPoint.BASE;
|
||||
const { Fp } = ed25519ph.CURVE;
|
||||
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
|
||||
deepStrictEqual(numberToBytesLE(u, 32), x25519.GuBytes);
|
||||
});
|
||||
|
||||
describe('RFC7748', () => {
|
||||
describe('RFC7748 X25519 ECDH', () => {
|
||||
const rfc7748Mul = [
|
||||
{
|
||||
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
|
||||
@ -127,7 +126,7 @@ describe('RFC7748', () => {
|
||||
];
|
||||
for (let i = 0; i < rfc7748Iter.length; i++) {
|
||||
const { scalar, iters } = rfc7748Iter[i];
|
||||
should(`scalarMult iteration (${i})`, () => {
|
||||
should(`scalarMult iteration x${iters}`, () => {
|
||||
let k = x25519.GuBytes;
|
||||
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(k, u), k];
|
||||
deepStrictEqual(hex(k), scalar);
|
||||
@ -145,10 +144,16 @@ describe('RFC7748', () => {
|
||||
deepStrictEqual(hex(x25519.scalarMult(alicePrivate, bobPublic)), shared);
|
||||
deepStrictEqual(hex(x25519.scalarMult(bobPrivate, alicePublic)), shared);
|
||||
});
|
||||
});
|
||||
describe('Wycheproof', () => {
|
||||
|
||||
should('base point', () => {
|
||||
const { y } = ed25519ph.ExtendedPoint.BASE;
|
||||
const { Fp } = ed25519ph.CURVE;
|
||||
const u = Fp.create((y + 1n) * Fp.inv(1n - y));
|
||||
deepStrictEqual(numberToBytesLE(u, 32), x25519.GuBytes);
|
||||
});
|
||||
|
||||
const group = x25519vectors.testGroups[0];
|
||||
should(`X25519`, () => {
|
||||
should('wycheproof', () => {
|
||||
for (let i = 0; i < group.tests.length; i++) {
|
||||
const v = group.tests[i];
|
||||
const comment = `(${i}, ${v.result}) ${v.comment}`;
|
||||
|
@ -513,8 +513,11 @@ describe('ed448', () => {
|
||||
const v = VECTORS_RFC8032_CTX[i];
|
||||
should(`${i}`, () => {
|
||||
deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey);
|
||||
deepStrictEqual(hex(ed.sign(v.message, v.secretKey, v.context)), v.signature);
|
||||
deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey, v.context), true);
|
||||
deepStrictEqual(hex(ed.sign(v.message, v.secretKey, { context: v.context })), v.signature);
|
||||
deepStrictEqual(
|
||||
ed.verify(v.signature, v.message, v.publicKey, { context: v.context }),
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -559,8 +562,14 @@ describe('ed448', () => {
|
||||
const v = VECTORS_RFC8032_PH[i];
|
||||
should(`${i}`, () => {
|
||||
deepStrictEqual(hex(ed448ph.getPublicKey(v.secretKey)), v.publicKey);
|
||||
deepStrictEqual(hex(ed448ph.sign(v.message, v.secretKey, v.context)), v.signature);
|
||||
deepStrictEqual(ed448ph.verify(v.signature, v.message, v.publicKey, v.context), true);
|
||||
deepStrictEqual(
|
||||
hex(ed448ph.sign(v.message, v.secretKey, { context: v.context })),
|
||||
v.signature
|
||||
);
|
||||
deepStrictEqual(
|
||||
ed448ph.verify(v.signature, v.message, v.publicKey, { context: v.context }),
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -661,34 +670,32 @@ describe('ed448', () => {
|
||||
deepStrictEqual(hex(x448.scalarMult(bobPrivate, alicePublic)), shared);
|
||||
});
|
||||
|
||||
describe('wycheproof', () => {
|
||||
should('wycheproof', () => {
|
||||
const group = x448vectors.testGroups[0];
|
||||
should(`X448`, () => {
|
||||
for (let i = 0; i < group.tests.length; i++) {
|
||||
const v = group.tests[i];
|
||||
const index = `(${i}, ${v.result}) ${v.comment}`;
|
||||
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||
try {
|
||||
const shared = hex(x448.scalarMult(v.private, v.public));
|
||||
deepStrictEqual(shared, v.shared, index);
|
||||
} catch (e) {
|
||||
// We are more strict
|
||||
if (e.message.includes('Expected valid scalar')) return;
|
||||
if (e.message.includes('Invalid private or public key received')) return;
|
||||
if (e.message.includes('Expected 56 bytes')) return;
|
||||
throw e;
|
||||
}
|
||||
} else if (v.result === 'invalid') {
|
||||
let failed = false;
|
||||
try {
|
||||
x448.scalarMult(v.private, v.public);
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
deepStrictEqual(failed, true, index);
|
||||
} else throw new Error('unknown test result');
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < group.tests.length; i++) {
|
||||
const v = group.tests[i];
|
||||
const index = `(${i}, ${v.result}) ${v.comment}`;
|
||||
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||
try {
|
||||
const shared = hex(x448.scalarMult(v.private, v.public));
|
||||
deepStrictEqual(shared, v.shared, index);
|
||||
} catch (e) {
|
||||
// We are more strict
|
||||
if (e.message.includes('Expected valid scalar')) return;
|
||||
if (e.message.includes('Invalid private or public key received')) return;
|
||||
if (e.message.includes('Expected 56 bytes')) return;
|
||||
throw e;
|
||||
}
|
||||
} else if (v.result === 'invalid') {
|
||||
let failed = false;
|
||||
try {
|
||||
x448.scalarMult(v.private, v.public);
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
deepStrictEqual(failed, true, index);
|
||||
} else throw new Error('unknown test result');
|
||||
}
|
||||
});
|
||||
|
||||
should('have proper base point', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user