schnorr 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27c028f23478e028b813e4bf12bbb1bd6b9f92b75d758554c71e298b0f3a9077
4
- data.tar.gz: 2943cdc9afc73fecc8804b6c498ef0f55a850a63b71422ecf716c0d3b496e164
3
+ metadata.gz: 552fd9baac0e3e4242003e0844c1e5d08dc69f46bf6b605f59293f9793c2c8a1
4
+ data.tar.gz: 4de25e1831dc171450435256e4e15f5734bfa8da522c57b5ffb90c9d0f1c32f9
5
5
  SHA512:
6
- metadata.gz: 94e5c230cbda7f0fa66a30da0f3a62d2950aaf42b34e2c0d24980f65616d0c31b5b289eac48eaf717fd3052d3ea57d8664d373bec16298c36611c38d51162804
7
- data.tar.gz: f83ad1edee227ee190495ae58743dc461ca1e0753925ad3c01fe6d68ab92bf02b8e4d00c3043fdb1eb2f2bd568791e0f80b3615d478c6c99f66565a72090eddf
6
+ metadata.gz: dcff61f96ee167a43e31fa71b196c118ae8471c949271282c8d0f7b7bee3cc0ce94323d24e70c43619fdd3571f0084e652c1d251e0c07c59deccf63155757085
7
+ data.tar.gz: 891acabe7a536901ae0209798df11a6f3031dc90e534446ac8982a225ce1ce21c1d011d4e4378942af7cd52c11962082690c4bd51830d604fb0ff31d1a057391
data/.travis.yml CHANGED
@@ -2,6 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 2.6.2
4
4
  - 2.5.5
5
+ - 2.4.5
5
6
 
6
7
  bundler_args: --jobs=2
7
8
 
data/README.md CHANGED
@@ -98,9 +98,61 @@ Schnorr.valid_sig?(message, public_key, signature, group: ECDSA::Group::xxx)
98
98
 
99
99
  Note: But this library has only been tested with `secp256k1`. So another curve are not tested.
100
100
 
