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



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 = public_key.to_hex(compressed)
  [privkey.bth, pubkey]
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))
  public_key.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.



119
120
121
122
123
124
125
126
# File 'lib/bitcoin/secp256k1/ruby.rb', line 119

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:



76
77
78
79
80
81
82
83
84
# File 'lib/bitcoin/secp256k1/ruby.rb', line 76

def recover_compact(data, signature, rec, compressed)
  r = ECDSA::Format::IntegerOctetString.decode(signature[1...33])
  s = ECDSA::Format::IntegerOctetString.decode(signature[33..-1])
  ECDSA.recover_public_key(Bitcoin::Secp256k1::GROUP, data, ECDSA::Signature.new(r, s)).each do |p|
    if p.y & 1 == rec
      return Bitcoin::Key.from_point(p, compressed: compressed)
    end
  end
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



104
105
106
107
108
109
110
111
112
113
# File 'lib/bitcoin/secp256k1/ruby.rb', line 104

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


65
66
67
68
# File 'lib/bitcoin/secp256k1/ruby.rb', line 65

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



50
51
52
53
54
55
56
57
58
59
# File 'lib/bitcoin/secp256k1/ruby.rb', line 50

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



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/bitcoin/secp256k1/ruby.rb', line 128

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.new_point(nonce)

  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



159
160
161
# File 'lib/bitcoin/secp256k1/ruby.rb', line 159

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.



33
34
35
36
37
38
39
40
41
42
# File 'lib/bitcoin/secp256k1/ruby.rb', line 33

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

.verify_ecdsa(data, sig, pubkey) ⇒ Object



163
164
165
166
167
168
169
170
171
# File 'lib/bitcoin/secp256k1/ruby.rb', line 163

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



173
174
175
# File 'lib/bitcoin/secp256k1/ruby.rb', line 173

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



91
92
93
94
95
96
97
98
99
100
# File 'lib/bitcoin/secp256k1/ruby.rb', line 91

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