Class: Bitcoin::Key
- Inherits:
-
Object
- Object
- Bitcoin::Key
- Defined in:
- lib/bitcoin/key.rb
Overview
bitcoin key class
Constant Summary collapse
- PUBLIC_KEY_SIZE =
65
- COMPRESSED_PUBLIC_KEY_SIZE =
33
- SIGNATURE_SIZE =
72
- COMPACT_SIGNATURE_SIZE =
65
- COMPACT_SIG_HEADER_BYTE =
0x1b
- TYPES =
{uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, p2wpkh_p2sh: 0x12, p2tr: 0x13}
- MIN_PRIV_KEY_MOD_ORDER =
0x01
- MAX_PRIV_KEY_MOD_ORDER =
Order of secp256k1’s generator minus 1.
ECDSA::Group::Secp256k1.order - 1
Instance Attribute Summary collapse
-
#key_type ⇒ Object
Returns the value of attribute key_type.
-
#priv_key ⇒ Object
Returns the value of attribute priv_key.
-
#pubkey ⇒ Object
Returns the value of attribute pubkey.
-
#secp256k1_module ⇒ Object
readonly
Returns the value of attribute secp256k1_module.
Class Method Summary collapse
-
.compress_or_uncompress_pubkey?(pubkey) ⇒ Boolean
check
pubkey
(hex) is compress or uncompress pubkey. -
.compress_pubkey?(pubkey) ⇒ Boolean
check
pubkey
(hex) is compress pubkey. -
.from_point(point, compressed: true) ⇒ Bitcoin::Key
Generate from public key point.
-
.from_wif(wif) ⇒ Object
import private key from wif format en.bitcoin.it/wiki/Wallet_import_format.
-
.from_xonly_pubkey(xonly_pubkey) ⇒ Bitcoin::Key
Generate from xonly public key.
-
.generate(key_type = TYPES[:compressed]) ⇒ Object
generate key pair.
-
.low_signature?(sig) ⇒ Boolean
check
sig
is low. -
.recover_compact(data, signature) ⇒ Bitcoin::Key
Recover public key from compact signature.
-
.valid_signature_encoding?(sig) ⇒ Boolean
check
sig
is correct der encoding.
Instance Method Summary collapse
- #compressed? ⇒ Boolean
-
#create_ell_pubkey ⇒ Bitcoin::BIP324::EllSwiftPubkey
Create an ellswift-encoded public key for this key, with specified entropy.
-
#decompress_pubkey ⇒ String
Convert this key to decompress key.
-
#fully_valid_pubkey?(allow_hybrid = false) ⇒ Boolean
fully validate whether this is a valid public key (more expensive than IsValid()).
-
#hash160 ⇒ Object
get hash160 public key.
-
#initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false) ⇒ Bitcoin::Key
constructor
initialize private key.
-
#nested_p2wpkh? ⇒ Boolean
Determine if it is a nested P2WPKH from key_type.
-
#p2pkh? ⇒ Boolean
Determine if it is a P2PKH from key_type.
-
#p2tr? ⇒ Boolean
Determine if it is a nested P2TR from key_type.
-
#p2wpkh? ⇒ Boolean
Determine if it is a P2WPKH from key_type.
-
#sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa) ⇒ String
sign
data
with private key. -
#sign_compact(data) ⇒ String
Sign compact signature.
-
#to_addr ⇒ String
Returns the address corresponding to key_type.
- #to_nested_p2wpkh ⇒ Object deprecated Deprecated.
-
#to_p2pkh ⇒ String
get pay to pubkey hash address.
-
#to_p2tr ⇒ String
Get pay to taproot address.
-
#to_p2wpkh ⇒ String
get pay to witness pubkey hash address.
-
#to_point ⇒ ECDSA::Point
generate pubkey ec point.
-
#to_wif ⇒ Object
export private key with wif format.
-
#verify(sig, data, algo: :ecdsa) ⇒ Boolean
verify signature using public key.
-
#xonly_pubkey ⇒ String
get xonly public key (32 bytes).
Constructor Details
#initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false) ⇒ Bitcoin::Key
initialize private key
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/bitcoin/key.rb', line 32 def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false) if key_type @key_type = key_type compressed = @key_type != TYPES[:uncompressed] else @key_type = compressed ? TYPES[:compressed] : TYPES[:uncompressed] end @secp256k1_module = Bitcoin.secp_impl @priv_key = priv_key if @priv_key raise ArgumentError, 'Private key must be 32 bytes.' unless priv_key.htb.bytesize == 32 raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key) end if pubkey @pubkey = pubkey else @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key end raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid) end |
Instance Attribute Details
#key_type ⇒ Object
Returns the value of attribute key_type.
17 18 19 |
# File 'lib/bitcoin/key.rb', line 17 def key_type @key_type end |
#priv_key ⇒ Object
Returns the value of attribute priv_key.
15 16 17 |
# File 'lib/bitcoin/key.rb', line 15 def priv_key @priv_key end |
#pubkey ⇒ Object
Returns the value of attribute pubkey.
16 17 18 |
# File 'lib/bitcoin/key.rb', line 16 def pubkey @pubkey end |
#secp256k1_module ⇒ Object (readonly)
Returns the value of attribute secp256k1_module.
18 19 20 |
# File 'lib/bitcoin/key.rb', line 18 def secp256k1_module @secp256k1_module end |
Class Method Details
.compress_or_uncompress_pubkey?(pubkey) ⇒ Boolean
check pubkey
(hex) is compress or uncompress pubkey.
271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/bitcoin/key.rb', line 271 def self.compress_or_uncompress_pubkey?(pubkey) p = pubkey.htb return false if p.bytesize < COMPRESSED_PUBLIC_KEY_SIZE case p[0] when "\x04" return false unless p.bytesize == PUBLIC_KEY_SIZE when "\x02", "\x03" return false unless p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE else return false end true end |
.compress_pubkey?(pubkey) ⇒ Boolean
check pubkey
(hex) is compress pubkey.
286 287 288 289 |
# File 'lib/bitcoin/key.rb', line 286 def self.compress_pubkey?(pubkey) p = pubkey.htb p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE && ["\x02", "\x03"].include?(p[0]) end |
.from_point(point, compressed: true) ⇒ Bitcoin::Key
Generate from public key point.
93 94 95 96 |
# File 'lib/bitcoin/key.rb', line 93 def self.from_point(point, compressed: true) pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed]) end |
.from_wif(wif) ⇒ Object
import private key from wif format en.bitcoin.it/wiki/Wallet_import_format
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/bitcoin/key.rb', line 61 def self.from_wif(wif) hex = Base58.decode(wif) raise ArgumentError, 'data is too short' if hex.htb.bytesize < 4 version = hex[0..1] data = hex[2...-8].htb checksum = hex[-8..-1] raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(version + data.bth) == checksum key_len = data.bytesize if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack1('C') == 1 key_type = TYPES[:compressed] data = data[0..-2] elsif key_len == 32 key_type = TYPES[:uncompressed] else raise ArgumentError, 'Wrong number of bytes for a private key, not 32 or 33' end new(priv_key: data.bth, key_type: key_type) end |
.from_xonly_pubkey(xonly_pubkey) ⇒ Bitcoin::Key
Generate from xonly public key.
84 85 86 87 |
# File 'lib/bitcoin/key.rb', line 84 def self.from_xonly_pubkey(xonly_pubkey) raise ArgumentError, "xonly_pubkey must be #{X_ONLY_PUBKEY_SIZE} bytes" unless xonly_pubkey.htb.bytesize == X_ONLY_PUBKEY_SIZE Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:p2tr]) end |
.generate(key_type = TYPES[:compressed]) ⇒ Object
generate key pair
54 55 56 57 |
# File 'lib/bitcoin/key.rb', line 54 def self.generate(key_type = TYPES[:compressed]) priv_key, pubkey = Bitcoin.secp_impl.generate_key_pair(compressed: key_type != TYPES[:uncompressed]) new(priv_key: priv_key, pubkey: pubkey, key_type: key_type) end |
.low_signature?(sig) ⇒ Boolean
check sig
is low.
292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/bitcoin/key.rb', line 292 def self.low_signature?(sig) s = sig.unpack('C*') len_r = s[3] len_s = s[5 + len_r] val_s = s.slice(6 + len_r, len_s) max_mod_half_order = [ 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x5d,0x57,0x6e,0x73,0x57,0xa4,0x50,0x1d, 0xdf,0xe9,0x2f,0x46,0x68,0x1b,0x20,0xa0] compare_big_endian(val_s, [0]) > 0 && compare_big_endian(val_s, max_mod_half_order) <= 0 end |
.recover_compact(data, signature) ⇒ Bitcoin::Key
Recover public key from compact signature.
147 148 149 150 151 152 153 154 |
# File 'lib/bitcoin/key.rb', line 147 def self.recover_compact(data, signature) rec_id = signature.unpack1('C') rec = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 3 raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 3 compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0 Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed) end |
.valid_signature_encoding?(sig) ⇒ Boolean
check sig
is correct der encoding. This function is consensus-critical since BIP66.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/bitcoin/key.rb', line 309 def self.valid_signature_encoding?(sig) return false if sig.bytesize < 9 || sig.bytesize > 73 # Minimum and maximum size check s = sig.unpack('C*') return false if s[0] != 0x30 || s[1] != s.size - 3 # A signature is of type 0x30 (compound). Make sure the length covers the entire signature. len_r = s[3] return false if 5 + len_r >= s.size # Make sure the length of the S element is still inside the signature. len_s = s[5 + len_r] return false unless len_r + len_s + 7 == s.size #Verify that the length of the signature matches the sum of the length of the elements. return false unless s[2] == 0x02 # Check whether the R element is an integer. return false if len_r == 0 # Zero-length integers are not allowed for R. return false unless s[4] & 0x80 == 0 # Negative numbers are not allowed for R. # Null bytes at the start of R are not allowed, unless R would otherwise be interpreted as a negative number. return false if len_r > 1 && (s[4] == 0x00) && (s[5] & 0x80 == 0) return false unless s[len_r + 4] == 0x02 # Check whether the S element is an integer. return false if len_s == 0 # Zero-length integers are not allowed for S. return false unless (s[len_r + 6] & 0x80) == 0 # Negative numbers are not allowed for S. # Null bytes at the start of S are not allowed, unless S would otherwise be interpreted as a negative number. return false if len_s > 1 && (s[len_r + 6] == 0x00) && (s[len_r + 7] & 0x80 == 0) true end |
Instance Method Details
#compressed? ⇒ Boolean
246 247 248 |
# File 'lib/bitcoin/key.rb', line 246 def compressed? key_type != TYPES[:uncompressed] end |
#create_ell_pubkey ⇒ Bitcoin::BIP324::EllSwiftPubkey
Create an ellswift-encoded public key for this key, with specified entropy.
350 351 352 353 354 355 356 357 |
# File 'lib/bitcoin/key.rb', line 350 def create_ell_pubkey raise ArgumentError, "private_key required." unless priv_key if secp256k1_module.is_a?(Bitcoin::Secp256k1::Native) Bitcoin::BIP324::EllSwiftPubkey.new(secp256k1_module.ellswift_create(priv_key)) else Bitcoin::BIP324::EllSwiftPubkey.new(Bitcoin::BIP324.xelligatorswift(xonly_pubkey)) end end |
#decompress_pubkey ⇒ String
Convert this key to decompress key.
266 267 268 |
# File 'lib/bitcoin/key.rb', line 266 def decompress_pubkey pubkey.htb.bytesize == PUBLIC_KEY_SIZE ? pubkey : to_point.to_hex(false) end |
#fully_valid_pubkey?(allow_hybrid = false) ⇒ Boolean
fully validate whether this is a valid public key (more expensive than IsValid())
343 344 345 |
# File 'lib/bitcoin/key.rb', line 343 def fully_valid_pubkey?(allow_hybrid = false) valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid) end |
#hash160 ⇒ Object
get hash160 public key.
179 180 181 |
# File 'lib/bitcoin/key.rb', line 179 def hash160 Bitcoin.hash160(pubkey) end |
#nested_p2wpkh? ⇒ Boolean
Determine if it is a nested P2WPKH from key_type.
221 222 223 |
# File 'lib/bitcoin/key.rb', line 221 def nested_p2wpkh? key_type == TYPES[:p2wpkh_p2sh] end |
#p2pkh? ⇒ Boolean
Determine if it is a P2PKH from key_type.
209 210 211 |
# File 'lib/bitcoin/key.rb', line 209 def p2pkh? [TYPES[:uncompressed], TYPES[:compressed], TYPES[:p2pkh]].include?(key_type) end |
#p2tr? ⇒ Boolean
Determine if it is a nested P2TR from key_type.
227 228 229 |
# File 'lib/bitcoin/key.rb', line 227 def p2tr? key_type == TYPES[:p2tr] end |
#p2wpkh? ⇒ Boolean
Determine if it is a P2WPKH from key_type.
215 216 217 |
# File 'lib/bitcoin/key.rb', line 215 def p2wpkh? key_type == TYPES[:p2wpkh] end |
#sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa) ⇒ String
sign data
with private key
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/bitcoin/key.rb', line 113 def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa) case algo when :ecdsa sig = secp256k1_module.sign_data(data, priv_key, extra_entropy) if low_r && !sig_has_low_r?(sig) counter = 1 until sig_has_low_r?(sig) extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb sig = secp256k1_module.sign_data(data, priv_key, extra_entropy) counter += 1 end end sig when :schnorr secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :schnorr) else raise ArgumentError "Unsupported algo specified: #{algo}" end end |
#sign_compact(data) ⇒ String
Sign compact signature.
136 137 138 139 140 141 |
# File 'lib/bitcoin/key.rb', line 136 def sign_compact(data) signature, rec = secp256k1_module.sign_compact(data, priv_key) rec = Bitcoin::Key::COMPACT_SIG_HEADER_BYTE + rec + (compressed? ? 4 : 0) [rec].pack('C') + ECDSA::Format::IntegerOctetString.encode(signature.r, 32) + ECDSA::Format::IntegerOctetString.encode(signature.s, 32) end |
#to_addr ⇒ String
Returns the address corresponding to key_type.
233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/bitcoin/key.rb', line 233 def to_addr case key_type when TYPES[:uncompressed], TYPES[:compressed], TYPES[:p2pkh] to_p2pkh when TYPES[:p2wpkh] to_p2wpkh when TYPES[:p2wpkh_p2sh] to_nested_p2wpkh when TYPES[:p2tr] to_p2tr end end |
#to_nested_p2wpkh ⇒ Object
get p2wpkh address nested in p2sh.
203 204 205 |
# File 'lib/bitcoin/key.rb', line 203 def to_nested_p2wpkh Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr end |
#to_p2pkh ⇒ String
get pay to pubkey hash address
185 186 187 |
# File 'lib/bitcoin/key.rb', line 185 def to_p2pkh Bitcoin::Script.to_p2pkh(hash160).to_addr end |
#to_p2tr ⇒ String
Get pay to taproot address
197 198 199 |
# File 'lib/bitcoin/key.rb', line 197 def to_p2tr Bitcoin::Script.to_p2tr(self).to_addr end |
#to_p2wpkh ⇒ String
get pay to witness pubkey hash address
191 192 193 |
# File 'lib/bitcoin/key.rb', line 191 def to_p2wpkh Bitcoin::Script.to_p2wpkh(hash160).to_addr end |
#to_point ⇒ ECDSA::Point
generate pubkey ec point
252 253 254 255 256 |
# File 'lib/bitcoin/key.rb', line 252 def to_point p = pubkey p ||= generate_pubkey(priv_key, compressed: compressed?) ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP) end |
#to_wif ⇒ Object
export private key with wif format
99 100 101 102 103 104 105 |
# File 'lib/bitcoin/key.rb', line 99 def to_wif version = Bitcoin.chain_params.privkey_version hex = version + priv_key hex += '01' if compressed? hex += Bitcoin.calc_checksum(hex) Base58.encode(hex) end |
#verify(sig, data, algo: :ecdsa) ⇒ Boolean
verify signature using public key
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/bitcoin/key.rb', line 161 def verify(sig, data, algo: :ecdsa) return false unless valid_pubkey? begin case algo when :ecdsa sig = ecdsa_signature_parse_der_lax(sig) secp256k1_module.verify_sig(data, sig, pubkey) when :schnorr secp256k1_module.verify_sig(data, sig, xonly_pubkey, algo: :schnorr) else false end rescue Exception false end end |
#xonly_pubkey ⇒ String
get xonly public key (32 bytes).
260 261 262 |
# File 'lib/bitcoin/key.rb', line 260 def xonly_pubkey pubkey[2..65] end |