secp256k1rb 0.1.1 → 0.2.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 +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
|