Skip to content
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

NUT-11 uses compressed pubkeys where xonly is called for #133

Open
conduition opened this issue Jun 4, 2024 · 2 comments
Open

NUT-11 uses compressed pubkeys where xonly is called for #133

conduition opened this issue Jun 4, 2024 · 2 comments

Comments

@conduition
Copy link

NUT-11 says:

The recipient who owns the private key of the public key Secret.data can spend this proof by providing a signature on the serialized Proof.secret string that is then added to Proof.witness.signatures

We use libsecp256k1's serialized 64 byte Schnorr signatures on the SHA256 hash of the message to sign.

However, in the examples, the pubkey specified in the Secret.data field is a 33-byte compressed key. The schnorr signature verification function of libsecp256k1, as with any BIP340 implementation in general, requires a 32-byte x-only public key.

I suggest either:

  • We modify the spec to use x-only public keys for the P2PK Secret.data field
  • We specify explicitly that NUT-11 public keys must always be even parity (leading 02 byte)
  • We specify explicitly that NUT-11 public keys can be either parity, and implementations must chop off the leading byte before verifying signatures.
@callebtc
Copy link
Contributor

callebtc commented Jun 4, 2024

It seems odd to me that your secp implementation requires an x-only key. The libsecp256k1 Schnorr signature verification in the Python interface does not assume any parity and requires the public key to be either 33 or 65 bytes.

Could you confirm that your lib is not able to verify signatures with an odd parity (03)?

@conduition
Copy link
Author

I'm not using any specific library for anything at the moment. The BIP-340 specification - which libsecp256k1's schnorr signatures are based on - takes an xonly key as input to the verification algorithm:

screenshot

libsecp256k1 specifically takes an xonly key as input when verifying schnorr sigs.

I'm assuming you're using the secp256k1 package from PyPi? That library only allows schnorr verification after parsing an xonly key as a point, but internally it passes the xonly representation of the key to libsecp256k1, so the parity bit is not necessary information.

Some libraries do this same thing, accepting a Point-like type:

Others like libsecp256k1 expose a separate type for xonly keys or simply require an untyped 32-byte array as input. References:

Verification works regardless of the points' parity, because fundamentally BIP340 verification only uses the X-coordinate of the pubkey and signature nonce as input.

My point is that in the cashu spec, we should reference upstream specs (BIP340), not upstream implementations (secp256k1-py). The input we're providing to BIP340 signature verification in NUT-11 doesn't match the upstream spec. It leaves the cashu implementation to decide how to fix the ambiguity.

Speaking of ambiguity: A signature issued by 02xxxx... will also verify under the pubkey 03xxxx.... I don't think this is a problem for NUT-11, but for future NUTs which use NUT-11 as precedent, it could be.

If you want to keep the status quo without changing the spec too much, i'd suggest we go this route.

We specify explicitly that NUT-11 public keys can be either parity, and implementations must chop off the leading byte before verifying signatures.

But going forward with new NUTs if we use BIP340 signatures again, we should use the spec as intended, and use xonly pubkeys to avoid potential unforeseen bugs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants