Module: NwcRuby::Crypto::Keys

Defined in:
lib/nwc_ruby/crypto/keys.rb

Overview

Key helpers: hex <-> bytes, private key -> x-only pubkey, validation.

Nostr uses BIP-340 Schnorr signatures over secp256k1. Public keys are “x-only” — just the 32-byte X coordinate. We use rbsecp256k1 (which wraps libsecp256k1) to derive them correctly.

Class Method Summary collapse

Class Method Details

.bytes_to_hex(bytes) ⇒ Object



35
36
37
# File 'lib/nwc_ruby/crypto/keys.rb', line 35

def bytes_to_hex(bytes)
  bytes.unpack1('H*')
end

.generate_private_keyObject

Generate a new 32-byte private key as a lowercase hex string.



16
17
18
# File 'lib/nwc_ruby/crypto/keys.rb', line 16

def generate_private_key
  SecureRandom.bytes(32).unpack1('H*')
end

.hex_to_bytes(hex) ⇒ Object



31
32
33
# File 'lib/nwc_ruby/crypto/keys.rb', line 31

def hex_to_bytes(hex)
  [hex].pack('H*')
end

.public_key_from_private(privkey_hex) ⇒ Object

Derive the 32-byte x-only public key (hex) from a private key (hex).



21
22
23
24
25
26
27
28
29
# File 'lib/nwc_ruby/crypto/keys.rb', line 21

def public_key_from_private(privkey_hex)
  validate_hex32!(privkey_hex, 'private key')
  ctx = ::Secp256k1::Context.create
  kp  = ctx.key_pair_from_private_key(hex_to_bytes(privkey_hex))
  # rbsecp256k1 gives us a compressed public key (33 bytes, 02/03 prefix).
  # X-only pubkey is just bytes 1..32.
  compressed = kp.public_key.compressed
  bytes_to_hex(compressed[1, 32])
end

.validate_hex32!(hex, label = 'value') ⇒ Object



39
40
41
42
43
# File 'lib/nwc_ruby/crypto/keys.rb', line 39

def validate_hex32!(hex, label = 'value')
  return if hex.is_a?(String) && hex.match?(/\A[0-9a-fA-F]{64}\z/)

  raise InvalidConnectionStringError, "#{label} must be 64 hex characters (32 bytes)"
end