Module: Bitcoin::Secp256k1::Ruby

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

Overview

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

Class Method Summary collapse

Class Method Details

.generate_key(compressed: true) ⇒ Object

generate bitcoin key object



23
24
25
26
# File 'lib/bitcoin/secp256k1/ruby.rb', line 23

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



14
15
16
17
18
19
20
# File 'lib/bitcoin/secp256k1/ruby.rb', line 14

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

.generate_pubkey(privkey, compressed: true) ⇒ Object



28
29
30
31
# File 'lib/bitcoin/secp256k1/ruby.rb', line 28

def generate_pubkey(privkey, compressed: true)
  public_key = GROUP.generator.to_jacobian * privkey.to_i(16)
  public_key.to_affine.to_hex(compressed)
end

.parse_ec_pubkey?(pubkey, allow_hybrid = false) ⇒ Boolean

validate whether this is a valid public key (more expensive than IsValid())

Parameters:

  • pubkey (String)

    public key with hex format.

  • allow_hybrid (Boolean) (defaults to: false)

    whether support hybrid public key.

Returns:

  • (Boolean)

    If valid public key return true, otherwise false.



139
140
141
142
143
144
145
146
# File 'lib/bitcoin/secp256k1/ruby.rb', line 139

def parse_ec_pubkey?(pubkey, allow_hybrid = false)
  begin
    point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
    ECDSA::Group::Secp256k1.valid_public_key?(point)
  rescue ECDSA::Format::DecodeError
    false
  end
end

.recover_compact(data, signature, rec, compressed) ⇒ Bitcoin::Key

Recover public key from compact signature.

Parameters:

  • data (String)

    message digest using signature.

  • signature (String)

    signature with binary format.

  • rec (Integer)

    recovery id.

  • compressed (Boolean)

    whether compressed public key or not.

Returns:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/bitcoin/secp256k1/ruby.rb', line 79

def recover_compact(data, signature, rec, compressed)
  group = Bitcoin::Secp256k1::GROUP
  r = ECDSA::Format::IntegerOctetString.decode(signature[1...33])
  s = ECDSA::Format::IntegerOctetString.decode(signature[33..-1])
  return nil if r.zero?
  return nil if s.zero?

  digest = ECDSA.normalize_digest(data, group.bit_length)
  field = ECDSA::PrimeField.new(group.order)

  unless rec & 2 == 0
    r = field.mod(r + group.order)
  end

  is_odd = (rec & 1 == 1)
  y_coordinate = group.solve_for_y(r).find{|y| is_odd ? y.odd? : y.even?}

  p = group.new_point([r, y_coordinate])

  inv_r = field.inverse(r)
  u1 = field.mod(inv_r * digest)
  u2 = field.mod(inv_r * s)
  q = p * u2 + (group.new_point(u1)).negate
  return nil if q.infinity?
  Bitcoin::Key.from_point(q, compressed: compressed)
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



124
125
126
127
128
129
130
131
132
133
# File 'lib/bitcoin/secp256k1/ruby.rb', line 124

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

.sign_compact(data, privkey) ⇒ Array[signature, recovery id]

Sign data with compact format.

Parameters:

  • data (String)

    a data to be signed with binary format

  • privkey (String)

    a private key using sign with hex format

Returns:

  • (Array[signature, recovery id])


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

def sign_compact(data, privkey)
  sig, rec = sign_ecdsa(data, privkey, nil)
  [ECDSA::Format::SignatureDerString.decode(sig), rec]
end

.sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa) ⇒ String

sign data.

Parameters:

  • data (String)

    a data to be signed with binary format

  • privkey (String)

    a private key using sign with hex format

  • extra_entropy (String) (defaults to: nil)

    a extra entropy with binary format for rfc6979

  • algo (Symbol) (defaults to: :ecdsa)

    signature algorithm. ecdsa(default) or schnorr.

Returns:

  • (String)

    signature data with binary format



53
54
55
56
57
58
59
60
61
62
# File 'lib/bitcoin/secp256k1/ruby.rb', line 53

def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
  case algo
  when :ecdsa
    sign_ecdsa(data, privkey, extra_entropy)&.first
  when :schnorr
    sign_schnorr(data, privkey, extra_entropy)
  else
    nil
  end
end

.sign_ecdsa(data, privkey, extra_entropy) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/bitcoin/secp256k1/ruby.rb', line 148

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

  # port form ecdsa gem.
  r_point = (GROUP.generator.to_jacobian * nonce).to_affine

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

  rec = r_point.y & 1

  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
    rec ^= 1
  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, rec]
end

.sign_schnorr(data, privkey, aux_rand) ⇒ Object



179
180
181
# File 'lib/bitcoin/secp256k1/ruby.rb', line 179

def sign_schnorr(data, privkey, aux_rand)
  aux_rand ? Schnorr.sign(data, privkey.htb, aux_rand).encode : Schnorr.sign(data, privkey.htb).encode
end

.valid_xonly_pubkey?(pub_key) ⇒ Boolean

Check whether valid x-only public key or not.

Parameters:

  • pub_key (String)

    x-only public key with hex format(32 bytes).

Returns:

  • (Boolean)

    result.



36
37
38
39
40
41
42
43
44
45
# File 'lib/bitcoin/secp256k1/ruby.rb', line 36

def valid_xonly_pubkey?(pub_key)
  pubkey = pub_key.htb
  return false unless pubkey.bytesize == X_ONLY_PUBKEY_SIZE
  begin
    ECDSA::Format::PointOctetString.decode(pubkey, ECDSA::Group::Secp256k1)
  rescue Exception
    return false
  end
  true
end

.verify_ecdsa(data, sig, pubkey) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/bitcoin/secp256k1/ruby.rb', line 183

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

.verify_schnorr(data, sig, pubkey) ⇒ Object



193
194
195
# File 'lib/bitcoin/secp256k1/ruby.rb', line 193

def verify_schnorr(data, sig, pubkey)
  Schnorr.valid_sig?(data, pubkey.htb, sig)
end

.verify_sig(data, sig, pubkey, algo: :ecdsa) ⇒ Boolean

verify signature using public key

Parameters:

  • data (String)

    a SHA-256 message digest with binary format

  • sig (String)

    a signature for data with binary format

  • pubkey (String)

    a public key with hex format.

Returns:

  • (Boolean)

    verify result



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

def verify_sig(data, sig, pubkey, algo: :ecdsa)
  case algo
  when :ecdsa
    verify_ecdsa(data, sig, pubkey)
  when :schnorr
    verify_schnorr(data, sig, pubkey)
  else
    false
  end
end