Module: NwcRuby::Crypto::ECDH
- Defined in:
- lib/nwc_ruby/crypto/ecdh.rb
Overview
ECDH for Nostr: compute the shared secret between our private key and their x-only public key.
CRITICAL: Nostr ECDH returns the **X coordinate of the shared point only** (32 bytes). This differs from libsecp256k1’s default ‘ecdh()` function, which returns SHA256(compressed_point). We have to do the multiplication ourselves using the `ecdsa` gem.
This is used by both NIP-04 (as the AES key directly) and NIP-44 v2 (as the IKM for HKDF).
Constant Summary collapse
- GROUP =
::ECDSA::Group::Secp256k1
Class Method Summary collapse
-
.lift_x(x) ⇒ Object
BIP-340 “lift_x”: given an x coordinate, return the point with even Y.
-
.shared_x(privkey_hex, xonly_pubkey_hex) ⇒ String
Returns the raw 32-byte X coordinate of the shared point.
Class Method Details
.lift_x(x) ⇒ Object
BIP-340 “lift_x”: given an x coordinate, return the point with even Y.
41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/nwc_ruby/crypto/ecdh.rb', line 41 def lift_x(x) raise EncryptionError, 'x out of range' if x.zero? || x >= GROUP.field.prime p = GROUP.field.prime c = (x.pow(3, p) + 7) % p y = c.pow((p + 1) / 4, p) raise EncryptionError, 'x is not on the curve' unless y.pow(2, p) == c y = p - y if y.odd? GROUP.new_point([x, y]) end |
.shared_x(privkey_hex, xonly_pubkey_hex) ⇒ String
Returns the raw 32-byte X coordinate of the shared point.
28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/nwc_ruby/crypto/ecdh.rb', line 28 def shared_x(privkey_hex, xonly_pubkey_hex) Keys.validate_hex32!(privkey_hex, 'private key') Keys.validate_hex32!(xonly_pubkey_hex, 'public key') # BIP-340 x-only pubkeys always correspond to the even-Y lifted point. pubkey_point = lift_x(Keys.hex_to_bytes(xonly_pubkey_hex).unpack1('H*').to_i(16)) priv_int = privkey_hex.to_i(16) shared_point = pubkey_point.multiply_by_scalar(priv_int) [shared_point.x.to_s(16).rjust(64, '0')].pack('H*') end |