Class: Bitcoin::Key

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

Overview

Elliptic Curve key as used in bitcoin.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(privkey = nil, pubkey = nil, opts = {compressed: true}) ⇒ Key

Create a new key with given privkey and pubkey.

Bitcoin::Key.new
Bitcoin::Key.new(privkey)
Bitcoin::Key.new(nil, pubkey)


37
38
39
40
41
42
43
# File 'lib/bitcoin/key.rb', line 37

def initialize(privkey = nil, pubkey = nil, opts={compressed: true})
  compressed = opts.is_a?(Hash) ? opts.fetch(:compressed, true) : opts
  @key = Bitcoin.bitcoin_elliptic_curve
  @pubkey_compressed = pubkey ? self.class.is_compressed_pubkey?(pubkey) : compressed
  set_priv(privkey)  if privkey
  set_pub(pubkey, @pubkey_compressed)  if pubkey
end

Instance Attribute Details

#keyObject (readonly)

Returns the value of attribute key.



8
9
10
# File 'lib/bitcoin/key.rb', line 8

def key
  @key
end

Class Method Details

.from_base58(str) ⇒ Object

Import private key from base58 fromat as described in en.bitcoin.it/wiki/Private_key#Base_58_Wallet_Import_format and en.bitcoin.it/wiki/Base58Check_encoding#Encoding_a_private_key. See also #to_base58



20
21
22
23
24
25
26
27
# File 'lib/bitcoin/key.rb', line 20

def self.from_base58(str)
  hex = Bitcoin.decode_base58(str)
  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 + flag) == checksum
  key = new(key, nil, compressed)
end

.from_bip38(encrypted_privkey, passphrase) ⇒ Object

Import private key from bip38 (non-ec-multiply) fromat as described in github.com/bitcoin/bips/blob/master/bip-0038.mediawiki See also #to_bip38



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/bitcoin/key.rb', line 200

def self.from_bip38(encrypted_privkey, passphrase)
  version, flagbyte, addresshash, encryptedhalf1, encryptedhalf2, checksum =
    [ Bitcoin.decode_base58(encrypted_privkey) ].pack("H*").unpack("a2aa4a16a16a4")
  compressed = (flagbyte == "\xe0") ? true : false

  raise "Invalid version"   unless version == "\x01\x42"
  raise "Invalid checksum"  unless Digest::SHA256.digest(Digest::SHA256.digest(version + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2))[0...4] == checksum

  require 'scrypt' unless defined?(::SCrypt::Engine)
  buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
  derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]

  aes = proc{|k,a|
    cipher = OpenSSL::Cipher::AES.new(256, :ECB); cipher.decrypt; cipher.padding = 0; cipher.key = k
    cipher.update(a)
  }

  decryptedhalf2 = aes.call(derivedhalf2, encryptedhalf2)
  decryptedhalf1 = aes.call(derivedhalf2, encryptedhalf1)

  priv = decryptedhalf1 + decryptedhalf2
  priv = (priv.unpack("H*")[0].to_i(16) ^ derivedhalf1.unpack("H*")[0].to_i(16)).to_s(16).rjust(64, '0')
  key = Bitcoin::Key.new(priv, nil, compressed)

  if Digest::SHA256.digest( Digest::SHA256.digest( key.addr ) )[0...4] != addresshash
    raise "Invalid addresshash! Password is likely incorrect."
  end

  key
end

.from_warp(passphrase, salt = "", compressed = false) ⇒ Object

Import private key from warp fromat as described in github.com/keybase/warpwallet keybase.io/warp/



234
235
236
237
238
239
240
241
242
243
# File 'lib/bitcoin/key.rb', line 234

def self.from_warp(passphrase, salt="", compressed=false)
  require 'scrypt' unless defined?(::SCrypt::Engine)
  s1 = SCrypt::Engine.scrypt(passphrase+"\x01", salt+"\x01", 2**18, 8, 1, 32)
  s2 = OpenSSL::PKCS5.pbkdf2_hmac(passphrase+"\x02", salt+"\x02", 2**16, 32, OpenSSL::Digest::SHA256.new)
  s3 = s1.bytes.zip(s2.bytes).map{|a,b| a ^ b }.pack("C*")

  key = Bitcoin::Key.new(s3.unpack("H*")[0], nil, compressed)
  # [key.addr, key.to_base58, [s1,s2,s3].map{|i| i.unpack("H*")[0] }, compressed]
  key
end

.generate(opts = {compressed: true}) ⇒ Object

Generate a new keypair.

Bitcoin::Key.generate


12
13
14
# File 'lib/bitcoin/key.rb', line 12

def self.generate(opts={compressed: true})
  k = new(nil, nil, opts); k.generate; k
end

.recover_compact_signature_to_key(data, signature_base64) ⇒ Object

Thanks to whoever wrote 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.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/bitcoin/key.rb', line 146

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

.verify_message(address, signature, message) ⇒ Object



129
130
131
# File 'lib/bitcoin/key.rb', line 129

def self.verify_message(address, signature, message)
  Bitcoin.verify_message(address, signature, message)
