Data Integrity BIP340 Cryptosuite
New Work Item Proposal
See W3C-CCG New Work Item Process
Include Link to Abstract or Draft
https://dcdpr.github.io/data-integrity-schnorr-secp256k1/
List Owners
Lead: Will Abramson @wip-abramson (Legendary Requirements / Digital Contract Design) Co-ownder: Markus Sabadello @peacekeeper (Danube Tech)
Work Item Questions
Answer the following questions in order to document how you are meeting the requirements for a new work item at the W3C Credentials Community Group. Please note if this work item supports the Silicon Valley Innovation program or another government or private sector project.
- Explain what you are trying to do using no jargon or acronyms.
We are defining a new Data Integrity cryptosuite that uses BIP340 Schnorr Signatures over the secp256k1 elliptic curve to produce and verify proofs.
- How is it done today, and what are the limits of the current practice?
There is no existing Data Integrity cryptosuite for BIP340 signatures.
- What is new in your approach and why do you think it will be successful?
Many people use secp256k1 and BIP340 schnorr signatures already. There are multiple implementations and tooling. This would enable blockchain people, especially those in the Bitcoin space who already control secp256k1 keys to create and verify Data Integrity proofs without having to change the cryptographic primitives they use.
- How are you involving participants from multiple skill sets and global locations in this work item? (Skill sets: technical, design, product, marketing, anthropological, and UX. Global locations: the Americas, APAC, Europe, Middle East.)
We have people working on this in North America and Europe. It is mostly a highly technical project so that is where our skill sets are primarily focused.
- What actions are you taking to make this work item accessible to a non-technical audience?
We have developed a set of Jupyter notebooks that walk through the cryptosuite steps. These can be found here: https://github.com/dcdpr/did-btc1/tree/main/notebooks/SchnorrSecp256k12024. As part of this Work Item we will update and refine this educational material.
Actions if Adopted:
- Migrate the repository into the CCG. Repo name should be
data-integrity-bip340
I support the adoption of the BIP340 Cryptosuite
I also support the adoption of this cryptosuite.
Likewise. I support the adoption of this work item.
I support adoption of this work item.
I support this new work item.
Nostr CG Chair here
-1 (details below)
Full rationale already on the CCG mailing list:
https://lists.w3.org/Archives/Public/public-credentials/2025Aug/0014.html
- Existing W3C work already covers BIP-340 with raw-hex
-
The W3C Nostr Community Group maintains the draft DID method
did:nostr:— https://github.com/nostrcg/did-nostr -
That draft (plus running wallets/relays) uses the 64-char lowercase BIP-340 pubkey as-is:
did:nostr:abcd…
If this cryptosuite only allows Multikey, we will have two W3C specs for the same key that cannot interoperate:
| Layer | Draft | Key format |
|---|---|---|
| DID method | did:nostr: (CG) |
raw hex |
| DI cryptosuite | this proposal | Multikey |
- Scale and network effect
- Tens of millions of keys already published in hex across Bitcoin & Nostr (millions of users).
- Hex strings live in DBs, QR codes, PSBTs, invoices, message IDs, etc.
- Multibase is great when the curve is unknown, but here it’s fixed
(secp256k1 / BIP-340).
Adding afe701…prefix silently breaks byte-for-byte matches and forces every existing tool to strip prefixes.
- Minimal path to interop
Keep Multikey and add one sibling encoding that mirrors the DID draft:
{
"type": "publicKeyHex", // name bikesheddable :)
"curve": "secp256k1",
"publicKeyHex": "abcd..."
}
Verifiers MUST accept either encoding.
Signers choose whichever matches their stack.
Immediate interoperability; no ecosystem fork.
- Two specs are already forming
At least two devs are ready to add VC support to did:nostr:. If raw-hex isn’t recognised here, we’ll end up with two incompatible BIP-340 specs (VC WG vs. Nostr CG). That seems avoidable.
Happy to PR text for publicKeyHex (or similar name) if there’s agreement.
Until then, I must object to adoption in its current form, or we agree to have two similar CG specs, one for what's deployed today in bitcoin/nostr, and another one for CCG compat.
TL; DR: I think we can be flexible here to be supportive of an existing community, but the proposal above doesn't seem like the optimal way to do that.
I would want to make sure there's actually a net gain here. From the proposal above, I get the impression that a JSON format that uses publicKeyHex that can be expressed in CID/DID documents is not already in use, but rather, hex-encoded public keys are being shipped around in various places inside URLs, etc. Additionally, there's some expression in a DID document here: https://github.com/nostrcg/did-nostr -- that is different from the publicKeyHex proposal. If I'm not mistaken, what is proposed above would mean we're talking about inventing yet another format that both isn't quite what Nostr is using now and isn't multikey.
I don't think we should do that. If we have to spec some "here's how you can translate existing (in wide use) key expression for 'Nostr-keys' to multikey" with a note that you MUST support the legacy Nostr-key format, that's probably doable somehow. That's not too different from what was done with the legacy Ed25519VerificationKey2020.
From an implementer's perspective, there is little difference in importing/exporting a public key type by using new JSON properties/values to express it vs. using existing JSON properties and a new value with a fe701 prefix. It's new code I'll have to write either way to map to whatever I'm already doing. As a developer, if I have to support multiple ways to express a key, I'm going to write some translation layer to normalize to whatever format is most useful to me or my library's users.
If I'm interfacing with hex-encoded public key values in Nostr, I'm going to, for example, expose an API that will take "whatever key format" as an input and output a string with a hex-encoded public key (and vice versa). Maybe I'll input/output a full Nostr DID URL too. What's important is that it will be easier to implement the internals of this API if I can just copy+paste the hex string where I need to, i.e., I don't need some library to parse the key material into EC coordinates and do other mathematical translations.
So, if I have to write some code to work with some new JSON expression of public keys, what I care about is whether I can just copy+paste existing hex strings; everything else is a detail in some new code I have to write anyway. New software will either:
- read a value directly from a new JSON field
publicKeyHex(or whatever it gets named) and check some other new values: a "type" ofpublicKeyHex(or whatever it gets named) and a "curve" ofsecp256k1, or ... - read a value from an existing JSON field
publicKeyMultibase, check for afe701prefix, and then slice off that prefix and use the remainder as the hex public key value.
Now, suppose we say that consumers/verifiers must accept both of these JSON key expressions (as the above proposal suggests -- and I expect that would be the only amenable offering).
If option 1 isn't already widely implemented by the Nostr community, everyone must implement option 1, where the only savings is in being able to copy+paste hex-encoded public key values. With option 2, there is savings for existing multikey implementers: they only need to support the additional f and e701 prefixes; the Nostr community gets savings in being able to copy+paste hex-encoded public key values, just in a different spot from option 1.
From what I can tell, the savings and the costs for a Nostr developer are equivalent for both options -- they just have double the work to do. If both options are required then the burden is simply higher for everyone.
Why, specifically, as a developer from the Nostr community, would I prefer to implement both of these over just implementing option 2? Why (as the same developer) would I strongly prefer option 1 over option 2 anyway? Lastly, (as the same developer) wouldn't I rather just implement option 2 and get to keep whatever I'm currently doing as well?
Something else to note: One of the advantages of the multikey approach is to include the encoding and key type identifiers all in a single string. The "broken out" or "parameterized" approach is something that JWK already does. A new, third format that does something in between these two approaches seems hard to justify. Including special spec text for handling a widely used format (whatever it may be) to bridge that community to others seems like a much easier lift.
Thanks, @dlongley that clarifies things. I’ll dig into the verifier-side prepend approach, but on first read it looks like a workable compromise as long as the did:nostr spec can keep its existing canonical raw-hex form. I’ll follow up after a closer look.
fande701
So I think we can do this.
I think there's one issue outstanding though. And that is that taproot and schnorr use 32 byte public keys. Whereas the the spec is using 33 byte (compressed format). Taproot/schnorr and compressed bitcoin pubkeys are different things. And nostr uses the 32 byte form. It is impossible to tell 02 or 03 from the pubkey. I dont know how the spec will be able to handle this.
As long as this is compatible and we have a consistent name for schnorr signature algorithm in 2025, then I think things can interop.
As you say, the 0xe7 code (encoded as 0xe701 as a varint) entry in the multikey table is for a compressed secp256k1 public key (i.e., 33 bytes w/1 byte for compression header + 32 byte for key material): https://github.com/multiformats/multicodec/blob/master/table.csv?plain=1#L93C42-L93C55
If another key type is needed (and it sounds like it), you can just file a PR on the multicode table repo to add another one for this other 32 byte form. Then the multikey prefix part will not be e701 but something else for that key type -- and the keys will be easily distinguishable on that basis. That should work fine, I would think. The f will remain the same regardless because it just specifies that the base encoding is hex.
Thanks @dlongley for clearly articulating the trade offs involved here and a path forwards. You said what I was trying to get at much clearer than I could. I appreciate you commenting on the issue aswell!
@melvincarvalho regarding the use of the compressed 33 byte key. This was discussed extensively by those of use working on the spec - https://github.com/dcdpr/data-integrity-schnorr-secp256k1/issues/7.
Initially, I was thinking to propose a new multikey header for the 32 byte representation. However, after some investigation it seemed simpler to just support the 33 byte representation with the existing, understood header.
There is a simple translation from a 33 byte key to a 32 byte key. You simply drop the first byte. e.g. 32_byte_key = 33_byte_key[1:].
In fact if you look at the BIP340 specification - https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki - and search for implict Y coordinates they state:
Implicit Y coordinates In order to support efficient verification and batch verification, the Y coordinate of P and of R cannot be ambiguous (every valid X coordinate has two possible Y coordinates). We have a choice between several options for symmetry breaking:
- Implicitly choosing the Y coordinate that is in the lower half.
- Implicitly choosing the Y coordinate that is even[6]. Implicitly choosing the Y coordinate that is a quadratic residue (i.e. has a square root modulo p).
The second option offers the greatest compatibility with existing key generation systems, where the standard 33-byte compressed public key format consists of a byte indicating the oddness of the Y coordinate, plus the full X coordinate. To avoid gratuitous incompatibilities, we pick that option for P, and thus our X-only public keys become equivalent to a compressed public key that is the X-only key prefixed by the byte 0x02. For consistency, the same is done for R[7].
This is what we do in the spec. If you have a 32 byte x-only key, you simply prepend a 0x02 implicitly choosing the Y coordinate as even.
Thanks @wip-abramson @dlongley
Let's say for a moment we can assume nostr keys are 02 for the purposes of the verification method. How could we write a compliant verification method in JSON?
"verificationMethod": [
{
"id": "did:nostr:a72de5b729c6ecadc3a4c474c62b0aaa25a7a0166d6d57ed94e074de6f27f738#key1",
"type": "Multikey",
"controller": "did:nostr:a72de5b729c6ecadc3a4c474c62b0aaa25a7a0166d6d57ed94e074de6f27f738",
"publicKeyMultibase": "fe701 02 a72de5b729c6ecadc3a4c474c62b0aaa25a7a0166d6d57ed94e074de6f27f738"
}
]
Spaces for emphasis
Would this be correct and spec compliant?
This looks good to me @melvincarvalho
@melvincarvalho,
Yeah, that looks right to me too.
Thanks @dlongley @wip-abramson I have already patched the spec. I've spoken to our devs, and we'll implement this verification method in our libraries.