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