Class: Bitcoin::Key

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

Overview

Elliptic Curve key as used in bitcoin.

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)


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

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

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



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

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



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
230
231
# File 'lib/bitcoin/key.rb', line 202

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/



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

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


10
11
12
# File 'lib/bitcoin/key.rb', line 10

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.



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

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



131
132
133
# File 'lib/bitcoin/key.rb', line 131

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

Instance Method Details

#==(other) ⇒ Object



27
28
29
# File 'lib/bitcoin/key.rb', line 27

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

#addrObject

Get the address corresponding to the public key.



93
94
95
# File 'lib/bitcoin/key.rb', line 93

def addr
  Bitcoin.hash160_to_address(hash160)
end

#compressedObject



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

def compressed
  @pubkey_compressed
end

#generateObject

Generate new priv/pub key.



44
45
46
# File 'lib/bitcoin/key.rb', line 44

def generate
  @key.generate_key
end

#hash160Object

Get the hash160 of the public key.



88
89
90
# File 'lib/bitcoin/key.rb', line 88

def hash160
  Bitcoin.hash160(pub)
end

#privObject

Get the private key (in hex).



49
50
51
52
# File 'lib/bitcoin/key.rb', line 49

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).



55
56
57
# File 'lib/bitcoin/key.rb', line 55

def priv= priv
  set_priv(priv)
end

#pubObject

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



62
63
64
65
66
# File 'lib/bitcoin/key.rb', line 62

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).



83
84
85
# File 'lib/bitcoin/key.rb', line 83

def pub= pub
  set_pub(pub)
end

#pub_compressedObject



68
69
70
71
# File 'lib/bitcoin/key.rb', line 68

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

#pub_uncompressedObject



73
74
75
76
# File 'lib/bitcoin/key.rb', line 73

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")


100
101
102
103
104
105
106
107
# File 'lib/bitcoin/key.rb', line 100

def sign(data)
  sig = @key.dsa_sign_asn1(data)
  if Script::is_low_der_signature?(sig)
    sig
  else
    Bitcoin::OpenSSL_EC.signature_to_low_s(sig)
  end
end

#sign_message(message) ⇒ Object



123
124
125
# File 'lib/bitcoin/key.rb', line 123

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



166
167
168
169
170
171
# File 'lib/bitcoin/key.rb', line 166

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



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

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)


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

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



127
128
129
# File 'lib/bitcoin/key.rb', line 127

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