node icon indicating copy to clipboard operation
node copied to clipboard

subset of small-order Ed25519 vectors is failing in WPTs

Open avivkeller opened this issue 1 year ago • 9 comments

I've been working on upgrading the Web Platform Tests in PR #54468, but I'm running into an issue with the WebCryptoAPI tests failing. After investigating, I found that I could reproduce the problem in Node.js. The error seems to be related to the crypto.subtle API, which isn't producing the expected results when verifying data in Ed25519.

FWIW Node.js isn't the only runtime per https://wpt.fyi/results/WebCryptoAPI/sign_verify/eddsa.https.any.html?label=experimental&label=master&aligned

The original error from running the Web Platform Tests is:

[UNEXPECTED_FAILURE][FAIL] Ed25519 Verification checks with small-order key of order - Test 0
assert_equals: Signature verification result. expected false but got true
    at Test.<anonymous> (/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js:235:15)
Command: /out/Release/node  /test/wpt/test-webcrypto.js 'sign_verify/eddsa.https.any.js'

[UNEXPECTED_FAILURE][FAIL] Ed25519 Verification checks with small-order key of order - Test 1
assert_equals: Signature verification result. expected false but got true
    at Test.<anonymous> (/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js:235:15)
Command: /out/Release/node  /test/wpt/test-webcrypto.js 'sign_verify/eddsa.https.any.js'

[UNEXPECTED_FAILURE][FAIL] Ed25519 Verification checks with small-order key of order - Test 2
assert_equals: Signature verification result. expected false but got true
    at Test.<anonymous> (/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa.js:235:15)
Command: /out/Release/node  /test/wpt/test-webcrypto.js 'sign_verify/eddsa.https.any.js'

My minimal reproduction is:

var pubKeys = [
	[0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa], // kSmallOrderPoints #5
	[0xf7, 0xba, 0xde, 0xc5, 0xb8, 0xab, 0xea, 0xf6, 0x99, 0x58, 0x39, 0x92, 0x21, 0x9b, 0x7b, 0x22, 0x3f, 0x1d, 0xf3, 0xfb, 0xbe, 0xa9, 0x19, 0x84, 0x4e, 0x3f, 0x7c, 0x55, 0x4a, 0x43, 0xdd, 0x43], // highest 32 bytes of case "1" signature
	[0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], // kSmallOrderPoints #9
	[0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], // kSmallOrderPoints #1
]

