Module: Bitcoin::Secp256k1::Ruby

Extended by:
Schnorr::Util
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



30
31
32
33
# File 'lib/bitcoin/secp256k1/ruby.rb', line 30

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



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

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



35
36
37
38
# File 'lib/bitcoin/secp256k1/ruby.rb', line 35

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

.native?Boolean

Whether this module is native c wrapper or not?

Returns:

  • (Boolean)


16
17
18
# File 'lib/bitcoin/secp256k1/ruby.rb', line 16

def native?
  false
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.



155
156
157
158
159
160
161
162
# File 'lib/bitcoin/secp256k1/ruby.rb', line 155

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, compressed) ⇒ Bitcoin::Key

Recover public key from compact signature.

Parameters:

  • data (String)

    message digest using signature.

  • signature (String)

    signature with binary format(65 bytes).

  • compressed (Boolean)

    whether compressed public key or not.

Returns:

Raises:

  • (ArgumentError)

    If invalid arguments specified.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/bitcoin/secp256k1/ruby.rb', line 86

def recover_compact(data, signature, compressed)
  raise ArgumentError, "data must be String." unless data.is_a?(String)
  raise ArgumentError, "signature must be String." unless signature.is_a?(String)
  signature = hex2bin(signature)
  raise ArgumentError, "signature must be 64 bytes." unless signature.bytesize == 65
  data = hex2bin(data)
  raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
  rec = (signature[0].ord - 0x1b) & 3
  raise ArgumentError, "rec must be between 0 and 3." if rec < 0 || rec > 3

  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



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

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


75
76
77
78
# File 'lib/bitcoin/secp256k1/ruby.rb', line 75

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



60
61
62
63
64
65
66
67
68
69
# File 'lib/bitcoin/secp256k1/ruby.rb', line 60

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



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/bitcoin/secp256k1/ruby.rb', line 164

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



195
196
197
# File 'lib/bitcoin/secp256k1/ruby.rb', line 195

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.



43
44
45
46
47
48
49
50
51
52
# File 'lib/bitcoin/secp256k1/ruby.rb', line 43

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



199
200
201
202
203
204
205
206
207
# File 'lib/bitcoin/secp256k1/ruby.rb', line 199

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



209
210
211
# File 'lib/bitcoin/secp256k1/ruby.rb', line 209

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



127
128
129
130
131
132
133
134
135
136
# File 'lib/bitcoin/secp256k1/ruby.rb', line 127

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