Module: Bitcoin::ContractHash

Defined in:
lib/bitcoin/contracthash.rb

Overview

Constant Summary collapse

HMAC_DIGEST =
OpenSSL::Digest.new('SHA256')
EC_GROUP =
OpenSSL::PKey::EC::Group.new('secp256k1')

Class Method Summary collapse

Class Method Details

.claim(private_key_wif, payee_address_or_ascii, nonce_hex) ⇒ Object

claim a contract



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/bitcoin/contracthash.rb', line 40

def self.claim(private_key_wif, payee_address_or_ascii, nonce_hex)
  key = Bitcoin::Key.from_base58(private_key_wif)
  data = compute_data(payee_address_or_ascii, nonce_hex)[1]

  pubkey = [key.pub].pack('H*')
  tweak = hmac(pubkey, data).to_i(16)
  raise 'order exceeded, verify parameters' if tweak >= EC_GROUP.order.to_i

  derived_key = (tweak + key.priv.to_i(16)) % EC_GROUP.order.to_i
  raise 'zero' if derived_key.zero?

  Bitcoin::Key.new(derived_key.to_s(16))
end

.compute_data(address_or_ascii, nonce_hex) ⇒ Object

compute HMAC data



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/bitcoin/contracthash.rb', line 55

def self.compute_data(address_or_ascii, nonce_hex)
  nonce = nonce_hex ? [nonce_hex].pack('H32') : SecureRandom.random_bytes(16)
  if Bitcoin.valid_address?(address_or_ascii)
    address_type = case Bitcoin.address_type(address_or_ascii)
                   when :hash160 then  'P2PH'
                   when :p2sh then     'P2SH'
                   else
                     raise "unsupported address type #{address_type}"
                   end
    contract_bytes = [Bitcoin.hash160_from_address(address_or_ascii)].pack('H*')
  else
    address_type = 'TEXT'
    contract_bytes = address_or_ascii
  end
  [nonce.unpack('H*')[0], address_type + nonce + contract_bytes]
end

.generate(redeem_script_hex, payee_address_or_ascii, nonce_hex = nil) ⇒ Object

generate a contract address



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/bitcoin/contracthash.rb', line 12

def self.generate(redeem_script_hex, payee_address_or_ascii, nonce_hex = nil)
  redeem_script = Bitcoin::Script.new([redeem_script_hex].pack('H*'))
  raise 'only multisig redeem scripts are currently supported' unless redeem_script.is_multisig?
  nonce_hex, data = compute_data(payee_address_or_ascii, nonce_hex)

  derived_keys = []
  redeem_script.get_multisig_pubkeys.each do |pubkey|
    tweak = hmac(pubkey, data).to_i(16)
    raise 'order exceeded, pick a new nonce' if tweak >= EC_GROUP.order.to_i
    tweak = OpenSSL::BN.new(tweak.to_s)

    key = Bitcoin::Key.new(nil, pubkey.unpack('H*')[0])
    key = key.instance_variable_get(:@key)
    point = EC_GROUP.generator.mul(tweak).ec_add(key.public_key).to_bn.to_i
    raise 'infinity' if point == 1 / 0.0

    key = Bitcoin::Key.new(nil, point.to_s(16))
    key.instance_eval { @pubkey_compressed = true }
    derived_keys << key.pub
  end

  m = redeem_script.get_signatures_required
  p2sh_script, redeem_script = Bitcoin::Script.to_p2sh_multisig_script(m, *derived_keys)

  [nonce_hex, redeem_script.unpack('H*')[0], Bitcoin::Script.new(p2sh_script).get_p2sh_address]
end

.hmac(pubkey, data) ⇒ Object



7
8
9
# File 'lib/bitcoin/contracthash.rb', line 7

def self.hmac(pubkey, data)
  OpenSSL::HMAC.hexdigest(HMAC_DIGEST, pubkey, data)
end