101
+ ### MuSig
102
+
103
+ The MuSig signature scheme is based on the implementation of the
104
+ [secp256k1-zkp](https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/musig/musig.md)
105
+ and [bip-schnorr](https://github.com/guggero/bip-schnorr).
106
+
107
+ Note: In this scheme, this library only supports secp256k1 curve.
108
+
109
+ ```ruby
110
+ require 'schnorr'
111
+
112
+ # Key generation
113
+ ## First, MuSig participants must compute combined public key.
114
+
115
+ combined_pubkey = Schnorr::MuSig.pubkey_combine(pubkeys) # pubkeys is an array of public key with binary format.
116
+
117
+ ## combined_pubkey is the point which added the point which performed the following multiplication to each participant's public key.
118
+ ## SHA256(TAG || TAG || ell || pubkey index) * Participant's Pubkey Point
119
+ ## ell is calculated by SHA256(pubkey1 + pubkey2 + .... pubkeyn)
120
+
121
+ ell = Schnorr::MuSig.compute_ell(pubkeys)
122
+
123
+ # Signing participant
124
+
125
+ ## the signer create new session
126
+ session = Schnorr::MuSig.session_initialize(nil, private_key, message, combined_pubkey, ell, index) # index = 0
127
+
128
+ ## each participant use same session id
129
+ session_id = session.id
130
+
131
+ ## each participant create own session.
132
+ session = Schnorr::MuSig.session_initialize(session_id, private_key, message, combined_pubkey, ell, index)
133
+
134
+ ## participant send his nonce and collect other participant's nonce.
135
+ session.nonce
136
+
137
+ other_nonces = [...]
138
+
139
+ ## If collect all participant's nonce, then calculate combined nonce.
140
+ ## In this method, if jacobi(y(combined_point)) != 1,
141
+ ## combined_point changed to combined_point.negate and session#nonce_negate changed to true.
142
+ combined_nonce = session.nonce_combine(other_nonces)
143
+
144
+ ## each participants create partial signature
145
+ partial_sig = session.partial_sign(message, combined_nonce, combined_pubkey)
146
+
147
+ ## Aggregate signature.
148
+ signature = Schnorr::MuSig.partial_sig_combine(combined_nonce, signatures)
149
+
150
+ ## Verify. If signature is valid, following method will return true.
151
+ Schnorr.valid_sig?(message, combined_pubkey, signature.encode)
152
+ ```
153
+
101
154
  ## TODO
102
155
 
103
156
  The following is unimplemented now.
104
157
 
105
- * batch verification.
106
- * MuSig
158
+ * (t, n) threshold signature.
data/lib/schnorr.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'ecdsa'
2
2
  require 'securerandom'
3
3
  require_relative 'schnorr/signature'
4
+ require_relative 'schnorr/mu_sig'
4
5
 
5
6
  module Schnorr
6
7
 
@@ -115,6 +116,31 @@ module Schnorr
115
116
  field.mod(ECDSA.normalize_digest(Digest::SHA256.digest(r + public_key + message), group.bit_length))
116
117
  end
117
118
 
118
- private_class_method :create_challenge
119
+ class ::Integer
120
+
121
+ def to_hex
122
+ hex = to_s(16)
123
+ hex.rjust((hex.length / 2.0).ceil * 2, '0')
124
+ end
125
+
126
+ def method_missing(method, *args)
127
+ return mod_pow(args[0], args[1]) if method == :pow && args.length < 3
128
+ super
129
+ end
130
+
131
+ # alternative implementation of Integer#pow for ruby 2.4 and earlier.
132
+ def mod_pow(x, y)
133
+ return self ** x unless y
134
+ b = self
135
+ result = 1
136
+ while x > 0
137
+ result = (result * b) % y if (x & 1) == 1
138
+ x >>= 1
139
+ b = (b * b) % y
140
+ end
141
+ result
142
+ end
143
+
144
+ end
119
145
 
120
146
  end
@@ -0,0 +1,84 @@
1
+ require_relative 'mu_sig/session'
2
+
3
+ module Schnorr
4
+
5
+ # https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/musig/musig.md
6
+ module MuSig
7
+
8
+ # SHA256("MuSig coefficient")
9
+ TAG = ['74894a2bec01af68225002cae9b0430ab63151ede5d31f641791976c7140b57b'].pack('H*')
10
+
11
+ module_function
12
+
13
+ # Computes ell = SHA256(pk[0], ..., pk[np-1])
14
+ # @param public_keys (Array[String]) The set of public keys with binary format.
15
+ # @return (String) ell
16
+ def compute_ell(public_keys)
17
+ Digest::SHA256.digest(public_keys.join)
18
+ end
19
+
20
+ # Combine public keys.
21
+ # @param public_keys (Array[String]) The set of public keys with binary format.
22
+ # @return (String) a combined public key point with binary format.
23
+ def pubkey_combine(public_keys, ell: nil)
24
+ ell = compute_ell(public_keys) unless ell
25
+ points = public_keys.map.with_index do |p, idx|
26
+ xi = ECDSA::Format::PointOctetString.decode(p, ECDSA::Group::Secp256k1)
27
+ xi.multiply_by_scalar(coefficient(ell, idx))
28
+ end
29
+ sum = points.inject(:+)
30
+ ECDSA::Format::PointOctetString.encode(sum, compression: true)
31
+ end
32
+
33
+ # Computes MuSig coefficient SHA256(TAG || TAG || ++ell++ || ++idx++).
34
+ # @param ell (String) a ell with binary format.
35
+ # @param idx (Integer) an index of public key.
36
+ # @return (Integer) coefficient value.
37
+ def coefficient(ell, idx)
38
+ field = ECDSA::PrimeField.new(ECDSA::Group::Secp256k1.order)
39
+ field.mod(Digest::SHA256.digest(TAG + TAG + ell + [idx].pack('I*')).unpack('H*').first.to_i(16))
40
+ end
41
+
42
+ # Initialize session to signer starts the session.
43
+ # @param session_id (String) if ++session_id++ is nil, generate new one.
44
+ # @param private_key (Integer) a private key.
45
+ # @param message (String) a message for sign with binary format.
46
+ # @param combined_pubkey (String) combined public key with binary format.
47
+ # @param ell (String) ell with binary format.
48
+ # @param index (Integer) public key index.
49
+ # @return (Schnorr::MuSig::Session) session object.
50
+ def session_initialize(session_id, private_key, message, combined_pubkey, ell, index)
51
+ raise ArgumentError, 'session_id must be 32 bytes.' if session_id && session_id.bytesize != 32
52
+ raise ArgumentError, 'message must be 32 bytes.' unless message.bytesize == 32
53
+ raise ArgumentError, 'combined_pubkey must be 33 bytes.' unless combined_pubkey.bytesize == 33
54
+ raise ArgumentError, 'ell must be 32 bytes.' unless ell.bytesize == 32
55
+ secret = ECDSA::Format::IntegerOctetString.encode(private_key, ECDSA::Group::Secp256k1.byte_length)
56
+
57
+ field = ECDSA::PrimeField.new(ECDSA::Group::Secp256k1.order)
58
+ session = Schnorr::MuSig::Session.new(session_id)
59
+ coefficient = coefficient(ell, index)
60
+ session.secret_key = field.mod(private_key * coefficient)
61
+ session.secret_nonce = Digest::SHA256.digest(session.id + message + combined_pubkey + secret).unpack('H*').first.to_i(16)
62
+ raise ArgumentError, 'secret nonce must be an integer in the ragen 1..n-1' unless field.include?(session.secret_nonce)
63
+ point_r = ECDSA::Group::Secp256k1.new_point(session.secret_nonce)
64
+ session.nonce = ECDSA::Format::PointOctetString.encode(point_r, compression: true)
65
+ session.commitment = Digest::SHA256.digest(session.nonce)
66
+ session
67
+ end
68
+
69
+ # Combine the partial signatures to obtain a complete signature.
70
+ # @param combined_nonce (Array)
71
+ # @param signatures (Array) co-signer's signature.
72
+ # @return (Schnorr::Signature) a combined signature.
73
+ def partial_sig_combine(combined_nonce, signatures)
74
+ point_r = ECDSA::Format::PointOctetString.decode(combined_nonce, ECDSA::Group::Secp256k1)
75
+ field = ECDSA::PrimeField.new(ECDSA::Group::Secp256k1.order)
76
+ signature = signatures.inject{|sum, s|field.mod(sum + s)}
77
+ Schnorr::Signature.new(point_r.x, signature)
78
+ end
79
+
80
+ private_class_method :coefficient
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,51 @@
1
+ module Schnorr
2
+ module MuSig
3
+ class Session
4
+
5
+ attr_reader :id # binary
6
+ attr_accessor :secret_key # Integer
7
+ attr_accessor :secret_nonce # Integer
8
+ attr_accessor :nonce # binary
9
+ attr_accessor :commitment # binary
10
+ attr_accessor :nonce_negate # Boolean
11
+
12
+ def initialize(session_id = SecureRandom.random_bytes(32))
13
+ @id = session_id
14
+ @nonce_negate = false
15
+ end
16
+
17
+ def nonce_negate?
18
+ @nonce_negate
19
+ end
20
+
21
+ # Combine nonce
22
+ # @param nonces (Array[String]) an array of other signer's nonce with binary format.
23
+ # @return (String) combined nonce with binary format.
24
+ def nonce_combine(nonces)
25
+ points = ([nonce]+ nonces).map.with_index {|n, index|ECDSA::Format::PointOctetString.decode(n, ECDSA::Group::Secp256k1)}
26
+ r_point = points.inject(:+)
27
+ unless ECDSA::PrimeField.jacobi(r_point.y, ECDSA::Group::Secp256k1.field.prime) == 1
28
+ self.nonce_negate = true
29
+ r_point = r_point.negate
30
+ end
31
+ ECDSA::Format::PointOctetString.encode(r_point, compression: true)
32
+ end
33
+
34
+ # Compute partial signature.
35
+ # @param message (String) a message for signature with binary format.
36
+ # @param combined_nonce (String) combined nonce with binary format.
37
+ # @param combined_pubkey (String) combined public key with binary format.
38
+ # @return (Integer) a partial signature.
39
+ def partial_sign(message, combined_nonce, combined_pubkey)
40
+ field = ECDSA::PrimeField.new(ECDSA::Group::Secp256k1.order)
41
+ point_r = ECDSA::Format::PointOctetString.decode(combined_nonce, ECDSA::Group::Secp256k1)
42
+ point_p = ECDSA::Format::PointOctetString.decode(combined_pubkey, ECDSA::Group::Secp256k1)
43
+ e = Schnorr.create_challenge(point_r.x, point_p, message, field, point_r.group)
44
+ k = secret_nonce
45
+ k = 0 - k if nonce_negate?
46
+ field.mod(secret_key * e + k)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module Schnorr
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schnorr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-01 00:00:00.000000000 Z
11
+ date: 2019-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -86,6 +86,8 @@ files:
86
86
  - bin/console
87
87
  - bin/setup
88
88
  - lib/schnorr.rb
89
+ - lib/schnorr/mu_sig.rb
90
+ - lib/schnorr/mu_sig/session.rb
89
91
  - lib/schnorr/signature.rb
90
92
  - lib/schnorr/version.rb
91
93
  - schnorrrb.gemspec
@@ -108,8 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
110
  - !ruby/object:Gem::Version
109
111
  version: '0'
110
112
  requirements: []
111
- rubyforge_project:
112
- rubygems_version: 2.7.8
113
+ rubygems_version: 3.0.3
113
114
  signing_key:
114
115
  specification_version: 4
115
116
  summary: The ruby implementation of Schnorr signature.