Module: Bitcoin::Secp256k1::Ruby

Defined in:
lib/bitcoin/secp256k1/ruby.rb

Overview

secp256 module using ecdsa gem github.com/DavidEGrayson/ruby_ecdsa

Constant Summary collapse

INITIAL_V =
'0101010101010101010101010101010101010101010101010101010101010101'.htb
INITIAL_K =
'0000000000000000000000000000000000000000000000000000000000000000'.htb
ZERO_B =
'00'.htb
ONE_B =
'01'.htb

Class Method Summary collapse

Class Method Details

.generate_key(compressed: true) ⇒ Object

generate bitcoin key object



20
21
22
23
# File 'lib/bitcoin/secp256k1/ruby.rb', line 20

def generate_key(compressed: true)
  privkey, pubkey = generate_key_pair(compressed: compressed)
  Bitcoin::Key.new(priv_key: privkey, pubkey: pubkey, compressed: compressed)
end

.generate_key_pair(compressed: true) ⇒ Object

generate ec private key and public key



11
12
13
14
15
16
17
# File 'lib/bitcoin/secp256k1/ruby.rb', line 11

def generate_key_pair(compressed: true)
  private_key = 1 + SecureRandom.random_number(GROUP.order - 1)
  public_key = GROUP.generator.multiply_by_scalar(private_key)
  privkey = ECDSA::Format::IntegerOctetString.encode(private_key, 32)
  pubkey = ECDSA::Format::PointOctetString.encode(public_key, compression: compressed)
  [privkey.bth, pubkey.bth]
end

.generate_pubkey(privkey, compressed: true) ⇒ Object



25
26
27
28
# File 'lib/bitcoin/secp256k1/ruby.rb', line 25

def generate_pubkey(privkey, compressed: true)
  public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(privkey.to_i(16))
  ECDSA::Format::PointOctetString.encode(public_key, compression: compressed).bth
end

.generate_rfc6979_nonce(data, privkey, extra_entropy) ⇒ Object

generate temporary key k to be used when ECDSA sign. tools.ietf.org/html/rfc6979#section-3.2



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/bitcoin/secp256k1/ruby.rb', line 97

def generate_rfc6979_nonce(data, privkey, extra_entropy)
  v = INITIAL_V # 3.2.b
  k = INITIAL_K # 3.2.c
  # 3.2.d
  k = Bitcoin.hmac_sha256(k, v + ZERO_B + privkey + data + extra_entropy)
  # 3.2.e
  v = Bitcoin.hmac_sha256(k, v)
  # 3.2.f
  k = Bitcoin.hmac_sha256(k, v + ONE_B + privkey + data + extra_entropy)
  # 3.2.g
  v = Bitcoin.hmac_sha256(k, v)
  # 3.2.h
  t = ''
  10000.times do
    v = Bitcoin.hmac_sha256(k, v)
    t = (t + v)
    t_num = t.bth.to_i(16)
    return t_num if 1 <= t_num && t_num < GROUP.order
    k = Bitcoin.hmac_sha256(k, v + '00'.htb)
    v = Bitcoin.hmac_sha256(k, v)
  end
  raise 'A valid nonce was not found.'
end

.repack_pubkey(pubkey) ⇒ Object

if pubkey is hybrid public key format, it convert uncompressed format. lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-June/001578.html



79
80
81
82
83
84
85
86
87
88
# File 'lib/bitcoin/secp256k1/ruby.rb', line 79

def repack_pubkey(pubkey)
  p = pubkey.htb
  case p[0]
    when "\x06", "\x07"
      p[0] = "\x04"
      p
    else
      pubkey.htb
  end
end

.sign_data(data, privkey, extra_entropy) ⇒ String

sign data.

Parameters:

  • data (String)

    a data to be signed with binary format

  • privkey (String)

    a private key using sign

Returns:

  • (String)

    signature data with binary format



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/bitcoin/secp256k1/ruby.rb', line 34

def sign_data(data, privkey, extra_entropy)
  privkey = privkey.htb
  private_key = ECDSA::Format::IntegerOctetString.decode(privkey)
  extra_entropy ||= ''
  nonce = generate_rfc6979_nonce(data, privkey, extra_entropy)

  # port form ecdsa gem.
  r_point = GROUP.new_point(nonce)

  point_field = ECDSA::PrimeField.new(GROUP.order)
  r = point_field.mod(r_point.x)
  return nil if r.zero?

  e = ECDSA.normalize_digest(data, GROUP.bit_length)
  s = point_field.mod(point_field.inverse(nonce) * (e + r * private_key))

  if s > (GROUP.order / 2) # convert low-s
    s = GROUP.order - s
  end

  return nil if s.zero?

  signature = ECDSA::Signature.new(r, s).to_der
  public_key = Bitcoin::Key.new(priv_key: privkey.bth).pubkey
  raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
  signature
end

.verify_sig(digest, sig, pubkey) ⇒ Boolean

verify signature using public key

Parameters:

  • digest (String)

    a SHA-256 message digest with binary format

  • sig (String)

    a signature for data with binary format

  • pubkey (String)

    a public key corresponding to the private key used for sign

Returns:

  • (Boolean)

    verify result



67
68
69
70
71
72
73
74
75
# File 'lib/bitcoin/secp256k1/ruby.rb', line 67

def verify_sig(digest, sig, pubkey)
  begin
    k = ECDSA::Format::PointOctetString.decode(repack_pubkey(pubkey), GROUP)
    signature = ECDSA::Format::SignatureDerString.decode(sig)
    ECDSA.valid_signature?(k, digest, signature)
  rescue Exception
    false
  end
end