end

Instance Method Details

#==(other) ⇒ Object



29
30
31
# File 'lib/bitcoin/key.rb', line 29

def ==(other)
  self.priv == other.priv
end

#addrObject

Get the address corresponding to the public key.



96
97
98
# File 'lib/bitcoin/key.rb', line 96

def addr
  Bitcoin.hash160_to_address(hash160)
end

#compressedObject



81
82
83
# File 'lib/bitcoin/key.rb', line 81

def compressed
  @pubkey_compressed
end

#generateObject

Generate new priv/pub key.



46
47
48
# File 'lib/bitcoin/key.rb', line 46

def generate
  @key.generate_key
end

#hash160Object

Get the hash160 of the public key.



91
92
93
# File 'lib/bitcoin/key.rb', line 91

def hash160
  Bitcoin.hash160(pub)
end

#privObject

Get the private key (in hex).



51
52
53
54
# File 'lib/bitcoin/key.rb', line 51

def priv
  return nil  unless @key.private_key
  @key.private_key.to_hex.rjust(64, '0')
end

#priv=(priv) ⇒ Object

Set the private key to priv (in hex).



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

def priv= priv
  set_priv(priv)
  regenerate_pubkey
end

#pubObject

Get the public key (in hex). In case the key was initialized with only a private key, the public key is regenerated.



65
66
67
68
69
# File 'lib/bitcoin/key.rb', line 65

def pub
  regenerate_pubkey unless @key.public_key
  return nil        unless @key.public_key
  @pubkey_compressed ? pub_compressed : pub_uncompressed
end

#pub=(pub) ⇒ Object

Set the public key (in hex).



86
87
88
# File 'lib/bitcoin/key.rb', line 86

def pub= pub
  set_pub(pub)
end

#pub_compressedObject



71
72
73
74
# File 'lib/bitcoin/key.rb', line 71

def pub_compressed
  @key.public_key.group.point_conversion_form = :compressed
  @key.public_key.to_hex.rjust(66, '0')
end

#pub_uncompressedObject



76
77
78
79
# File 'lib/bitcoin/key.rb', line 76

def pub_uncompressed
  @key.public_key.group.point_conversion_form = :uncompressed
  @key.public_key.to_hex.rjust(130, '0')
end

#sign(data) ⇒ Object

Sign data with the key.

key1 = Bitcoin::Key.generate
sig = key1.sign("some data")


103
104
105
# File 'lib/bitcoin/key.rb', line 103

def sign(data)
  Bitcoin.sign_data(key, data)
end

#sign_message(message) ⇒ Object



121
122
123
# File 'lib/bitcoin/key.rb', line 121

def sign_message(message)
  Bitcoin.sign_message(priv, pub, message)['signature']
end

#to_base58Object

Export private key to base58 format. See also Key.from_base58



164
165
166
167
168
169
# File 'lib/bitcoin/key.rb', line 164

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

#to_bip38(passphrase) ⇒ Object

Export private key to bip38 (non-ec-multiply) format as described in github.com/bitcoin/bips/blob/master/bip-0038.mediawiki See also Key.from_bip38



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/bitcoin/key.rb', line 175

def to_bip38(passphrase)
  flagbyte = compressed ? "\xe0" : "\xc0"
  addresshash = Digest::SHA256.digest( Digest::SHA256.digest( self.addr ) )[0...4]

  require 'scrypt' unless defined?(::SCrypt::Engine)
  buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
  derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]

  aes = proc{|k,a,b|
    cipher = OpenSSL::Cipher::AES.new(256, :ECB); cipher.encrypt; cipher.padding = 0; cipher.key = k
    cipher.update [ (a.to_i(16) ^ b.unpack("H*")[0].to_i(16)).to_s(16).rjust(32, '0') ].pack("H*")
  }

  encryptedhalf1 = aes.call(derivedhalf2, self.priv[0...32], derivedhalf1[0...16])
  encryptedhalf2 = aes.call(derivedhalf2, self.priv[32..-1], derivedhalf1[16..-1])

  encrypted_privkey = "\x01\x42" + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2
  encrypted_privkey += Digest::SHA256.digest( Digest::SHA256.digest( encrypted_privkey ) )[0...4]

  encrypted_privkey = Bitcoin.encode_base58( encrypted_privkey.unpack("H*")[0] )
end

#verify(data, sig) ⇒ Object

Verify signature sig for data.

key2 = Bitcoin::Key.new(nil, key1.pub)
key2.verify("some data", sig)


110
111
112
113
114
115
116
117
118
# File 'lib/bitcoin/key.rb', line 110

def verify(data, sig)
  regenerate_pubkey unless @key.public_key
  sig = Bitcoin::OpenSSL_EC.repack_der_signature(sig)
  if sig
    @key.dsa_verify_asn1(data, sig)
  else
    false
  end
end

#verify_message(signature, message) ⇒ Object



125
126
127
# File 'lib/bitcoin/key.rb', line 125

def verify_message(signature, message)
  Bitcoin.verify_message(addr, signature, message)
end