lib/secp256k1/ffi.rb
# encoding: ascii-8bit
# Wraps libsecp256k1 (https://github.com/bitcoin/secp256k1)
module Secp256k1
require 'ffi'
extend ::FFI::Library
SECP256K1_START_VERIFY = (1 << 0)
SECP256K1_START_SIGN = (1 << 1)
# Load secp256k1 library and attach functions from C to Ruby via FFI.
def self.included(_base)
ffi_lib 'secp256k1'
attach_functions!
rescue LoadError => e
warn e.message
end
def self.attach_functions!
attach_function :secp256k1_start, [:int], :void
attach_function :secp256k1_stop, [], :void
attach_function :secp256k1_ec_seckey_verify, [:pointer], :int
attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer, :int], :int
attach_function :secp256k1_ecdsa_sign, [:pointer, :int, :pointer, :pointer, :pointer, :pointer], :int
attach_function :secp256k1_ecdsa_verify, [:pointer, :int, :pointer, :int, :pointer, :int], :int
attach_function :secp256k1_ecdsa_sign_compact, [:pointer, :int, :pointer, :pointer, :pointer, :pointer], :int
attach_function :secp256k1_ecdsa_recover_compact, [:pointer, :int, :pointer, :pointer, :pointer, :int, :int], :int
end
def self.init
return if @secp256k1_started
secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN)
@secp256k1_started = true
end
def self.generate_key_pair(compressed=true)
init
while true do
priv_key = SecureRandom.random_bytes(32)
priv_key_buf = FFI::MemoryPointer.new(:uchar, 32)
priv_key_buf.put_bytes(0, priv_key)
break if secp256k1_ec_seckey_verify(priv_key_buf)
end
pub_key_buf = FFI::MemoryPointer.new(:uchar, 65)
pub_key_size = FFI::MemoryPointer.new(:int)
result = secp256k1_ec_pubkey_create(pub_key_buf, pub_key_size, priv_key_buf, compressed ? 1 : 0)
raise "error creating pubkey" unless result
[ priv_key, pub_key_buf.read_string(pub_key_size.read_int) ]
end
def self.sign(data, priv_key)
init
data_buf = FFI::MemoryPointer.new(:uchar, data.bytesize)
data_buf.put_bytes(0, data)
sig_buf = FFI::MemoryPointer.new(:uchar, 72)
sig_size = FFI::MemoryPointer.new(:int)
sig_size.write_int(72)
priv_key_buf = FFI::MemoryPointer.new(:uchar, priv_key.bytesize)
priv_key_buf.put_bytes(0, priv_key)
while true do
nonce = FFI::MemoryPointer.new(:uchar, 32)
nonce.put_bytes(0, SecureRandom.random_bytes(32))
break if secp256k1_ecdsa_sign(data_buf, data.bytesize, sig_buf, sig_size, priv_key, nonce)
end
sig_buf.read_string(sig_size.read_int)
end
def self.verify(data, signature, pub_key)
init
data_buf = FFI::MemoryPointer.new(:uchar, data.bytesize)
data_buf.put_bytes(0, data)
sig_buf = FFI::MemoryPointer.new(:uchar, signature.bytesize)
sig_buf.put_bytes(0, signature)
pub_key_buf = FFI::MemoryPointer.new(:uchar, pub_key.bytesize)
pub_key_buf.put_bytes(0, pub_key)
result = secp256k1_ecdsa_verify(data_buf, data.bytesize,
sig_buf, signature.bytesize,
pub_key_buf, pub_key.bytesize)
if result == -1
raise "error invalid pubkey"
elsif result == -2
raise "error invalid signature"
end
result == 1
end
def self.sign_compact(message, priv_key, compressed=true)
init
message_buf = FFI::MemoryPointer.new(:uchar, message.bytesize)
message_buf.put_bytes(0, message)
sig_buf = FFI::MemoryPointer.new(:uchar, 64)
priv_key_buf = FFI::MemoryPointer.new(:uchar, priv_key.bytesize)
priv_key_buf.put_bytes(0, priv_key)
rec_id = FFI::MemoryPointer.new(:int)
while true do
nonce = FFI::MemoryPointer.new(:uchar, 32)
nonce.put_bytes(0, SecureRandom.random_bytes(32))
break if secp256k1_ecdsa_sign_compact(message_buf, message.bytesize, sig_buf, priv_key, nonce, rec_id)
end
header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack("C")
[ header, sig_buf.read_string(64) ].join
end
def self.recover_compact(message, signature)
init
return nil if signature.bytesize != 65
version = signature.unpack('C')[0]
return nil if version < 27 || version > 34
compressed = version >= 31 ? true : false
version -= 4 if compressed
rec_id = version - 27
message_buf = FFI::MemoryPointer.new(:uchar, message.bytesize)
message_buf.put_bytes(0, message)
signature[0] = ''
sig_buf = FFI::MemoryPointer.new(:uchar, signature.bytesize)
sig_buf.put_bytes(0, signature)
pub_key_len = compressed ? 33 : 65
pub_key_buf = FFI::MemoryPointer.new(:uchar, pub_key_len)
pub_key_size = FFI::MemoryPointer.new(:int)
pub_key_size.write_int(pub_key_len)
result = secp256k1_ecdsa_recover_compact(message_buf, message.bytesize,
sig_buf,
pub_key_buf, pub_key_size,
compressed ? 1 : 0,
rec_id)
return nil unless result
pub_key_buf.read_bytes(pub_key_size.read_int)
end
end