-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add secp256k1 support #31
base: main
Are you sure you want to change the base?
add secp256k1 support #31
Conversation
ok I've found that SPKI does refer to Simple PKI but to https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo |
Ok so reading through this: https://www.rfc-editor.org/rfc/rfc5480 there does not seem to be room for secp256k1. I thought I could potentially bypass the SPKI code by first importing the key with the I'm not an expert at the nodejs internals, but it seems that the C code allows for secp256k1 export to JWK: https://github.com/nodejs/node/blame/9f6c12413cde5074893ffb378b8c3310275aa016/src/crypto/crypto_ec.cc#L809, but importing a secp256k1 key yields the following error:
Highlighting that lack of support. What was @kezike's idea on how to add support for secp256k1? |
Hi, I don't have a lot of cycles to respond here at the moment, but the use of SPKI is just an internal implementation detail (IIRC) -- and the public interfaces work with raw (compressed) keys, multibase-encoded multikeys, and JWKs. To my knowledge supporting P256K/secp256k1 in this library shouldn't be a problem, we just haven't gotten around to it. I would expect some library like It may be the case that we don't want to expose any of the secp256k1 keypairs using the CryptoKey interface (or that we'll have to polyfill it) -- so there may have to be a difference there for practicality purposes. Sorry for not having more time to look into this at the moment! A proposal / PR with something that seems workable enough, even if slightly different for secp256k1 might allow others to iterate and help get support in sooner. |
Hi @dlongley, thanks for your answer. Would you still be able to help me figure out where this piece of information: https://github.com/digitalbazaar/ecdsa-multikey/blob/main/lib/serialize.js#L51 comes from? That would help piece things together. |
I believe those are DER/ASN.1 prefixes for those keyTypes. They should match the values produced when exporting a key of that type in node using the key export interface https://nodejs.org/api/crypto.html#keyobjectexportoptions and the DER encoding not the PEM encoding (we usually prefer DER to PEM). The easiest way to figure out the prefix is generate a few keys, export them as DER and see what common prefix they have. p.s. there is a comment a few lines down that suggests I might be partially correct: ecdsa-multikey/lib/serialize.js Lines 323 to 326 in 41c5a49
|
Yes, the For more information on ECC SubjectPublicKeyInfo you can see: https://www.rfc-editor.org/rfc/rfc5480.html#section-2 Notably, ECDSA has two different raw formats, an uncompressed format and a compressed one, each starting with a header identifying which form is used (and if the compressed form is used, whether the The 'raw' format output by WebCrypto (today at least) is the uncompressed form, so it needs to be converted to the compressed form for expression as a multikey. This isn't a complicated operation, it just means detecting whether ECDSA raw => multikey: The Ed25519 public key is much simpler, there is just one raw format, so you just export to Note that for ECDSA, the SPKI format includes the compressed form of the key, making it easy to import a compressed public key into WebCrypto without having to perform any ECC math to uncompress the key. This is why our libraries import ECDSA keys from SPKI. To do so, you just need to inject the compressed key into what is essentially a SPKI DER template, as constructed by |
I was able to move forward with the public key (SPKI formatting and public key decompression) and I think I've managed it as expected, thanks to your comment as well as this one: https://stackoverflow.com/questions/67428299/how-to-do-ec-compression-on-a-public-key-in-python/67433909?noredirect=1#comment138519430_67433909. I've pushed the changes so you can take a look, at the end of it all I'll remove the trailing Now I'm facing the issue with the private key, trying to convert to PKCS 8 (https://github.com/digitalbazaar/ecdsa-multikey/blob/main/lib/serialize.js#L303). I'm not sure I should be using the same tool (https://lapo.it/asn1js/#) to get the same information. It looks like at least that the public key header is not consistent with the other headers defined already (https://github.com/digitalbazaar/ecdsa-multikey/blob/main/lib/serialize.js#L31), and I'm getting something for the private key but with no certainty that it is the expected value. Do you know how I could easily gather the expected information, or if not easily it does not matter I can also achieve it if I have some guidelines. Thanks |
Ok I'm getting closer but I need an external and experienced eye. Using this private key which I believe I converted to PKCS#8 using openssl:
And opening it in ans1js parser: https://asn1js.eu/#MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgTECmkTCUcLx6ps6peq4jRYSx2kwzF1LlTmTDIqXS1CahRANCAAQTBU1DyMMTbUkwlulXLS1AvynKZvFpbCtpaF2_aEV0LZhcTgyOxSKWK8H2_wIx9X9l_pFtcqLHSXazFNleNhM9 I get the following ASN1 object:
Which translates to the following HEX value (in bold are the private key/public key info):
Now, as the site handily allows to read the different blocks of data, I took the following sequence as the private key prefix:
And for the public key prefix: which gives me the following uint8array data:
Which altogether looks acceptable from my novice eye. But when I try to execute the signature of ECDSA-SD-2023 with this key, I get the following error:
I've tried investigating into the parser but I'm not exactly sure what's happening, here is the debug output I'm getting
That's coming from this file https://github.com/PeculiarVentures/ASN1.js/blob/master/src/Constructed.ts#L25 (as published into the npm package, so build.js line 967). It's taking Anything obvious to you guys? |
@dlongley I was finally able to get to sign and verify with secp256k1. what I was stuck on was the length of the global key, apparently the webcrypto polyfill only accepts uncompressed public keys so I needed to adjust the header accordingly. At this time I am dynamically importing the webcrypto polyfill wherever it is required. I think it is generally cleaner as it is easily identifiable, rather than requiring on the fly somewhere else. From a bundling perspective it also means the polyfill is loaded side by side so not polluting a bundle when not needed. I have added tests based on the ones already in place, they all pass. I am also using this in the context of signing/verifying ECDSA-SD-2023, for which I had to allow specifying a "requiredAlgorithm" to match secp256k1 (I have settled on using K-256 as it is the one understood by the webcrypto polyfill). All in all this is ready for review and publishing. |
// ensure compatibility with https://github.com/PeculiarVentures/webcrypto | ||
secp256k1: 'K-256' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm not sure whether 'K-256'
or 'secp256k1'
is what is commonly used (as the string value), e.g., in JWKs. This is fine if it's the commonly used value, but we don't want to pick something just for compatibility with a particular library. We might need some additional translation code, I don't know. Just need a bit more research / link to RFCs, common libs (like panva's node.js jose lib) or some other docs in support of this approach. It's fine (but unfortunate) if we have to support two different expressions of this curve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I haven't seen K-256
before and I initially named it secp256k1
.
That naming convention will be important for future usage, I'll see what I can find about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so I couldn't find a clear nomenclature definition about this.
However, section 2.1 of this file https://www.secg.org/sec2-v2.pdf does name k
for Koblitz curve, and this paragraph has quite a sensible explanation about why it could be named K-256
: https://lib.rs/crates/k256#readme-about-secp256k1-k-256.
But it seems the more common way is to call it secp256k1
, but it's also not consistent with calling secp256r1
P-256
.
I'll let you be the judge as this is ultimately your package, my inclination is to keep things simple with K-256
support only (and document it) as it is a valid and consistent naming, although a bit uncommon at this point in time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the NIST document about sanctioned algorithm, the nomenclature is indeed as detailed in the rust package's paragraph: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf (P, K, B).
Appendix H.2 mentions secp256k1
without explicitly naming it K-256.
return new Uint8Array(await webcrypto.subtle.sign( | ||
algorithm, secretKey, data)); | ||
if(curve === ECDSA_CURVE.secp256k1) { | ||
const {Crypto} = await import('@peculiar/webcrypto'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will have to figure something else out here, I don't expect us to pull in this whole library nor list it as a dependency. I'm not sure what static analysis will do here either, even though this is "just in time" / dynamic imported. We have to think of this multikey library being included in places where the additional @peculiar/webcrypto
dependency isn't needed and the security/auditing overhead/issues it creates. cc: @mattcollier @davidlehn
Instead, we could possibly allow for some API (interface) injection via a param where the key is imported/generated or an additional optional registration (aka use()
) API as setup. Either that or just require the caller to have polyfilled this and provide some docs on how to do it. A proper polyfill installation would also remove all of the extra optional and duplicated code below (I'd hope).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I agree,
However it has been stated that there is no intent to support secp256k1 in the webcrypto
node implementation (not sure if I can find that conversation again).
DI would be good as a per customer basis, but the thing is it's not just about importing the library, it's also consuming it instead of the native webcrypto
. I think there would be a bigger vulnerability risk by allowing injection of a webcrypto polyfill (which could overwrite any curve) than by fully controlling where, when and how it's used by a dynamic require.
Dynamic import in my experience is quite smart about being there but only used as needed, so not polluting other cases.
Co-authored-by: Dave Longley <[email protected]>
Co-authored-by: Dave Longley <[email protected]>
Still trying to get back to this, too many other items on the plate at the moment to look closely and get this integrated. I do want to note that even once this is incorporated, its use with data integrity cryptosuites will be non-standard, since those focus on providing cryptosuites that are always NIST-approved for ECDSA (I will note that there is now a somewhat strange NIST carve-out for secp256k1 "but for blockchain applications only" that is not easy to deal with). Regardless, non-standard use is still acceptable use for a lot of people, we just need to make sure software can be configured to tell the difference. |
FWIW, with a multikey representation made by this package (and PR), I was able to create, sign and verify a did:tdw using @noble/secp256k1, so it looks like the computations are correct. |
|
@davidlehn done |
I have had some issues with the Thus, I am currently trying to replace the However the API is not the same, especially on the signature output, so I'm trying to reconcile it with the Would you mind taking a look at the problem stated there: paulmillr/noble-secp256k1#125 to see if:
Thanks |
+1 for the answer to B) that was given in the linked issue. |
Hey guys,
due to key management in the broader system I integrate with, I need to have secp256k1 support for multibase format.
I have started some work and this far I've managed to get a JWK serialized to multibase (both public and private key. I was able to confirm the public key multibase decodes back to the expected value in JWK with this library (https://github.com/public-square/jwk-multibase-key-converter-js), I couldn't test with the private key as it does not handle the
d
value (https://github.com/public-square/jwk-multibase-key-converter-js/blob/main/src/converters/ES256K.ts#L67).However now as I'm trying to sign with ECDSA-SD-2023 and my secp256k1 multibase key, I am facing some expected hurdles, first of which is the lack of prefix (https://github.com/digitalbazaar/ecdsa-multikey/blob/main/lib/serialize.js#L51):
I tried researching but I couldn't find something that resembles this information online, so that I could add it and move on to the next hurdle.
Could you explain to me what is the purpose of using SPKI here? And where I could find the expected value?
I'm not a trained cryptographer, so I'm mostly going forward with trial and error, but would be happy to contribute my work back into this library, and I'm hoping you could guide me towards a satisfactory point for both of us.