Reputation: 262
I'm attempting to re-create a P-521 JWK from just the private key.
According to the spec (section 4.2.1):
"The x member contains the x coordinate for the elliptic curve point. It is represented as the base64url encoding of the coordinate's big endian representation."
I've attempted to base64url-decode the private key, and scalar-multiply it with the P-521 base point to get the public key point. I've then attempted to convert the x and y co-ordinates into a big-endian octet representation, and encode that back to base64url format. The results do not match the original x and y base64url-encoded fields in the JWK.
I've also tried this with the P-256 curve, and that does not work either.
const elliptic = require('elliptic');
const EC = elliptic.ec;
const {base16, base64url} = require('rfc4648');
const BN = require("bn.js");
const padBase16ToWholeOctets = s => s.length%2===0 ? s : '0'+s;
const bnToB64 = n => base64url.stringify(base16.parse(padBase16ToWholeOctets(n.toString(16))))
console.log('begin'); // forces console output to show from async IIFE
(async () => {
let keyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-521" }, true, ['sign'])
let jwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey)
console.log(jwk)
const dHex = base16.stringify(base64url.parse(jwk.d, { loose: true }))
const ec = new EC('p521')
const [x,y] = ec.curve.g.mul(new BN(dHex, 16, 'be')).toJSON()
console.log(`expected x: ` + jwk.x)
console.log(`actual x: ` + bnToB64(x))
console.log(`expected y: ` + jwk.y)
console.log(`actual y: ` + bnToB64(y))
})();
Upvotes: 1
Views: 312
Reputation: 49341
The data generated with the WebCrypto API is correct. Your different data is caused by two problems during calculation:
getX()
and getY()
.The first problem can be fixed with:
const point = ec.curve.g.mul(new BN(dHex, 16, 'be'))
console.log(`actual x: ` + bnToB64(point.getX()))
console.log(`actual y: ` + bnToB64(point.getY()))
the second with:
const bnToB64 = n => base64url.stringify(base16.parse(padBase16ToWholeOctets(n.toString(16)).padStart(132, '0')))
Upvotes: 1