ruby-bitcoin-secp256k1 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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