rbsecp256k1 5.1.1 → 6.0.0
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 +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
|
[](https://github.com/etscrivner/rbsecp256k1/actions/workflows/spec.yml) [](https://badge.fury.io/rb/rbsecp256k1) [](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')
|