secp256k1rb 0.1.1 → 0.2.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 +22 -13
- data/lib/secp256k1/c.rb +22 -0
- data/lib/secp256k1/ellswift.rb +5 -10
- data/lib/secp256k1/musig/key_agg.rb +74 -0
- data/lib/secp256k1/musig/session.rb +123 -0
- data/lib/secp256k1/musig.rb +165 -0
- data/lib/secp256k1/recovery.rb +15 -9
- data/lib/secp256k1/schnorrsig.rb +17 -9
- data/lib/secp256k1/version.rb +1 -1
- data/lib/secp256k1.rb +29 -33
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11620dd56323837c51b4adaf78b80e683c039b6086af0d501df6890f31c32e07
|
4
|
+
data.tar.gz: 27b5544236685afd32b11dd636d1f78ca626cfb63a120c6da88504238aa0c6b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14177d6aa88872ae15db021b45d492ec59b083ad751d224d852c9f5440fd2a9b51855608a0c0456175aadd09b06d68591122b799fc2a7d08724c4f726c84c980
|
7
|
+
data.tar.gz: '0805a6d29bad9054f581115919365a3e8623adc8d93f64e92d18435f33012056a227ccbb4bd08c5421ec4a3bbc1e85f94d8b116cd47d4c205f51e0efe5306f00'
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# secp256k1rb
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
This is a Ruby binding for Bitcoin Core's [secp256k1 library](https://github.com/bitcoin-core/secp256k1/).
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -22,22 +20,33 @@ Or install it yourself as:
|
|
22
20
|
|
23
21
|
## Usage
|
24
22
|
|
25
|
-
|
23
|
+
To use this library, you need to specify the path of the secp256k1 shared library in environment variable
|
24
|
+
`SECP256K1_LIB_PATH`, e.g: `$ export SECP256K1_LIB_PATH=/var/local/lib/libsecp256k1.so`.
|
25
|
+
|
26
|
+
Note: This library also implements the recovery module, so you must have built the secp256k1 library with the
|
27
|
+
`--enable-module-recovery` option.
|
26
28
|
|
27
|
-
|
29
|
+
By including the Secp256k1 module, you can use the features provided by the `libsepc256k1` library. For example:
|
28
30
|
|
29
|
-
|
31
|
+
```ruby
|
32
|
+
require 'secp256k1'
|
30
33
|
|
31
|
-
|
34
|
+
include Secp256k1
|
32
35
|
|
33
|
-
|
36
|
+
generate_key_pair
|
37
|
+
=> ["e00c2ae99e59b5262be3d507d026081f0e6cf9972ffdd4f2d45a390f7a41b053", "027e0f70b540d627422cf7bb77d86ae1bb6829c80104dd48dc2539e6277ea25624"]
|
38
|
+
```
|
34
39
|
|
35
|
-
|
40
|
+
See [here](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1) for available methods.
|
41
|
+
In addition, the following modules are also included, so you can use them as they are.
|
36
42
|
|
37
|
-
|
43
|
+
* [Recover](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/Recover)
|
44
|
+
* [SchnorrSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SchnorrSig)
|
45
|
+
* [EllSwift](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/EllSwift)
|
38
46
|
|
39
|
-
|
47
|
+
### Compatibility
|
40
48
|
|
41
|
-
|
49
|
+
secp256k1 version | secp256k1rb version
|
50
|
+
:---:|:---:
|
51
|
+
v0.4.0 | v0.1.x
|
42
52
|
|
43
|
-
Everyone interacting in the Secp256k1 project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/secp256k1/blob/master/CODE_OF_CONDUCT.md).
|
data/lib/secp256k1/c.rb
CHANGED
@@ -28,10 +28,32 @@ module Secp256k1
|
|
28
28
|
attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
|
29
29
|
attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
|
30
30
|
attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
|
31
|
+
# for EllSwift module
|
31
32
|
attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
|
32
33
|
attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
|
33
34
|
attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :pointer)
|
34
35
|
attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
|
36
|
+
# for ExtraKeys module
|
37
|
+
attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
|
38
|
+
attach_function(:secp256k1_xonly_pubkey_serialize, [:pointer, :pointer, :pointer], :int)
|
39
|
+
attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
|
40
|
+
# for MuSig module
|
41
|
+
attach_function(:secp256k1_musig_pubkey_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
|
42
|
+
attach_function(:secp256k1_musig_pubkey_get, [:pointer, :pointer, :pointer], :int)
|
43
|
+
attach_function(:secp256k1_musig_pubkey_ec_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
|
44
|
+
attach_function(:secp256k1_musig_pubkey_xonly_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
|
45
|
+
attach_function(:secp256k1_musig_nonce_gen, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
46
|
+
attach_function(:secp256k1_musig_pubnonce_parse, [:pointer, :pointer, :pointer], :int)
|
47
|
+
attach_function(:secp256k1_musig_pubnonce_serialize, [:pointer, :pointer, :pointer], :int)
|
48
|
+
attach_function(:secp256k1_musig_aggnonce_serialize, [:pointer, :pointer, :pointer], :int)
|
49
|
+
attach_function(:secp256k1_musig_aggnonce_parse, [:pointer, :pointer, :pointer], :int)
|
50
|
+
attach_function(:secp256k1_musig_nonce_agg, [:pointer, :pointer, :pointer, :size_t], :int)
|
51
|
+
attach_function(:secp256k1_musig_nonce_process, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
52
|
+
attach_function(:secp256k1_musig_partial_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
53
|
+
attach_function(:secp256k1_musig_partial_sig_serialize, [:pointer, :pointer, :pointer], :int)
|
54
|
+
attach_function(:secp256k1_musig_partial_sig_parse, [:pointer, :pointer, :pointer], :int)
|
55
|
+
attach_function(:secp256k1_musig_partial_sig_verify, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
56
|
+
attach_function(:secp256k1_musig_partial_sig_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
|
35
57
|
|
36
58
|
# Pointer to secp256k1_ellswift_xdh_hash_function_bip324 constant.
|
37
59
|
# @return [FFI::Pointer]
|
data/lib/secp256k1/ellswift.rb
CHANGED
@@ -8,9 +8,8 @@ module Secp256k1
|
|
8
8
|
# @raise [Secp256k1::Error] If decode failed.
|
9
9
|
# @raise [ArgumentError] If invalid arguments specified.
|
10
10
|
def ellswift_decode(ell_key, compressed: true)
|
11
|
-
|
11
|
+
validate_string!("ell_key", ell_key, ELL_SWIFT_KEY_SIZE)
|
12
12
|
ell_key = hex2bin(ell_key)
|
13
|
-
raise ArgumentError, "ell_key must be 64 bytes." unless ell_key.bytesize == 64
|
14
13
|
with_context do |context|
|
15
14
|
ell64 = FFI::MemoryPointer.new(:uchar, ell_key.bytesize).put_bytes(0, ell_key)
|
16
15
|
internal = FFI::MemoryPointer.new(:uchar, 64)
|
@@ -26,9 +25,8 @@ module Secp256k1
|
|
26
25
|
# @raise [Secp256k1::Error] If failed to create elligattor swhift public key.
|
27
26
|
# @raise [ArgumentError] If invalid arguments specified.
|
28
27
|
def ellswift_create(private_key)
|
29
|
-
|
28
|
+
validate_string!("private_key", private_key, 32)
|
30
29
|
private_key = hex2bin(private_key)
|
31
|
-
raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
|
32
30
|
with_context(flags: CONTEXT_SIGN) do |context|
|
33
31
|
ell64 = FFI::MemoryPointer.new(:uchar, 64)
|
34
32
|
seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
|
@@ -46,15 +44,12 @@ module Secp256k1
|
|
46
44
|
# @return [String] x coordinate with hex format.
|
47
45
|
# @raise [Secp256k1::Error] If secret is invalid or hashfp return 0.
|
48
46
|
def ellswift_ecdh_xonly(their_ell_pubkey, our_ell_pubkey, private_key, initiating)
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
validate_string!("their_ell_pubkey", their_ell_pubkey, ELL_SWIFT_KEY_SIZE)
|
48
|
+
validate_string!("our_ell_pubkey", our_ell_pubkey, ELL_SWIFT_KEY_SIZE)
|
49
|
+
validate_string!("private_key", private_key, 32)
|
52
50
|
their_ell_pubkey = hex2bin(their_ell_pubkey)
|
53
51
|
our_ell_pubkey = hex2bin(our_ell_pubkey)
|
54
52
|
private_key = hex2bin(private_key)
|
55
|
-
raise ArgumentError, "their_ell_pubkey must be #{ELL_SWIFT_KEY_SIZE} bytes." unless their_ell_pubkey.bytesize == ELL_SWIFT_KEY_SIZE
|
56
|
-
raise ArgumentError, "our_ell_pubkey must be #{ELL_SWIFT_KEY_SIZE} bytes." unless our_ell_pubkey.bytesize == ELL_SWIFT_KEY_SIZE
|
57
|
-
raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
|
58
53
|
|
59
54
|
with_context(flags: CONTEXT_SIGN) do |context|
|
60
55
|
output = FFI::MemoryPointer.new(:uchar, 32)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Secp256k1
|
2
|
+
module MuSig
|
3
|
+
|
4
|
+
# Opaque data structure that caches information about public key aggregation.
|
5
|
+
class KeyAggCache < FFI::Struct
|
6
|
+
layout :data, [:uchar, 197]
|
7
|
+
end
|
8
|
+
|
9
|
+
# Key aggregation context class.
|
10
|
+
class KeyAggContext
|
11
|
+
include Secp256k1
|
12
|
+
|
13
|
+
attr_reader :cache
|
14
|
+
|
15
|
+
# Constructor.
|
16
|
+
# @param [Secp256k1::MuSig::KeyAggCache] key_agg_cache Key aggregation cache.
|
17
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
18
|
+
def initialize(key_agg_cache)
|
19
|
+
raise ArgumentError, "key_agg_cache must be Secp256k1::KeyAggCache." unless key_agg_cache.is_a?(Secp256k1::KeyAggCache)
|
20
|
+
@cache = key_agg_cache
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get aggregate public key.
|
24
|
+
# @return [String] An aggregated public key.
|
25
|
+
def aggregate_public_key
|
26
|
+
with_context do |context|
|
27
|
+
agg_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
28
|
+
if secp256k1_musig_pubkey_get(context, agg_pubkey, cache.pointer) == 0
|
29
|
+
raise Error, "secp256k1_musig_pubkey_get arguments invalid."
|
30
|
+
end
|
31
|
+
serialize_pubkey(context, agg_pubkey)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Apply ordinary "EC" tweaking to a public key.
|
36
|
+
# @param [String] tweak Tweak value to tweak the aggregated key.
|
37
|
+
# @param [Boolean] xonly Apply x-only tweaking or not.
|
38
|
+
# @return [String] Tweaked x-only public key with hex format.
|
39
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
40
|
+
# @raise [Secp256k1::Error]
|
41
|
+
def tweak_add(tweak, xonly: false)
|
42
|
+
validate_string!("tweak", tweak, 32)
|
43
|
+
with_context do |context|
|
44
|
+
tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(tweak))
|
45
|
+
pubkey_ptr = FFI::MemoryPointer.new(:uchar, 64)
|
46
|
+
if xonly
|
47
|
+
if secp256k1_musig_pubkey_xonly_tweak_add(context, pubkey_ptr, cache.pointer, tweak_ptr) == 0
|
48
|
+
raise Error, "secp256k1_musig_pubkey_tweak_add arguments invalid."
|
49
|
+
end
|
50
|
+
else
|
51
|
+
if secp256k1_musig_pubkey_ec_tweak_add(context, pubkey_ptr, cache.pointer, tweak_ptr) == 0
|
52
|
+
raise Error, "secp256k1_musig_pubkey_tweak_add arguments invalid."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
serialize_pubkey(context, pubkey_ptr)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get KeyAggCache pointer.
|
60
|
+
# @return [FFI::MemoryPointer]
|
61
|
+
def pointer
|
62
|
+
cache.pointer
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def serialize_pubkey(context, pubkey_ptr)
|
68
|
+
xonly = FFI::MemoryPointer.new(:uchar, 32)
|
69
|
+
secp256k1_xonly_pubkey_serialize(context, xonly, pubkey_ptr)
|
70
|
+
xonly.read_string(32).unpack1('H*')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Secp256k1
|
2
|
+
module MuSig
|
3
|
+
class Session
|
4
|
+
include Secp256k1
|
5
|
+
attr_reader :session
|
6
|
+
attr_reader :key_agg_ctx
|
7
|
+
attr_reader :agg_nonce
|
8
|
+
attr_reader :msg
|
9
|
+
|
10
|
+
# Create signing session.
|
11
|
+
# @param [Secp256k1::MuSig::KeyAggContext] key_agg_ctx The key aggregation context.
|
12
|
+
# @param [String] agg_nonce An aggregated public nonce.
|
13
|
+
# @param [String] msg The message to be signed.
|
14
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
15
|
+
# @raise [Secp256k1::Error]
|
16
|
+
def initialize(key_agg_ctx, agg_nonce, msg)
|
17
|
+
raise ArgumentError, 'key_agg_ctx must be KeyAggContext.' unless key_agg_ctx.is_a?(KeyAggContext)
|
18
|
+
validate_string!('msg', msg, 32)
|
19
|
+
validate_string!('agg_nonce', agg_nonce, 66)
|
20
|
+
agg_nonce = hex2bin(agg_nonce)
|
21
|
+
msg = hex2bin(msg)
|
22
|
+
with_context do |context|
|
23
|
+
@session = FFI::MemoryPointer.new(:uchar, 133)
|
24
|
+
agg66 = FFI::MemoryPointer.new(:uchar, 66).put_bytes(0, agg_nonce)
|
25
|
+
agg_ptr = FFI::MemoryPointer.new(:uchar, 132)
|
26
|
+
if secp256k1_musig_aggnonce_parse(context, agg_ptr, agg66) == 0
|
27
|
+
raise Error, "secp256k1_musig_aggnonce_parse failed."
|
28
|
+
end
|
29
|
+
msg_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, msg)
|
30
|
+
if secp256k1_musig_nonce_process(context, @session, agg_ptr, msg_ptr, key_agg_ctx.pointer) == 0
|
31
|
+
raise Error, "secp256k1_musig_nonce_process arguments invalid."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@agg_nonce = agg_nonce.unpack1('H*')
|
35
|
+
@msg = msg.unpack1('H*')
|
36
|
+
@key_agg_ctx = key_agg_ctx
|
37
|
+
end
|
38
|
+
|
39
|
+
# Produces a partial signature for a given key pair and secret nonce.
|
40
|
+
# @param [String] sec_nonce The secret nonce to sign the message.
|
41
|
+
# @param [String] private_key The private key to sign the message.
|
42
|
+
# @return [String] A partial signature.
|
43
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
44
|
+
# @raise [Secp256k1::Error]
|
45
|
+
def partial_sign(sec_nonce, private_key)
|
46
|
+
raise ArgumentError, 'key_agg_ctx must be KeyAggContext.' unless key_agg_ctx.is_a?(KeyAggContext)
|
47
|
+
validate_string!('sec_nonce', sec_nonce, 132)
|
48
|
+
validate_string!('private_key', private_key, 32)
|
49
|
+
with_context do |context|
|
50
|
+
partial_sig = FFI::MemoryPointer.new(:uchar, 36)
|
51
|
+
sec_nonce_ptr = FFI::MemoryPointer.new(:uchar, 132).put_bytes(0, hex2bin(sec_nonce))
|
52
|
+
private_key_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(private_key))
|
53
|
+
key_pair = FFI::MemoryPointer.new(:uchar, 86)
|
54
|
+
if secp256k1_keypair_create(context, key_pair, private_key_ptr) == 0
|
55
|
+
raise Error, "secp256k1_keypair_create invalid private_key."
|
56
|
+
end
|
57
|
+
if secp256k1_musig_partial_sign(
|
58
|
+
context, partial_sig, sec_nonce_ptr, key_pair, key_agg_ctx.cache.pointer, session) == 0
|
59
|
+
raise Error, "secp256k1_musig_partial_sign arguments invalid or sec_nonce has already been used for signing."
|
60
|
+
end
|
61
|
+
out32 = FFI::MemoryPointer.new(:uchar, 32)
|
62
|
+
secp256k1_musig_partial_sig_serialize(context, out32, partial_sig)
|
63
|
+
out32.read_string(32).unpack1('H*')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Checks that an individual partial signature verifies.
|
68
|
+
# @param [String] partial_sig The partial signature to verify, sent by the signer associated with +pub_nonce+ and +public_key+.
|
69
|
+
# @param [String] pub_nonce The public nonce of the signer in the signing session.
|
70
|
+
# @param [String] public_key The public key of the signer in the signing session.
|
71
|
+
# @return [Boolean] The verification result.
|
72
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
73
|
+
# @raise [Secp256k1::Error]
|
74
|
+
def verify_partial_sig(partial_sig, pub_nonce, public_key)
|
75
|
+
validate_string!('partial_sig', partial_sig, 32)
|
76
|
+
validate_string!('pub_nonce', pub_nonce, 66)
|
77
|
+
validate_string!('public_key', public_key, 33)
|
78
|
+
with_context do |context|
|
79
|
+
sig_ptr = parse_partial_sig(context, partial_sig)
|
80
|
+
public_key = FFI::MemoryPointer.new(:uchar, 33).put_bytes(0, hex2bin(public_key))
|
81
|
+
pubkey_ptr = FFI::MemoryPointer.new(:uchar, 64)
|
82
|
+
raise Error, "pubkey is invalid." unless secp256k1_ec_pubkey_parse(context, pubkey_ptr, public_key, 33) == 1
|
83
|
+
pub_nonce = FFI::MemoryPointer.new(:uchar, 66).put_bytes(0, hex2bin(pub_nonce))
|
84
|
+
nonce_ptr = FFI::MemoryPointer.new(:uchar, 132)
|
85
|
+
if secp256k1_musig_pubnonce_parse(context, nonce_ptr, pub_nonce) == 0
|
86
|
+
raise Error, "secp256k1_musig_pubnonce_parse failed."
|
87
|
+
end
|
88
|
+
secp256k1_musig_partial_sig_verify(context, sig_ptr, nonce_ptr, pubkey_ptr, key_agg_ctx.pointer, session) == 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Aggregates partial signatures
|
93
|
+
# @param [Array] partial_sigs Array of partial signatures.
|
94
|
+
# @return [String] An aggregated signature.
|
95
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
96
|
+
# @raise [Secp256k1::Error]
|
97
|
+
def aggregate_partial_sigs(partial_sigs)
|
98
|
+
raise ArgumentError, "partial_sigs must be Array." unless partial_sigs.is_a?(Array)
|
99
|
+
raise ArgumentError, "partial_sigs must not be empty." if partial_sigs.empty?
|
100
|
+
with_context do |context|
|
101
|
+
sigs_ptr = FFI::MemoryPointer.new(:pointer, partial_sigs.length)
|
102
|
+
sigs_ptr.write_array_of_pointer(partial_sigs.map{|partial_sig| parse_partial_sig(context, partial_sig)})
|
103
|
+
sig64 = FFI::MemoryPointer.new(:uchar, 64)
|
104
|
+
if secp256k1_musig_partial_sig_agg(context, sig64, session, sigs_ptr, partial_sigs.length) == 0
|
105
|
+
raise Error, "secp256k1_musig_partial_sig_agg arguments invalid."
|
106
|
+
end
|
107
|
+
sig64.read_string(64).unpack1('H*')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def parse_partial_sig(context, partial_sig)
|
114
|
+
partial_sig = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(partial_sig))
|
115
|
+
sig_ptr = FFI::MemoryPointer.new(:uchar, 36)
|
116
|
+
if secp256k1_musig_partial_sig_parse(context, sig_ptr, partial_sig) == 0
|
117
|
+
raise Error, "secp256k1_musig_partial_sig_parse failed."
|
118
|
+
end
|
119
|
+
sig_ptr
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require_relative 'musig/key_agg'
|
2
|
+
require_relative 'musig/session'
|
3
|
+
|
4
|
+
module Secp256k1
|
5
|
+
|
6
|
+
# MuSig module
|
7
|
+
# @example
|
8
|
+
# include Secp256k1
|
9
|
+
#
|
10
|
+
# # Two signer.
|
11
|
+
# sk1, pk1 = generate_key_pair
|
12
|
+
# sk2, pk2 = generate_key_pair
|
13
|
+
#
|
14
|
+
# # Aggregate public key
|
15
|
+
# key_agg_ctx = aggregate_pubkey([pk1, pk2])
|
16
|
+
# agg_pubkey = key_agg_ctx.aggregate_public_key
|
17
|
+
#
|
18
|
+
# # If you need tweak
|
19
|
+
# key_agg_ctx.tweak_add('7468697320636f756c64206265206120424950333220747765616b2e2e2e2e00')
|
20
|
+
# # If you need tweak with xonly
|
21
|
+
# key_agg_ctx.tweak_add('7468697320636f756c64206265206120546170726f6f7420747765616b2e2e00', xonly: true)
|
22
|
+
#
|
23
|
+
# agg_pubkey = key_agg_ctx.aggregate_public_key
|
24
|
+
#
|
25
|
+
# msg = Digest::SHA256.digest('message')
|
26
|
+
#
|
27
|
+
# # Nonce generation
|
28
|
+
# session_id1 = generate_musig_session_id
|
29
|
+
# secnonce1, pubnonce1 = target.generate_musig_nonce(
|
30
|
+
# session_id1,
|
31
|
+
# pk1,
|
32
|
+
# sk: sk1,
|
33
|
+
# key_agg_ctx: key_agg_ctx,
|
34
|
+
# msg: msg
|
35
|
+
# )
|
36
|
+
#
|
37
|
+
# session_id2 = generate_musig_session_id
|
38
|
+
# secnonce2, pubnonce2 = target.generate_musig_nonce(
|
39
|
+
# session_id2,
|
40
|
+
# pk2,
|
41
|
+
# sk: sk2,
|
42
|
+
# key_agg_ctx: key_agg_ctx,
|
43
|
+
# msg: msg
|
44
|
+
# )
|
45
|
+
#
|
46
|
+
# # Aggregate public nonces
|
47
|
+
# agg_nonce = aggregate_musig_nonce([pubnonce1, pubnonce2])
|
48
|
+
#
|
49
|
+
# # Generate partial sig
|
50
|
+
# musig_session = Secp256k1::MuSig::Session.new(key_agg_ctx, agg_nonce, msg)
|
51
|
+
# partial_sig1 = musig_session.partial_sign(secnonce1, sk1)
|
52
|
+
# partial_sig2 = musig_session.partial_sign(secnonce2, sk2)
|
53
|
+
#
|
54
|
+
# # Aggregate signature
|
55
|
+
# agg_sig = musig_session.aggregate_partial_sigs([partial_sig1, partial_sig2])
|
56
|
+
#
|
57
|
+
# # Verify schnorr signature
|
58
|
+
# verify_schnorr(msg, agg_sig, agg_pubkey)
|
59
|
+
module MuSig
|
60
|
+
|
61
|
+
# Aggregate public keys.
|
62
|
+
# @param [Array] pubkeys An array of public keys.
|
63
|
+
# @return [Secp2561k::MuSig::KeyAggContext]
|
64
|
+
# @raise [Secp256k1::Error]
|
65
|
+
def aggregate_pubkey(pubkeys)
|
66
|
+
raise ArgumentError, "pubkeys must be an array." unless pubkeys.is_a?(Array)
|
67
|
+
with_context do |context|
|
68
|
+
pubkeys_ptrs = pubkeys.map do |pubkey|
|
69
|
+
pubkey = hex2bin(pubkey)
|
70
|
+
validate_string!('pubkey', pubkey, 33)
|
71
|
+
input = FFI::MemoryPointer.new(:uchar, 33).put_bytes(0, pubkey)
|
72
|
+
pubkey_ptr = FFI::MemoryPointer.new(:uchar, 64)
|
73
|
+
raise Error, "pubkey is invalid." unless secp256k1_ec_pubkey_parse(context, pubkey_ptr, input, 33) == 1
|
74
|
+
pubkey_ptr
|
75
|
+
end
|
76
|
+
pubkeys_ptr = FFI::MemoryPointer.new(:pointer, pubkeys.length)
|
77
|
+
pubkeys_ptr.write_array_of_pointer(pubkeys_ptrs)
|
78
|
+
agg_pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
79
|
+
cache = Secp256k1::MuSig::KeyAggCache.new
|
80
|
+
if secp256k1_musig_pubkey_agg(context, agg_pubkey, cache.pointer, pubkeys_ptr, pubkeys.length) == 0
|
81
|
+
raise Error, "secp256k1_musig_pubkey_agg argument error."
|
82
|
+
end
|
83
|
+
Secp256k1::MuSig::KeyAggContext.new(cache)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Generate fresh session id for musig signing session.
|
88
|
+
# @return [String] The session id.
|
89
|
+
def generate_musig_session_id
|
90
|
+
SecureRandom.random_bytes(32).unpack1('H*')
|
91
|
+
end
|
92
|
+
|
93
|
+
# Generate nonce pair.
|
94
|
+
# @param [String] session_id The uniform random identifier for this session.
|
95
|
+
# @param [String] pk The public key for which the partial signature is generated.
|
96
|
+
# @param [String] sk (Optional) The private key for which the partial signature is generated.
|
97
|
+
# @param [Secp256k1::MuSig::KeyAggContext] key_agg_ctx (Optional) The aggregated public key context.
|
98
|
+
# @param [String] msg (Optional) The message to be signed.
|
99
|
+
# @param [String] extra_in (Optional) The auxiliary input.
|
100
|
+
# @return [Array(String)] The array of secret nonce and public nonce with hex format.
|
101
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
102
|
+
# @raise [Secp256k1::Error]
|
103
|
+
def generate_musig_nonce(session_id, pk, sk: nil, key_agg_ctx: nil, msg: nil, extra_in: nil)
|
104
|
+
validate_string!("session_id", session_id, 32)
|
105
|
+
validate_string!("pk", pk, 33)
|
106
|
+
validate_string!("sk", sk, 32) if sk
|
107
|
+
validate_string!("msg", msg, 32) if msg
|
108
|
+
validate_string!("extra_in", extra_in, 32) if extra_in
|
109
|
+
|
110
|
+
if key_agg_ctx
|
111
|
+
raise ArgumentError, "key_agg must be Secp256k1::MuSig::KeyAggContext." unless key_agg_ctx.is_a?(KeyAggContext)
|
112
|
+
end
|
113
|
+
|
114
|
+
with_context do |context|
|
115
|
+
pk_ptr = FFI::MemoryPointer.new(:uchar, 33).put_bytes(0, hex2bin(pk))
|
116
|
+
pubkey = FFI::MemoryPointer.new(:uchar, 64)
|
117
|
+
raise Error, "pk is invalid public key." unless secp256k1_ec_pubkey_parse(context, pubkey, pk_ptr, 33) == 1
|
118
|
+
|
119
|
+
pubnonce = FFI::MemoryPointer.new(:uchar, 132)
|
120
|
+
secnonce = FFI::MemoryPointer.new(:uchar, 132)
|
121
|
+
seckey = sk ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(sk)) : nil
|
122
|
+
msg32 = msg ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(msg)) : nil
|
123
|
+
extra_input32 = extra_in ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(extra_in)) : nil
|
124
|
+
session_secrand32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(session_id))
|
125
|
+
|
126
|
+
raise Error, "arguments is invalid." unless secp256k1_musig_nonce_gen(
|
127
|
+
context, secnonce, pubnonce, session_secrand32, seckey, pubkey, msg32, key_agg_ctx.pointer, extra_input32) == 1
|
128
|
+
|
129
|
+
pub66 = FFI::MemoryPointer.new(:uchar, 66)
|
130
|
+
secp256k1_musig_pubnonce_serialize(context, pub66, pubnonce)
|
131
|
+
[secnonce.read_string(132).unpack1('H*'), pub66.read_string(66).unpack1('H*')]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Aggregates the nonces of all signers into a single nonce.
|
136
|
+
# @param [Array] pub_nonces An array of public nonces sent by the signers.
|
137
|
+
# @return [String] An aggregated public nonce.
|
138
|
+
# @raise [Secp256k1::Error]
|
139
|
+
# @raise [ArgumentError] If invalid arguments specified.
|
140
|
+
def aggregate_musig_nonce(pub_nonces)
|
141
|
+
raise ArgumentError, "pub_nonces must be Array." unless pub_nonces.is_a?(Array)
|
142
|
+
|
143
|
+
with_context do |context|
|
144
|
+
nonce_ptrs = pub_nonces.map do |pub_nonce|
|
145
|
+
pub_nonce = hex2bin(pub_nonce)
|
146
|
+
validate_string!("pub_nonce", pub_nonce, 66)
|
147
|
+
in66 = FFI::MemoryPointer.new(:uchar, 66).put_bytes(0, pub_nonce)
|
148
|
+
pub_nonce_ptr = FFI::MemoryPointer.new(:uchar, 132)
|
149
|
+
if secp256k1_musig_pubnonce_parse(context, pub_nonce_ptr, in66) == 0
|
150
|
+
raise Error, "secp256k1_musig_pubnonce_parse error."
|
151
|
+
end
|
152
|
+
pub_nonce_ptr
|
153
|
+
end
|
154
|
+
agg_nonce = FFI::MemoryPointer.new(:uchar, 132)
|
155
|
+
pubnonces = FFI::MemoryPointer.new(:pointer, pub_nonces.length)
|
156
|
+
pubnonces.write_array_of_pointer(nonce_ptrs)
|
157
|
+
result = secp256k1_musig_nonce_agg(context, agg_nonce, pubnonces, pub_nonces.length)
|
158
|
+
raise Error, "nonce aggregation failed." if result == 0
|
159
|
+
out66 = FFI::MemoryPointer.new(:uchar, 66)
|
160
|
+
secp256k1_musig_aggnonce_serialize(context, out66, agg_nonce)
|
161
|
+
out66.read_string(66).unpack1("H*")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/secp256k1/recovery.rb
CHANGED
@@ -1,4 +1,15 @@
|
|
1
1
|
module Secp256k1
|
2
|
+
# Recover module
|
3
|
+
# @example
|
4
|
+
# include Secp256k1
|
5
|
+
#
|
6
|
+
# sk, _ = generate_key_pair
|
7
|
+
# msg = Digest::SHA256.digest('message')
|
8
|
+
# sig, rec = sign_recoverable(msg, sk)
|
9
|
+
# full_sig = [rec + 0x1b + 4].pack('C') + [sig].pack('H*')
|
10
|
+
# compressed = true
|
11
|
+
# recover_pubkey = target.recover(msg, full_sig, compressed)
|
12
|
+
#
|
2
13
|
module Recover
|
3
14
|
# Sign data with compact format.
|
4
15
|
# @param [String] data The 32-byte message hash being signed.
|
@@ -7,13 +18,10 @@ module Secp256k1
|
|
7
18
|
# @raise [Secp256k1::Error] If recovery failed.
|
8
19
|
# @raise [ArgumentError] If invalid arguments specified.
|
9
20
|
def sign_recoverable(data, private_key)
|
10
|
-
|
11
|
-
|
21
|
+
validate_string!("private_key", private_key, 32)
|
22
|
+
validate_string!("data", data, 32)
|
12
23
|
private_key = hex2bin(private_key)
|
13
|
-
raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
|
14
24
|
data = hex2bin(data)
|
15
|
-
raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
|
16
|
-
|
17
25
|
with_context do |context|
|
18
26
|
sig = FFI::MemoryPointer.new(:uchar, 65)
|
19
27
|
hash =FFI::MemoryPointer.new(:uchar, data.bytesize).put_bytes(0, data)
|
@@ -39,12 +47,10 @@ module Secp256k1
|
|
39
47
|
# @raise [Secp256k1::Error] If recover failed.
|
40
48
|
# @raise [ArgumentError] If invalid arguments specified.
|
41
49
|
def recover(data, signature, compressed)
|
42
|
-
|
43
|
-
|
50
|
+
validate_string!("data", data, 32)
|
51
|
+
validate_string!("signature", signature, 65)
|
44
52
|
signature = hex2bin(signature)
|
45
|
-
raise ArgumentError, "signature must be 65 bytes." unless signature.bytesize == 65
|
46
53
|
data = hex2bin(data)
|
47
|
-
raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
|
48
54
|
rec = (signature[0].ord - 0x1b) & 3
|
49
55
|
raise ArgumentError, "rec must be between 0 and 3." if rec < 0 || rec > 3
|
50
56
|
|
data/lib/secp256k1/schnorrsig.rb
CHANGED
@@ -1,20 +1,29 @@
|
|
1
1
|
module Secp256k1
|
2
|
+
# SchnorrSig module
|
3
|
+
# @example
|
4
|
+
# include Secp256k1
|
5
|
+
#
|
6
|
+
# sk, pk = generate_key_pair
|
7
|
+
#
|
8
|
+
# # sign and verify (Schnorr)
|
9
|
+
# signature = sign_schnorr(msg, sk)
|
10
|
+
# verify_schnorr(msg, signature, pk[2..-1]) # public key must be 32 bytes
|
11
|
+
#
|
2
12
|
module SchnorrSig
|
3
13
|
|
4
14
|
# Sign to data using schnorr.
|
5
15
|
# @param [String] data The 32-byte message hash being signed with binary format.
|
6
16
|
# @param [String] private_key a private key with hex format using sign.
|
7
|
-
# @param [String] aux_rand
|
17
|
+
# @param [String] aux_rand The 32-byte extra entropy.
|
8
18
|
# @return [String] signature data with binary format. If unsupported algorithm specified, return nil.
|
9
19
|
# @raise [ArgumentError] If invalid arguments specified.
|
10
20
|
def sign_schnorr(data, private_key, aux_rand = nil)
|
11
|
-
|
12
|
-
|
21
|
+
validate_string!("data", data, 32)
|
22
|
+
validate_string!("private_key", private_key, 32)
|
23
|
+
validate_string!("aux_rand", aux_rand, 32) if aux_rand
|
13
24
|
raise ArgumentError, "aux_rand must be String." if !aux_rand.nil? && !aux_rand.is_a?(String)
|
14
25
|
private_key = hex2bin(private_key)
|
15
|
-
raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
|
16
26
|
data = hex2bin(data)
|
17
|
-
raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
|
18
27
|
|
19
28
|
with_context do |context|
|
20
29
|
keypair = [create_keypair(private_key)].pack('H*')
|
@@ -34,11 +43,10 @@ module Secp256k1
|
|
34
43
|
# @return [Boolean] verification result.
|
35
44
|
# @raise [ArgumentError] If invalid arguments specified.
|
36
45
|
def verify_schnorr(data, signature, pubkey)
|
37
|
-
|
38
|
-
|
39
|
-
|
46
|
+
validate_string!("data", data, 32)
|
47
|
+
validate_string!("signature", signature, 64)
|
48
|
+
validate_string!("pubkey", pubkey, 32)
|
40
49
|
data = hex2bin(data)
|
41
|
-
raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
|
42
50
|
pubkey = hex2bin(pubkey)
|
43
51
|
signature = hex2bin(signature)
|
44
52
|
with_context do |context|
|
data/lib/secp256k1/version.rb
CHANGED
data/lib/secp256k1.rb
CHANGED
@@ -6,8 +6,23 @@ require_relative 'secp256k1/c'
|
|
6
6
|
require_relative 'secp256k1/recovery'
|
7
7
|
require_relative 'secp256k1/ellswift'
|
8
8
|
require_relative 'secp256k1/schnorrsig'
|
9
|
+
require_relative 'secp256k1/musig'
|
9
10
|
|
10
11
|
# Binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
|
12
|
+
# @example
|
13
|
+
# include Secp256k1
|
14
|
+
#
|
15
|
+
# # Generate key pair
|
16
|
+
# sk, pk = generate_key_pair
|
17
|
+
#
|
18
|
+
# # Generate public key
|
19
|
+
# pk = generate_pubkey(sk)
|
20
|
+
#
|
21
|
+
# # sign and verify (ECDSA)
|
22
|
+
# msg = Digest::SHA256.digest('message')
|
23
|
+
# signature = sign_ecdsa(msg, sk)
|
24
|
+
# verify_ecdsa(msg, signature, pk)
|
25
|
+
#
|
11
26
|
module Secp256k1
|
12
27
|
|
13
28
|
class Error < StandardError; end
|
@@ -16,6 +31,7 @@ module Secp256k1
|
|
16
31
|
include Recover
|
17
32
|
include SchnorrSig
|
18
33
|
include EllSwift
|
34
|
+
include MuSig
|
19
35
|
|
20
36
|
FLAGS_TYPE_MASK = ((1 << 8) - 1)
|
21
37
|
FLAGS_TYPE_CONTEXT = (1 << 0)
|
@@ -79,34 +95,13 @@ module Secp256k1
|
|
79
95
|
# @return [String] Public key with hex format.
|
80
96
|
# @raise [ArgumentError] If invalid arguments specified.
|
81
97
|
def generate_pubkey(private_key, compressed: true)
|
82
|
-
|
98
|
+
validate_string!("private_key", private_key, 32)
|
83
99
|
private_key = hex2bin(private_key)
|
84
|
-
raise ArgumentError, "private_key must by 32 bytes." unless private_key.bytesize == 32
|
85
100
|
with_context do |context|
|
86
101
|
generate_pubkey_in_context(context, private_key, compressed: compressed)
|
87
102
|
end
|
88
103
|
end
|
89
104
|
|
90
|
-
# Sign to data.
|
91
|
-
# @param [String] data The 32-byte message hash being signed with binary format.
|
92
|
-
# @param [String] private_key a private key with hex format using sign.
|
93
|
-
# @param [String] extra_entropy a extra entropy with binary format for rfc6979.
|
94
|
-
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
95
|
-
# @return [String] signature data with binary format. If unsupported algorithm specified, return nil.
|
96
|
-
# @raise [ArgumentError] If invalid arguments specified.
|
97
|
-
def sign_data(data, private_key, extra_entropy = nil, algo: :ecdsa)
|
98
|
-
|
99
|
-
case algo
|
100
|
-
when :ecdsa
|
101
|
-
sign_ecdsa(data, private_key, extra_entropy)
|
102
|
-
when :schnorr
|
103
|
-
sign_schnorr(data, private_key, extra_entropy)
|
104
|
-
else
|
105
|
-
raise ArgumentError, "unknown algo: #{algo}"
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
105
|
# Validate whether this is a valid public key.
|
111
106
|
# @param [String] pubkey public key with hex format.
|
112
107
|
# @param [Boolean] allow_hybrid whether support hybrid public key.
|
@@ -131,9 +126,8 @@ module Secp256k1
|
|
131
126
|
# @raise [Secp256k1::Error] If private_key is invalid.
|
132
127
|
# @raise [ArgumentError] If invalid arguments specified.
|
133
128
|
def create_keypair(private_key)
|
134
|
-
|
129
|
+
validate_string!("private_key", private_key, 32)
|
135
130
|
private_key = hex2bin(private_key)
|
136
|
-
raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
|
137
131
|
with_context do |context|
|
138
132
|
secret = FFI::MemoryPointer.new(:uchar, private_key.bytesize).put_bytes(0, private_key)
|
139
133
|
raise Error, 'private_key is invalid.' unless secp256k1_ec_seckey_verify(context, secret)
|
@@ -159,17 +153,15 @@ module Secp256k1
|
|
159
153
|
# Sign to data using ecdsa.
|
160
154
|
# @param [String] data The 32-byte message hash being signed with binary format.
|
161
155
|
# @param [String] private_key a private key with hex format using sign.
|
162
|
-
# @param [String] extra_entropy
|
156
|
+
# @param [String] extra_entropy (Optional)An extra entropy with binary format for rfc6979.
|
163
157
|
# @return [String] signature data with binary format. If unsupported algorithm specified, return nil.
|
164
158
|
# @raise [ArgumentError] If invalid arguments specified.
|
165
|
-
def sign_ecdsa(data, private_key, extra_entropy)
|
166
|
-
|
167
|
-
|
168
|
-
|
159
|
+
def sign_ecdsa(data, private_key, extra_entropy = nil)
|
160
|
+
validate_string!("private_key", private_key, 32)
|
161
|
+
validate_string!("data", data, 32)
|
162
|
+
validate_string!("extra_entropy", extra_entropy, 32) if extra_entropy
|
169
163
|
private_key = hex2bin(private_key)
|
170
|
-
raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
|
171
164
|
data = hex2bin(data)
|
172
|
-
raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
|
173
165
|
|
174
166
|
with_context do |context|
|
175
167
|
secret = FFI::MemoryPointer.new(:uchar, private_key.bytesize).put_bytes(0, private_key)
|
@@ -205,9 +197,8 @@ module Secp256k1
|
|
205
197
|
def verify_ecdsa(data, signature, pubkey)
|
206
198
|
raise ArgumentError, "sig must be String." unless signature.is_a?(String)
|
207
199
|
raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
|
208
|
-
|
200
|
+
validate_string!("data", data, 32)
|
209
201
|
data = hex2bin(data)
|
210
|
-
raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
|
211
202
|
pubkey = hex2bin(pubkey)
|
212
203
|
signature = hex2bin(signature)
|
213
204
|
with_context do |context|
|
@@ -281,5 +272,10 @@ module Secp256k1
|
|
281
272
|
def hex2bin(str)
|
282
273
|
hex_string?(str) ? [str].pack('H*') : str
|
283
274
|
end
|
275
|
+
|
276
|
+
def validate_string!(name, target, byte_length)
|
277
|
+
raise ArgumentError, "#{name} must be String." unless target.is_a?(String)
|
278
|
+
raise ArgumentError, "#{name} must be #{byte_length} bytes." unless hex2bin(target).bytesize == byte_length
|
279
|
+
end
|
284
280
|
end
|
285
281
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secp256k1rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -43,6 +43,9 @@ files:
|
|
43
43
|
- lib/secp256k1.rb
|
44
44
|
- lib/secp256k1/c.rb
|
45
45
|
- lib/secp256k1/ellswift.rb
|
46
|
+
- lib/secp256k1/musig.rb
|
47
|
+
- lib/secp256k1/musig/key_agg.rb
|
48
|
+
- lib/secp256k1/musig/session.rb
|
46
49
|
- lib/secp256k1/recovery.rb
|
47
50
|
- lib/secp256k1/schnorrsig.rb
|
48
51
|
- lib/secp256k1/version.rb
|