Class: Sibit::Key

Inherits:
Object
  • Object
show all
Defined in:
lib/sibit/key.rb

Overview

Bitcoin ECDSA key using secp256k1 curve.

Supports OpenSSL 3.0+ by constructing keys via DER encoding instead of using deprecated mutable key APIs.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright (c) 2019-2026 Yegor Bugayenko

License

MIT

Constant Summary collapse

MIN_PRIV =
0x01
MAX_PRIV =
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140
SECP256K1_N =
OpenSSL::BN.new(
  'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16
)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(privkey, network: nil) ⇒ Key

Returns a new instance of Key.



46
47
48
49
50
51
52
# File 'lib/sibit/key.rb', line 46

def initialize(privkey, network: nil)
  @override = network
  @network = :mainnet
  @compressed = true
  @privkey = decode(privkey)
  @key = build(@privkey)
end

Instance Attribute Details

#networkObject (readonly)

Returns the value of attribute network.



30
31
32
# File 'lib/sibit/key.rb', line 30

def network
  @network
end

Class Method Details

.generate(network: :mainnet) ⇒ Object

Raises:



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/sibit/key.rb', line 32

def self.generate(network: :mainnet)
  key = OpenSSL::PKey::EC.generate('secp256k1')
  pvt = key.private_key
  raise(Sibit::Error, 'Invalid private key: zero') if pvt.zero?
  raise(Sibit::Error, 'Invalid private key: out of range') if pvt >= SECP256K1_N
  raise(Sibit::Error, 'Invalid public key: not on curve') unless key.public_key.on_curve?
  hex = key.private_key.to_s(16).rjust(64, '0').downcase
  raise(Sibit::Error, 'Invalid private key encoding') unless hex.match?(/\A[0-9a-f]{64}\z/)
  unless OpenSSL::BN.new(hex, 16) == pvt
    raise(Sibit::Error, 'Private key serialization is lossy')
  end
  new(hex, network: network)
end

Instance Method Details

#base58Object

Raises:



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/sibit/key.rb', line 74

def base58
  hex = pub
  raise(Error, 'Invalid public key: not on curve') unless @key.public_key.on_curve?
  raise(Error, 'Invalid public key format') unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
  versioned = "#{@network == :mainnet ? '00' : '6f'}#{hash160(hex)}"
  addr = Base58.new(versioned + Base58.new(versioned).check).encode
  mainnet = /\A1[1-9A-HJ-NP-Za-km-z]{25,34}\z/
  testnet = /\A[mn][1-9A-HJ-NP-Za-km-z]{25,34}\z/
  unless addr.match?(@network == :mainnet ? mainnet : testnet)
    raise(Error, "Invalid base58 address: #{addr}")
  end
  addr
end

#bech32Object

Raises:



62
63
64
65
66
67
68
69
70
71
72
# File 'lib/sibit/key.rb', line 62

def bech32
  hrp = { mainnet: 'bc', testnet: 'tb', regtest: 'bcrt' }[@network]
  hex = pub
  raise(Error, 'Invalid public key: not on curve') unless @key.public_key.on_curve?
  raise(Error, 'Invalid public key format') unless hex.match?(/\A0[23][0-9a-f]{64}\z/)
  addr = Bech32.encode(hrp, 0, hash160(hex))
  unless addr.match?(/\A#{hrp}1q[a-z0-9]{38,58}\z/)
    raise(Error, "Invalid bech32 address: #{addr}")
  end
  addr
end

#privObject



54
55
56
# File 'lib/sibit/key.rb', line 54

def priv
  @privkey
end

#pubObject



58
59
60
# File 'lib/sibit/key.rb', line 58

def pub
  @key.public_key.to_octet_string(@compressed ? :compressed : :uncompressed).unpack1('H*')
end

#sign(data) ⇒ Object

Raises:



88
89
90
91
92
# File 'lib/sibit/key.rb', line 88

def sign(data)
  sig = @key.dsa_sign_asn1(data)
  raise(Error, 'Signature verification failed') unless verify(data, sig)
  sig
end

#verify(data, sig) ⇒ Object



94
95
96
97
98
# File 'lib/sibit/key.rb', line 94

def verify(data, sig)
  @key.dsa_verify_asn1(data, sig)
rescue OpenSSL::PKey::PKeyError
  false
end