class Bitcoin::Key
bitcoin key class
Constants
- COMPACT_SIGNATURE_SIZE
- COMPACT_SIG_HEADER_BYTE
- COMPRESSED_PUBLIC_KEY_SIZE
- MAX_PRIV_KEY_MOD_ORDER
Order of secp256k1's generator minus 1.
- MIN_PRIV_KEY_MOD_ORDER
- PUBLIC_KEY_SIZE
- SIGNATURE_SIZE
- TYPES
Attributes
Public Class Methods
check pubkey
(hex) is compress or uncompress pubkey.
# File lib/bitcoin/key.rb, line 221 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
check pubkey
(hex) is compress pubkey.
# File lib/bitcoin/key.rb, line 236 def self.compress_pubkey?(pubkey) p = pubkey.htb p.bytesize == COMPRESSED_PUBLIC_KEY_SIZE && ["\x02", "\x03"].include?(p[0]) end
Generate from public key point. @param [ECDSA::Point] point Public key point. @param [Boolean] compressed whether compressed or not. @return [Bitcoin::Key]
# 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
import private key from wif format en.bitcoin.it/wiki/Wallet_import_format
# 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
Generate from xonly public key. @param [String] xonly_pubkey
xonly public key with hex format. @return [Bitcoin::Key] key object has public key.
# File lib/bitcoin/key.rb, line 84 def self.from_xonly_pubkey(xonly_pubkey) raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32 Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed]) end
generate key pair
# File lib/bitcoin/key.rb, line 54 def self.generate(key_type = TYPES[:compressed]) priv_key, pubkey = Bitcoin.secp_impl.generate_key_pair new(priv_key: priv_key, pubkey: pubkey, key_type: key_type) end
check sig
is low.
# File lib/bitcoin/key.rb, line 242 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
initialize private key @param [String] priv_key
a private key with hex format. @param [String] pubkey a public key with hex format. @param [Integer] key_type
a key type which determine address type. @param [Boolean] compressed [Deprecated] whether public key is compressed. @return [Bitcoin::Key] a key object.
# File lib/bitcoin/key.rb, line 32 def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false) puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil? 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, 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
Recover public key from compact signature. @param [String] data message digest using signature. @param [String] signature signature with binary format. @return [Bitcoin::Key] Recovered public key.
# 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 raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15 rec = rec & 3 compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0 Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed) end
check sig
is correct der encoding. This function is consensus-critical since BIP66.
# File lib/bitcoin/key.rb, line 259 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
Private Class Methods
# File lib/bitcoin/key.rb, line 299 def self.compare_big_endian(c1, c2) c1, c2 = c1.dup, c2.dup # Clone the arrays while c1.size > c2.size return 1 if c1.shift > 0 end while c2.size > c1.size return -1 if c2.shift > 0 end c1.size.times{|idx| return c1[idx] - c2[idx] if c1[idx] != c2[idx] } 0 end
Public Instance Methods
# File lib/bitcoin/key.rb, line 201 def compressed? key_type != TYPES[:uncompressed] end
fully validate whether this is a valid public key (more expensive than IsValid())
# File lib/bitcoin/key.rb, line 293 def fully_valid_pubkey?(allow_hybrid = false) valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid) end
get hash160 public key.
# File lib/bitcoin/key.rb, line 179 def hash160 Bitcoin.hash160(pubkey) end
sign data
with private key @param [String] data a data to be signed with binary format @param [Boolean] low_r flag to apply low-R. @param [String] extra_entropy the extra entropy with binary format for rfc6979. @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr. @return [String] signature data with binary format
# 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 signature. @param [String] data message digest to be signed. @return [String] compact signature with binary format.
# 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
get p2wpkh address nested in p2sh. @deprecated
# File lib/bitcoin/key.rb, line 197 def to_nested_p2wpkh Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr end
get pay to pubkey hash address @deprecated
# File lib/bitcoin/key.rb, line 185 def to_p2pkh Bitcoin::Script.to_p2pkh(hash160).to_addr end
get pay to witness pubkey hash address @deprecated
# File lib/bitcoin/key.rb, line 191 def to_p2wpkh Bitcoin::Script.to_p2wpkh(hash160).to_addr end
generate pubkey ec point @return [ECDSA::Point]
# File lib/bitcoin/key.rb, line 207 def to_point p = pubkey p ||= generate_pubkey(priv_key, compressed: compressed?) ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP) end
export private key with wif format
# 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 signature using public key @param [String] sig signature data with binary format @param [String] data original message @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr. @return [Boolean] verify result
# 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
get xonly public key (32 bytes). @return [String] xonly public key with hex format
# File lib/bitcoin/key.rb, line 215 def xonly_pubkey puts "Derive a public key whose y-coordinate is different from this public key." if compressed? && pubkey[0...2] != '02' pubkey[2..65] end
Private Instance Methods
Supported violations include negative integers, excessive padding, garbage at the end, and overly long length descriptors. This is safe to use in Bitcoin
because since the activation of BIP66, signatures are verified to be strict DER before being passed to this module, and we know it supports all violations present in the blockchain before that point.
# File lib/bitcoin/key.rb, line 333 def ecdsa_signature_parse_der_lax(sig) sig_array = sig.unpack('C*') len_r = sig_array[3] r = sig_array[4...(len_r+4)].pack('C*').bth len_s = sig_array[len_r + 5] s = sig_array[(len_r + 6)...(len_r + 6 + len_s)].pack('C*').bth ECDSA::Signature.new(r.to_i(16), s.to_i(16)).to_der end
generate publick key from private key @param [String] privkey a private key with string format @param [Boolean] compressed pubkey compressed? @return [String] a pubkey which generate from privkey
# File lib/bitcoin/key.rb, line 318 def generate_pubkey(privkey, compressed: true) @secp256k1_module.generate_pubkey(privkey, compressed: compressed) end
check whether the signature is low-R @param [String] sig the signature data @return [Boolean] result
# File lib/bitcoin/key.rb, line 349 def sig_has_low_r?(sig) sig[3].bth.to_i(16) == 0x20 && sig[4].bth.to_i(16) < 0x80 end
# File lib/bitcoin/key.rb, line 342 def valid_pubkey? !pubkey.nil? && pubkey.size > 0 end
check private key range.
# File lib/bitcoin/key.rb, line 323 def validate_private_key_range(private_key) value = private_key.to_i(16) MIN_PRIV_KEY_MOD_ORDER <= value && value <= MAX_PRIV_KEY_MOD_ORDER end