bitcoin-secp256k1 0.1.1

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
+ SHA1:
3
+ metadata.gz: 07e0b24c6a5eff2bebfa1287f30299e3e20516a9
4
+ data.tar.gz: 64b92f4a1274272b62a2b4f8447f7f97b3726e05
5
+ SHA512:
6
+ metadata.gz: f4123386a3b2add1c25e711eae40013a418fad7d614f0283caa0f5f8e961889de623be103b023035d441d42d4823b964ea129218d857ad66c722560b29f910f1
7
+ data.tar.gz: 45abd20b35e91cbf4ac5b33fa3b472a32383f140426edce77eabab58180f39ea9afc4e63b85f4cfc6a59f257e6e1dfe6ed8bca1ac3199e8239dedae860ada7bb
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 = git@github.com:bitcoin/secp256k1.git
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
+ bitcoin-secp256k1 (0.1.1)
5
+ ffi (>= 1.9.10)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ffi (1.9.10)
11
+ minitest (5.8.3)
12
+ rake (10.5.0)
13
+ yard (0.8.7.6)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ bitcoin-secp256k1!
20
+ minitest (= 5.8.3)
21
+ rake (~> 10.5)
22
+ yard (= 0.8.7.6)
23
+
24
+ BUNDLED WITH
25
+ 1.11.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,29 @@
1
+ # ruby-bitcoin-secp256k1
2
+
3
+ ## Prerequiste
4
+
5
+ In order to use this gem, [libsecp256k1](https://github.com/bitcoin/secp256k1) with recovery module enabled must be in place.
6
+
7
+ If you have cloned the project and in project root:
8
+
9
+ ```
10
+ git submodule update --init --recursive
11
+ ./install_lib.sh
12
+ ```
13
+
14
+ ## Install
15
+
16
+ ```
17
+ gem i bitcoin-secp256k1
18
+ ```
19
+
20
+ Then require 'secp256k1' (without `bitcoin-` prefix) in your source code.
21
+
22
+ ## Usage
23
+
24
+ Check [test](test) for examples.
25
+
26
+ ## LICENSE
27
+
28
+ [MIT License](LICENSE)
29
+
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]
@@ -0,0 +1,23 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require 'secp256k1/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "bitcoin-secp256k1"
7
+ s.version = Secp256k1::VERSION
8
+ s.authors = ["Jan Xie"]
9
+ s.email = ["jan.h.xie@gmail.com"]
10
+ s.homepage = "https://github.com/janx/ruby-bitcoin-secp256k1"
11
+ s.summary = "Ruby binding to bitcoin's secp256k1 implementation."
12
+ s.description = "Ruby binding to bitcoin's secp256k1 implementation."
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+
18
+ s.add_dependency('ffi', '>= 1.9.10')
19
+
20
+ s.add_development_dependency('rake', '~> 10.5')
21
+ s.add_development_dependency('minitest', '5.8.3')
22
+ s.add_development_dependency('yard', '0.8.7.6')
23
+ end
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
data/lib/secp256k1.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'secp256k1/version'
2
+ require 'secp256k1/c'
3
+ require 'secp256k1/utils'
4
+ require 'secp256k1/ecdsa'
5
+ require 'secp256k1/key'
6
+
7
+ module Secp256k1
8
+
9
+ EC_COMPRESSED = C::Constants['SECP256K1_EC_COMPRESSED'].to_i
10
+ EC_UNCOMPRESSED = C::Constants['SECP256K1_EC_UNCOMPRESSED'].to_i
11
+
12
+ FLAG_SIGN = C::Constants['SECP256K1_CONTEXT_SIGN'].to_i
13
+ FLAG_VERIFY = C::Constants['SECP256K1_CONTEXT_VERIFY'].to_i
14
+ NO_FLAGS = C::Constants['SECP256K1_CONTEXT_NONE'].to_i
15
+ ALL_FLAGS = FLAG_SIGN | FLAG_VERIFY
16
+
17
+ class AssertError < StandardError; end
18
+
19
+ end
@@ -0,0 +1,61 @@
1
+ require 'ffi'
2
+ require 'ffi/tools/const_generator'
3
+
4
+ module Secp256k1
5
+ module C
6
+ extend FFI::Library
7
+
8
+ ffi_lib (ENV['LIBSECP256K1'] || 'libsecp256k1')
9
+
10
+ Constants = FFI::ConstGenerator.new('Secp256k1', required: true) do |gen|
11
+ gen.include 'secp256k1.h'
12
+
13
+ gen.const(:SECP256K1_EC_COMPRESSED)
14
+ gen.const(:SECP256K1_EC_UNCOMPRESSED)
15
+
16
+ gen.const(:SECP256K1_CONTEXT_SIGN)
17
+ gen.const(:SECP256K1_CONTEXT_VERIFY)
18
+ gen.const(:SECP256K1_CONTEXT_NONE)
19
+ end
20
+
21
+ class Pubkey < FFI::Struct
22
+ layout :data, [:uchar, 64]
23
+ end
24
+
25
+ class ECDSASignature < FFI::Struct
26
+ layout :data, [:uchar, 64]
27
+ end
28
+
29
+ class ECDSARecoverableSignature < FFI::Struct
30
+ layout :data, [:uchar, 65]
31
+ end
32
+
33
+ # secp256k1_context* secp256k1_context_create(unsigned int flags)
34
+ attach_function :secp256k1_context_create, [:uint], :pointer
35
+
36
+ # int secp256k1_ec_pubkey_create(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *seckey)
37
+ attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int
38
+
39
+ # int secp256k1_ec_seckey_verify(const secp256k1_context* ctx, const unsigned char *seckey)
40
+ attach_function :secp256k1_ec_seckey_verify, [:pointer, :pointer], :int
41
+
42
+ # int secp256k1_ecdsa_signature_serialize_der(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_ecdsa_signature* sig)
43
+ attach_function :secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int
44
+
45
+ # int secp256k1_ecdsa_signature_serialize_compact(const secp256k1_context* ctx, unsigned char *output64, const secp256k1_ecdsa_signature* sig)
46
+ attach_function :secp256k1_ecdsa_signature_serialize_compact, [:pointer, :pointer, :pointer], :int
47
+
48
+ # int secp256k1_ecdsa_signature_parse_der(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen)
49
+ attach_function :secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int
50
+
51
+ # int secp256k1_ecdsa_signature_parse_compact(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input64)
52
+ attach_function :secp256k1_ecdsa_signature_parse_compact, [:pointer, :pointer, :pointer], :int
53
+
54
+ # 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)
55
+ attach_function :secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
56
+
57
+ # int secp256k1_ecdsa_verify(const secp256k1_context *ctx, const secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const secp256k1_pubkey *pubkey)
58
+ attach_function :secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int
59
+
60
+ end
61
+ end
@@ -0,0 +1,47 @@
1
+ module Secp256k1
2
+ module ECDSA
3
+
4
+ SIZE_SERIALIZED = 74
5
+ SIZE_COMPACT = 64
6
+
7
+ def ecdsa_serialize(raw_sig)
8
+ output = FFI::MemoryPointer.new(:uchar, SIZE_SERIALIZED)
9
+ outputlen = FFI::MemoryPointer.new(:size_t).put_uint(0, SIZE_SERIALIZED)
10
+
11
+ res = C.secp256k1_ecdsa_signature_serialize_der(@ctx, output, outputlen, raw_sig)
12
+ raise AssertError, "failed to seriazlie signature" unless res == 1
13
+
14
+ output.read_bytes(outputlen.read_uint)
15
+ end
16
+
17
+ def ecdsa_deserialize(ser_sig)
18
+ raw_sig = C::ECDSASignature.new.pointer
19
+
20
+ res = C.secp256k1_ecdsa_signature_parse_der(@ctx, raw_sig, ser_sig, ser_sig.size)
21
+ raise AssertError, "raw signature parse failed" unless res == 1
22
+
23
+ raw_sig
24
+ end
25
+
26
+ def ecdsa_serialize_compact(raw_sig)
27
+ output = FFI::MemoryPointer.new(:uchar, SIZE_COMPACT)
28
+
29
+ res = C.secp256k1_ecdsa_signature_serialize_compact(@ctx, output, raw_sig)
30
+ raise AssertError, "failed to seriazlie compact signature" unless res == 1
31
+
32
+ output.read_bytes(SIZE_COMPACT)
33
+ end
34
+
35
+ def ecdsa_deserialize_compact(ser_sig)
36
+ raise ArgumentError, 'invalid signature length' unless ser_sig.size == 64
37
+
38
+ raw_sig = C::ECDSASignature.new.pointer
39
+
40
+ res = C.secp256k1_ecdsa_signature_parse_compact(@ctx, raw_sig, ser_sig)
41
+ raise AssertError, "failed to deserialize compact signature" unless res == 1
42
+
43
+ raw_sig
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,253 @@
1
+ require 'digest'
2
+ require 'securerandom'
3
+
4
+ module Secp256k1
5
+
6
+ class BaseKey
7
+ def initialize(ctx, flags)
8
+ @destroy = false
9
+
10
+ unless ctx
11
+ raise ArgumentError, "invalid flags" unless [NO_FLAGS, FLAG_SIGN, FLAG_VERIFY, ALL_FLAGS].include?(flags)
12
+ ctx = C.secp256k1_context_create flags
13
+ @destroy = true
14
+ end
15
+
16
+ @flags = flags
17
+ @ctx = ctx
18
+
19
+ ObjectSpace.define_finalizer(self) do |id|
20
+ C.secp256k1_context_destroy @ctx if @destroy
21
+ end
22
+ end
23
+ end
24
+
25
+ class PublicKey < BaseKey
26
+ include ECDSA, Utils
27
+
28
+ attr :public_key
29
+
30
+ def initialize(pubkey: nil, raw: false, flags: FLAG_VERIFY, ctx: nil)
31
+ super(ctx, flags)
32
+
33
+ if pubkey
34
+ if raw
35
+ raise ArgumentError, 'raw pubkey must be bytes' unless pubkey.instance_of?(String)
36
+ @public_key = deserialize pubkey
37
+ else
38
+ #raise ArgumentError, 'pubkey must be an internal object' unless pubkey.instance_of?(String)
39
+ @public_key = pubkey
40
+ end
41
+ else
42
+ @public_key = nil
43
+ end
44
+ end
45
+
46
+ def serialize(compressed: true)
47
+ raise AssertError, 'No public key defined' unless @public_key
48
+
49
+ len_compressed = compressed ? 33 : 65
50
+ res_compressed = FFI::MemoryPointer.new :char, len_compressed
51
+ outlen = FFI::MemoryPointer.new(:size_t).write_uint(len_compressed)
52
+ compflag = compressed ? EC_COMPRESSED : EC_UNCOMPRESSED
53
+
54
+ res = C.secp256k1_ec_pubkey_serialize(@ctx, res_compressed, outlen, @public_key, compflag)
55
+ raise AssertError, 'pubkey serialization failed' unless res == 1
56
+
57
+ res_compressed.read_bytes(len_compressed)
58
+ end
59
+
60
+ def deserialize(pubkey_ser)
61
+ raise ArgumentError, 'unknown public key size (expected 33 or 65)' unless [33,65].include?(pubkey_ser.size)
62
+
63
+ pubkey = C::Pubkey.new.pointer
64
+
65
+ res = C.secp256k1_ec_pubkey_parse(@ctx, pubkey, pubkey_ser, pubkey_ser.size)
66
+ raise AssertError, 'invalid public key' unless res == 1
67
+
68
+ @public_key = pubkey
69
+ pubkey
70
+ end
71
+
72
+ ##
73
+ # Add a number of public keys together.
74
+ #
75
+ def combine(pubkeys)
76
+ raise ArgumentError, 'must give at least 1 pubkey' if pubkeys.empty?
77
+
78
+ outpub = FFI::Pubkey.new.pointer
79
+ #pubkeys.each {|item| }
80
+
81
+ res = C.secp256k1_ec_pubkey_combine(@ctx, outpub, pubkeys, pubkeys.size)
82
+ raise AssertError, 'failed to combine public keys' unless res == 1
83
+
84
+ @public_key = outpub
85
+ outpub
86
+ end
87
+
88
+ ##
89
+ # Tweak the current public key by adding a 32 byte scalar times the
90
+ # generator to it and return a new PublicKey instance.
91
+ #
92
+ def tweak_add(scalar)
93
+ tweak_public :secp256k1_ec_pubkey_tweak_add, scalar
94
+ end
95
+
96
+ ##
97
+ # Tweak the current public key by multiplying it by a 32 byte scalar and
98
+ # return a new PublicKey instance.
99
+ #
100
+ def tweak_mul(scalar)
101
+ tweak_public :secp256k1_ec_pubkey_tweak_mul, scalar
102
+ end
103
+
104
+ def ecdsa_verify(msg, raw_sig, raw: false, digest: Digest::SHA256)
105
+ raise AssertError, 'No public key defined' unless @public_key
106
+ raise AssertError, 'instance not configured for sig verification' if (@flags & FLAG_VERIFY) != FLAG_VERIFY
107
+
108
+ msg32 = hash32 msg, raw, digest
109
+
110
+ !!C.secp256k1_ecdsa_verify(@ctx, raw_sig, msg32, @public_key)
111
+ end
112
+
113
+ def ecdh(scalar)
114
+ raise AssertError, 'No public key defined' unless @public_key
115
+ raise ArgumentError, 'scalar must be composed of 32 bytes' unless scalar.instance_of?(String) && scalar.size == 32
116
+
117
+ result = FFI::MemoryPointer.new :char, 32
118
+
119
+ res = C.secp256k1_ecdh @ctx, result, @public_key, scalar
120
+ raise AssertError, "invalid scalar (#{scalar})" unless res == 1
121
+
122
+ result.read_bytes(32)
123
+ end
124
+
125
+ private
126
+
127
+ def tweak_public(meth, scalar)
128
+ raise ArgumentError, 'scalar must be composed of 32 bytes' unless scalar.instance_of?(String) && scalar.size == 32
129
+ raise AssertError, 'No public key defined.' unless @public_key
130
+
131
+ newpub = self.class.new serialize, raw: true
132
+
133
+ res = C.send meth, newpub.public_key, scalar
134
+ raise AssertError, 'Tweak is out of range' unless res == 1
135
+
136
+ newpub
137
+ end
138
+
139
+ end
140
+
141
+ class PrivateKey < BaseKey
142
+ include ECDSA, Utils
143
+
144
+ attr :pubkey
145
+
146
+ def initialize(privkey: nil, raw: true, flags: ALL_FLAGS, ctx: nil)
147
+ raise AssertError, "invalid flags" unless [ALL_FLAGS, FLAG_SIGN].include?(flags)
148
+
149
+ super(ctx, flags)
150
+
151
+ @pubkey = nil
152
+ @private_key = nil
153
+
154
+ if privkey
155
+ if raw
156
+ raise ArgumentError, "privkey must be composed of 32 bytes" unless privkey.instance_of?(String) && privkey.size == 32
157
+ set_raw_privkey privkey
158
+ else
159
+ deserialize privkey
160
+ end
161
+ else
162
+ set_raw_privkey generate_private_key
163
+ end
164
+ end
165
+
166
+ def ecdsa_sign(msg, raw: false, digest: Digest::SHA256)
167
+ msg32 = hash32 msg, raw, digest
168
+ raw_sig = C::ECDSASignature.new.pointer
169
+
170
+ res = C.secp256k1_ecdsa_sign @ctx, raw_sig, msg32, @private_key, nil, nil
171
+ raise AssertError, "failed to sign" unless res == 1
172
+
173
+ raw_sig
174
+ end
175
+
176
+ def ecdsa_sign_recoverable(msg, raw: false, digest: Digest::SHA256)
177
+ msg32 = hash32 msg, raw, digest
178
+ raw_sig = FFI::MemoryPointer.new :byte, C::ECDSARecoverableSignature, false
179
+
180
+ res = C.secp256k1_ecdsa_sign_recoverable @ctx, raw_sig, msg32, @private_key, nil, nil
181
+ raise AssertError, "failed to sign" unless res == 1
182
+
183
+ raw_sig
184
+ end
185
+
186
+ def set_raw_privkey(privkey)
187
+ raise ArgumentError, "invalid private key" unless C.secp256k1_ec_seckey_verify(@ctx, privkey)
188
+ @private_key = privkey
189
+ update_public_key
190
+ end
191
+
192
+ ##
193
+ # Tweak the current private key by adding a 32 bytes scalar to it and
194
+ # return a new raw private key composed of 32 bytes.
195
+ #
196
+ def tweak_add(scalar)
197
+ tweak_private :secp256k1_ec_privkey_tweak_add, scalar
198
+ end
199
+
200
+ ##
201
+ # Tweak the current private key by multiplying it by a 32 byte scalar and
202
+ # return a new raw private key composed of 32 bytes.
203
+ #
204
+ def tweak_mul(scalar)
205
+ tweak_private :secp256k1_ec_pubkey_tweak_mul, scalar
206
+ end
207
+
208
+ private
209
+
210
+ def tweak_private(meth, scalar)
211
+ raise ArgumentError, "scalar must be composed of 32 bytes" unless scalar.instance_of?(String) && scalar.size == 32
212
+
213
+ key = FFI::MemoryPointer.new(:uchar, 32).put_string(@private_key)
214
+
215
+ C.send meth, @ctx, key, scalar
216
+ raise AssertError, "Tweak is out of range" unless res == 1
217
+
218
+ key.read_string(32)
219
+ end
220
+
221
+ def update_public_key
222
+ public_key = generate_public_key @private_key
223
+ @pubkey = PublicKey.new pubkey: public_key, raw: false, ctx: @ctx, flags: @flags
224
+ end
225
+
226
+ def generate_public_key(privkey)
227
+ pubkey_ptr = C::Pubkey.new.pointer
228
+
229
+ res = C.secp256k1_ec_pubkey_create @ctx, pubkey_ptr, privkey
230
+ raise AssertError, "failed to generate public key" unless res == 1
231
+
232
+ pubkey_ptr
233
+ end
234
+
235
+ def generate_private_key
236
+ SecureRandom.random_bytes(32)
237
+ end
238
+
239
+ def serialize
240
+ encode_hex @private_key
241
+ end
242
+
243
+ def deserialize(privkey_serialized)
244
+ raise ArgumentError, "invalid private key" unless privkey_serialized.size == 64
245
+
246
+ rawkey = decode_hex privkey_serialized
247
+ set_raw_privkey rawkey
248
+
249
+ @private_key
250
+ end
251
+
252
+ end
253
+ end