rbsecp256k1 5.1.1 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -34
- data/documentation/context.md +21 -4
- data/documentation/index.md +63 -4
- data/documentation/key_pair.md +4 -7
- data/documentation/public_key.md +4 -0
- data/documentation/schnorr_signature.md +30 -0
- data/documentation/secp256k1.md +5 -0
- data/documentation/shared_secret.md +0 -2
- data/documentation/xonly_public_key.md +29 -0
- data/ext/rbsecp256k1/extconf.rb +19 -14
- data/ext/rbsecp256k1/rbsecp256k1.c +626 -117
- data/lib/rbsecp256k1/context.rb +7 -0
- data/lib/rbsecp256k1/version.rb +1 -1
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbd0a847d061c266d044174528f14b1351484f58a703e7ec76719122689ae6d4
|
4
|
+
data.tar.gz: c7bbdaf0dc9bdaf24dd5de7c40d95c16f08b72d466216af6789e523ee14bba8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf695d750f38e4d27de61caa0e1ec8d9160925d7c10cc556691c7335478137f71f7f49901d50ade3c9e2c917779b38a3d4ee49ca931a8691cc99e7a751fd8243
|
7
|
+
data.tar.gz: 7b5a75562a90c3e845a0b42477873562991d19ca610b89aef95581e0f7dc010f123e8ed4055225978cb2ad4b6667e184d18915114f69cfdaabb993bfcf4b06a7
|
data/README.md
CHANGED
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
[![Spec](https://github.com/etscrivner/rbsecp256k1/actions/workflows/spec.yml/badge.svg?branch=master)](https://github.com/etscrivner/rbsecp256k1/actions/workflows/spec.yml) [![Gem Version](https://badge.fury.io/rb/rbsecp256k1.svg)](https://badge.fury.io/rb/rbsecp256k1) [![Maintainability](https://api.codeclimate.com/v1/badges/d4b6e27bfa00030ca412/maintainability)](https://codeclimate.com/github/etscrivner/rbsecp256k1/maintainability)
|
4
4
|
|
5
|
-
Native extension gem for secp256k1 ECDSA. Wraps [libsecp256k1](https://github.com/bitcoin-core/secp256k1).
|
6
|
-
rbsecp256k1 3.0.0 and later libsecp256k1 is bundled with the gem.
|
5
|
+
Native extension gem for secp256k1 ECDSA and Schnorr signatures. Wraps [libsecp256k1](https://github.com/bitcoin-core/secp256k1) without the need for FFI.
|
7
6
|
|
8
7
|
* [Documentation](https://github.com/etscrivner/rbsecp256k1/blob/master/documentation/index.md)
|
9
8
|
* [Examples](https://github.com/etscrivner/rbsecp256k1/blob/master/examples/README.md)
|
10
9
|
|
10
|
+
### Features
|
11
|
+
|
12
|
+
* Native extension gem wrapping [libsecp256k1](https://github.com/bitcoin-core/secp256k1), no FFI.
|
13
|
+
* Schnorr signature support.
|
14
|
+
|
11
15
|
### Why wrap libsecp256k1?
|
12
16
|
|
13
17
|
[libsecp256k1](https://github.com/bitcoin-core/secp256k1) is an extremely optimized implementation of public key derivation,
|
@@ -100,38 +104,6 @@ To test with recovery functionality disabled run:
|
|
100
104
|
make test WITH_RECOVERY=0
|
101
105
|
```
|
102
106
|
|
103
|
-
To test with ECDH functionality disabled run:
|
104
|
-
|
105
|
-
```
|
106
|
-
make test WITH_ECDH=0
|
107
|
-
```
|
108
|
-
|
109
|
-
To test with both disabled run:
|
110
|
-
|
111
|
-
```
|
112
|
-
make test WITH_RECOVERY=0 WITH_ECDH=0
|
113
|
-
```
|
114
|
-
|
115
|
-
Testing for memory leaks with valgrind:
|
116
|
-
|
117
|
-
```
|
118
|
-
make memcheck
|
119
|
-
```
|
120
|
-
|
121
|
-
### Building Gem
|
122
|
-
|
123
|
-
```
|
124
|
-
make gem
|
125
|
-
```
|
126
|
-
|
127
|
-
### Installing Gem Locally
|
128
|
-
|
129
|
-
To install the gem locally and verify builds you can run:
|
130
|
-
|
131
|
-
```
|
132
|
-
make install
|
133
|
-
```
|
134
|
-
|
135
107
|
### Uninstall Gem Locally
|
136
108
|
|
137
109
|
You can similarly uninstall the local gem by running the following:
|
data/documentation/context.md
CHANGED
@@ -35,16 +35,16 @@ Instance Methods
|
|
35
35
|
|
36
36
|
#### ecdh(point, scalar)
|
37
37
|
|
38
|
-
**Requires:** libsecp256k1 was built with the experimental ECDH module.
|
39
|
-
|
40
38
|
Takes a `point` ([PublicKey](public_key.md)) and a `scalar` ([PrivateKey](private_key.md)) and returns a new
|
41
39
|
[SharedSecret](shared_secret.md) containing the 32-byte shared secret. Raises a `Secp256k1::Error` if
|
42
40
|
the `scalar` is invalid (zero or causes an overflow).
|
43
41
|
|
44
42
|
#### generate_key_pair
|
45
43
|
|
46
|
-
Generates and returns a new [KeyPair](key_pair.md) using
|
47
|
-
secure random number generator
|
44
|
+
Generates and returns a new [KeyPair](key_pair.md) using Ruby's built-in
|
45
|
+
`SecureRandom` cryptographically secure random number generator. This method
|
46
|
+
simply invokes `key_pair_from_private_key` with the value from
|
47
|
+
`SecureRandom.random_bytes`.
|
48
48
|
|
49
49
|
#### key_pair_from_private_key(private_key_data)
|
50
50
|
|
@@ -73,6 +73,23 @@ Signs the data represented by the SHA-256 hash `hash32` using `private_key` and
|
|
73
73
|
new [RecoverableSignature](recoverable_signature.md). The `private_key` is expected to be a [PrivateKey](private_key.md) and
|
74
74
|
`data` can be either a binary string or text.
|
75
75
|
|
76
|
+
#### sign_schnorr(keypair, message)
|
77
|
+
|
78
|
+
Same as `sign_schnorr_custom(keypair, message, auxrand)` but generates
|
79
|
+
`auxrand` for you using `SecureRandom`. This should almost always be the method
|
80
|
+
used for Schnorr signing.
|
81
|
+
|
82
|
+
#### sign_schnorr_custom(keypair, message, auxrand)
|
83
|
+
|
84
|
+
Sign the given 32-byte binary string `message` using the given `keypair`. It is
|
85
|
+
recommend that `message` be generated using `tagged_sha256`. `auxrand` should
|
86
|
+
be 32-bytes of fresh randomness, but can optionally be `nil` if generating
|
87
|
+
randomness is expensive.
|
88
|
+
|
89
|
+
#### tagged_sha256(tag, message)
|
90
|
+
|
91
|
+
Computes the tagged hash as defined in [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). Returns the 32-byte binary string tagged hash.
|
92
|
+
|
76
93
|
#### verify(signature, public_key, hash32)
|
77
94
|
|
78
95
|
Verifies the given `signature` ([Signature](signature.md)) was signed by
|
data/documentation/index.md
CHANGED
@@ -11,10 +11,12 @@ Classes and Modules
|
|
11
11
|
| [Secp256k1](secp256k1.md) | [Context](context.md) | [Util](util.md)
|
12
12
|
| | [KeyPair](key_pair.md) |
|
13
13
|
| | [PublicKey](public_key.md) |
|
14
|
+
| | [XOnlyPublicKey](xonly_public_key.md) |
|
14
15
|
| | [PrivateKey](private_key.md) |
|
15
16
|
| | [SharedSecret](shared_secret.md) |
|
16
17
|
| | [Signature](signature.md) |
|
17
18
|
| | [RecoverableSignature](recoverable_signature.md) |
|
19
|
+
| | [SchnorrSignature](schnorr_signature.md) |
|
18
20
|
|
19
21
|
Glossary
|
20
22
|
--------
|
@@ -28,6 +30,8 @@ efficient.
|
|
28
30
|
**[PublicKey](public_key.md)** is a Secp256k1 public key. It can come in either
|
29
31
|
compressed or uncompressed format.
|
30
32
|
|
33
|
+
**[XOnlyPublicKey](xonly_public_key.md)** is a Secp256k1 x-only public key.
|
34
|
+
|
31
35
|
**[PrivateKey](private_key.md)** is a 64-byte Secp256k1 private key.
|
32
36
|
|
33
37
|
**[SharedSecret](shared_secret.md)** A 32-byte shared secret computed from a
|
@@ -39,6 +43,8 @@ of a piece of data.
|
|
39
43
|
**[RecoverableSignature](recoverable_signature.md)** is a recoverable ECDSA signature of the SHA-256 message
|
40
44
|
hash of a piece of data.
|
41
45
|
|
46
|
+
**[SchnorrSignature](schnorr_signature.md)** is a Schnorr signature of a 32-byte message.
|
47
|
+
|
42
48
|
Examples
|
43
49
|
--------
|
44
50
|
|
@@ -59,7 +65,7 @@ This example shows how to generate a new public-private key pair:
|
|
59
65
|
```ruby
|
60
66
|
context = Secp256k1::Context.create
|
61
67
|
key_pair = context.generate_key_pair
|
62
|
-
# => #<Secp256k1::KeyPair:0x0000559b0bc876b0
|
68
|
+
# => #<Secp256k1::KeyPair:0x0000559b0bc876b0>
|
63
69
|
```
|
64
70
|
|
65
71
|
### 3. Getting compressed and uncompressed public key representations
|
@@ -162,11 +168,11 @@ context = Secp256k1::Context.create
|
|
162
168
|
|
163
169
|
#1. Load private key alone
|
164
170
|
private_key = Secp256k1::PrivateKey.from_data("I\nX\x85\xAEz}\n\x9B\xA4\\\x81)\xD4\x9Aq\xFDH\t\xBE\x8EP\xC5.\xC6\x1F7-\x86\xA0\xCB\xF9")
|
165
|
-
# => #<Secp256k1::PrivateKey:0x00005647df1bcd30
|
171
|
+
# => #<Secp256k1::PrivateKey:0x00005647df1bcd30>
|
166
172
|
|
167
173
|
# 2. Load key pair from private key data
|
168
174
|
key_pair = context.key_pair_from_private_key("I\nX\x85\xAEz}\n\x9B\xA4\\\x81)\xD4\x9Aq\xFDH\t\xBE\x8EP\xC5.\xC6\x1F7-\x86\xA0\xCB\xF9")
|
169
|
-
# => #<Secp256k1::KeyPair:0x0000559b0bbf9a90
|
175
|
+
# => #<Secp256k1::KeyPair:0x0000559b0bbf9a90>
|
170
176
|
```
|
171
177
|
|
172
178
|
### 7. Loading a public key from binary data
|
@@ -197,6 +203,59 @@ signature = Secp256k1::Signature.from_compact("<\xC6\x7F/\x921l\x89Z\xFBs\x89p\x
|
|
197
203
|
# => #<Secp256k1::Signature:0x0000559b0bdcaa68>
|
198
204
|
```
|
199
205
|
|
206
|
+
Schnorr Signature Examples
|
207
|
+
--------------------------
|
208
|
+
|
209
|
+
### 1. Checking for the Schnorr module
|
210
|
+
|
211
|
+
To check if you have compiled the schnorr signature module into your local
|
212
|
+
libsecp256k1 run the following:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
Secp256k1.have_schnorr?
|
216
|
+
# => true
|
217
|
+
```
|
218
|
+
|
219
|
+
### 2. Calculate the tagged SHA-256 hash of data
|
220
|
+
|
221
|
+
You can produce the tagged SHA-256 hash recommended for use with Schnorr
|
222
|
+
signatures as follows:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
context = Secp256k1::Context.create
|
226
|
+
|
227
|
+
context.tagged_sha256('my_test_tag', 'data')
|
228
|
+
=> "0\x85|\xC8h\x9A\x8C^\x0FoD\xDF\xD0,\x1C\x1EV}\xE8\xA8<\xC5\xA5vI\x9E`=\x9B\xC4\xA8\xAE"
|
229
|
+
```
|
230
|
+
|
231
|
+
### 3. Sign and verify a message using Schnorr signatures
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
context = Secp256k1::Context.create
|
235
|
+
key_pair = context.generate_key_pair
|
236
|
+
message = context.tagged_sha256('my_test_tag', 'data')
|
237
|
+
|
238
|
+
schnorr_sig = context.sign_schnorr(key_pair, message)
|
239
|
+
=> #<Secp256k1::SchnorrSignature:0x00007f4de11e5768>
|
240
|
+
|
241
|
+
schnorr_sig.verify(message, key_pair.xonly_public_key)
|
242
|
+
=> true
|
243
|
+
|
244
|
+
schnorr_sig.serialized
|
245
|
+
=> "\x16\xEF\x04\xB0JP\xE9\x90\xC2\xB1\xE6-l\xDB\x1D\xAE\xAAc\xBF@9s\x02\xC2\xD5[\xFB\x19Q\xFAI^Hj\xD1)\xBE\xEA\xA5!a\xAD\xBEO\xCE7\x9Em\x13;\xE8R\x8A\x81$\nv\xF4\xD3\xB25\xA9\xF9\xC0"
|
246
|
+
```
|
247
|
+
|
248
|
+
### 4. Load a Schnorr signature from binary serialized version
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
context = Secp256k1::Context.create
|
252
|
+
|
253
|
+
schnorr_sig_raw = "\x16\xEF\x04\xB0JP\xE9\x90\xC2\xB1\xE6-l\xDB\x1D\xAE\xAAc\xBF@9s\x02\xC2\xD5[\xFB\x19Q\xFAI^Hj\xD1)\xBE\xEA\xA5!a\xAD\xBEO\xCE7\x9Em\x13;\xE8R\x8A\x81$\nv\xF4\xD3\xB25\xA9\xF9\xC0"
|
254
|
+
|
255
|
+
Secp256k1::SchnorrSignature.from_data(schnorr_sig_raw)
|
256
|
+
=> #<Secp256k1::SchnorrSignature:0x00007f4de1225818>
|
257
|
+
```
|
258
|
+
|
200
259
|
Recoverable Signature Examples
|
201
260
|
------------------------------
|
202
261
|
|
@@ -215,7 +274,7 @@ Secp256k1.have_recovery?
|
|
215
274
|
You can sign data producing a recoverable signature as follows:
|
216
275
|
|
217
276
|
```ruby
|
218
|
-
|
277
|
+
yrequire 'digest'
|
219
278
|
|
220
279
|
hash = Digest::SHA256.digest('test message')
|
221
280
|
context = Secp256k1::Context.create
|
data/documentation/key_pair.md
CHANGED
@@ -5,13 +5,6 @@ Secp256k1::KeyPair
|
|
5
5
|
|
6
6
|
Secp256k1::KeyPair represents a public-private Secp256k1 key pair.
|
7
7
|
|
8
|
-
Initializers
|
9
|
-
------------
|
10
|
-
|
11
|
-
#### new(public_key, private_key)
|
12
|
-
|
13
|
-
Initializes a new key pair with `public_key` (type: [PublicKey](public_key.md)) and `private_key` (type: [PrivateKey](private_key.md)).
|
14
|
-
|
15
8
|
Instance Methods
|
16
9
|
----------------
|
17
10
|
|
@@ -19,6 +12,10 @@ Instance Methods
|
|
19
12
|
|
20
13
|
Returns the [PublicKey](public_key.md) part of this key pair.
|
21
14
|
|
15
|
+
#### xonly_public_key
|
16
|
+
|
17
|
+
Returns the [XOnlyPublicKey](xonly_public_key.md).
|
18
|
+
|
22
19
|
#### private_key
|
23
20
|
|
24
21
|
Returns the [PrivateKey](private_key.md) part of this key pair.
|
data/documentation/public_key.md
CHANGED
@@ -27,6 +27,10 @@ Returns the binary compressed representation of this public key.
|
|
27
27
|
|
28
28
|
Returns the binary uncompressed representation of this public key.
|
29
29
|
|
30
|
+
### to_xonly
|
31
|
+
|
32
|
+
Returns the `XOnlyPublicKey` equivalent of this key.
|
33
|
+
|
30
34
|
#### ==(other)
|
31
35
|
|
32
36
|
Return `true` if this public key matches `other`.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
[Index](index.md)
|
2
|
+
|
3
|
+
Secp256k1::SchnorrSignature
|
4
|
+
===========================
|
5
|
+
|
6
|
+
Secp256k1::SchnorrSignature represents an Schnorr signature signing a 32-byte message.
|
7
|
+
|
8
|
+
Class Methods
|
9
|
+
-------------
|
10
|
+
|
11
|
+
#### from_data(schnorr_sig_data)
|
12
|
+
|
13
|
+
Loads a new Schnorr signature from the given 64-byte binary
|
14
|
+
`schnorr_sig_data`. Does not perform any validation on the loaded data.
|
15
|
+
|
16
|
+
Instance Methods
|
17
|
+
----------------
|
18
|
+
|
19
|
+
#### serialized
|
20
|
+
|
21
|
+
Returns the 64-byte binary `String` of the serialized Schnorr signature.
|
22
|
+
|
23
|
+
#### verify(msg, xonly_pubkey)
|
24
|
+
|
25
|
+
Returns `true` if the schnorr signature is a valid signing of `msg` with the
|
26
|
+
private key for `xonly_pubkey`, `false` otherwise.
|
27
|
+
|
28
|
+
#### ==(other)
|
29
|
+
|
30
|
+
Returns `true` if this signature matches `other`.
|
data/documentation/secp256k1.md
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
[Index](index.md)
|
2
|
+
|
3
|
+
Secp256k1::XOnlyPublicKey
|
4
|
+
=========================
|
5
|
+
|
6
|
+
Secp256k1::XOnlyPublicKey represents an x-only public key version of a public key.
|
7
|
+
|
8
|
+
See: [KeyPair](key_pair.md)
|
9
|
+
|
10
|
+
Class Methods
|
11
|
+
-------------
|
12
|
+
|
13
|
+
#### from_data(xonly_public_key_serialized)
|
14
|
+
|
15
|
+
Parses the 32-byte serialized binary `xonly_public_key_serialized` and creates
|
16
|
+
and returns a new x-only public key from it. Raises a
|
17
|
+
`Secp256k1::DeserializationError` if the given x-only public key data is
|
18
|
+
invalid.
|
19
|
+
|
20
|
+
Instance Methods
|
21
|
+
----------------
|
22
|
+
|
23
|
+
#### serialized
|
24
|
+
|
25
|
+
Serializes the `XOnlyPublicKey` into a 32-byte binary `String`.
|
26
|
+
|
27
|
+
#### ==(other)
|
28
|
+
|
29
|
+
Return `true` if this x-only public key matches `other`.
|
data/ext/rbsecp256k1/extconf.rb
CHANGED
@@ -4,32 +4,28 @@ require 'mini_portile2'
|
|
4
4
|
require 'mkmf'
|
5
5
|
require 'zip'
|
6
6
|
|
7
|
+
# Enable the recovery module by default
|
8
|
+
WITH_RECOVERY = ENV.fetch('WITH_RECOVERY', '1') == '1'
|
9
|
+
|
7
10
|
# Recipe for downloading and building libsecp256k1 as part of installation
|
8
11
|
class Secp256k1Recipe < MiniPortile
|
9
|
-
# Hard-coded URL for libsecp256k1 zipfile (
|
10
|
-
LIBSECP256K1_ZIP_URL = 'https://github.com/bitcoin-core/secp256k1/archive/
|
12
|
+
# Hard-coded URL for libsecp256k1 zipfile (Official release v0.2.0)
|
13
|
+
LIBSECP256K1_ZIP_URL = 'https://github.com/bitcoin-core/secp256k1/archive/refs/tags/v0.2.0.zip'
|
11
14
|
|
12
15
|
# Expected SHA-256 of the zipfile above (computed using sha256sum)
|
13
|
-
LIBSECP256K1_SHA256 = '
|
14
|
-
|
15
|
-
WITH_RECOVERY = ENV.fetch('WITH_RECOVERY', '1') == '1'
|
16
|
-
WITH_ECDH = ENV.fetch('WITH_ECDH', '1') == '1'
|
16
|
+
LIBSECP256K1_SHA256 = '6ece280c0e6ea9d861051077c28a25b7f48800c43a4098a800b7d3b0c124e406'
|
17
17
|
|
18
18
|
def initialize
|
19
|
-
super('libsecp256k1', '0.
|
19
|
+
super('libsecp256k1', '0.2.0')
|
20
20
|
@tarball = File.join(Dir.pwd, "/ports/archives/libsecp256k1.zip")
|
21
21
|
@files = ["file://#{@tarball}"]
|
22
22
|
self.configure_options += [
|
23
|
-
"--disable-benchmark",
|
24
|
-
"--disable-exhaustive-tests",
|
25
|
-
"--disable-tests",
|
26
|
-
"--disable-debug",
|
27
|
-
"--enable-experimental",
|
28
23
|
"--with-pic=yes"
|
29
24
|
]
|
30
25
|
|
26
|
+
# ECDH is enabled by default in release v0.2.0, but recovery still needs to
|
27
|
+
# be enabled manually.
|
31
28
|
configure_options << "--enable-module-recovery" if WITH_RECOVERY
|
32
|
-
configure_options << "--enable-module-ecdh" if WITH_ECDH
|
33
29
|
end
|
34
30
|
|
35
31
|
def configure
|
@@ -106,11 +102,20 @@ else
|
|
106
102
|
have_library("gmp")
|
107
103
|
end
|
108
104
|
|
105
|
+
# Sanity check for the basic library
|
106
|
+
have_header('secp256k1.h')
|
107
|
+
|
109
108
|
# Check if we have the libsecp256k1 recoverable signature header.
|
110
|
-
have_header('secp256k1_recovery.h')
|
109
|
+
have_header('secp256k1_recovery.h') if WITH_RECOVERY
|
111
110
|
|
112
111
|
# Check if we have EC Diffie-Hellman functionality
|
113
112
|
have_header('secp256k1_ecdh.h')
|
114
113
|
|
114
|
+
# Check if we have Schnorr signatures
|
115
|
+
have_header('secp256k1_schnorrsig.h')
|
116
|
+
|
117
|
+
# Check if we have extra keys module
|
118
|
+
have_header('secp256k1_extrakeys.h')
|
119
|
+
|
115
120
|
# See: https://guides.rubygems.org/gems-with-extensions/
|
116
121
|
create_makefile('rbsecp256k1/rbsecp256k1')
|