ruby-bitcoin-secp256k1 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 03ab3f4eeaa3515c7ef68d9bc438f0a8447d93958b35d7220af1e734408516bd
4
+ data.tar.gz: 2b470759548d5611adf3351dd7bbe06559e2953f7d5e314a0babe1a208573a2c
5
+ SHA512:
6
+ metadata.gz: '08d8e8b4dd92b3088f25cff4f326e6d09b01b417dc89bc7376628162e2205445cfd61117fa2fb1db1f15d9f7e6c1a0961e8c141ebe898f0d4031a1e23311cf8b'
7
+ data.tar.gz: 3bd4282824621b3f127d748176e8b5a5b6b43a898c296f7eb09a9294c07231ae32b505743d1a66540173542067d42af57a07303c8874d255fa30743601c6ff25
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ tmp
3
+ lib/ethash/ethash.so
4
+ tags
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "secp256k1"]
2
+ path = secp256k1
3
+ url = https://github.com/bitcoin-core/secp256k1.git
data/.travis.yml ADDED
@@ -0,0 +1,61 @@
1
+ ################
2
+ # CI Workflow:
3
+ # feature development, trigger by `git push`:
4
+ # test -> code_audit(code_security_audit and code_quality_audit)
5
+ #
6
+ # feature deploy, trigger by `git push --tags`
7
+ # test -> deploy
8
+ #
9
+ # Stages:
10
+ # - test: run `rake test`
11
+ # - code_audit: run `code_quality security_audit` and `code_quality quality_audit`
12
+ # - deploy: auto build and upload a gem package to rubygems.org after `git push --tags`
13
+ #
14
+ # Principle:
15
+ # - fail fast
16
+ # - done is better than perfect
17
+ ################
18
+
19
+ stages:
20
+ - test
21
+ - code_audit
22
+ - deploy
23
+
24
+ sudo: false
25
+ language: ruby
26
+ rvm:
27
+ - 2.4.4
28
+ - 2.5.3
29
+ before_install:
30
+ - gem install bundler
31
+ - git submodule update --init --recursive
32
+ - ./install_lib.sh
33
+
34
+ # config GitHub OAuth Token
35
+ env:
36
+ global:
37
+ secure: ENV_GLOBAL_SECURE
38
+
39
+ jobs:
40
+ include:
41
+ - stage: code_audit
42
+ if: branch != master
43
+ install: gem install code_quality --no-ri --no-rdoc
44
+ script:
45
+ - bundle install # to generate Gemfile.lock
46
+ - code_quality security_audit:bundler_audit
47
+ - code_quality quality_audit fail_fast=false generate_index=true lowest_score=80 rubocop_max_offenses=200
48
+
49
+ # automatically release Ruby gem to RubyGems after a successful build with `git push --tags`
50
+ # refs: https://docs.travis-ci.com/user/deployment/rubygems/
51
+ # 1. how to get your api_key: https://rubygems.org/profile/edit
52
+ # 2. intall `travis` cli: $ gem install travis
53
+ # 3. get encrypted api_key secure: $ travis setup rubygems
54
+ deploy:
55
+ - provider: rubygems
56
+ api_key:
57
+ secure: oAeCF8k1NW3a94NXJoQs6kBRJG5TY9zdGJAVgvMxFuEnDVgjmuoKceTLdeQzKin+2PS2KRzGhT5E27wrNvCnQGfjKmQA9kPDqSQeJNnRBkbTjxKwkbi2P4g2EE1wR5PYgKq0mJ/4BMxVWZOleYT+ihwMiZDzoadAglbs5PSlC1HNnaQbnPQ3nfN1iG7o1KXdva5Bw4mcm86QPDrSoR9Fl/7ZJvteVouvC71Qt4QhkcaVV3piDUDM6JRbxa6ydsDOV6zq4nZP7UwufgK1GIpVNiLnVZtL3jCndY84a4MYz5sgBbvSEmSVbF1OdrPgllCV2wnauOtuzTqRYF5ATYyM3Hx58jYLUyd6YwXQrEoirHCyt2XaKSF1r2f9zvllVdXSCj+6/CJ70HkHrJTO3K2C9/uZEYIfNbTFZjMASWKyB52Vch5dFmlTloI/eLF2vp5jjY4iYTInPeb59UzqyBkMT4jtwconbre8eVKW/9qHTdTJDKQqmp4jKD+raeia/FbVaOTKyZ9nWxSQI3mVy49diK2j+Pd1LVw3nXY9zMz5f5W/D/zyGVf/Xt6gRNo0aj8UdMvZJm3awvruwQpJTa2tcp5m/iv74JzsFMdgit1pEbimF6xs7Ph8wVAI7eKVctZtI/zeaw+nv3mcg3+/GNGzSSj0RsjdXetrFSh6eh+qfL0=
58
+ gem: bitcoin-secp256k1
59
+ on:
60
+ tags: true
61
+ repo: cryptape/ruby-bitcoin-secp256k1
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,25 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby-bitcoin-secp256k1 (0.5.2)
5
+ ffi (>= 1.9.25)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ffi (1.15.5)
11
+ minitest (5.11.3)
12
+ rake (12.3.2)
13
+ yard (0.9.20)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ minitest (= 5.11.3)
20
+ rake (~> 12.3)
21
+ ruby-bitcoin-secp256k1!
22
+ yard (= 0.9.20)
23
+
24
+ BUNDLED WITH
25
+ 1.17.2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Jan Xie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # ruby-bitcoin-secp256k1
2
+
3
+ ## Prerequisite
4
+
5
+ In order to use this gem, [libsecp256k1](https://github.com/bitcoin/secp256k1) must be in place.
6
+
7
+ ### macOS
8
+
9
+ ```bash
10
+ brew tap nervosnetwork/tap
11
+ brew install libsecp256k1
12
+ ```
13
+
14
+ ### Ubuntu 18.04 or above
15
+
16
+ ```bash
17
+ sudo apt install libsecp256k1-dev
18
+ ```
19
+
20
+ ### Ubuntu 16.04 or below
21
+
22
+ ```
23
+ $ git clone https://github.com/bitcoin-core/secp256k1.git && cd secp256k1
24
+ $ ./autogen.sh
25
+ $ ./configure
26
+ $ make
27
+ $ sudo make install
28
+ ```
29
+
30
+ Or if you have cloned the project, you could go to project root and run this install script:
31
+
32
+ ```
33
+ git submodule update --init --recursive
34
+ ./install_lib.sh
35
+ ```
36
+
37
+ The recovery and ecdh modules are optional. If your local installation of secp256k1 doesn't enable them then the gem would throw `LoadModuleError` when related functions are invoked.
38
+
39
+ ## Install
40
+
41
+ ```
42
+ gem i bitcoin-secp256k1
43
+ ```
44
+
45
+ Then `require 'secp256k1'` (without `bitcoin-` prefix) in your source code.
46
+
47
+ ## Usage
48
+
49
+ Check [test](test) for examples.
50
+
51
+ ## LICENSE
52
+
53
+ [MIT License](LICENSE)
54
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs += %w(lib test)
5
+ t.test_files = FileList['test/**/*_test.rb']
6
+ t.verbose = true
7
+ end
8
+
9
+ task default: [:test]
data/install_lib.sh ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ pushd secp256k1
4
+ ./autogen.sh
5
+ ./configure --enable-module-recovery --enable-experimental --enable-module-ecdh --enable-module-schnorr
6
+ make && sudo make install
7
+ popd
@@ -0,0 +1,107 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+ require 'ffi'
3
+ require 'ffi/tools/const_generator'
4
+
5
+ module BitcoinSecp256k1
6
+ module C
7
+ extend FFI::Library
8
+
9
+ ffi_lib (ENV['LIBSECP256K1'] || 'libsecp256k1')
10
+
11
+ Constants = FFI::ConstGenerator.new('BitcoinSecp256k1', required: true) do |gen|
12
+ gen.include 'secp256k1.h'
13
+
14
+ gen.const(:SECP256K1_EC_COMPRESSED)
15
+ gen.const(:SECP256K1_EC_UNCOMPRESSED)
16
+
17
+ gen.const(:SECP256K1_CONTEXT_SIGN)
18
+ gen.const(:SECP256K1_CONTEXT_VERIFY)
19
+ gen.const(:SECP256K1_CONTEXT_NONE)
20
+ end
21
+
22
+ class Pubkey < FFI::Struct
23
+ layout :data, [:uchar, 64]
24
+ end
25
+
26
+ class ECDSASignature < FFI::Struct
27
+ layout :data, [:uchar, 64]
28
+ end
29
+
30
+ class ECDSARecoverableSignature < FFI::Struct
31
+ layout :data, [:uchar, 65]
32
+ end
33
+
34
+ # secp256k1_context* secp256k1_context_create(unsigned int flags)
35
+ attach_function :secp256k1_context_create, [:uint], :pointer
36
+
37
+ # void secp256k1_context_destroy(secp256k1_context* ctx)
38
+ attach_function :secp256k1_context_destroy, [:pointer], :void
39
+
40
+ # int secp256k1_ec_pubkey_parse(const secp256k1_context* ctx, secp256k1_pubkey* pubkey, const unsigned char *input, size_t inputlen)
41
+ attach_function :secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int
42
+
43
+ # int secp256k1_ec_pubkey_create(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *seckey)
44
+ attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int
45
+
46
+ # int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_pubkey *pubkey, unsigned int flags)
47
+ attach_function :secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int
48
+
49
+ # int secp256k1_ec_seckey_verify(const secp256k1_context* ctx, const unsigned char *seckey)
50
+ attach_function :secp256k1_ec_seckey_verify, [:pointer, :pointer], :int
51
+
52
+ # int secp256k1_ecdsa_signature_serialize_der(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_ecdsa_signature* sig)
53
+ attach_function :secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int
54
+
55
+ # int secp256k1_ecdsa_signature_serialize_compact(const secp256k1_context* ctx, unsigned char *output64, const secp256k1_ecdsa_signature* sig)
56
+ attach_function :secp256k1_ecdsa_signature_serialize_compact, [:pointer, :pointer, :pointer], :int
57
+
58
+ # int secp256k1_ecdsa_signature_parse_der(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen)
59
+ attach_function :secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int
60
+
61
+ # int secp256k1_ecdsa_signature_parse_compact(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input64)
62
+ attach_function :secp256k1_ecdsa_signature_parse_compact, [:pointer, :pointer, :pointer], :int
63
+
64
+ # int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void *ndata)
65
+ attach_function :secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
66
+
67
+ # int secp256k1_ecdsa_verify(const secp256k1_context *ctx, const secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const secp256k1_pubkey *pubkey)
68
+ attach_function :secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int
69
+
70
+ # int secp256k1_ecdsa_signature_normalize(const secp256k1_context *ctx, const secp256k1_ecdsa_signature *sigout, const secp256k1_ecdsa_signature *sigin)
71
+ attach_function :secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int
72
+
73
+ # recovery module
74
+ begin
75
+ # int secp256k1_ecdsa_recoverable_signature_parse_compact(const secp256k1_context *ctx, secp256k1_ecdsa_recoverable_signature *sig, const unsigned char *input64, int recid)
76
+ attach_function :secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int
77
+
78
+ # int secp256k1_ecdsa_recoverable_signature_convert(const secp256k1_context *ctx, secp256k1_ecdsa_signature *sig, const secp256k1_ecdsa_recoverable_signature *sigin)
79
+ attach_function :secp256k1_ecdsa_recoverable_signature_convert, [:pointer, :pointer, :pointer], :int
80
+
81
+ # int secp256k1_ecdsa_recoverable_signature_serialize_compact(const secp256k1_context *ctx, unsigned char *output64, int *recid, const secp256k1_ecdsa_recoverable_signature *sig)
82
+ attach_function :secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int
83
+
84
+ # int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *sig, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void *ndata)
85
+ attach_function :secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
86
+
87
+ # int secp256k1_ecdsa_recover(const secp256k1_context* ctx, secp256k1_pubkey* pubkey, secp256k1_ecdsa_recoverable_signature *sig, const unsigned char *msg32)
88
+ attach_function :secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int
89
+ rescue FFI::NotFoundError
90
+ end
91
+
92
+ def self.module_recovery_enabled?
93
+ respond_to? :secp256k1_ecdsa_recover
94
+ end
95
+
96
+ # ecdh module
97
+ begin
98
+ # int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *result, const secp256k1_pubkey *point, const unsigned char *scalar)
99
+ attach_function :secp256k1_ecdh, [:pointer, :pointer, :pointer, :pointer], :int
100
+ rescue FFI::NotFoundError
101
+ end
102
+
103
+ def self.module_ecdh_enabled?
104
+ respond_to :secp256k1_ecdh
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,102 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+ module BitcoinSecp256k1
3
+ module ECDSA
4
+
5
+ SIZE_SERIALIZED = 74
6
+ SIZE_COMPACT = 64
7
+
8
+ def ecdsa_serialize(raw_sig)
9
+ output = FFI::MemoryPointer.new(:uchar, SIZE_SERIALIZED)
10
+ outputlen = FFI::MemoryPointer.new(:size_t).put_uint(0, SIZE_SERIALIZED)
11
+
12
+ res = C.secp256k1_ecdsa_signature_serialize_der(@ctx, output, outputlen, raw_sig)
13
+ raise AssertError, "failed to seriazlie signature" unless res == 1
14
+
15
+ output.read_bytes(outputlen.read_uint)
16
+ end
17
+
18
+ def ecdsa_deserialize(ser_sig)
19
+ raw_sig = C::ECDSASignature.new.pointer
20
+
21
+ res = C.secp256k1_ecdsa_signature_parse_der(@ctx, raw_sig, ser_sig, ser_sig.size)
22
+ raise AssertError, "raw signature parse failed" unless res == 1
23
+
24
+ raw_sig
25
+ end
26
+
27
+ def ecdsa_serialize_compact(raw_sig)
28
+ output = FFI::MemoryPointer.new(:uchar, SIZE_COMPACT)
29
+
30
+ res = C.secp256k1_ecdsa_signature_serialize_compact(@ctx, output, raw_sig)
31
+ raise AssertError, "failed to seriazlie compact signature" unless res == 1
32
+
33
+ output.read_bytes(SIZE_COMPACT)
34
+ end
35
+
36
+ def ecdsa_deserialize_compact(ser_sig)
37
+ raise ArgumentError, 'invalid signature length' unless ser_sig.size == 64
38
+
39
+ raw_sig = C::ECDSASignature.new.pointer
40
+
41
+ res = C.secp256k1_ecdsa_signature_parse_compact(@ctx, raw_sig, ser_sig)
42
+ raise AssertError, "failed to deserialize compact signature" unless res == 1
43
+
44
+ raw_sig
45
+ end
46
+
47
+ ##
48
+ # Check and optionally convert a signature to a normalized lower-S form. If
49
+ # check_only is `true` then the normalized signature is not returned.
50
+ #
51
+ # This function always return a tuple containing a boolean (`true` if not
52
+ # previously normalized or `false` if signature was already normalized),
53
+ # and the normalized signature. When check_only is `true`, the normalized
54
+ # signature returned is always `nil`.
55
+ #
56
+ def ecdsa_signature_normalize(raw_sig, check_only: false)
57
+ sigout = check_only ? nil : C::ECDSASignature.new.pointer
58
+ res = C.secp256k1_ecdsa_signature_normalize(@ctx, sigout, raw_sig)
59
+ [res == 1, sigout]
60
+ end
61
+
62
+ def ecdsa_recover(msg, recover_sig, raw: false, digest: Digest::SHA256)
63
+ raise AssertError, 'instance not configured for ecdsa recover' if (@flags & ALL_FLAGS) != ALL_FLAGS
64
+
65
+ msg32 = hash32 msg, raw, digest
66
+ pubkey = C::Pubkey.new.pointer
67
+
68
+ res = C.secp256k1_ecdsa_recover(@ctx, pubkey, recover_sig, msg32)
69
+ raise AssertError, 'failed to recover ECDSA public key' unless res == 1
70
+
71
+ pubkey
72
+ end
73
+
74
+ def ecdsa_recoverable_serialize(recover_sig)
75
+ output = FFI::MemoryPointer.new :uchar, SIZE_COMPACT
76
+ recid = FFI::MemoryPointer.new :int
77
+
78
+ C.secp256k1_ecdsa_recoverable_signature_serialize_compact(@ctx, output, recid, recover_sig)
79
+
80
+ [output.read_bytes(SIZE_COMPACT), recid.read_int]
81
+ end
82
+
83
+ def ecdsa_recoverable_deserialize(ser_sig, rec_id)
84
+ raise ArgumentError, 'invalid rec_id' if rec_id < 0 || rec_id > 3
85
+ raise ArgumentError, 'invalid signature length' if ser_sig.size != 64
86
+
87
+ recover_sig = C::ECDSARecoverableSignature.new.pointer
88
+
89
+ res = C.secp256k1_ecdsa_recoverable_signature_parse_compact(@ctx, recover_sig, ser_sig, rec_id)
90
+ raise AssertError, 'failed to parse ECDSA compact sig' unless res == 1
91
+
92
+ recover_sig
93
+ end
94
+
95
+ def ecdsa_recoverable_convert(recover_sig)
96
+ normal_sig = C::ECDSASignature.new.pointer
97
+ C.secp256k1_ecdsa_recoverable_signature_convert(@ctx, normal_sig, recover_sig)
98
+ normal_sig
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,252 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+ require 'digest'
3
+ require 'securerandom'
4
+
5
+ module BitcoinSecp256k1
6
+
7
+ class BaseKey
8
+ def initialize(ctx, flags)
9
+ @destroy = false
10
+
11
+ unless ctx
12
+ raise ArgumentError, "invalid flags" unless [NO_FLAGS, FLAG_SIGN, FLAG_VERIFY, ALL_FLAGS].include?(flags)
13
+ ctx = FFI::AutoPointer.new C.secp256k1_context_create(flags), C.method(:secp256k1_context_destroy)
14
+ @destroy = true
15
+ end
16
+
17
+ @flags = flags
18
+ @ctx = ctx
19
+ end
20
+ end
21
+
22
+ class PublicKey < BaseKey
23
+ include ECDSA, Utils
24
+
25
+ attr_accessor :public_key
26
+
27
+ def initialize(pubkey: nil, raw: false, flags: FLAG_VERIFY, ctx: nil)
28
+ super(ctx, flags)
29
+
30
+ if pubkey
31
+ if raw
32
+ raise ArgumentError, 'raw pubkey must be bytes' unless pubkey.instance_of?(String)
33
+ @public_key = deserialize pubkey
34
+ else
35
+ #raise ArgumentError, 'pubkey must be an internal object' unless pubkey.instance_of?(String)
36
+ @public_key = pubkey
37
+ end
38
+ else
39
+ @public_key = nil
40
+ end
41
+ end
42
+
43
+ def serialize(compressed: true)
44
+ raise AssertError, 'No public key defined' unless @public_key
45
+
46
+ len_compressed = compressed ? 33 : 65
47
+ res_compressed = FFI::MemoryPointer.new :char, len_compressed
48
+ outlen = FFI::MemoryPointer.new(:size_t).write_uint(len_compressed)
49
+ compflag = compressed ? EC_COMPRESSED : EC_UNCOMPRESSED
50
+
51
+ res = C.secp256k1_ec_pubkey_serialize(@ctx, res_compressed, outlen, @public_key, compflag)
52
+ raise AssertError, 'pubkey serialization failed' unless res == 1
53
+
54
+ res_compressed.read_bytes(len_compressed)
55
+ end
56
+
57
+ def deserialize(pubkey_ser)
58
+ raise ArgumentError, 'unknown public key size (expected 33 or 65)' unless [33,65].include?(pubkey_ser.size)
59
+
60
+ pubkey = C::Pubkey.new.pointer
61
+
62
+ res = C.secp256k1_ec_pubkey_parse(@ctx, pubkey, pubkey_ser, pubkey_ser.size)
63
+ raise AssertError, 'invalid public key' unless res == 1
64
+
65
+ @public_key = pubkey
66
+ pubkey
67
+ end
68
+
69
+ ##
70
+ # Add a number of public keys together.
71
+ #
72
+ def combine(pubkeys)
73
+ raise ArgumentError, 'must give at least 1 pubkey' if pubkeys.empty?
74
+
75
+ outpub = FFI::Pubkey.new.pointer
76
+ #pubkeys.each {|item| }
77
+
78
+ res = C.secp256k1_ec_pubkey_combine(@ctx, outpub, pubkeys, pubkeys.size)
79
+ raise AssertError, 'failed to combine public keys' unless res == 1
80
+
81
+ @public_key = outpub
82
+ outpub
83
+ end
84
+
85
+ ##
86
+ # Tweak the current public key by adding a 32 byte scalar times the
87
+ # generator to it and return a new PublicKey instance.
88
+ #
89
+ def tweak_add(scalar)
90
+ tweak_public :secp256k1_ec_pubkey_tweak_add, scalar
91
+ end
92
+
93
+ ##
94
+ # Tweak the current public key by multiplying it by a 32 byte scalar and
95
+ # return a new PublicKey instance.
96
+ #
97
+ def tweak_mul(scalar)
98
+ tweak_public :secp256k1_ec_pubkey_tweak_mul, scalar
99
+ end
100
+
101
+ def ecdsa_verify(msg, raw_sig, raw: false, digest: Digest::SHA256)
102
+ raise AssertError, 'No public key defined' unless @public_key
103
+ raise AssertError, 'instance not configured for sig verification' if (@flags & FLAG_VERIFY) != FLAG_VERIFY
104
+
105
+ msg32 = hash32 msg, raw, digest
106
+
107
+ C.secp256k1_ecdsa_verify(@ctx, raw_sig, msg32, @public_key) == 1
108
+ end
109
+
110
+ def ecdh(scalar)
111
+ raise AssertError, 'No public key defined' unless @public_key
112
+ raise ArgumentError, 'scalar must be composed of 32 bytes' unless scalar.instance_of?(String) && scalar.size == 32
113
+
114
+ result = FFI::MemoryPointer.new :char, 32
115
+
116
+ res = C.secp256k1_ecdh @ctx, result, @public_key, scalar
117
+ raise AssertError, "invalid scalar (#{scalar})" unless res == 1
118
+
119
+ result.read_bytes(32)
120
+ end
121
+
122
+ private
123
+
124
+ def tweak_public(meth, scalar)
125
+ raise ArgumentError, 'scalar must be composed of 32 bytes' unless scalar.instance_of?(String) && scalar.size == 32
126
+ raise AssertError, 'No public key defined.' unless @public_key
127
+
128
+ newpub = self.class.new serialize, raw: true
129
+
130
+ res = C.send meth, newpub.public_key, scalar
131
+ raise AssertError, 'Tweak is out of range' unless res == 1
132
+
133
+ newpub
134
+ end
135
+
136
+ end
137
+
138
+ class PrivateKey < BaseKey
139
+ include ECDSA, Utils
140
+
141
+ attr :pubkey
142
+
143
+ def initialize(privkey: nil, raw: true, flags: ALL_FLAGS, ctx: nil)
144
+ raise AssertError, "invalid flags" unless [ALL_FLAGS, FLAG_SIGN].include?(flags)
145
+
146
+ super(ctx, flags)
147
+
148
+ @pubkey = nil
149
+ @private_key = nil
150
+
151
+ if privkey
152
+ if raw
153
+ raise ArgumentError, "privkey must be composed of 32 bytes" unless privkey.instance_of?(String) && privkey.size == 32
154
+ set_raw_privkey privkey
155
+ else
156
+ deserialize privkey
157
+ end
158
+ else
159
+ set_raw_privkey generate_private_key
160
+ end
161
+ end
162
+
163
+ def ecdsa_sign(msg, raw: false, digest: Digest::SHA256)
164
+ msg32 = hash32 msg, raw, digest
165
+ raw_sig = C::ECDSASignature.new.pointer
166
+
167
+ res = C.secp256k1_ecdsa_sign @ctx, raw_sig, msg32, @private_key, nil, nil
168
+ raise AssertError, "failed to sign" unless res == 1
169
+
170
+ raw_sig
171
+ end
172
+
173
+ def ecdsa_sign_recoverable(msg, raw: false, digest: Digest::SHA256)
174
+ raise LoadModuleError, "libsecp256k1 recovery module is not enabled" unless C.module_recovery_enabled?
175
+
176
+ msg32 = hash32 msg, raw, digest
177
+ raw_sig = C::ECDSARecoverableSignature.new.pointer
178
+
179
+ res = C.secp256k1_ecdsa_sign_recoverable @ctx, raw_sig, msg32, @private_key, nil, nil
180
+ raise AssertError, "failed to sign" unless res == 1
181
+
182
+ raw_sig
183
+ end
184
+
185
+ def set_raw_privkey(privkey)
186
+ raise ArgumentError, "invalid private key" unless C.secp256k1_ec_seckey_verify(@ctx, privkey)
187
+ @private_key = privkey
188
+ update_public_key
189
+ end
190
+
191
+ ##
192
+ # Tweak the current private key by adding a 32 bytes scalar to it and
193
+ # return a new raw private key composed of 32 bytes.
194
+ #
195
+ def tweak_add(scalar)
196
+ tweak_private :secp256k1_ec_privkey_tweak_add, scalar
197
+ end
198
+
199
+ ##
200
+ # Tweak the current private key by multiplying it by a 32 byte scalar and
201
+ # return a new raw private key composed of 32 bytes.
202
+ #
203
+ def tweak_mul(scalar)
204
+ tweak_private :secp256k1_ec_pubkey_tweak_mul, scalar
205
+ end
206
+
207
+ private
208
+
209
+ def tweak_private(meth, scalar)
210
+ raise ArgumentError, "scalar must be composed of 32 bytes" unless scalar.instance_of?(String) && scalar.size == 32
211
+
212
+ key = FFI::MemoryPointer.new(:uchar, 32).put_string(@private_key)
213
+
214
+ C.send meth, @ctx, key, scalar
215
+ raise AssertError, "Tweak is out of range" unless res == 1
216
+
217
+ key.read_string(32)
218
+ end
219
+
220
+ def update_public_key
221
+ public_key = generate_public_key @private_key
222
+ @pubkey = PublicKey.new pubkey: public_key, raw: false, ctx: @ctx, flags: @flags
223
+ end
224
+
225
+ def generate_public_key(privkey)
226
+ pubkey_ptr = C::Pubkey.new.pointer
227
+
228
+ res = C.secp256k1_ec_pubkey_create @ctx, pubkey_ptr, privkey
229
+ raise AssertError, "failed to generate public key" unless res == 1
230
+
231
+ pubkey_ptr
232
+ end
233
+
234
+ def generate_private_key
235
+ SecureRandom.random_bytes(32)
236
+ end
237
+
238
+ def serialize
239
+ encode_hex @private_key
240
+ end
241
+
242
+ def deserialize(privkey_serialized)
243
+ raise ArgumentError, "invalid private key" unless privkey_serialized.size == 64
244
+
245
+ rawkey = decode_hex privkey_serialized
246
+ set_raw_privkey rawkey
247
+
248
+ @private_key
249
+ end
250
+
251
+ end
252
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+ module BitcoinSecp256k1
3
+ module Utils
4
+
5
+ extend self
6
+
7
+ def hash32(msg, raw, digest)
8
+ msg32 = raw ? msg : digest.digest(msg)
9
+ raise AssertError, "digest function must produce 256 bits" unless msg32.size == 32
10
+ msg32
11
+ end
12
+
13
+ def encode_hex(b)
14
+ b.unpack('H*').first
15
+ end
16
+
17
+ def decode_hex(s)
18
+ [s].pack('H*')
19
+ end
20
+
21
+ end
22
+ end