module Bitcoin::Secp256k1::Ruby

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

Public Instance Methods

generate_key(compressed: true) click to toggle source

generate bitcoin key object

# 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) click to toggle source

generate ec private key and public key

# 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) click to toggle source
# 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) click to toggle source

validate whether this is a valid public key (more expensive than IsValid()) @param [String] pubkey public key with hex format. @param [Boolean] allow_hybrid whether support hybrid public key. @return [Boolean] If valid public key return true, otherwise false.

# 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) click to toggle source

Recover public key from compact signature. @param [String] data message digest using signature. @param [String] signature signature with binary format. @param [Integer] rec recovery id. @param [Boolean] compressed whether compressed public key or not. @return [Bitcoin::Key] Recovered public key.

# 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) click to toggle source

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

# 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) click to toggle source

Sign data with compact format. @param [String] data a data to be signed with binary format @param [String] privkey a private key using sign with hex format @return [Array[signature, recovery id]]

# 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) click to toggle source

sign data. @param [String] data a data to be signed with binary format @param [String] privkey a private key using sign with hex format @param [String] extra_entropy a extra entropy with binary format for rfc6979 @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr. @return [String] signature data with binary format

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

Check whether valid x-only public key or not. @param [String] pub_key x-only public key with hex format(32 bytes). @return [Boolean] result.

# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

verify signature using public key @param [String] data a SHA-256 message digest with binary format @param [String] sig a signature for data with binary format @param [String] pubkey a public key with hex format. @return [Boolean] verify result

# 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