schnorr 0.2.0 → 0.3.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: 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.