Class: BSV::Primitives::PublicKey

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/primitives/public_key.rb

Overview

A secp256k1 public key for address derivation and signature verification.

Public keys are points on the secp256k1 curve. They can be serialised in compressed (33-byte) or uncompressed (65-byte) form, converted to Bitcoin addresses, and used to verify ECDSA signatures.

Examples:

Derive address from a public key

pub = private_key.public_key
pub.address #=> "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"

Constant Summary collapse

MAINNET_PUBKEY_HASH =

Address version byte for mainnet P2PKH addresses.

"\x00".b
TESTNET_PUBKEY_HASH =

Address version byte for testnet P2PKH addresses.

"\x6f".b

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(point) ⇒ PublicKey

Returns a new instance of PublicKey.

Parameters:

Raises:

  • (ArgumentError)

    if point is not an EC point or is at infinity



28
29
30
31
32
33
# File 'lib/bsv/primitives/public_key.rb', line 28

def initialize(point)
  raise ArgumentError, 'point must be an EC point' unless point.is_a?(OpenSSL::PKey::EC::Point)
  raise ArgumentError, 'point is at infinity' if point.infinity?

  @point = point
end

Instance Attribute Details

#pointOpenSSL::PKey::EC::Point (readonly)

Returns the underlying curve point.

Returns:



24
25
26
# File 'lib/bsv/primitives/public_key.rb', line 24

def point
  @point
end

Class Method Details

.from_bytes(bytes) ⇒ PublicKey

Create a public key from raw bytes (compressed or uncompressed).

Parameters:

  • bytes (String)

    33-byte compressed or 65-byte uncompressed encoding

Returns:



39
40
41
42
# File 'lib/bsv/primitives/public_key.rb', line 39

def self.from_bytes(bytes)
  point = Curve.point_from_bytes(bytes)
  new(point)
end

.from_hex(hex) ⇒ PublicKey

Create a public key from a hex string.

Parameters:

  • hex (String)

    hex-encoded compressed or uncompressed public key

Returns:



48
49
50
# File 'lib/bsv/primitives/public_key.rb', line 48

def self.from_hex(hex)
  from_bytes(Hex.decode(hex, name: 'public key hex'))
end

.from_private_key(private_key) ⇒ PublicKey

Derive the public key from a BSV::Primitives::PrivateKey.

Parameters:

Returns:



56
57
58
# File 'lib/bsv/primitives/public_key.rb', line 56

def self.from_private_key(private_key)
  private_key.public_key
end

Instance Method Details

#==(other) ⇒ Boolean

Returns true if both keys represent the same curve point.

Parameters:

  • other (Object)

    the object to compare

Returns:

  • (Boolean)

    true if both keys represent the same curve point



152
153
154
# File 'lib/bsv/primitives/public_key.rb', line 152

def ==(other)
  other.is_a?(PublicKey) && compressed == other.compressed
end

#address(network: :mainnet) ⇒ String

Derive a Base58Check-encoded Bitcoin address.

Parameters:

  • network (Symbol) (defaults to: :mainnet)

    :mainnet or :testnet

Returns:

  • (String)

    the P2PKH address



97
98
99
100
# File 'lib/bsv/primitives/public_key.rb', line 97

def address(network: :mainnet)
  prefix = network == :mainnet ? MAINNET_PUBKEY_HASH : TESTNET_PUBKEY_HASH
  Base58.check_encode(prefix + hash160)
end

#compressedString

Return the compressed (33-byte) encoding.

Returns:

  • (String)

    compressed public key bytes



63
64
65
# File 'lib/bsv/primitives/public_key.rb', line 63

def compressed
  @point.to_octet_string(:compressed)
end

#derive_child(private_key, invoice_number) ⇒ PublicKey

Derive a child public key using BRC-42 key derivation.

Computes HMAC-SHA256(key: ECDH_shared_secret, msg: invoice_number) and adds the corresponding curve point to this public key. The result matches the public key of BSV::Primitives::PrivateKey#derive_child with the same inputs, enabling public-key-only derivation.

Parameters:

  • private_key (PrivateKey)

    the counterparty’s private key

  • invoice_number (String)

    the invoice number (UTF-8)

Returns:

  • (PublicKey)

    the derived child public key



132
133
134
135
136
137
138
139
# File 'lib/bsv/primitives/public_key.rb', line 132

def derive_child(private_key, invoice_number)
  shared = derive_shared_secret(private_key)
  hmac = Digest.hmac_sha256(shared.compressed, invoice_number.encode('UTF-8'))
  hmac_bn = OpenSSL::BN.new(hmac.unpack1('H*'), 16)
  hmac_point = Curve.multiply_generator_ct(hmac_bn)
  child_point = Curve.add_points(@point, hmac_point)
  PublicKey.new(child_point)
end

#derive_shared_secret(private_key) ⇒ PublicKey

Derive an ECDH shared secret with another party’s private key.

Computes the shared point by multiplying this public key by the given private key’s scalar. The result is commutative:

alice_pub.derive_shared_secret(bob_priv) ==
  bob_pub.derive_shared_secret(alice_priv)

Uses constant-time scalar multiplication to protect the private key scalar from timing side-channels.

This is the foundational primitive for BRC-42 key derivation, BRC-77/78 messaging, and ECIES encryption.

Parameters:

  • private_key (PrivateKey)

    the other party’s private key

Returns:

  • (PublicKey)

    the shared secret as a public key (curve point)



117
118
119
120
# File 'lib/bsv/primitives/public_key.rb', line 117

def derive_shared_secret(private_key)
  shared_point = Curve.multiply_point_ct(@point, private_key.bn)
  PublicKey.new(shared_point)
end

#hash160String

Compute the Hash160 (RIPEMD-160 of SHA-256) of the compressed public key.

Returns:

  • (String)

    20-byte public key hash



89
90
91
# File 'lib/bsv/primitives/public_key.rb', line 89

def hash160
  Digest.hash160(compressed)
end

#to_hex(compressed: true) ⇒ String

Return the public key as a hex string.

Parameters:

  • compressed (Boolean) (defaults to: true)

    whether to use compressed encoding (default: true)

Returns:

  • (String)

    hex-encoded public key



78
79
80
81
82
83
84
# File 'lib/bsv/primitives/public_key.rb', line 78

def to_hex(compressed: true)
  if compressed
    self.compressed.unpack1('H*')
  else
    uncompressed.unpack1('H*')
  end
end

#uncompressedString

Return the uncompressed (65-byte) encoding.

Returns:

  • (String)

    uncompressed public key bytes



70
71
72
# File 'lib/bsv/primitives/public_key.rb', line 70

def uncompressed
  @point.to_octet_string(:uncompressed)
end

#verify(hash, signature) ⇒ Boolean

Verify an ECDSA signature against a message hash.

Parameters:

  • hash (String)

    32-byte message digest

  • signature (Signature)

    the signature to verify

Returns:

  • (Boolean)

    true if the signature is valid



146
147
148
# File 'lib/bsv/primitives/public_key.rb', line 146

def verify(hash, signature)
  ECDSA.verify(hash, signature, @point)
end