lib/bitcoin/key.rb in bitcoin-ruby-0.0.1 vs lib/bitcoin/key.rb in bitcoin-ruby-0.0.2

- old
+ new

@@ -1,5 +1,7 @@ +# encoding: ascii-8bit + module Bitcoin # Elliptic Curve key as used in bitcoin. class Key @@ -13,26 +15,28 @@ # https://en.bitcoin.it/wiki/Private_key#Base_58_Wallet_Import_format and # https://en.bitcoin.it/wiki/Base58Check_encoding#Encoding_a_private_key. # See also #to_base58 def self.from_base58(str) hex = Bitcoin.decode_base58(str) - version, key, checksum = hex.unpack("a2a64a8") + compressed = hex.size == 76 + version, key, flag, checksum = hex.unpack("a2a64a#{compressed ? 2 : 0}a8") raise "Invalid version" unless version == Bitcoin.network[:privkey_version] - raise "Invalid checksum" unless Bitcoin.checksum(version + key) == checksum - new(key) + raise "Invalid checksum" unless Bitcoin.checksum(version + key + flag) == checksum + key = new(key, nil, compressed) end def == other self.priv == other.priv end # Create a new key with given +privkey+ and +pubkey+. # Bitcoin::Key.new # Bitcoin::Key.new(privkey) # Bitcoin::Key.new(nil, pubkey) - def initialize privkey = nil, pubkey = nil + def initialize privkey = nil, pubkey = nil, compressed = false @key = Bitcoin.bitcoin_elliptic_curve + @pubkey_compressed = pubkey ? self.class.is_compressed_pubkey?(pubkey) : compressed set_priv(privkey) if privkey set_pub(pubkey) if pubkey end # Generate new priv/pub key. @@ -53,20 +57,33 @@ # Get the public key (in hex). # In case the key was initialized with only # a private key, the public key is regenerated. def pub - unless @key.public_key - if @key.private_key - set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1]) - else - return nil - end - end + @pubkey_compressed ? pub_compressed : pub_uncompressed + end + + def pub_compressed + regenerate_pubkey unless @key.public_key + return nil unless @key.public_key + @key.public_key.group.point_conversion_form = :compressed + hex = @key.public_key.to_hex.rjust(66, '0') + @key.public_key.group.point_conversion_form = :uncompressed + hex + end + + def pub_uncompressed + regenerate_pubkey unless @key.public_key + return nil unless @key.public_key + @key.public_key.group.point_conversion_form = :uncompressed @key.public_key.to_hex.rjust(130, '0') end + def compressed + @pubkey_compressed + end + # Set the public key (in hex). def pub= pub set_pub(pub) end @@ -92,32 +109,81 @@ # key2.verify("some data", sig) def verify(data, sig) @key.dsa_verify_asn1(data, sig) end + + def sign_message(message) + Bitcoin.sign_message(priv, pub, message)['signature'] + end + + def verify_message(signature, message) + Bitcoin.verify_message(addr, signature, message) + end + + def self.verify_message(address, signature, message) + Bitcoin.verify_message(address, signature, message) + end + + # Thanks to whoever wrote http://pastebin.com/bQtdDzHx + # for help with compact signatures + # + # Given +data+ and a compact signature (65 bytes, base64-encoded to + # a larger string), recover the public components of the key whose + # private counterpart validly signed +data+. + # + # If the signature validly signed +data+, create a new Key + # having the signing public key and address. Otherwise return nil. + # + # Be sure to check that the returned Key matches the one you were + # expecting! Otherwise you are merely checking that *someone* validly + # signed the data. + def self.recover_compact_signature_to_key(data, signature_base64) + signature = signature_base64.unpack("m0")[0] + return nil if signature.size != 65 + + version = signature.unpack('C')[0] + return nil if version < 27 or version > 34 + + compressed = (version >= 31) ? (version -= 4; true) : false + + hash = Bitcoin.bitcoin_signed_message_hash(data) + pub_hex = Bitcoin::OpenSSL_EC.recover_public_key_from_signature(hash, signature, version-27, compressed) + return nil unless pub_hex + + Key.new(nil, pub_hex) + end + # Export private key to base58 format. # See also Key.from_base58 def to_base58 data = Bitcoin.network[:privkey_version] + priv + data += "01" if @pubkey_compressed hex = data + Bitcoin.checksum(data) Bitcoin.int_to_base58( hex.to_i(16) ) end protected # Regenerate public key from the private key. def regenerate_pubkey + return nil unless @key.private_key set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1]) end # Set +priv+ as the new private key (converting from hex). def set_priv(priv) @key.private_key = OpenSSL::BN.from_hex(priv) end # Set +pub+ as the new public key (converting from hex). def set_pub(pub) + @pubkey_compressed ||= self.class.is_compressed_pubkey?(pub) @key.public_key = OpenSSL::PKey::EC::Point.from_hex(@key.group, pub) + end + + def self.is_compressed_pubkey?(pub) + ["02","03"].include?(pub[0..1]) end end end