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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aff0d24f08ccdd2fb1d622ca2cb8655a9e36d3960890710861878e32e55850b3
4
- data.tar.gz: 6915105a7e248f5a1ca810e5f0b8af3dea75731a7b65bd111df74a8691f2d907
3
+ metadata.gz: 11620dd56323837c51b4adaf78b80e683c039b6086af0d501df6890f31c32e07
4
+ data.tar.gz: 27b5544236685afd32b11dd636d1f78ca626cfb63a120c6da88504238aa0c6b2
5
5
  SHA512:
6
- metadata.gz: 307ef35d2afa388b1640a2df6438ca19b14cc5d1bf1920f509e2f1768d610d0534f926ad0122122b5ad30da9ab6a9da3f49b17b6d1215fc0bd1e1fc55c42ab71
7
- data.tar.gz: a533565583b3af85dbfbb9f259e3c644a4de3ec5a8c52e08b37eb46ff0bdba922e5a14cd3f2588792caa1ae4934881b9563b045c6907dda50571349a97dcef9c
6
+ metadata.gz: 14177d6aa88872ae15db021b45d492ec59b083ad751d224d852c9f5440fd2a9b51855608a0c0456175aadd09b06d68591122b799fc2a7d08724c4f726c84c980
7
+ data.tar.gz: '0805a6d29bad9054f581115919365a3e8623adc8d93f64e92d18435f33012056a227ccbb4bd08c5421ec4a3bbc1e85f94d8b116cd47d4c205f51e0efe5306f00'
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # secp256k1rb
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/secp256k1`. To experiment with that code, run `bin/console` for an interactive prompt.
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
- TODO: Write usage instructions here
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
- ## Development
29
+ By including the Secp256k1 module, you can use the features provided by the `libsepc256k1` library. For example:
28
30
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
31
+ ```ruby
32
+ require 'secp256k1'
30
33
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+ include Secp256k1
32
35
 
33
- ## Contributing
36
+ generate_key_pair
37
+ => ["e00c2ae99e59b5262be3d507d026081f0e6cf9972ffdd4f2d45a390f7a41b053", "027e0f70b540d627422cf7bb77d86ae1bb6829c80104dd48dc2539e6277ea25624"]
38
+ ```
34
39
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/secp256k1. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/secp256k1/blob/master/CODE_OF_CONDUCT.md).
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
- ## License
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
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
47
+ ### Compatibility
40
48
 
41
- ## Code of Conduct
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]
@@ -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
- raise ArgumentError, "ell_key must be String." unless ell_key.is_a?(String)
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
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
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
- raise ArgumentError, "their_ell_pubkey must be String." unless their_ell_pubkey.is_a?(String)
50
- raise ArgumentError, "our_ell_pubkey must be String." unless our_ell_pubkey.is_a?(String)
51
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
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
@@ -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
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
11
- raise ArgumentError, "data must by String." unless data.is_a?(String)
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
- raise ArgumentError, "data must be String." unless data.is_a?(String)
43
- raise ArgumentError, "signature must be String." unless signature.is_a?(String)
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
 
@@ -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 a extra entropy.
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
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
12
- raise ArgumentError, "data must by String." unless data.is_a?(String)
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
- raise ArgumentError, "sig must be String." unless signature.is_a?(String)
38
- raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
39
- raise ArgumentError, "data must be String." unless data.is_a?(String)
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|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secp256k1
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
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
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
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
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
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 a extra entropy with binary format for rfc6979.
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
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
167
- raise ArgumentError, "data must by String." unless data.is_a?(String)
168
- raise ArgumentError, "extra_entropy must be String." if !extra_entropy.nil? && !extra_entropy.is_a?(String)
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
- raise ArgumentError, "data must be String." unless data.is_a?(String)
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.1.1
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-14 00:00:00.000000000 Z
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