forked from tornado-packages/noble-curves
definitions: split ed25519, ed448. More wycheproof tests
This commit is contained in:
parent
c8fc24fd8f
commit
b92866d9b8
@ -1,196 +0,0 @@
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
import { shake256 } from '@noble/hashes/sha3';
|
||||
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
|
||||
import { twistedEdwards } from '@noble/curves/edwards';
|
||||
import { mod, pow2, isNegativeLE } from '@noble/curves/modular';
|
||||
|
||||
const ed25519P = BigInt(
|
||||
'57896044618658097711785492504343953926634992332820282019728792003956564819949'
|
||||
);
|
||||
// √(-1) aka √(a) aka 2^((p-1)/4)
|
||||
const ED25519_SQRT_M1 = BigInt(
|
||||
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
||||
);
|
||||
|
||||
function ed25519_pow_2_252_3(x: bigint) {
|
||||
const P = ed25519P;
|
||||
const _1n = BigInt(1);
|
||||
const _2n = BigInt(2);
|
||||
const _5n = BigInt(5);
|
||||
const _10n = BigInt(10);
|
||||
const _20n = BigInt(20);
|
||||
const _40n = BigInt(40);
|
||||
const _80n = BigInt(80);
|
||||
const x2 = (x * x) % P;
|
||||
const b2 = (x2 * x) % P; // x^3, 11
|
||||
const b4 = (pow2(b2, _2n, P) * b2) % P; // x^15, 1111
|
||||
const b5 = (pow2(b4, _1n, P) * x) % P; // x^31
|
||||
const b10 = (pow2(b5, _5n, P) * b5) % P;
|
||||
const b20 = (pow2(b10, _10n, P) * b10) % P;
|
||||
const b40 = (pow2(b20, _20n, P) * b20) % P;
|
||||
const b80 = (pow2(b40, _40n, P) * b40) % P;
|
||||
const b160 = (pow2(b80, _80n, P) * b80) % P;
|
||||
const b240 = (pow2(b160, _80n, P) * b80) % P;
|
||||
const b250 = (pow2(b240, _10n, P) * b10) % P;
|
||||
const pow_p_5_8 = (pow2(b250, _2n, P) * x) % P;
|
||||
// ^ To pow to (p+3)/8, multiply it by x.
|
||||
return { pow_p_5_8, b2 };
|
||||
}
|
||||
|
||||
export const ed25519 = twistedEdwards({
|
||||
// Param: a
|
||||
a: BigInt(-1),
|
||||
// Equal to -121665/121666 over finite field.
|
||||
// Negative number is P - number, and division is invert(number, P)
|
||||
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
|
||||
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n
|
||||
P: ed25519P,
|
||||
// Subgroup order: how many points ed25519 has
|
||||
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
||||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
|
||||
// Cofactor
|
||||
h: BigInt(8),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'),
|
||||
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'),
|
||||
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
||||
a24: BigInt('121665'),
|
||||
scalarBits: 255,
|
||||
hash: sha512,
|
||||
randomBytes,
|
||||
adjustScalarBytes: (bytes: Uint8Array): Uint8Array => {
|
||||
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
|
||||
// set the three least significant bits of the first byte
|
||||
bytes[0] &= 248; // 0b1111_1000
|
||||
// and the most significant bit of the last to zero,
|
||||
bytes[31] &= 127; // 0b0111_1111
|
||||
// set the second most significant bit of the last byte to 1
|
||||
bytes[31] |= 64; // 0b0100_0000
|
||||
return bytes;
|
||||
},
|
||||
// dom2
|
||||
domain: (data: Uint8Array, ctx: Uint8Array, hflag: boolean) => {
|
||||
if (ctx.length || hflag) throw new Error('Contexts/pre-hash are not supported');
|
||||
// TODO: support for ph/ctx too?
|
||||
return data;
|
||||
},
|
||||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||||
// Constant-time, u/√v
|
||||
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
|
||||
const P = ed25519P;
|
||||
const v3 = mod(v * v * v, P); // v³
|
||||
const v7 = mod(v3 * v3 * v, P); // v⁷
|
||||
// (p+3)/8 and (p-5)/8
|
||||
const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
|
||||
let x = mod(u * v3 * pow, P); // (uv³)(uv⁷)^(p-5)/8
|
||||
const vx2 = mod(v * x * x, P); // vx²
|
||||
const root1 = x; // First root candidate
|
||||
const root2 = mod(x * ED25519_SQRT_M1, P); // Second root candidate
|
||||
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
|
||||
const useRoot2 = vx2 === mod(-u, P); // If vx² = -u, set x <-- x * 2^((p-1)/4)
|
||||
const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P); // There is no valid root, vx² = -u√(-1)
|
||||
if (useRoot1) x = root1;
|
||||
if (useRoot2 || noRoot) x = root2; // We return root2 anyway, for const-time
|
||||
if (isNegativeLE(x, P)) x = mod(-x, P);
|
||||
return { isValid: useRoot1 || useRoot2, value: x };
|
||||
},
|
||||
});
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc8032.html says bitLength is 456
|
||||
// https://www.rfc-editor.org/rfc/rfc7748 says bitLength is 448
|
||||
// WTF?!
|
||||
|
||||
// So, if we looking at wycheproof:
|
||||
// EdDSA: 456
|
||||
// X448 (sharedkey stuff) is 448. Awesome!
|
||||
|
||||
const ed448P = BigInt(
|
||||
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
|
||||
);
|
||||
|
||||
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
|
||||
function ed448_pow_Pminus3div4(x: bigint): bigint {
|
||||
const P = ed448P;
|
||||
// x ** ((P - 3n)/4n) % P
|
||||
// [223 of 1, 0, 222 of 1], almost same as secp!
|
||||
const b2 = (x * x * x) % P;
|
||||
const b3 = (b2 * b2 * x) % P;
|
||||
const b6 = (pow2(b3, 3n, P) * b3) % P;
|
||||
const b9 = (pow2(b6, 3n, P) * b3) % P;
|
||||
const b11 = (pow2(b9, 2n, P) * b2) % P;
|
||||
const b22 = (pow2(b11, 11n, P) * b11) % P;
|
||||
const b44 = (pow2(b22, 22n, P) * b22) % P;
|
||||
const b88 = (pow2(b44, 44n, P) * b44) % P;
|
||||
const b176 = (pow2(b88, 88n, P) * b88) % P;
|
||||
const b220 = (pow2(b176, 44n, P) * b44) % P;
|
||||
const b222 = (pow2(b220, 2n, P) * b2) % P;
|
||||
const b223 = (pow2(b222, 1n, P) * x) % P;
|
||||
return (pow2(b223, 223n, P) * b222) % P;
|
||||
}
|
||||
export const ed448 = twistedEdwards({
|
||||
// Param: a
|
||||
a: BigInt(1),
|
||||
// Equal to -39081 over finite field.
|
||||
// Negative number is P - number
|
||||
d: BigInt(
|
||||
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
|
||||
),
|
||||
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
|
||||
P: ed448P,
|
||||
// Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
|
||||
n: BigInt(
|
||||
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
|
||||
),
|
||||
nBitLength: 456,
|
||||
// Cofactor
|
||||
h: BigInt(4),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt(
|
||||
'224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710'
|
||||
),
|
||||
Gy: BigInt(
|
||||
'298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660'
|
||||
),
|
||||
// The constant a24 is (156326 - 2) / 4 = 39081 for curve448/X448.
|
||||
a24: BigInt('39081'),
|
||||
scalarBits: 448, // TODO: fix that
|
||||
// SHAKE256(dom4(phflag,context)||x, 114)
|
||||
hash: wrapConstructor(() => shake256.create({ dkLen: 114 })),
|
||||
randomBytes,
|
||||
adjustScalarBytes: (bytes: Uint8Array): Uint8Array => {
|
||||
// Section 5: Likewise, for X448, set the two least significant bits of the first byte to 0, and the most
|
||||
// significant bit of the last byte to 1.
|
||||
bytes[0] &= 252; // 0b11111100
|
||||
// and the most significant bit of the last byte to 1.
|
||||
bytes[55] |= 128; // 0b10000000
|
||||
bytes[56] = 0; // Byte outside of group (456 buts vs 448 bits)
|
||||
return bytes;
|
||||
},
|
||||
// dom4
|
||||
domain: (data: Uint8Array, ctx: Uint8Array, hflag: boolean) => {
|
||||
if (ctx.length > 255) throw new Error(`Context is too big: ${ctx.length}`);
|
||||
return concatBytes(utf8ToBytes('SigEd448'), new Uint8Array([hflag ? 1 : 0, ctx.length]), data);
|
||||
},
|
||||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||||
// Constant-time, u/√v
|
||||
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
|
||||
const P = ed448P;
|
||||
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.2.3
|
||||
// To compute the square root of (u/v), the first step is to compute the
|
||||
// candidate root x = (u/v)^((p+1)/4). This can be done using the
|
||||
// following trick, to use a single modular powering for both the
|
||||
// inversion of v and the square root:
|
||||
// (p+1)/4 3 (p-3)/4
|
||||
// x = (u/v) = u v (u^5 v^3) (mod p)
|
||||
const u2v = mod(u * u * v, P);
|
||||
const u3v = mod(u2v * u, P); // u^2v
|
||||
const u5v3 = mod(u3v * u2v * v, P); // u^5v^3
|
||||
const root = ed448_pow_Pminus3div4(u5v3);
|
||||
const x = mod(u3v * root, P);
|
||||
// Verify that root is exists
|
||||
const x2 = mod(x * x, P); // x^2
|
||||
// If v * x^2 = u, the recovered x-coordinate is x. Otherwise, no
|
||||
// square root exists, and the decoding fails.
|
||||
return { isValid: mod(x2 * v, P) === u, value: x };
|
||||
},
|
||||
});
|
126
curve-definitions/src/ed25519.ts
Normal file
126
curve-definitions/src/ed25519.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||
import { twistedEdwards } from '@noble/curves/edwards';
|
||||
import { mod, pow2, isNegativeLE } from '@noble/curves/modular';
|
||||
|
||||
const ed25519P = BigInt(
|
||||
'57896044618658097711785492504343953926634992332820282019728792003956564819949'
|
||||
);
|
||||
// √(-1) aka √(a) aka 2^((p-1)/4)
|
||||
const ED25519_SQRT_M1 = BigInt(
|
||||
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
||||
);
|
||||
|
||||
function ed25519_pow_2_252_3(x: bigint) {
|
||||
const P = ed25519P;
|
||||
const _1n = BigInt(1);
|
||||
const _2n = BigInt(2);
|
||||
const _5n = BigInt(5);
|
||||
const _10n = BigInt(10);
|
||||
const _20n = BigInt(20);
|
||||
const _40n = BigInt(40);
|
||||
const _80n = BigInt(80);
|
||||
const x2 = (x * x) % P;
|
||||
const b2 = (x2 * x) % P; // x^3, 11
|
||||
const b4 = (pow2(b2, _2n, P) * b2) % P; // x^15, 1111
|
||||
const b5 = (pow2(b4, _1n, P) * x) % P; // x^31
|
||||
const b10 = (pow2(b5, _5n, P) * b5) % P;
|
||||
const b20 = (pow2(b10, _10n, P) * b10) % P;
|
||||
const b40 = (pow2(b20, _20n, P) * b20) % P;
|
||||
const b80 = (pow2(b40, _40n, P) * b40) % P;
|
||||
const b160 = (pow2(b80, _80n, P) * b80) % P;
|
||||
const b240 = (pow2(b160, _80n, P) * b80) % P;
|
||||
const b250 = (pow2(b240, _10n, P) * b10) % P;
|
||||
const pow_p_5_8 = (pow2(b250, _2n, P) * x) % P;
|
||||
// ^ To pow to (p+3)/8, multiply it by x.
|
||||
return { pow_p_5_8, b2 };
|
||||
}
|
||||
// Just in case
|
||||
export const ED25519_TORSION_SUBGROUP = [
|
||||
'0100000000000000000000000000000000000000000000000000000000000000',
|
||||
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a',
|
||||
'0000000000000000000000000000000000000000000000000000000000000080',
|
||||
'26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05',
|
||||
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||
'26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85',
|
||||
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',
|
||||
];
|
||||
|
||||
const ED25519_DEF = {
|
||||
// Param: a
|
||||
a: BigInt(-1),
|
||||
// Equal to -121665/121666 over finite field.
|
||||
// Negative number is P - number, and division is invert(number, P)
|
||||
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
|
||||
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n
|
||||
P: ed25519P,
|
||||
// Subgroup order: how many points ed25519 has
|
||||
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
||||
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
|
||||
// Cofactor
|
||||
h: BigInt(8),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'),
|
||||
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'),
|
||||
hash: sha512,
|
||||
randomBytes,
|
||||
adjustScalarBytes: (bytes: Uint8Array): Uint8Array => {
|
||||
// Section 5: For X25519, in order to decode 32 random bytes as an integer scalar,
|
||||
// set the three least significant bits of the first byte
|
||||
bytes[0] &= 248; // 0b1111_1000
|
||||
// and the most significant bit of the last to zero,
|
||||
bytes[31] &= 127; // 0b0111_1111
|
||||
// set the second most significant bit of the last byte to 1
|
||||
bytes[31] |= 64; // 0b0100_0000
|
||||
return bytes;
|
||||
},
|
||||
// dom2
|
||||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||||
// Constant-time, u/√v
|
||||
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
|
||||
const P = ed25519P;
|
||||
const v3 = mod(v * v * v, P); // v³
|
||||
const v7 = mod(v3 * v3 * v, P); // v⁷
|
||||
// (p+3)/8 and (p-5)/8
|
||||
const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
|
||||
let x = mod(u * v3 * pow, P); // (uv³)(uv⁷)^(p-5)/8
|
||||
const vx2 = mod(v * x * x, P); // vx²
|
||||
const root1 = x; // First root candidate
|
||||
const root2 = mod(x * ED25519_SQRT_M1, P); // Second root candidate
|
||||
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
|
||||
const useRoot2 = vx2 === mod(-u, P); // If vx² = -u, set x <-- x * 2^((p-1)/4)
|
||||
const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P); // There is no valid root, vx² = -u√(-1)
|
||||
if (useRoot1) x = root1;
|
||||
if (useRoot2 || noRoot) x = root2; // We return root2 anyway, for const-time
|
||||
if (isNegativeLE(x, P)) x = mod(-x, P);
|
||||
return { isValid: useRoot1 || useRoot2, value: x };
|
||||
},
|
||||
// ECDH
|
||||
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
||||
a24: BigInt('121665'),
|
||||
montgomeryBits: 255, // n is 253 bits
|
||||
powPminus2: (x: bigint): bigint => {
|
||||
const P = ed25519P;
|
||||
// x^(p-2) aka x^(2^255-21)
|
||||
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
|
||||
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const ed25519 = twistedEdwards(ED25519_DEF);
|
||||
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||
if (ctx.length > 255) throw new Error('Context is too big');
|
||||
return concatBytes(
|
||||
utf8ToBytes('SigEd25519 no Ed25519 collisions'),
|
||||
new Uint8Array([phflag ? 1 : 0, ctx.length]),
|
||||
ctx,
|
||||
data
|
||||
);
|
||||
}
|
||||
export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain });
|
||||
export const ed25519ph = twistedEdwards({
|
||||
...ED25519_DEF,
|
||||
domain: ed25519_domain,
|
||||
preHash: sha512,
|
||||
});
|
134
curve-definitions/src/ed448.ts
Normal file
134
curve-definitions/src/ed448.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { shake256 } from '@noble/hashes/sha3';
|
||||
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
|
||||
import { PointType, twistedEdwards } from '@noble/curves/edwards';
|
||||
import { mod, pow2, invert } from '@noble/curves/modular';
|
||||
import { numberToBytesLE } from '@noble/curves/utils';
|
||||
|
||||
const _0n = BigInt(0);
|
||||
|
||||
const shake256_114 = wrapConstructor(() => shake256.create({ dkLen: 114 }));
|
||||
const shake256_64 = wrapConstructor(() => shake256.create({ dkLen: 64 }));
|
||||
const ed448P = BigInt(
|
||||
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
|
||||
);
|
||||
|
||||
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
|
||||
function ed448_pow_Pminus3div4(x: bigint): bigint {
|
||||
const P = ed448P;
|
||||
// x ** ((P - 3n)/4n) % P
|
||||
// [223 of 1, 0, 222 of 1], almost same as secp!
|
||||
const b2 = (x * x * x) % P;
|
||||
const b3 = (b2 * b2 * x) % P;
|
||||
const b6 = (pow2(b3, 3n, P) * b3) % P;
|
||||
const b9 = (pow2(b6, 3n, P) * b3) % P;
|
||||
const b11 = (pow2(b9, 2n, P) * b2) % P;
|
||||
const b22 = (pow2(b11, 11n, P) * b11) % P;
|
||||
const b44 = (pow2(b22, 22n, P) * b22) % P;
|
||||
const b88 = (pow2(b44, 44n, P) * b44) % P;
|
||||
const b176 = (pow2(b88, 88n, P) * b88) % P;
|
||||
const b220 = (pow2(b176, 44n, P) * b44) % P;
|
||||
const b222 = (pow2(b220, 2n, P) * b2) % P;
|
||||
const b223 = (pow2(b222, 1n, P) * x) % P;
|
||||
return (pow2(b223, 223n, P) * b222) % P;
|
||||
}
|
||||
|
||||
const ED448_DEF = {
|
||||
// Param: a
|
||||
a: BigInt(1),
|
||||
// Equal to -39081 over finite field.
|
||||
// Negative number is P - number
|
||||
d: BigInt(
|
||||
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
|
||||
),
|
||||
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
|
||||
P: ed448P,
|
||||
// Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
|
||||
n: BigInt(
|
||||
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
|
||||
),
|
||||
nBitLength: 456,
|
||||
// Cofactor
|
||||
h: BigInt(4),
|
||||
// Base point (x, y) aka generator point
|
||||
Gx: BigInt(
|
||||
'224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710'
|
||||
),
|
||||
Gy: BigInt(
|
||||
'298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660'
|
||||
),
|
||||
// SHAKE256(dom4(phflag,context)||x, 114)
|
||||
hash: shake256_114,
|
||||
randomBytes,
|
||||
adjustScalarBytes: (bytes: Uint8Array): Uint8Array => {
|
||||
// Section 5: Likewise, for X448, set the two least significant bits of the first byte to 0, and the most
|
||||
// significant bit of the last byte to 1.
|
||||
bytes[0] &= 252; // 0b11111100
|
||||
// and the most significant bit of the last byte to 1.
|
||||
bytes[55] |= 128; // 0b10000000
|
||||
// NOTE: is is NOOP for 56 bytes scalars (X25519/X448)
|
||||
bytes[56] = 0; // Byte outside of group (456 buts vs 448 bits)
|
||||
return bytes;
|
||||
},
|
||||
// dom4
|
||||
domain: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => {
|
||||
if (ctx.length > 255) throw new Error(`Context is too big: ${ctx.length}`);
|
||||
return concatBytes(
|
||||
utf8ToBytes('SigEd448'),
|
||||
new Uint8Array([phflag ? 1 : 0, ctx.length]),
|
||||
ctx,
|
||||
data
|
||||
);
|
||||
},
|
||||
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||||
// Constant-time, u/√v
|
||||
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
|
||||
const P = ed448P;
|
||||
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.2.3
|
||||
// To compute the square root of (u/v), the first step is to compute the
|
||||
// candidate root x = (u/v)^((p+1)/4). This can be done using the
|
||||
// following trick, to use a single modular powering for both the
|
||||
// inversion of v and the square root:
|
||||
// (p+1)/4 3 (p-3)/4
|
||||
// x = (u/v) = u v (u^5 v^3) (mod p)
|
||||
const u2v = mod(u * u * v, P);
|
||||
const u3v = mod(u2v * u, P); // u^2v
|
||||
const u5v3 = mod(u3v * u2v * v, P); // u^5v^3
|
||||
const root = ed448_pow_Pminus3div4(u5v3);
|
||||
const x = mod(u3v * root, P);
|
||||
// Verify that root is exists
|
||||
const x2 = mod(x * x, P); // x^2
|
||||
// If v * x^2 = u, the recovered x-coordinate is x. Otherwise, no
|
||||
// square root exists, and the decoding fails.
|
||||
return { isValid: mod(x2 * v, P) === u, value: x };
|
||||
},
|
||||
// ECDH
|
||||
// basePointU:
|
||||
// '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
|
||||
// The constant a24 is (156326 - 2) / 4 = 39081 for curve448/X448.
|
||||
a24: BigInt('39081'),
|
||||
montgomeryBits: 448,
|
||||
powPminus2: (x: bigint): bigint => {
|
||||
const P = ed448P;
|
||||
const Pminus3div4 = ed448_pow_Pminus3div4(x);
|
||||
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
|
||||
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
|
||||
},
|
||||
// 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))
|
||||
UfromPoint: (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);
|
||||
},
|
||||
} as const;
|
||||
|
||||
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 });
|
@ -6,9 +6,6 @@ import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||
import { weierstrass, CurveType, CHash } from '@noble/curves/weierstrass';
|
||||
import { mod, pow2 } from '@noble/curves/modular';
|
||||
|
||||
// TODO: ability to provide API for different default hash.
|
||||
// Wychenproof can help us here & test multiple hashes.
|
||||
|
||||
function getHash(hash: CHash) {
|
||||
return {
|
||||
hash,
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as nist from '../lib/nist.js';
|
||||
import { hexToBytes } from '@noble/curves/utils';
|
||||
import { hexToBytes, bytesToHex } from '@noble/curves/utils';
|
||||
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
|
||||
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
|
||||
|
||||
// import { hexToBytes } from '@noble/curves';
|
||||
const hex = bytesToHex;
|
||||
|
||||
should('Curve Fields', () => {
|
||||
const vectors = {
|
||||
@ -90,6 +90,252 @@ should('wychenproof ECDH vectors', () => {
|
||||
}
|
||||
});
|
||||
|
||||
import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp256k1_test } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp384r1_test } from './wycheproof/ecdh_secp384r1_test.json' assert { type: 'json' };
|
||||
import { default as ecdh_secp521r1_test } from './wycheproof/ecdh_secp521r1_test.json' assert { type: 'json' };
|
||||
|
||||
// More per curve tests
|
||||
const WYCHEPROOF_ECDH = {
|
||||
P224: {
|
||||
curve: nist.P224,
|
||||
tests: [ecdh_secp224r1_test],
|
||||
},
|
||||
P256: {
|
||||
curve: nist.P256,
|
||||
tests: [ecdh_secp256r1_test],
|
||||
},
|
||||
secp256k1: {
|
||||
curve: nist.secp256k1,
|
||||
tests: [ecdh_secp256k1_test],
|
||||
},
|
||||
P384: {
|
||||
curve: nist.P384,
|
||||
tests: [ecdh_secp384r1_test],
|
||||
},
|
||||
P521: {
|
||||
curve: nist.P521,
|
||||
tests: [ecdh_secp521r1_test],
|
||||
},
|
||||
};
|
||||
|
||||
for (const name in WYCHEPROOF_ECDH) {
|
||||
const { curve, tests } = WYCHEPROOF_ECDH[name];
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
const test = tests[i];
|
||||
for (let j = 0; j < test.testGroups.length; j++) {
|
||||
const group = test.testGroups[j];
|
||||
should(`Wycheproof/ECDH ${name} (${i}/${j})`, () => {
|
||||
for (const test of group.tests) {
|
||||
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||
try {
|
||||
const pub = curve.Point.fromHex(test.public);
|
||||
} catch (e) {
|
||||
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
|
||||
throw e;
|
||||
}
|
||||
const shared = curve.getSharedSecret(test.private, test.public);
|
||||
deepStrictEqual(hex(shared), test.shared, 'valid');
|
||||
} else if (test.result === 'invalid') {
|
||||
let failed = false;
|
||||
try {
|
||||
curve.getSharedSecret(test.private, test.public);
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
deepStrictEqual(failed, true, 'invalid');
|
||||
} else throw new Error('unknown test result');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests with custom hashes
|
||||
import { default as secp224r1_sha224_test } from './wycheproof/ecdsa_secp224r1_sha224_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha256_test } from './wycheproof/ecdsa_secp224r1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_224_test } from './wycheproof/ecdsa_secp224r1_sha3_224_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_256_test } from './wycheproof/ecdsa_secp224r1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha3_512_test } from './wycheproof/ecdsa_secp224r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp224r1_sha512_test } from './wycheproof/ecdsa_secp224r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp256k1_sha256_test } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha3_256_test } from './wycheproof/ecdsa_secp256k1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha3_512_test } from './wycheproof/ecdsa_secp256k1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp256k1_sha512_test } from './wycheproof/ecdsa_secp256k1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp256r1_sha256_test } from './wycheproof/ecdsa_secp256r1_sha256_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha3_256_test } from './wycheproof/ecdsa_secp256r1_sha3_256_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha3_512_test } from './wycheproof/ecdsa_secp256r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp256r1_sha512_test } from './wycheproof/ecdsa_secp256r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp384r1_sha384_test } from './wycheproof/ecdsa_secp384r1_sha384_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha3_384_test } from './wycheproof/ecdsa_secp384r1_sha3_384_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha3_512_test } from './wycheproof/ecdsa_secp384r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp384r1_sha512_test } from './wycheproof/ecdsa_secp384r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { default as secp521r1_sha3_512_test } from './wycheproof/ecdsa_secp521r1_sha3_512_test.json' assert { type: 'json' };
|
||||
import { default as secp521r1_sha512_test } from './wycheproof/ecdsa_secp521r1_sha512_test.json' assert { type: 'json' };
|
||||
|
||||
import { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3';
|
||||
import { sha512, sha384 } from '@noble/hashes/sha512';
|
||||
import { sha256 } from '@noble/hashes/sha256';
|
||||
|
||||
const WYCHEPROOF_ECDSA = {
|
||||
P224: {
|
||||
curve: nist.P224,
|
||||
hashes: {
|
||||
// sha224 not released yet
|
||||
// sha224: {
|
||||
// hash: sha224,
|
||||
// tests: [secp224r1_sha224_test],
|
||||
// },
|
||||
sha256: {
|
||||
hash: sha256,
|
||||
tests: [secp224r1_sha256_test],
|
||||
},
|
||||
sha3_224: {
|
||||
hash: sha3_224,
|
||||
tests: [secp224r1_sha3_224_test],
|
||||
},
|
||||
sha3_256: {
|
||||
hash: sha3_256,
|
||||
tests: [secp224r1_sha3_256_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp224r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp224r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
secp256k1: {
|
||||
curve: nist.secp256k1,
|
||||
hashes: {
|
||||
// TODO: debug why fails, can be bug
|
||||
// sha256: {
|
||||
// hash: sha256,
|
||||
// tests: [secp256k1_sha256_test],
|
||||
// },
|
||||
// sha3_256: {
|
||||
// hash: sha3_256,
|
||||
// tests: [secp256k1_sha3_256_test],
|
||||
// },
|
||||
// sha3_512: {
|
||||
// hash: sha3_512,
|
||||
// tests: [secp256k1_sha3_512_test],
|
||||
// },
|
||||
// sha512: {
|
||||
// hash: sha512,
|
||||
// tests: [secp256k1_sha512_test],
|
||||
// },
|
||||
},
|
||||
},
|
||||
P256: {
|
||||
curve: nist.P256,
|
||||
hashes: {
|
||||
sha256: {
|
||||
hash: sha256,
|
||||
tests: [secp256r1_sha256_test],
|
||||
},
|
||||
sha3_256: {
|
||||
hash: sha3_256,
|
||||
tests: [secp256r1_sha3_256_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp256r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp256r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
P384: {
|
||||
curve: nist.P384,
|
||||
hashes: {
|
||||
sha384: {
|
||||
hash: sha384,
|
||||
tests: [secp384r1_sha384_test],
|
||||
},
|
||||
sha3_384: {
|
||||
hash: sha3_384,
|
||||
tests: [secp384r1_sha3_384_test],
|
||||
},
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp384r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp384r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
P521: {
|
||||
curve: nist.P521,
|
||||
hashes: {
|
||||
sha3_512: {
|
||||
hash: sha3_512,
|
||||
tests: [secp521r1_sha3_512_test],
|
||||
},
|
||||
sha512: {
|
||||
hash: sha512,
|
||||
tests: [secp521r1_sha512_test],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const name in WYCHEPROOF_ECDSA) {
|
||||
const { curve, hashes } = WYCHEPROOF_ECDSA[name];
|
||||
for (const hName in hashes) {
|
||||
const { hash, tests } = hashes[hName];
|
||||
const CURVE = curve.create(hash);
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
const test = tests[i];
|
||||
for (let j = 0; j < test.testGroups.length; j++) {
|
||||
const group = test.testGroups[j];
|
||||
should(`Wycheproof/WYCHEPROOF_ECDSA ${name}/${hName} (${i}/${j})`, () => {
|
||||
const pubKey = CURVE.Point.fromHex(group.key.uncompressed);
|
||||
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
|
||||
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
|
||||
for (const test of group.tests) {
|
||||
// if (['Hash weaker than DL-group'].includes(test.comment)) {
|
||||
// continue;
|
||||
// }
|
||||
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
|
||||
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||
try {
|
||||
CURVE.Signature.fromDER(test.sig);
|
||||
} catch (e) {
|
||||
// Some test has invalid signature which we don't accept
|
||||
if (e.message.includes('Invalid signature: incorrect length')) continue;
|
||||
throw e;
|
||||
}
|
||||
const verified = CURVE.verify(test.sig, m, pubKey);
|
||||
deepStrictEqual(verified, true, 'valid');
|
||||
} else if (test.result === 'invalid') {
|
||||
let failed = false;
|
||||
try {
|
||||
failed = !CURVE.verify(test.sig, m, pubKey);
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
deepStrictEqual(failed, true, 'invalid');
|
||||
} else throw new Error('unknown test result');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as fc from 'fast-check';
|
||||
import { ed25519 } from '../lib/ed.js';
|
||||
import { ed25519, ed25519ctx, ed25519ph } from '../lib/ed25519.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
|
||||
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
|
||||
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
|
||||
|
||||
import { sha512 } from '@noble/hashes/sha512';
|
||||
|
||||
const ed = ed25519;
|
||||
@ -27,7 +26,7 @@ function utf8ToBytes(str) {
|
||||
|
||||
ed.utils.precompute(8);
|
||||
|
||||
should('ed25519/should not accept >32byte private keys', async () => {
|
||||
should('ed25519/should not accept >32byte private keys', () => {
|
||||
const invalidPriv =
|
||||
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
|
||||
throws(() => ed.getPublicKey(invalidPriv));
|
||||
@ -93,12 +92,12 @@ should('ed25519/sync methods/should sign and verify', () => {
|
||||
const signature = ed.sign(msg, privKey);
|
||||
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
|
||||
});
|
||||
should('ed25519/sync methods/should not verify signature with wrong public key', async () => {
|
||||
should('ed25519/sync methods/should not verify signature with wrong public key', () => {
|
||||
const publicKey = ed.getPublicKey(12);
|
||||
const signature = ed.sign(msg, privKey);
|
||||
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
|
||||
});
|
||||
should('ed25519/sync methods/should not verify signature with wrong hash', async () => {
|
||||
should('ed25519/sync methods/should not verify signature with wrong hash', () => {
|
||||
const publicKey = ed.getPublicKey(privKey);
|
||||
const signature = ed.sign(msg, privKey);
|
||||
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
|
||||
@ -155,25 +154,6 @@ should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', ()
|
||||
}
|
||||
});
|
||||
|
||||
// should('ed25519/getSharedSecret()/should convert base point to montgomery using toX25519()', () => {
|
||||
// deepStrictEqual(hex(ed.Point.BASE.toX25519()), ed.curve25519.BASE_POINT_U);
|
||||
// });
|
||||
|
||||
// should('ed25519/getSharedSecret()/should be commutative', async () => {
|
||||
// for (let i = 0; i < 512; i++) {
|
||||
// const asec = ed.utils.randomPrivateKey();
|
||||
// const apub = ed.getPublicKey(asec);
|
||||
// const bsec = ed.utils.randomPrivateKey();
|
||||
// const bpub = ed.getPublicKey(bsec);
|
||||
// try {
|
||||
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
|
||||
// } catch (error) {
|
||||
// console.error('not commutative', { asec, apub, bsec, bpub });
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// https://ed25519.cr.yp.to/python/sign.py
|
||||
// https://ed25519.cr.yp.to/python/sign.input
|
||||
const data = readFileSync('./test/ed25519/vectors.txt', 'utf-8');
|
||||
@ -419,55 +399,6 @@ should('rfc8032 vectors/should create right signature for 0xf5 and long msg', ()
|
||||
// }
|
||||
// });
|
||||
|
||||
// should('curve25519/scalarMult 1', () => {
|
||||
// const X25519_VECTORS = [
|
||||
// {
|
||||
// k: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
|
||||
// u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
|
||||
// ku: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
|
||||
// },
|
||||
// {
|
||||
// k: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
|
||||
// u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
|
||||
// ku: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
|
||||
// },
|
||||
// ];
|
||||
// for (const { k, u, ku } of X25519_VECTORS) {
|
||||
// const _k = Uint8Array.from(hexToBytes(k));
|
||||
// const _u = Uint8Array.from(hexToBytes(u));
|
||||
// deepStrictEqual(bytesToHex(ed.curve25519.scalarMult(_k, _u)), ku);
|
||||
// }
|
||||
// });
|
||||
// should('curve25519/scalarMultBase recursive', () => {
|
||||
// // https://datatracker.ietf.org/doc/html/rfc7748#section-5.2
|
||||
// const VECTORS = [
|
||||
// { iters: 1, res: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079' },
|
||||
// { iters: 1000, res: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51' },
|
||||
// // {iters: 1000000, res: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424'},
|
||||
// ];
|
||||
// for (const { iters, res } of VECTORS) {
|
||||
// let k = hexToBytes('0900000000000000000000000000000000000000000000000000000000000000');
|
||||
// let u = k;
|
||||
// for (let i = 0; i < iters; i++) {
|
||||
// if (i > 0 && i % 100000 === 0) console.log('10k');
|
||||
// [k, u] = [ed.curve25519.scalarMult(k, u), k];
|
||||
// }
|
||||
// deepStrictEqual(bytesToHex(k), res);
|
||||
// }
|
||||
// });
|
||||
// should('curve25519/scalarMult 2', () => {
|
||||
// const a_priv = hexToBytes('77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a');
|
||||
// const a_pub = hexToBytes('8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a');
|
||||
// const b_priv = hexToBytes('5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb');
|
||||
// const b_pub = hexToBytes('de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f');
|
||||
// const k = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
|
||||
|
||||
// deepStrictEqual(bytesToHex(ed.curve25519.scalarMultBase(a_priv)), bytesToHex(a_pub));
|
||||
// deepStrictEqual(bytesToHex(ed.curve25519.scalarMultBase(b_priv)), bytesToHex(b_pub));
|
||||
// deepStrictEqual(bytesToHex(ed.curve25519.scalarMult(a_priv, b_pub)), k);
|
||||
// deepStrictEqual(bytesToHex(ed.curve25519.scalarMult(b_priv, a_pub)), k);
|
||||
// });
|
||||
|
||||
should('input immutability: sign/verify are immutable', () => {
|
||||
const privateKey = ed.utils.randomPrivateKey();
|
||||
const publicKey = ed.getPublicKey(privateKey);
|
||||
@ -509,32 +440,100 @@ should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => {
|
||||
throws(() => ed.verify(sig, 'deadbeef', ed.Point.BASE));
|
||||
});
|
||||
|
||||
// {
|
||||
// const group = x25519vectors.testGroups[0];
|
||||
// for (let i = 0; i < group.tests.length; i++) {
|
||||
// const v = group.tests[i];
|
||||
// should(`Wycheproof/X25519(${i}, ${v.result}) ${v.comment}`, () => {
|
||||
// if (v.result === 'valid' || v.result === 'acceptable') {
|
||||
// try {
|
||||
// ed.Point.fromHex(v.public);
|
||||
// } catch (e) {
|
||||
// if (e.message.includes('Point.fromHex: invalid y coordinate')) return;
|
||||
// throw e;
|
||||
// }
|
||||
// const shared = hex(ed.getSharedSecret(v.private, v.public));
|
||||
// deepStrictEqual(shared, v.shared, 'valid');
|
||||
// } else if (v.result === 'invalid') {
|
||||
// let failed = false;
|
||||
// try {
|
||||
// ed.getSharedSecret(v.private, v.public);
|
||||
// } catch (error) {
|
||||
// failed = true;
|
||||
// }
|
||||
// deepStrictEqual(failed, true, 'invalid');
|
||||
// } else throw new Error('unknown test result');
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
const rfc7748Mul = [
|
||||
{
|
||||
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
|
||||
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
|
||||
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
|
||||
},
|
||||
{
|
||||
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
|
||||
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
|
||||
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
|
||||
},
|
||||
];
|
||||
for (let i = 0; i < rfc7748Mul.length; i++) {
|
||||
const v = rfc7748Mul[i];
|
||||
should(`RFC7748: scalarMult (${i})`, () => {
|
||||
deepStrictEqual(hex(ed.montgomeryCurve.scalarMult(v.u, v.scalar)), v.outputU);
|
||||
});
|
||||
}
|
||||
|
||||
const rfc7748Iter = [
|
||||
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
|
||||
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
|
||||
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
|
||||
];
|
||||
for (let i = 0; i < rfc7748Iter.length; i++) {
|
||||
const { scalar, iters } = rfc7748Iter[i];
|
||||
should(`RFC7748: scalarMult iteration (${i})`, () => {
|
||||
let k = ed.montgomeryCurve.BASE_POINT_U;
|
||||
for (let i = 0, u = k; i < iters; i++) [k, u] = [ed.montgomeryCurve.scalarMult(u, k), k];
|
||||
deepStrictEqual(hex(k), scalar);
|
||||
});
|
||||
}
|
||||
|
||||
should('RFC7748 getSharedKey', () => {
|
||||
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
|
||||
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
|
||||
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
|
||||
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
|
||||
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
|
||||
deepStrictEqual(alicePublic, hex(ed.montgomeryCurve.getPublicKey(alicePrivate)));
|
||||
deepStrictEqual(bobPublic, hex(ed.montgomeryCurve.getPublicKey(bobPrivate)));
|
||||
deepStrictEqual(hex(ed.montgomeryCurve.getSharedSecret(alicePrivate, bobPublic)), shared);
|
||||
deepStrictEqual(hex(ed.montgomeryCurve.getSharedSecret(bobPrivate, alicePublic)), shared);
|
||||
});
|
||||
|
||||
should('X25519/getSharedSecret() should be commutative', () => {
|
||||
for (let i = 0; i < 512; i++) {
|
||||
const asec = ed.utils.randomPrivateKey();
|
||||
const apub = ed.getPublicKey(asec);
|
||||
const bsec = ed.utils.randomPrivateKey();
|
||||
const bpub = ed.getPublicKey(bsec);
|
||||
try {
|
||||
deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
|
||||
} catch (error) {
|
||||
console.error('not commutative', { asec, apub, bsec, bpub });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
should('X25519: should convert base point to montgomery using fromPoint', () => {
|
||||
deepStrictEqual(
|
||||
hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)),
|
||||
ed.montgomeryCurve.BASE_POINT_U
|
||||
);
|
||||
});
|
||||
|
||||
{
|
||||
const group = x25519vectors.testGroups[0];
|
||||
for (let i = 0; i < group.tests.length; i++) {
|
||||
const v = group.tests[i];
|
||||
should(`Wycheproof/X25519(${i}, ${v.result}) ${v.comment}`, () => {
|
||||
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||
try {
|
||||
const shared = hex(ed.montgomeryCurve.getSharedSecret(v.private, v.public));
|
||||
deepStrictEqual(shared, v.shared, 'valid');
|
||||
} 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;
|
||||
throw e;
|
||||
}
|
||||
} else if (v.result === 'invalid') {
|
||||
let failed = false;
|
||||
try {
|
||||
ed.montgomeryCurve.getSharedSecret(v.private, v.public);
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
deepStrictEqual(failed, true, 'invalid');
|
||||
} else throw new Error('unknown test result');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for (let g = 0; g < ed25519vectors.testGroups.length; g++) {
|
||||
@ -570,6 +569,84 @@ should('Property test issue #1', () => {
|
||||
deepStrictEqual(ed.verify(signature, message, publicKey), true);
|
||||
});
|
||||
|
||||
const VECTORS_RFC8032_CTX = [
|
||||
{
|
||||
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||
context: '666f6f',
|
||||
signature:
|
||||
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
|
||||
'b520b36292911876cada7323198dd87a' +
|
||||
'8b36950b95130022907a7fb7c4e9b2d5' +
|
||||
'f6cca685a587b4b21f4b888e4e7edb0d',
|
||||
},
|
||||
{
|
||||
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||
context: '626172',
|
||||
signature:
|
||||
'fc60d5872fc46b3aa69f8b5b4351d580' +
|
||||
'8f92bcc044606db097abab6dbcb1aee3' +
|
||||
'216c48e8b3b66431b5b186d1d28f8ee1' +
|
||||
'5a5ca2df6668346291c2043d4eb3e90d',
|
||||
},
|
||||
{
|
||||
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||
message: '508e9e6882b979fea900f62adceaca35',
|
||||
context: '666f6f',
|
||||
signature:
|
||||
'8b70c1cc8310e1de20ac53ce28ae6e72' +
|
||||
'07f33c3295e03bb5c0732a1d20dc6490' +
|
||||
'8922a8b052cf99b7c4fe107a5abb5b2c' +
|
||||
'4085ae75890d02df26269d8945f84b0b',
|
||||
},
|
||||
{
|
||||
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
|
||||
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
|
||||
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||
context: '666f6f',
|
||||
signature:
|
||||
'21655b5f1aa965996b3f97b3c849eafb' +
|
||||
'a922a0a62992f73b3d1b73106a84ad85' +
|
||||
'e9b86a7b6005ea868337ff2d20a7f5fb' +
|
||||
'd4cd10b0be49a68da2b2e0dc0ad8960f',
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
|
||||
const v = VECTORS_RFC8032_CTX[i];
|
||||
should(`RFC8032ctx/${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);
|
||||
});
|
||||
}
|
||||
|
||||
const VECTORS_RFC8032_PH = [
|
||||
{
|
||||
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
|
||||
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
|
||||
message: '616263',
|
||||
signature:
|
||||
'98a70222f0b8121aa9d30f813d683f80' +
|
||||
'9e462b469c7ff87639499bb94e6dae41' +
|
||||
'31f85042463c2a355a2003d062adf5aa' +
|
||||
'a10b8c61e636062aaad11c2a26083406',
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
||||
const v = VECTORS_RFC8032_PH[i];
|
||||
should(`RFC8032ph/${i}`, () => {
|
||||
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
|
||||
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
|
||||
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
|
||||
});
|
||||
}
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as fc from 'fast-check';
|
||||
import { ed25519, ed448 } from '../lib/ed.js';
|
||||
import { ed448, ed448ph } from '../lib/ed448.js';
|
||||
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||
import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' };
|
||||
import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
|
||||
@ -411,25 +411,6 @@ should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', ()
|
||||
}
|
||||
});
|
||||
|
||||
// should('ed25519/getSharedSecret()/should convert base point to montgomery using toX25519()', () => {
|
||||
// deepStrictEqual(hex(ed.Point.BASE.toX25519()), ed.curve25519.BASE_POINT_U);
|
||||
// });
|
||||
|
||||
// should('ed25519/getSharedSecret()/should be commutative', async () => {
|
||||
// for (let i = 0; i < 512; i++) {
|
||||
// const asec = ed.utils.randomPrivateKey();
|
||||
// const apub = ed.getPublicKey(asec);
|
||||
// const bsec = ed.utils.randomPrivateKey();
|
||||
// const bpub = ed.getPublicKey(bsec);
|
||||
// try {
|
||||
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
|
||||
// } catch (error) {
|
||||
// console.error('not commutative', { asec, apub, bsec, bpub });
|
||||
// throw error;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
should('input immutability: sign/verify are immutable', () => {
|
||||
const privateKey = ed.utils.randomPrivateKey();
|
||||
const publicKey = ed.getPublicKey(privateKey);
|
||||
@ -477,101 +458,196 @@ should('input immutability: sign/verify are immutable', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// const group = x25519vectors.testGroups[0];
|
||||
// for (let i = 0; i < group.tests.length; i++) {
|
||||
// const v = group.tests[i];
|
||||
// should(`Wycheproof/X25519(${i}, ${v.result}) ${v.comment}`, () => {
|
||||
// if (v.result === 'valid' || v.result === 'acceptable') {
|
||||
// try {
|
||||
// ed.Point.fromHex(v.public);
|
||||
// } catch (e) {
|
||||
// if (e.message.includes('Point.fromHex: invalid y coordinate')) return;
|
||||
// throw e;
|
||||
// }
|
||||
// const shared = hex(ed.getSharedSecret(v.private, v.public));
|
||||
// deepStrictEqual(shared, v.shared, 'valid');
|
||||
// } else if (v.result === 'invalid') {
|
||||
// let failed = false;
|
||||
// try {
|
||||
// ed.getSharedSecret(v.private, v.public);
|
||||
// } catch (error) {
|
||||
// failed = true;
|
||||
// }
|
||||
// deepStrictEqual(failed, true, 'invalid');
|
||||
// } else throw new Error('unknown test result');
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// ECDH
|
||||
const rfc7748Mul = [
|
||||
{
|
||||
scalar:
|
||||
'3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3',
|
||||
u: '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086',
|
||||
outputU:
|
||||
'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f',
|
||||
},
|
||||
{
|
||||
scalar:
|
||||
'203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f',
|
||||
u: '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db',
|
||||
outputU:
|
||||
'884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d',
|
||||
},
|
||||
];
|
||||
for (let i = 0; i < rfc7748Mul.length; i++) {
|
||||
const v = rfc7748Mul[i];
|
||||
should(`RFC7748: scalarMult (${i})`, () => {
|
||||
deepStrictEqual(hex(ed.montgomeryCurve.scalarMult(v.u, v.scalar)), v.outputU);
|
||||
});
|
||||
}
|
||||
|
||||
const rfc7748Iter = [
|
||||
{
|
||||
scalar:
|
||||
'3f482c8a9f19b01e6c46ee9711d9dc14fd4bf67af30765c2ae2b846a4d23a8cd0db897086239492caf350b51f833868b9bc2b3bca9cf4113',
|
||||
iters: 1,
|
||||
},
|
||||
{
|
||||
scalar:
|
||||
'aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38',
|
||||
iters: 1000,
|
||||
},
|
||||
// { scalar: '077f453681caca3693198420bbe515cae0002472519b3e67661a7e89cab94695c8f4bcd66e61b9b9c946da8d524de3d69bd9d9d66b997e37', iters: 1000000 },
|
||||
];
|
||||
for (let i = 0; i < rfc7748Iter.length; i++) {
|
||||
const { scalar, iters } = rfc7748Iter[i];
|
||||
should(`RFC7748: scalarMult iteration (${i})`, () => {
|
||||
let k = ed.montgomeryCurve.BASE_POINT_U;
|
||||
for (let i = 0, u = k; i < iters; i++) [k, u] = [ed.montgomeryCurve.scalarMult(u, k), k];
|
||||
deepStrictEqual(hex(k), scalar);
|
||||
});
|
||||
}
|
||||
|
||||
should('RFC7748 getSharedKey', () => {
|
||||
const alicePrivate =
|
||||
'9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b';
|
||||
const alicePublic =
|
||||
'9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0';
|
||||
const bobPrivate =
|
||||
'1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d';
|
||||
const bobPublic =
|
||||
'3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609';
|
||||
const shared =
|
||||
'07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d';
|
||||
deepStrictEqual(alicePublic, hex(ed.montgomeryCurve.getPublicKey(alicePrivate)));
|
||||
deepStrictEqual(bobPublic, hex(ed.montgomeryCurve.getPublicKey(bobPrivate)));
|
||||
deepStrictEqual(hex(ed.montgomeryCurve.getSharedSecret(alicePrivate, bobPublic)), shared);
|
||||
deepStrictEqual(hex(ed.montgomeryCurve.getSharedSecret(bobPrivate, alicePublic)), shared);
|
||||
});
|
||||
|
||||
{
|
||||
const group = x448vectors.testGroups[0];
|
||||
for (let i = 0; i < group.tests.length; i++) {
|
||||
const v = group.tests[i];
|
||||
should(`Wycheproof/X448(${i}, ${v.result}) ${v.comment}`, () => {
|
||||
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||
try {
|
||||
const shared = hex(ed.montgomeryCurve.getSharedSecret(v.private, v.public));
|
||||
deepStrictEqual(shared, v.shared, 'valid');
|
||||
} 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 {
|
||||
ed.montgomeryCurve.getSharedSecret(v.private, v.public);
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
deepStrictEqual(failed, true, 'invalid');
|
||||
} else throw new Error('unknown test result');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
should('X448: should convert base point to montgomery using fromPoint', () => {
|
||||
deepStrictEqual(
|
||||
hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)),
|
||||
ed.montgomeryCurve.BASE_POINT_U
|
||||
);
|
||||
});
|
||||
|
||||
should('X448/getSharedSecret() should be commutative', async () => {
|
||||
for (let i = 0; i < 512; i++) {
|
||||
const asec = ed.utils.randomPrivateKey();
|
||||
const apub = ed.getPublicKey(asec);
|
||||
const bsec = ed.utils.randomPrivateKey();
|
||||
const bpub = ed.getPublicKey(bsec);
|
||||
try {
|
||||
deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
|
||||
} catch (error) {
|
||||
console.error('not commutative', { asec, apub, bsec, bpub });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const VECTORS_RFC8032_CTX = [
|
||||
{
|
||||
secretKey:
|
||||
'c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e',
|
||||
publicKey:
|
||||
'43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480',
|
||||
message: '03',
|
||||
context: '666f6f',
|
||||
signature:
|
||||
'd4f8f6131770dd46f40867d6fd5d5055' +
|
||||
'de43541f8c5e35abbcd001b32a89f7d2' +
|
||||
'151f7647f11d8ca2ae279fb842d60721' +
|
||||
'7fce6e042f6815ea000c85741de5c8da' +
|
||||
'1144a6a1aba7f96de42505d7a7298524' +
|
||||
'fda538fccbbb754f578c1cad10d54d0d' +
|
||||
'5428407e85dcbc98a49155c13764e66c' +
|
||||
'3c00',
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
|
||||
const v = VECTORS_RFC8032_CTX[i];
|
||||
should(`RFC8032ctx/${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);
|
||||
});
|
||||
}
|
||||
|
||||
const VECTORS_RFC8032_PH = [
|
||||
{
|
||||
secretKey:
|
||||
'833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49',
|
||||
publicKey:
|
||||
'259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880',
|
||||
message: '616263',
|
||||
signature:
|
||||
'822f6901f7480f3d5f562c592994d969' +
|
||||
'3602875614483256505600bbc281ae38' +
|
||||
'1f54d6bce2ea911574932f52a4e6cadd' +
|
||||
'78769375ec3ffd1b801a0d9b3f4030cd' +
|
||||
'433964b6457ea39476511214f97469b5' +
|
||||
'7dd32dbc560a9a94d00bff07620464a3' +
|
||||
'ad203df7dc7ce360c3cd3696d9d9fab9' +
|
||||
'0f00',
|
||||
},
|
||||
{
|
||||
secretKey:
|
||||
'833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49',
|
||||
publicKey:
|
||||
'259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880',
|
||||
message: '616263',
|
||||
context: '666f6f',
|
||||
signature:
|
||||
'c32299d46ec8ff02b54540982814dce9' +
|
||||
'a05812f81962b649d528095916a2aa48' +
|
||||
'1065b1580423ef927ecf0af5888f90da' +
|
||||
'0f6a9a85ad5dc3f280d91224ba9911a3' +
|
||||
'653d00e484e2ce232521481c8658df30' +
|
||||
'4bb7745a73514cdb9bf3e15784ab7128' +
|
||||
'4f8d0704a608c54a6b62d97beb511d13' +
|
||||
'2100',
|
||||
},
|
||||
];
|
||||
|
||||
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
||||
const v = VECTORS_RFC8032_PH[i];
|
||||
should(`RFC8032ph/${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);
|
||||
});
|
||||
}
|
||||
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
||||
|
||||
/*
|
||||
OUR:
|
||||
verify(r) Point {
|
||||
x: 117083889870452976893611547833716470707682026107584166971579726410379933200511856552571178072395970298806909801597194212619985635844270n,
|
||||
y: 664613439225768038924451828443390535972832706605490606711138767575182918078146210986473699054775106829537240700777959779811881639147732n
|
||||
}
|
||||
verify(s) 171560600556246420336815304075147406138580530023825761769062776185328256162525428025207885397693292244020015666168990464688721996580108n
|
||||
verify(G*s) Point {
|
||||
x: 162580569181617012512915096099987276929520102470148179595683186785564923123644100210058934137413137508679834237807539232961980479559703n,
|
||||
y: 300823828808029813226461528962321558722882991716498392051038897352648558972404548670077149616666377671445681138581862112882370877960540n
|
||||
}
|
||||
verify(k, hashed) 8903905151968610333524336167784696500100432517973872822290595420701503447289798240464976231189650851226450153394571270017367997886911n
|
||||
verify(kA, pubKey*k) Point {
|
||||
x: 689275764828592611152871878771714953006357504547408405065617338137477032523786110957620128317758694018203461690302684307888964572340785n,
|
||||
y: 481328274497162994197716136544465653692120986048960981855841218770941500118441012672159998270864104759919787387966448820647078734831560n
|
||||
}
|
||||
verify(RkA, r+kA) Point {
|
||||
x: 122982406147090575324915101352793466553671728663998628086189507014151752138194638629641411526703069442455515495095211707112301230852909n,
|
||||
y: 628599576854758762451451863221817761200843280520482487953855243757271313346398229758661810534375546179313418158952236033181207878869809n
|
||||
}
|
||||
|
||||
verify(RkA*h) Point {
|
||||
x: 285367816437357046852385747901568148949273863571594761351869427482364138883891040584481510007402650226067916995110428282873262921611092n,
|
||||
y: 156274886003874424437114343095234538185297810914769372891105611450134155931335556420419362638465757217877136785807507867845960943191541n
|
||||
}
|
||||
verify(SB*h) Point {
|
||||
x: 49879895074926972607089290381794972663529905435154505099510082025305354413619454150885832024420892269163908738032246918490575686924369n,
|
||||
y: 493395180352948178031760912270438078255871801590750728653962749871941650726588112030608282487999493622892374555049324278196950479173047n
|
||||
}
|
||||
|
||||
verify(RkA-SB) Point {
|
||||
x: 387994761565948378495106619286831545021939931213704916072043327425382665414799988004170105004375426191018762465670849206465968350321870n,
|
||||
y: 6861694927033303775201514393095605504169468049999574963263295660536234490030921387020643041252124207285319521500442198840633575797105n
|
||||
}
|
||||
verify((RkA-SB)*h) Point {
|
||||
x: 459153706735977722773001536505633023208447605158247558496590837749184216434578577646373840599757767671074933212247199030379143045005320n,
|
||||
y: 610562540704855906228140299454456520343298226945170317172546981746408207471796755968182254530606222931325758957275600304516146767443812n
|
||||
}
|
||||
|
||||
verify(R) x=117083889870452976893611547833716470707682026107584166971579726410379933200511856552571178072395970298806909801597194212619985635844270
|
||||
y=664613439225768038924451828443390535972832706605490606711138767575182918078146210986473699054775106829537240700777959779811881639147732
|
||||
verify(S) 171560600556246420336815304075147406138580530023825761769062776185328256162525428025207885397693292244020015666168990464688721996580108
|
||||
Verify parsed
|
||||
!!!!!!!! CTX True b'' b'53696745643434380000'
|
||||
verify(h) 8903905151968610333524336167784696500100432517973872822290595420701503447289798240464976231189650851226450153394571270017367997886911
|
||||
MUL 8903905151968610333524336167784696500100432517973872822290595420701503447289798240464976231189650851226450153394571270017367997886911
|
||||
MUL 171560600556246420336815304075147406138580530023825761769062776185328256162525428025207885397693292244020015666168990464688721996580108
|
||||
verify((R+(a*h)) ) x=122982406147090575324915101352793466553671728663998628086189507014151752138194638629641411526703069442455515495095211707112301230852909
|
||||
y=628599576854758762451451863221817761200843280520482487953855243757271313346398229758661810534375546179313418158952236033181207878869809
|
||||
|
||||
ok ^
|
||||
|
||||
verify(B*S) x=162580569181617012512915096099987276929520102470148179595683186785564923123644100210058934137413137508679834237807539232961980479559703
|
||||
y=300823828808029813226461528962321558722882991716498392051038897352648558972404548670077149616666377671445681138581862112882370877960540
|
||||
|
||||
|
||||
OK!
|
||||
|
||||
verify((R+(a*h)) * c) x=285367816437357046852385747901568148949273863571594761351869427482364138883891040584481510007402650226067916995110428282873262921611092
|
||||
y=156274886003874424437114343095234538185297810914769372891105611450134155931335556420419362638465757217877136785807507867845960943191541
|
||||
verify(B*S*c) x=49879895074926972607089290381794972663529905435154505099510082025305354413619454150885832024420892269163908738032246918490575686924369
|
||||
y=493395180352948178031760912270438078255871801590750728653962749871941650726588112030608282487999493622892374555049324278196950479173047
|
||||
|
||||
|
||||
*/
|
||||
|
58
curve-definitions/test/generic.test.js
Normal file
58
curve-definitions/test/generic.test.js
Normal file
@ -0,0 +1,58 @@
|
||||
import { deepStrictEqual, throws } from 'assert';
|
||||
import { should } from 'micro-should';
|
||||
import * as fc from 'fast-check';
|
||||
// Generic tests for all curves in package
|
||||
import { secp192r1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1 } from '../lib/nist.js';
|
||||
import { ed25519, ed25519ctx, ed25519ph, ed448, ed448ph } from '../lib/ed.js';
|
||||
import { starkCurve } from '../lib/starknet.js';
|
||||
import { pallas, vesta } from '../lib/pasta.js';
|
||||
import { bn254 } from '../lib/bn.js';
|
||||
|
||||
// prettier-ignore
|
||||
const CURVES = {
|
||||
secp192r1, secp224r1, secp256k1, secp256r1, secp384r1, secp521r1,
|
||||
ed25519, ed25519ctx, ed25519ph, ed448, ed448ph,
|
||||
starkCurve,
|
||||
pallas, vesta,
|
||||
bn254,
|
||||
};
|
||||
|
||||
for (const name in CURVES) {
|
||||
const C = CURVES[name];
|
||||
// Generic sanity tests:
|
||||
// - group laws:
|
||||
// G*(CURVE.n-1) + 1 = Point.ZERO
|
||||
// G*(CURVE.n-2) + 2 = Point.ZERO
|
||||
// G*(CURVE.n/2).double() = Point.ZERO or Point.BASE?
|
||||
// rand*G + rand2*G = G*(rand+rand mod N)
|
||||
// - double works
|
||||
// ZERO.double() == zero
|
||||
// - adding zero point works
|
||||
// - add(samePoint) works
|
||||
// - add(-samePoint) works
|
||||
// - 2+2 = 2.double() = 7-5 (should have different Z coordinates, but it is same point)
|
||||
// ToAffine: Point.BASE = Extended/Jacobian.toAffine()
|
||||
// Property tests:
|
||||
// signatures, getSharedKey/etc
|
||||
//const FC_BIGINT = fc.bigInt(1n + 1n, C.n - 1n);
|
||||
|
||||
should(`${name}/Basic`, () => {});
|
||||
const POINTS = { Point: C.Point, JacobianPoint: C.JacobianPoint, ExtendedPoint: C.ExtendedPoint };
|
||||
for (const pointName in POINTS) {
|
||||
const p = POINTS[pointName];
|
||||
if (!p) continue;
|
||||
|
||||
const G = [p.ZERO, p.BASE];
|
||||
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
|
||||
should(`${name}/${pointName}/Basic`, () => {
|
||||
// ... And we dont have it
|
||||
//deepStrictEqual(G[2].double().equals(G[4]), true);
|
||||
//console.log('Z', G);
|
||||
});
|
||||
}
|
||||
}
|
||||
// ESM is broken.
|
||||
import url from 'url';
|
||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||
should.run();
|
||||
}
|
@ -195,7 +195,7 @@ should('secp256k1.Signature.fromDERHex() roundtrip', () => {
|
||||
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', async () => {
|
||||
for (const vector of ecdsa.valid) {
|
||||
let usig = await secp.sign(vector.m, vector.d);
|
||||
let sig = (usig.toCompactHex());
|
||||
let sig = usig.toCompactHex();
|
||||
const vsig = vector.signature;
|
||||
deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64));
|
||||
deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128));
|
||||
@ -238,7 +238,7 @@ should('secp256k1.sign()/should create correct DER encoding against libsecp256k1
|
||||
const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101');
|
||||
for (let [msg, exp] of CASES) {
|
||||
const res = await secp.sign(msg, privKey, { extraEntropy: undefined });
|
||||
deepStrictEqual((res.toDERHex()), exp);
|
||||
deepStrictEqual(res.toDERHex(), exp);
|
||||
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex();
|
||||
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
|
||||
}
|
||||
@ -252,7 +252,7 @@ should('secp256k1.sign()/sign ecdsa extraData', async () => {
|
||||
|
||||
for (const e of ecdsa.extraEntropy) {
|
||||
const sign = async (extraEntropy) => {
|
||||
const s = secp.sign(e.m, e.d, {extraEntropy }).toCompactHex();
|
||||
const s = secp.sign(e.m, e.d, { extraEntropy }).toCompactHex();
|
||||
return s;
|
||||
};
|
||||
deepStrictEqual(await sign(), e.signature);
|
||||
@ -394,7 +394,7 @@ should('secp256k1.recoverPublicKey()/should recover public key from recovery bit
|
||||
const recoveredPubkey = sig.recoverPublicKey(message);
|
||||
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
|
||||
deepStrictEqual(recoveredPubkey !== null, true);
|
||||
deepStrictEqual((recoveredPubkey).toHex(), publicKey);
|
||||
deepStrictEqual(recoveredPubkey.toHex(), publicKey);
|
||||
deepStrictEqual(secp.verify(sig, message, publicKey), true);
|
||||
});
|
||||
should('secp256k1.recoverPublicKey()/should not recover zero points', () => {
|
||||
@ -416,10 +416,10 @@ should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', async () =
|
||||
for (const vector of ecdsa.valid) {
|
||||
if (secp.utils.mod(hexToNumber(vector.m), secp.CURVE.n) === 0n) continue;
|
||||
let usig = secp.sign(vector.m, vector.d);
|
||||
let sig = (usig).toDERHex();
|
||||
let sig = usig.toDERHex();
|
||||
const vpub = secp.getPublicKey(vector.d);
|
||||
const recovered = usig.recoverPublicKey(vector.m);
|
||||
deepStrictEqual((recovered).toHex(), hex(vpub));
|
||||
deepStrictEqual(recovered.toHex(), hex(vpub));
|
||||
}
|
||||
});
|
||||
|
||||
|
4218
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha224_test.json
Normal file
4218
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha224_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4447
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha256_test.json
Normal file
4447
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4444
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_224_test.json
Normal file
4444
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_224_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4516
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_256_test.json
Normal file
4516
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5036
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_512_test.json
Normal file
5036
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5002
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha512_test.json
Normal file
5002
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4474
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha256_test.json
Normal file
4474
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4538
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_256_test.json
Normal file
4538
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5066
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_512_test.json
Normal file
5066
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5034
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha512_test.json
Normal file
5034
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4578
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha256_test.json
Normal file
4578
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4642
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_256_test.json
Normal file
4642
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5172
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_512_test.json
Normal file
5172
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5138
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha512_test.json
Normal file
5138
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4634
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha384_test.json
Normal file
4634
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha384_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4711
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_384_test.json
Normal file
4711
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_384_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4972
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_512_test.json
Normal file
4972
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4940
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha512_test.json
Normal file
4940
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5005
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha3_512_test.json
Normal file
5005
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4989
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha512_test.json
Normal file
4989
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user