const tests = [{
	id: "0", // S = 0 | A's order = small | R's order = small | (1) = pass | (2) = pass
	message: Uint8Array.from([0x8c, 0x93, 0x25, 0x5d, 0x71, 0xdc, 0xab, 0x10, 0xe8, 0xf3, 0x79, 0xc2, 0x62, 0x00, 0xf3, 0xc7, 0xbd, 0x5f, 0x09, 0xd9, 0xbc, 0x30, 0x68, 0xd3, 0xef, 0x4e, 0xde, 0xb4, 0x85, 0x30, 0x22, 0xb6]),
	keyData: Uint8Array.from(pubKeys[0]),
	signature: Uint8Array.from([0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
	verified: false,  // small-order signature's R fail in the verification.
},
{
	id: "1", // 0 < S < L | A's order = small | R's order = mixed | (1) = pass | (2) = pass
	message: Uint8Array.from([0x9b, 0xd9, 0xf4, 0x4f, 0x4d, 0xcc, 0x75, 0xbd, 0x53, 0x1b, 0x56, 0xb2, 0xcd, 0x28, 0x0b, 0x0b, 0xb3, 0x8f, 0xc1, 0xcd, 0x6d, 0x12, 0x30, 0xe1, 0x48, 0x61, 0xd8, 0x61, 0xde, 0x09, 0x2e, 0x79]),
	keyData: Uint8Array.from(pubKeys[0]),
	signature: Uint8Array.from([0xf7, 0xba, 0xde, 0xc5, 0xb8, 0xab, 0xea, 0xf6, 0x99, 0x58, 0x39, 0x92, 0x21, 0x9b, 0x7b, 0x22, 0x3f, 0x1d, 0xf3, 0xfb, 0xbe, 0xa9, 0x19, 0x84, 0x4e, 0x3f, 0x7c, 0x55, 0x4a, 0x43, 0xdd, 0x43, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false, // small-order key's data fail in the verification.
},
{
	id: "2", // 0 < S < L | A's order = mixed | R's order = small | (1) = pass | (2) = pass
	message: Uint8Array.from([0xae, 0xbf, 0x3f, 0x26, 0x01, 0xa0, 0xc8, 0xc5, 0xd3, 0x9c, 0xc7, 0xd8, 0x91, 0x16, 0x42, 0xf7, 0x40, 0xb7, 0x81, 0x68, 0x21, 0x8d, 0xa8, 0x47, 0x17, 0x72, 0xb3, 0x5f, 0x9d, 0x35, 0xb9, 0xab]),
	keyData: Uint8Array.from(pubKeys[1]),
	signature: Uint8Array.from([0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa, 0x8c, 0x4b, 0xd4, 0x5a, 0xec, 0xac, 0xa5, 0xb2, 0x4f, 0xb9, 0x7b, 0xc1, 0x0a, 0xc2, 0x7a, 0xc8, 0x75, 0x1a, 0x7d, 0xfe, 0x1b, 0xaf, 0xf8, 0xb9, 0x53, 0xec, 0x9f, 0x58, 0x33, 0xca, 0x26, 0x0e]),
	verified: false,  // small-order signature's R fail in the verification.
},
{
	id: "11", // 0 < S < L | A's order = small (non-canonical) | R's order = mixed | (1) = ? | (2) = ? Implementations that reduce A before hashing will accept #10 and accept #11, and viceversa
	message: Uint8Array.from([0x39, 0xa5, 0x91, 0xf5, 0x32, 0x1b, 0xbe, 0x07, 0xfd, 0x5a, 0x23, 0xdc, 0x2f, 0x39, 0xd0, 0x25, 0xd7, 0x45, 0x26, 0x61, 0x57, 0x46, 0x72, 0x7c, 0xee, 0xfd, 0x6e, 0x82, 0xae, 0x65, 0xc0, 0x6f]),
	keyData: Uint8Array.from(pubKeys[2]),
	signature: Uint8Array.from([0xa9, 0xd5, 0x52, 0x60, 0xf7, 0x65, 0x26, 0x1e, 0xb9, 0xb8, 0x4e, 0x10, 0x6f, 0x66, 0x5e, 0x00, 0xb8, 0x67, 0x28, 0x7a, 0x76, 0x19, 0x90, 0xd7, 0x13, 0x59, 0x63, 0xee, 0x0a, 0x7d, 0x59, 0xdc, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false, // non-canonical point should fail in the verificaton (RFC8032)
},
// https://eprint.iacr.org/2020/1244.pdf#section.A.2
// cases breaking non-repudiation
{
	id: "12", // 0 < S < L | A's order = small | R's order = mixed | (1) = ? | (2) = ?
	message: Uint8Array.from([0x53, 0x65, 0x6e, 0x64, 0x20, 0x31, 0x30, 0x30, 0x20, 0x55, 0x53, 0x44, 0x20, 0x74, 0x6f, 0x20, 0x41, 0x6c, 0x69, 0x63, 0x65]),
	keyData: Uint8Array.from(pubKeys[3]),
	signature: Uint8Array.from([0xa9, 0xd5, 0x52, 0x60, 0xf7, 0x65, 0x26, 0x1e, 0xb9, 0xb8, 0x4e, 0x10, 0x6f, 0x66, 0x5e, 0x00, 0xb8, 0x67, 0x28, 0x7a, 0x76, 0x19, 0x90, 0xd7, 0x13, 0x59, 0x63, 0xee, 0x0a, 0x7d, 0x59, 0xdc, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false,
},
{
	id: "13", // 0 < S < L | A's order = small | R's order = mixed | (1) = ? | (2) = ?
	message: Uint8Array.from([0x53, 0x65, 0x6e, 0x64, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x20, 0x55, 0x53, 0x44, 0x20, 0x74, 0x6f, 0x20, 0x41, 0x6c, 0x69, 0x63, 0x65]),
	keyData: Uint8Array.from(pubKeys[3]),
	signature: Uint8Array.from([0xa9, 0xd5, 0x52, 0x60, 0xf7, 0x65, 0x26, 0x1e, 0xb9, 0xb8, 0x4e, 0x10, 0x6f, 0x66, 0x5e, 0x00, 0xb8, 0x67, 0x28, 0x7a, 0x76, 0x19, 0x90, 0xd7, 0x13, 0x59, 0x63, 0xee, 0x0a, 0x7d, 0x59, 0xdc, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false,
}
]

const algorithm = { name: "Ed25519" };

for (let i = 0; i < tests.length; i++) {
	const test = tests[i];
	const publicKey = await crypto.subtle.importKey("raw", test.keyData, algorithm, false, ["verify"]);
	const isVerified = await crypto.subtle.verify(algorithm, publicKey, test.signature, test.message);
	console.log(test.id, isVerified === test.verified);
  }

Modified from https://github.com/web-platform-tests/wpt/blob/master/WebCryptoAPI/sign_verify/eddsa.js. I've excluded the passing tests.

Results:

(node:73243) ExperimentalWarning: The Ed25519 Web Crypto API algorithm is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
0 false
1 false
2 false
11 false
12 false
13 false
Expand for a version with all tests, including passing ones
var pubKeys = [
	[0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa], // kSmallOrderPoints #5
	[0xf7, 0xba, 0xde, 0xc5, 0xb8, 0xab, 0xea, 0xf6, 0x99, 0x58, 0x39, 0x92, 0x21, 0x9b, 0x7b, 0x22, 0x3f, 0x1d, 0xf3, 0xfb, 0xbe, 0xa9, 0x19, 0x84, 0x4e, 0x3f, 0x7c, 0x55, 0x4a, 0x43, 0xdd, 0x43], // highest 32 bytes of case "1" signature
	[0xcd, 0xb2, 0x67, 0xce, 0x40, 0xc5, 0xcd, 0x45, 0x30, 0x6f, 0xa5, 0xd2, 0xf2, 0x97, 0x31, 0x45, 0x93, 0x87, 0xdb, 0xf9, 0xeb, 0x93, 0x3b, 0x7b, 0xd5, 0xae, 0xd9, 0xa7, 0x65, 0xb8, 0x8d, 0x4d],
	[0x44, 0x2a, 0xad, 0x9f, 0x08, 0x9a, 0xd9, 0xe1, 0x46, 0x47, 0xb1, 0xef, 0x90, 0x99, 0xa1, 0xff, 0x47, 0x98, 0xd7, 0x85, 0x89, 0xe6, 0x6f, 0x28, 0xec, 0xa6, 0x9c, 0x11, 0xf5, 0x82, 0xa6, 0x23],
	[0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], // kSmallOrderPoints #9
	[0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], // kSmallOrderPoints #1
]

const tests = [{
	id: "0", // S = 0 | A's order = small | R's order = small | (1) = pass | (2) = pass
	message: Uint8Array.from([0x8c, 0x93, 0x25, 0x5d, 0x71, 0xdc, 0xab, 0x10, 0xe8, 0xf3, 0x79, 0xc2, 0x62, 0x00, 0xf3, 0xc7, 0xbd, 0x5f, 0x09, 0xd9, 0xbc, 0x30, 0x68, 0xd3, 0xef, 0x4e, 0xde, 0xb4, 0x85, 0x30, 0x22, 0xb6]),
	keyData: Uint8Array.from(pubKeys[0]),
	signature: Uint8Array.from([0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
	verified: false,  // small-order signature's R fail in the verification.
},
{
	id: "1", // 0 < S < L | A's order = small | R's order = mixed | (1) = pass | (2) = pass
	message: Uint8Array.from([0x9b, 0xd9, 0xf4, 0x4f, 0x4d, 0xcc, 0x75, 0xbd, 0x53, 0x1b, 0x56, 0xb2, 0xcd, 0x28, 0x0b, 0x0b, 0xb3, 0x8f, 0xc1, 0xcd, 0x6d, 0x12, 0x30, 0xe1, 0x48, 0x61, 0xd8, 0x61, 0xde, 0x09, 0x2e, 0x79]),
	keyData: Uint8Array.from(pubKeys[0]),
	signature: Uint8Array.from([0xf7, 0xba, 0xde, 0xc5, 0xb8, 0xab, 0xea, 0xf6, 0x99, 0x58, 0x39, 0x92, 0x21, 0x9b, 0x7b, 0x22, 0x3f, 0x1d, 0xf3, 0xfb, 0xbe, 0xa9, 0x19, 0x84, 0x4e, 0x3f, 0x7c, 0x55, 0x4a, 0x43, 0xdd, 0x43, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false, // small-order key's data fail in the verification.
},
{
	id: "2", // 0 < S < L | A's order = mixed | R's order = small | (1) = pass | (2) = pass
	message: Uint8Array.from([0xae, 0xbf, 0x3f, 0x26, 0x01, 0xa0, 0xc8, 0xc5, 0xd3, 0x9c, 0xc7, 0xd8, 0x91, 0x16, 0x42, 0xf7, 0x40, 0xb7, 0x81, 0x68, 0x21, 0x8d, 0xa8, 0x47, 0x17, 0x72, 0xb3, 0x5f, 0x9d, 0x35, 0xb9, 0xab]),
	keyData: Uint8Array.from(pubKeys[1]),
	signature: Uint8Array.from([0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa, 0x8c, 0x4b, 0xd4, 0x5a, 0xec, 0xac, 0xa5, 0xb2, 0x4f, 0xb9, 0x7b, 0xc1, 0x0a, 0xc2, 0x7a, 0xc8, 0x75, 0x1a, 0x7d, 0xfe, 0x1b, 0xaf, 0xf8, 0xb9, 0x53, 0xec, 0x9f, 0x58, 0x33, 0xca, 0x26, 0x0e]),
	verified: false,  // small-order signature's R fail in the verification.
},
{
	id: "3", // 0 < S < L | A's order = mixed | R's order = mixed | (1) = pass | (2) = pass
	message: Uint8Array.from([0x9b, 0xd9, 0xf4, 0x4f, 0x4d, 0xcc, 0x75, 0xbd, 0x53, 0x1b, 0x56, 0xb2, 0xcd, 0x28, 0x0b, 0x0b, 0xb3, 0x8f, 0xc1, 0xcd, 0x6d, 0x12, 0x30, 0xe1, 0x48, 0x61, 0xd8, 0x61, 0xde, 0x09, 0x2e, 0x79]),
	keyData: Uint8Array.from(pubKeys[2]),
	signature: Uint8Array.from([0x90, 0x46, 0xa6, 0x47, 0x50, 0x44, 0x49, 0x38, 0xde, 0x19, 0xf2, 0x27, 0xbb, 0x80, 0x48, 0x5e, 0x92, 0xb8, 0x3f, 0xdb, 0x4b, 0x65, 0x06, 0xc1, 0x60, 0x48, 0x4c, 0x01, 0x6c, 0xc1, 0x85, 0x2f, 0x87, 0x90, 0x9e, 0x14, 0x42, 0x8a, 0x7a, 0x1d, 0x62, 0xe9, 0xf2, 0x2f, 0x3d, 0x3a, 0xd7, 0x80, 0x2d, 0xb0, 0x2e, 0xb2, 0xe6, 0x88, 0xb6, 0xc5, 0x2f, 0xcd, 0x66, 0x48, 0xa9, 0x8b, 0xd0, 0x09]),
	verified: true, // mixed-order points are not checked.
},
{
	id: "4", // 0 < S < L | A's order = mixed | R's order = mixed | (1) = pass | (2) = fail
	message: Uint8Array.from([0xe4, 0x7d, 0x62, 0xc6, 0x3f, 0x83, 0x0d, 0xc7, 0xa6, 0x85, 0x1a, 0x0b, 0x1f, 0x33, 0xae, 0x4b, 0xb2, 0xf5, 0x07, 0xfb, 0x6c, 0xff, 0xec, 0x40, 0x11, 0xea, 0xcc, 0xd5, 0x5b, 0x53, 0xf5, 0x6c]),
	keyData: Uint8Array.from(pubKeys[2]),
	signature: Uint8Array.from([0x16, 0x0a, 0x1c, 0xb0, 0xdc, 0x9c, 0x02, 0x58, 0xcd, 0x0a, 0x7d, 0x23, 0xe9, 0x4d, 0x8f, 0xa8, 0x78, 0xbc, 0xb1, 0x92, 0x5f, 0x2c, 0x64, 0x24, 0x6b, 0x2d, 0xee, 0x17, 0x96, 0xbe, 0xd5, 0x12, 0x5e, 0xc6, 0xbc, 0x98, 0x2a, 0x26, 0x9b, 0x72, 0x3e, 0x06, 0x68, 0xe5, 0x40, 0x91, 0x1a, 0x9a, 0x6a, 0x58, 0x92, 0x1d, 0x69, 0x25, 0xe4, 0x34, 0xab, 0x10, 0xaa, 0x79, 0x40, 0x55, 0x1a, 0x09]),
	verified: false, // expect a cofactorless verification algorithm.
},
{
	id: "5", // 0 < S < L | A's order = mixed | R's order = L | (1) = pass | (2) = fail
	message: Uint8Array.from([0xe4, 0x7d, 0x62, 0xc6, 0x3f, 0x83, 0x0d, 0xc7, 0xa6, 0x85, 0x1a, 0x0b, 0x1f, 0x33, 0xae, 0x4b, 0xb2, 0xf5, 0x07, 0xfb, 0x6c, 0xff, 0xec, 0x40, 0x11, 0xea, 0xcc, 0xd5, 0x5b, 0x53, 0xf5, 0x6c]),
	keyData: Uint8Array.from(pubKeys[2]),
	signature: Uint8Array.from([0x21, 0x12, 0x2a, 0x84, 0xe0, 0xb5, 0xfc, 0xa4, 0x05, 0x2f, 0x5b, 0x12, 0x35, 0xc8, 0x0a, 0x53, 0x78, 0x78, 0xb3, 0x8f, 0x31, 0x42, 0x35, 0x6b, 0x2c, 0x23, 0x84, 0xeb, 0xad, 0x46, 0x68, 0xb7, 0xe4, 0x0b, 0xc8, 0x36, 0xda, 0xc0, 0xf7, 0x10, 0x76, 0xf9, 0xab, 0xe3, 0xa5, 0x3f, 0x9c, 0x03, 0xc1, 0xce, 0xee, 0xdd, 0xb6, 0x58, 0xd0, 0x03, 0x04, 0x94, 0xac, 0xe5, 0x86, 0x68, 0x74, 0x05]),
	verified: false, // expect a cofactorless verification algorithm.
},
{
	id: "6", // S > L | A's order = L | R's order = L | (1) = pass | (2) = pass
	message: Uint8Array.from([0x85, 0xe2, 0x41, 0xa0, 0x7d, 0x14, 0x8b, 0x41, 0xe4, 0x7d, 0x62, 0xc6, 0x3f, 0x83, 0x0d, 0xc7, 0xa6, 0x85, 0x1a, 0x0b, 0x1f, 0x33, 0xae, 0x4b, 0xb2, 0xf5, 0x07, 0xfb, 0x6c, 0xff, 0xec, 0x40]),
	keyData: Uint8Array.from(pubKeys[3]),
	signature: Uint8Array.from([0xe9, 0x6f, 0x66, 0xbe, 0x97, 0x6d, 0x82, 0xe6, 0x01, 0x50, 0xba, 0xec, 0xff, 0x99, 0x06, 0x68, 0x4a, 0xeb, 0xb1, 0xef, 0x18, 0x1f, 0x67, 0xa7, 0x18, 0x9a, 0xc7, 0x8e, 0xa2, 0x3b, 0x6c, 0x0e, 0x54, 0x7f, 0x76, 0x90, 0xa0, 0xe2, 0xdd, 0xcd, 0x04, 0xd8, 0x7d, 0xbc, 0x34, 0x90, 0xdc, 0x19, 0xb3, 0xb3, 0x05, 0x2f, 0x7f, 0xf0, 0x53, 0x8c, 0xb6, 0x8a, 0xfb, 0x36, 0x9b, 0xa3, 0xa5, 0x14]),
	verified: false, // S out of bounds
},
{
	id: "7", // S >> L | A's order = L | R's order = L | (1) = pass | (2) = pass
	message: Uint8Array.from([0x85, 0xe2, 0x41, 0xa0, 0x7d, 0x14, 0x8b, 0x41, 0xe4, 0x7d, 0x62, 0xc6, 0x3f, 0x83, 0x0d, 0xc7, 0xa6, 0x85, 0x1a, 0x0b, 0x1f, 0x33, 0xae, 0x4b, 0xb2, 0xf5, 0x07, 0xfb, 0x6c, 0xff, 0xec, 0x40]),
	keyData: Uint8Array.from(pubKeys[3]),
	signature: Uint8Array.from([0x8c, 0xe5, 0xb9, 0x6c, 0x8f, 0x26, 0xd0, 0xab, 0x6c, 0x47, 0x95, 0x8c, 0x9e, 0x68, 0xb9, 0x37, 0x10, 0x4c, 0xd3, 0x6e, 0x13, 0xc3, 0x35, 0x66, 0xac, 0xd2, 0xfe, 0x8d, 0x38, 0xaa, 0x19, 0x42, 0x7e, 0x71, 0xf9, 0x8a, 0x47, 0x34, 0xe7, 0x4f, 0x2f, 0x13, 0xf0, 0x6f, 0x97, 0xc2, 0x0d, 0x58, 0xcc, 0x3f, 0x54, 0xb8, 0xbd, 0x0d, 0x27, 0x2f, 0x42, 0xb6, 0x95, 0xdd, 0x7e, 0x89, 0xa8, 0xc2, 0x02]),
	verified: false, // S out of bounds
},
{
	id: "8", // 0 < S < L | A's order = mixed | R's order = small (non-canonical) | (1) = ? | (2) = ? Implementations that reduce A before hashing will accept #8 and accept #9, and viceversa
	message: Uint8Array.from([0x9b, 0xed, 0xc2, 0x67, 0x42, 0x37, 0x25, 0xd4, 0x73, 0x88, 0x86, 0x31, 0xeb, 0xf4, 0x59, 0x88, 0xba, 0xd3, 0xdb, 0x83, 0x85, 0x1e, 0xe8, 0x5c, 0x85, 0xe2, 0x41, 0xa0, 0x7d, 0x14, 0x8b, 0x41]),
	keyData: Uint8Array.from(pubKeys[1]),
	signature: Uint8Array.from([0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xbe, 0x96, 0x78, 0xac, 0x10, 0x2e, 0xdc, 0xd9, 0x2b, 0x02, 0x10, 0xbb, 0x34, 0xd7, 0x42, 0x8d, 0x12, 0xff, 0xc5, 0xdf, 0x5f, 0x37, 0xe3, 0x59, 0x94, 0x12, 0x66, 0xa4, 0xe3, 0x5f, 0x0f]),
	verified: false, // non-canonical point should fail in the verificaton (RFC8032)
},
{
	id: "9", // 0 < S < L | A's order = mixed | R's order = small (non-canonical) | (1) = ? | (2) = ?
	message: Uint8Array.from([0x9b, 0xed, 0xc2, 0x67, 0x42, 0x37, 0x25, 0xd4, 0x73, 0x88, 0x86, 0x31, 0xeb, 0xf4, 0x59, 0x88, 0xba, 0xd3, 0xdb, 0x83, 0x85, 0x1e, 0xe8, 0x5c, 0x85, 0xe2, 0x41, 0xa0, 0x7d, 0x14, 0x8b, 0x41]),
	keyData: Uint8Array.from(pubKeys[1]),
	signature: Uint8Array.from([0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, 0x8c, 0x5b, 0x64, 0xcd, 0x20, 0x89, 0x82, 0xaa, 0x38, 0xd4, 0x93, 0x66, 0x21, 0xa4, 0x77, 0x5a, 0xa2, 0x33, 0xaa, 0x05, 0x05, 0x71, 0x1d, 0x8f, 0xdc, 0xfd, 0xaa, 0x94, 0x3d, 0x49, 0x08]),
	verified: false, // non-canonical point should fail in the verificaton (RFC8032)
},
{
	id: "10", // 0 < S < L | A's order = small (non-canonical) | R's order = mixed | (1) = ? | (2) = ? Implementations that reduce A before hashing will accept #10 and accept #11, and viceversa
	message: Uint8Array.from([0xe9, 0x6b, 0x70, 0x21, 0xeb, 0x39, 0xc1, 0xa1, 0x63, 0xb6, 0xda, 0x4e, 0x30, 0x93, 0xdc, 0xd3, 0xf2, 0x13, 0x87, 0xda, 0x4c, 0xc4, 0x57, 0x2b, 0xe5, 0x88, 0xfa, 0xfa, 0xe2, 0x3c, 0x15, 0x5b]),
	keyData: Uint8Array.from(pubKeys[4]),
	signature: Uint8Array.from([0xa9, 0xd5, 0x52, 0x60, 0xf7, 0x65, 0x26, 0x1e, 0xb9, 0xb8, 0x4e, 0x10, 0x6f, 0x66, 0x5e, 0x00, 0xb8, 0x67, 0x28, 0x7a, 0x76, 0x19, 0x90, 0xd7, 0x13, 0x59, 0x63, 0xee, 0x0a, 0x7d, 0x59, 0xdc, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false, // non-canonical point should fail in the verificaton (RFC8032)
},
{
	id: "11", // 0 < S < L | A's order = small (non-canonical) | R's order = mixed | (1) = ? | (2) = ? Implementations that reduce A before hashing will accept #10 and accept #11, and viceversa
	message: Uint8Array.from([0x39, 0xa5, 0x91, 0xf5, 0x32, 0x1b, 0xbe, 0x07, 0xfd, 0x5a, 0x23, 0xdc, 0x2f, 0x39, 0xd0, 0x25, 0xd7, 0x45, 0x26, 0x61, 0x57, 0x46, 0x72, 0x7c, 0xee, 0xfd, 0x6e, 0x82, 0xae, 0x65, 0xc0, 0x6f]),
	keyData: Uint8Array.from(pubKeys[4]),
	signature: Uint8Array.from([0xa9, 0xd5, 0x52, 0x60, 0xf7, 0x65, 0x26, 0x1e, 0xb9, 0xb8, 0x4e, 0x10, 0x6f, 0x66, 0x5e, 0x00, 0xb8, 0x67, 0x28, 0x7a, 0x76, 0x19, 0x90, 0xd7, 0x13, 0x59, 0x63, 0xee, 0x0a, 0x7d, 0x59, 0xdc, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false, // non-canonical point should fail in the verificaton (RFC8032)
},
// https://eprint.iacr.org/2020/1244.pdf#section.A.2
// cases breaking non-repudiation
{
	id: "12", // 0 < S < L | A's order = small | R's order = mixed | (1) = ? | (2) = ?
	message: Uint8Array.from([0x53, 0x65, 0x6e, 0x64, 0x20, 0x31, 0x30, 0x30, 0x20, 0x55, 0x53, 0x44, 0x20, 0x74, 0x6f, 0x20, 0x41, 0x6c, 0x69, 0x63, 0x65]),
	keyData: Uint8Array.from(pubKeys[5]),
	signature: Uint8Array.from([0xa9, 0xd5, 0x52, 0x60, 0xf7, 0x65, 0x26, 0x1e, 0xb9, 0xb8, 0x4e, 0x10, 0x6f, 0x66, 0x5e, 0x00, 0xb8, 0x67, 0x28, 0x7a, 0x76, 0x19, 0x90, 0xd7, 0x13, 0x59, 0x63, 0xee, 0x0a, 0x7d, 0x59, 0xdc, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false,
},
{
	id: "13", // 0 < S < L | A's order = small | R's order = mixed | (1) = ? | (2) = ?
	message: Uint8Array.from([0x53, 0x65, 0x6e, 0x64, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x20, 0x55, 0x53, 0x44, 0x20, 0x74, 0x6f, 0x20, 0x41, 0x6c, 0x69, 0x63, 0x65]),
	keyData: Uint8Array.from(pubKeys[5]),
	signature: Uint8Array.from([0xa9, 0xd5, 0x52, 0x60, 0xf7, 0x65, 0x26, 0x1e, 0xb9, 0xb8, 0x4e, 0x10, 0x6f, 0x66, 0x5e, 0x00, 0xb8, 0x67, 0x28, 0x7a, 0x76, 0x19, 0x90, 0xd7, 0x13, 0x59, 0x63, 0xee, 0x0a, 0x7d, 0x59, 0xdc, 0xa5, 0xbb, 0x70, 0x47, 0x86, 0xbe, 0x79, 0xfc, 0x47, 0x6f, 0x91, 0xd3, 0xf3, 0xf8, 0x9b, 0x03, 0x98, 0x4d, 0x80, 0x68, 0xdc, 0xf1, 0xbb, 0x7d, 0xfc, 0x66, 0x37, 0xb4, 0x54, 0x50, 0xac, 0x04]),
	verified: false,
}
]

const algorithm = { name: "Ed25519" };

for (let i = 0; i < tests.length; i++) {
  const test = tests[i];
  const publicKey = await crypto.subtle.importKey("raw", test.keyData, algorithm, false, ["verify"]);
  const isVerified = await crypto.subtle.verify(algorithm, publicKey, test.signature, test.message);
  console.log(test.id, isVerified === test.verified);
}
(node:77667) ExperimentalWarning: The Ed25519 Web Crypto API algorithm is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
0 false
1 false
2 false
3 true
4 true
5 true
6 true
7 true
8 true
9 true
10 true
11 false
12 false
13 false

CC @nodejs/crypto

avivkeller avatar Aug 26 '24 19:08 avivkeller

To further complicate things when you acknowledge these failures as expected then in jenkins on s390x some of those vectors actually pass. But they're not flaky.

Our results are also not that different from other implementers, see wpt.fyi.

Normally I would just acknowledge these failures myself but because on jenkins s390x some of these failures pass and that we can't dynamically set the expectations, I left WebCryptoAPI WPTs not updated.

The Curve25519 secure curves part of the WICG spec is not yet merged into WebCryptoAPI so these are in theory still tentative. And it would seem the browser vendors are not willing to implement specific checks beyond what the crypto libraries do either.

panva avatar Aug 26 '24 20:08 panva

I'm happy to just skip these as expected failures, I just figured opening an issue and getting all the information would be better to do beforehand.

avivkeller avatar Aug 26 '24 20:08 avivkeller

You can't skip individual vectors, only the whole file, which is also not something we'd like to do.

panva avatar Aug 26 '24 20:08 panva

You can't skip individual vectors, only the whole file, which is also not something we'd like to do.

IIRC you can using the fail key.

For example,

  "getRandomValues.any.js": {
    "fail": {
      "note": "These types do not exist in Node.js",
      "expected": [
        "Float16 arrays",
        "Float arrays",
        "DataView"
      ]
    }

avivkeller avatar Aug 26 '24 20:08 avivkeller

https://github.com/nodejs/node/issues/54572#issuecomment-2311053407

Normally I would just acknowledge these failures myself but because on jenkins s390x some of these failures pass and that we can't dynamically set the expectations, I left WebCryptoAPI WPTs not updated.

You just didn't get to run full CI yet to encounter this. I did as soon as the WPTs landed.

panva avatar Aug 26 '24 20:08 panva

can i work on it or has it been fix

Official-ObiTech avatar Aug 27 '24 20:08 Official-ObiTech

can i work on it or has it been fix

You can try, but (AFAIK) we aren't even sure what the issue is. I suggest looking at issues marked good first issue.

avivkeller avatar Aug 27 '24 20:08 avivkeller

On Tue, 27 Aug 2024 at 21:34, Aviv Keller @.***> wrote:

can i work on it or has it been fix

You can try, but (AFAIK) we aren't even sure what the issue is. I suggest looking at issues marked good first issue.

— Reply to this email directly, view it on GitHub https://github.com/nodejs/node/issues/54572#issuecomment-2313466991, or unsubscribe https://github.com/notifications/unsubscribe-auth/BDII7FU4OWXW4ZEBBTDWL43ZTTPELAVCNFSM6AAAAABNEVUCHKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMJTGQ3DMOJZGE . You are receiving this because you commented.Message ID: @.***>

Sure I will

Official-ObiTech avatar Aug 27 '24 20:08 Official-ObiTech

To further complicate things when you acknowledge these failures as expected then in jenkins on s390x some of those vectors actually pass. But they're not flaky.

https://github.com/nodejs/node/blob/d5d1e80763202ffa73307213211148571deac27c/test/wpt/status/WebCryptoAPI.cjs#L30

I see these tests fail on s390 z13 while they succeed on s390 z15. On z13,

{
  "sign_verify/eddsa_small_order_points.https.any.js": {
    "fail": {
      "unexpected": [
        "Ed25519 Verification checks with small-order key of order - Test 0",
        "Ed25519 Verification checks with small-order key of order - Test 11"
      ],
      "expected": [
        "Ed25519 Verification checks with small-order key of order - Test 1",
        "Ed25519 Verification checks with small-order key of order - Test 2",
        "Ed25519 Verification checks with small-order key of order - Test 12",
        "Ed25519 Verification checks with small-order key of order - Test 13"
      ]
    }
  }
}

AdamMajer avatar Dec 02 '24 16:12 AdamMajer