Skip to content

ECDH is underspecified and not x-only #65

@Sjors

Description

@Sjors

The spec requires two ECDH operations:

  1. between both ephemeral keys
  2. between initiator ephemeral key and responder (upstream) static key

But it doesn't specify exactly how to do this.

Both ephemeral and static public keys are serialised as x-only. Note that being x-only does not imply that the y-coordinate is even. Depending on which ECDH algorithm is followed, this can cause two sides to get a different cipher.

This is because you don't know if the other side has an even or odd public key, so you don't know if the resulting combined point should be even or odd, which leads to totally different hash (i.e. key).

The current Bitcoin Core Template Provider implementation treats the other side's x-only key as if it's a compressed key and assumes the y-coordinate coordinate is even (0x02 prefix). It also negates its own private key if its corresponding public key is odd.

I haven't looked at the SRI code yet, but I assume it does the same thing. This strategy works if both sides follow it.

Currently libsecp's ECDH module can only perform ECDH between a private key and a compressed public key. Part of its algorithm is to hash the resulting point. That hash includes whether the y-coordinate is even.

Option 1: clarify the spec with the above

(see also @jakubtrnka's comment)

The above procedure of negating the private key works. But it's not elegant. It's also ambiguous, because you could either do it at generation time, or only temporarily when performing ECDH. Note that for the signed certificate no negation is necessary, because the BIP340 scheme works with x-only keys.

Option 2: "clarify" the spec to use x-only ECDH

There is a pull request bitcoin-core/secp256k1#1198 to the libsecp libary that adds x-only ECDH. It uses a hash that does not cover the y-coordinate. This would remove the need for negating the private key.

Technically this would be a "clarification" of what "ECDH" means in the spec, but in reality it's a breaking change.

It also requires waiting for that PR to be updated, merged and released.

Option 3: change the spec to use EllSwift: #66

If we make a breaking change anyway, then we might as well use EllSwift like BIP324 does. It's x-only by design and has the extra benefit of making the handshake pseudo-random. Currently the ephemeral key exchange is not pseudo-random because only ~50% of possible 32 random byte sequences represent a valid public key. EllSwift fixes that.

The code for it is already merged, available in libsecp releases and afaik also in the rust bindings (haven't checked).

Option 4: change implementations to follow the current spec

(added on 2024-01-22, see comments below)

Implemented in stratum-mining/stratum#724


My current plan is to make a draft implementation of (3) on the Bitcoin Core side and propose a spec change. But I don't know yet if this is difficult on the SRI side. I also don't know what other components are already out there in the wild, how they currently interpret ECDH and easy it is to port them to use EllSwift.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions