import * as assert from 'assert';
const { subtle } = globalThis.crypto;
const keyData = {
'P-256': {
uncompressed: Buffer.from([
48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149, 112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53, 131, 97, 232,
]),
compressed: Buffer.from([
48, 57, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 34, 0, 2, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93, 209,
]),
},
'P-384': {
uncompressed: Buffer.from([
48, 118, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, 34, 3, 98, 0, 4, 33, 156, 20, 214, 102, 23, 179, 110, 198, 216, 133, 107, 56, 91, 115, 167, 77, 52, 79, 216, 174, 117, 239, 4, 100, 53, 221, 165, 78, 59, 68, 189, 95, 189, 235, 209, 208, 141, 214, 158, 45, 125, 193, 220, 33, 140, 180, 53, 189, 40, 19, 140, 199, 120, 51, 122, 132, 47, 107, 214, 27, 36, 14, 116, 36, 159, 36, 102, 124, 42, 88, 16, 167, 107, 252, 40, 224, 51, 95, 136, 166, 80, 29, 236, 1, 151, 109, 168, 90, 251, 0, 134, 156, 182, 172, 232,
]),
compressed: Buffer.from([
48, 70, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, 34, 3, 50, 0, 2, 33, 156, 20, 214, 102, 23, 179, 110, 198, 216, 133, 107, 56, 91, 115, 167, 77, 52, 79, 216, 174, 117, 239, 4, 100, 53, 221, 165, 78, 59, 68, 189, 95, 189, 235, 209, 208, 141, 214, 158, 45, 125, 193, 220, 33, 140, 180, 53,
]),
},
'P-521': {
uncompressed: Buffer.from([
48, 129, 155, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, 35, 3, 129, 134, 0, 4, 1, 86, 244, 121, 248, 223, 30, 32, 167, 255, 192, 76, 228, 32, 195, 225, 84, 174, 37, 25, 150, 190, 228, 47, 3, 75, 132, 212, 27, 116, 63, 52, 228, 95, 49, 27, 129, 58, 156, 222, 200, 205, 165, 155, 187, 189, 49, 212, 96, 179, 41, 37, 33, 231, 193, 183, 34, 229, 102, 124, 3, 219, 47, 174, 117, 63, 1, 80, 23, 54, 207, 226, 71, 57, 67, 32, 216, 228, 175, 194, 253, 57, 181, 169, 51, 16, 97, 184, 30, 34, 65, 40, 43, 158, 23, 137, 24, 34, 181, 183, 158, 5, 47, 69, 151, 181, 150, 67, 253, 57, 55, 156, 81, 189, 81, 37, 196, 244, 139, 195, 240, 37, 206, 60, 211, 105, 83, 40, 108, 203, 56, 251,
]),
compressed: Buffer.from([
48, 88, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, 35, 3, 68, 0, 3, 1, 86, 244, 121, 248, 223, 30, 32, 167, 255, 192, 76, 228, 32, 195, 225, 84, 174, 37, 25, 150, 190, 228, 47, 3, 75, 132, 212, 27, 116, 63, 52, 228, 95, 49, 27, 129, 58, 156, 222, 200, 205, 165, 155, 187, 189, 49, 212, 96, 179, 41, 37, 33, 231, 193, 183, 34, 229, 102, 124, 3, 219, 47, 174, 117, 63,
]),
},
};
const curves = Object.keys(keyData);
const testVectors = [
{
name: 'ECDSA',
privateUsages: ['sign'],
publicUsages: ['verify'],
},
{
name: 'ECDH',
privateUsages: ['deriveKey', 'deriveBits'],
publicUsages: [],
},
];
async function testRoundTripSpki({ name, publicUsages }, namedCurve) {
const { compressed, uncompressed } = keyData[namedCurve];
const key = await subtle.importKey(
'spki',
compressed,
{ name, namedCurve },
true,
publicUsages
);
const spki = await subtle.exportKey('spki', key);
assert.deepStrictEqual(
Buffer.from(spki).toString('hex'),
uncompressed.toString('hex')
);
}
async function testRoundTripRaw({ name, publicUsages }, namedCurve) {
let { compressed, uncompressed } = keyData[namedCurve];
let i = uncompressed.indexOf(Buffer.from([0x00, 0x04])) + 1;
uncompressed = uncompressed.subarray(i);
i =
compressed.indexOf(Buffer.from([0x00, 0x02])) + 1 ||
compressed.indexOf(Buffer.from([0x00, 0x03])) + 1;
compressed = compressed.subarray(i);
const key = await subtle.importKey(
'raw',
compressed,
{ name, namedCurve },
true,
publicUsages
);
const raw = await subtle.exportKey('raw', key);
assert.strictEqual(
Buffer.from(raw).toString('hex'),
uncompressed.toString('hex')
);
}
for (const namedCurve of curves) {
for (const vector of testVectors) {
await testRoundTripSpki(vector, namedCurve).then(
() =>
console.log(namedCurve, 'spki'.padEnd(4), vector.name.padEnd(5), '✅'),
() =>
console.log(namedCurve, 'spki'.padEnd(4), vector.name.padEnd(5), '❌')
);
await testRoundTripRaw(vector, namedCurve).then(
() =>
console.log(namedCurve, 'raw'.padEnd(4), vector.name.padEnd(5), '✅'),
() =>
console.log(namedCurve, 'raw'.padEnd(4), vector.name.padEnd(5), '❌')
);
console.log('\n');
}
}
Our WebCryptoAPI implementation supports import of compressed point format EC keys in both
raw, andspkiforms. That is an optional to implement part of the API.The specification however requires that when keys are exported they unconditionally use the uncompressed point format. This is currently not the case in Node.js when compressed point
spkiwas used to import the CryptoKey.The following script demonstrates the issue.
Script
I'm opening this issue so that I have an issue tracker link to add to the WPT status file once it gets updated.