bip-schnorr 0.1.0 → 0.4.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: c22da99e3fed7eea60436b7007532b752066996c9f2dd87f1f71028d7eaec357
4
- data.tar.gz: 87bf98d7796bb6a31aad59e12914dd2f67584b852d45a7561e505061b3876284
3
+ metadata.gz: 726ee90533a30264534d3ba2fff1e967334c56e870a25e778d86688001e6a5e2
4
+ data.tar.gz: 50b8e0df0e3c5bacb276b5dc77b088e2b59dddf6485c8f62ba95f68f4a4a4e3d
5
5
  SHA512:
6
- metadata.gz: aa3263695c27d8453971bb881c19f5b05c5d69bea52770823de956fc5cfe8d65fdd5393790fbd282eb8a78a13f56fbde3046f0222d9bbfefc668fedb099d150d
7
- data.tar.gz: f828993f5d2c149aebc61dbd51c3569bf3ba97fe71aaab5e5aafbf1c7742d880e55b2db6dfa698e7dae521c50eea5d43c7ace1eb1e750b389ab11e8c2a0d9991
6
+ metadata.gz: 0ac0a2ec193d10e41cba96f1a5071998abf6602a0cf05fc44dc8bdd82853fb392f554857d6632cb1d3c655d7d0155c22efaea20ec0b0d089ee690de4a7443af0
7
+ data.tar.gz: 8b9023e307606166981b1ac136fb6e84382a0de003175fc67bc6e07624ae997c0554674ad96f5f56782539427d2d1cf441b993b78b9a62f0812881538604dd5a
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version: ['2.6', '2.7', '3.0']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
28
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
29
+ # uses: ruby/setup-ruby@v1
30
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
34
+ - name: Run tests
35
+ run: bundle exec rake spec
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.7.0
1
+ ruby-3.0.0
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  This is a Ruby implementation of the Schnorr signature scheme over the elliptic curve.
4
4
  This implementation relies on the [ecdsa gem](https://github.com/DavidEGrayson/ruby_ecdsa) for operate elliptic curves.
5
5
 
6
- The code is based upon the initial proposal of Pieter Wuille's [bip-schnorr](https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki).
6
+ The code is based upon the [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
7
7
 
8
8
  ## Installation
9
9
 
@@ -23,17 +23,20 @@ Or install it yourself as:
23
23
 
24
24
  ## Usage
25
25
 
26
- ### Singing
26
+ ### Signing
27
27
 
28
28
  ```ruby
29
29
  require 'schnorr'
30
30
 
31
- private_key = 0xB7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF
31
+ private_key = ['B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF'].pack("H*")
32
32
 
33
33
  message = ['5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C'].pack('H*')
34
34
 
35
35
  # create signature
36
36
  signature = Schnorr.sign(message, private_key)
37
+ # if use auxiliary random data, specify it to the 3rd arguments.
38
+ aux_rand = SecureRandom.bytes(32) # aux_rand must be a 32-byte binary.
39
+ signature = Schnorr.sign(message, private_key, aux_rand)
37
40
 
38
41
  # signature r value
39
42
  signature.r
@@ -55,9 +58,9 @@ require 'schnorr'
55
58
  # public key does not start with 02 or 03.
56
59
  public_key = ['DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659'].pack('H*')
57
60
 
58
- signature = ['e7758e20e67a0607433cf8a1f0383a02c7b56792aab71763173ab7085cab8768082aae167787bc3572d15176dc26c073d9b03726aed0b59c728aa6538dc03d57'].pack('H*')
61
+ signature = ['6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A'].pack('H*')
59
62
 
60
- message = ['5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C'].pack('H*')
63
+ message = ['243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89'].pack('H*')
61
64
 
62
65
  # verify signature.(result is true or false)
63
66
  result = Schnorr.valid_sig?(message, public_key, signature)
@@ -71,6 +74,6 @@ sig = Schnorr::Signature.decode(signature)
71
74
  This library changes the following functions of `ecdsa` gem in `lib/schnorr/ec_point_ext.rb`.
72
75
 
73
76
  * `ECDSA::Point` class has following two instance methods.
74
- * `#has_square_y?` check this point does not infinity and square?(y coordinate)
75
- * `#square?(x)` check whether `x` is a quadratic residue modulo p.
76
- * `ECDSA::Format::PointOctetString#decode` supports decoding only from x coordinate.
77
+ * `#has_even_y?` check the y-coordinate of this point is an even.
78
+ * `#encode(only_x = false)` encode this point into a binary string.
79
+ * `ECDSA::Format::PointOctetString#decode` supports decoding only from x coordinate.
@@ -23,6 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency "ecdsa", "~> 1.2.0"
24
24
 
25
25
  spec.add_development_dependency "bundler"
26
- spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rake", ">= 12.3.3"
27
27
  spec.add_development_dependency "rspec", "~> 3.0"
28
28
  end
data/lib/schnorr.rb CHANGED
@@ -4,32 +4,40 @@ require_relative 'schnorr/ec_point_ext'
4
4
  require_relative 'schnorr/signature'
5
5
 
6
6
  module Schnorr
7
-
8
7
  module_function
9
8
 
10
9
  GROUP = ECDSA::Group::Secp256k1
11
10
 
12
11
  # Generate schnorr signature.
13
12
  # @param message (String) A message to be signed with binary format.
14
- # @param private_key (Integer) The private key.
15
- # (The number of times to add the generator point to itself to get the public key.)
13
+ # @param private_key (String) The private key with binary format.
14
+ # @param aux_rand (String) The auxiliary random data with binary format.
15
+ # If not specified, random data is not used and the private key is used to calculate the nonce.
16
16
  # @return (Schnorr::Signature)
17
- def sign(message, private_key)
17
+ def sign(message, private_key, aux_rand = nil)
18
18
  raise 'The message must be a 32-byte array.' unless message.bytesize == 32
19
- p = GROUP.new_point(private_key)
20
- seckey = p.has_square_y? ? private_key : GROUP.order - private_key
21
- secret = ECDSA::Format::IntegerOctetString.encode(seckey, GROUP.byte_length)
22
19
 
23
- k0 = ECDSA::Format::IntegerOctetString.decode(tagged_hash('BIPSchnorrDerive', secret + message)) % GROUP.order
20
+ d0 = private_key.unpack1('H*').to_i(16)
21
+ raise 'private_key must be an integer in the range 1..n-1.' unless 0 < d0 && d0 <= (GROUP.order - 1)
22
+ raise 'aux_rand must be 32 bytes.' if !aux_rand.nil? && aux_rand.bytesize != 32
23
+
24
+ p = GROUP.new_point(d0)
25
+ d = p.has_even_y? ? d0 : GROUP.order - d0
26
+
27
+ t = aux_rand.nil? ? d : d ^ tagged_hash('BIP0340/aux', aux_rand).unpack1('H*').to_i(16)
28
+ t = ECDSA::Format::IntegerOctetString.encode(t, GROUP.byte_length)
29
+
30
+ k0 = ECDSA::Format::IntegerOctetString.decode(tagged_hash('BIP0340/nonce', t + p.encode(true) + message)) % GROUP.order
24
31
  raise 'Creation of signature failed. k is zero' if k0.zero?
25
32
 
26
33
  r = GROUP.new_point(k0)
34
+ k = r.has_even_y? ? k0 : GROUP.order - k0
35
+ e = create_challenge(r.x, p, message)
27
36
 
28
- k = r.has_square_y? ? k0 : GROUP.order - k0
29
-
30
- e = create_challenge(r.x, p, message, GROUP)
37
+ sig = Schnorr::Signature.new(r.x, (k + e * d) % GROUP.order)
38
+ raise 'The created signature does not pass verification.' unless valid_sig?(message, p.encode(true), sig.encode)
31
39
 
32
- Schnorr::Signature.new(r.x, (k + e * seckey) % GROUP.order)
40
+ sig
33
41
  end
34
42
 
35
43
  # Verifies the given {Signature} and returns true if it is valid.
@@ -49,22 +57,23 @@ module Schnorr
49
57
  # @param signature (String) The signature with binary format.
50
58
  # @return (Boolean)
51
59
  def check_sig!(message, public_key, signature)
60
+ raise InvalidSignatureError, 'The message must be a 32-byte array.' unless message.bytesize == 32
61
+ raise InvalidSignatureError, 'The public key must be a 32-byte array.' unless public_key.bytesize == 32
62
+
52
63
  sig = Schnorr::Signature.decode(signature)
53
- pubkey = ECDSA::Format::PointOctetString.decode(public_key, GROUP)
64
+ pubkey = ECDSA::Format::PointOctetString.decode_from_x(public_key, GROUP)
54
65
  field = GROUP.field
55
66
 
56
- raise Schnorr::InvalidSignatureError, 'Invalid signature: r is not in the field.' unless field.include?(sig.r)
57
- raise Schnorr::InvalidSignatureError, 'Invalid signature: s is not in the field.' unless field.include?(sig.s)
58
67
  raise Schnorr::InvalidSignatureError, 'Invalid signature: r is zero.' if sig.r.zero?
59
68
  raise Schnorr::InvalidSignatureError, 'Invalid signature: s is zero.' if sig.s.zero?
60
69
  raise Schnorr::InvalidSignatureError, 'Invalid signature: r is larger than field size.' if sig.r >= field.prime
61
70
  raise Schnorr::InvalidSignatureError, 'Invalid signature: s is larger than group order.' if sig.s >= GROUP.order
62
71
 
63
- e = create_challenge(sig.r, pubkey, message, GROUP)
72
+ e = create_challenge(sig.r, pubkey, message)
64
73
 
65
74
  r = GROUP.new_point(sig.s) + pubkey.multiply_by_scalar(GROUP.order - e)
66
75
 
67
- if r.infinity? || !r.has_square_y? || r.x != sig.r
76
+ if r.infinity? || !r.has_even_y? || r.x != sig.r
68
77
  raise Schnorr::InvalidSignatureError, 'signature verification failed.'
69
78
  end
70
79
 
@@ -74,22 +83,22 @@ module Schnorr
74
83
  # create signature digest.
75
84
  # @param (Integer) x a x coordinate for R.
76
85
  # @param (ECDSA::Point) p a public key.
77
- # @param (ECDSA::Group) group the group of elliptic curve.
78
86
  # @return (Integer) digest e.
79
- def create_challenge(x, p, message, group)
80
- r_x = ECDSA::Format::IntegerOctetString.encode(x, group.byte_length)
81
- p_x = ECDSA::Format::IntegerOctetString.encode(p.x, group.byte_length)
82
- (ECDSA.normalize_digest(tagged_hash('BIPSchnorr', r_x + p_x + message), group.bit_length)) % group.order
87
+ def create_challenge(x, p, message)
88
+ r_x = ECDSA::Format::IntegerOctetString.encode(x, GROUP.byte_length)
89
+ (ECDSA.normalize_digest(tagged_hash('BIP0340/challenge', r_x + p.encode(true) + message), GROUP.bit_length)) % GROUP.order
83
90
  end
84
91
 
85
92
  # Generate tagged hash value.
93
+ # @param (String) tag tag value.
94
+ # @param (String) msg the message to be hashed.
95
+ # @return (String) the hash value with binary format.
86
96
  def tagged_hash(tag, msg)
87
97
  tag_hash = Digest::SHA256.digest(tag)
88
98
  Digest::SHA256.digest(tag_hash + tag_hash + msg)
89
99
  end
90
100
 
91
101
  class ::Integer
92
-
93
102
  def to_hex
94
103
  hex = to_s(16)
95
104
  hex.rjust((hex.length / 2.0).ceil * 2, '0')
@@ -102,7 +111,8 @@ module Schnorr
102
111
 
103
112
  # alternative implementation of Integer#pow for ruby 2.4 and earlier.
104
113
  def mod_pow(x, y)
105
- return self ** x unless y
114
+ return self**x unless y
115
+
106
116
  b = self
107
117
  result = 1
108
118
  while x > 0
@@ -112,7 +122,5 @@ module Schnorr
112
122
  end
113
123
  result
114
124
  end
115
-
116
125
  end
117
-
118
126
  end
@@ -2,17 +2,20 @@
2
2
  module ECDSA
3
3
  class Point
4
4
 
5
- # Check this point does not infinity and square?(y coordinate)
6
- # @return (Boolean)
7
- def has_square_y?
8
- !infinity? && square?(y)
5
+ # Check the y-coordinate of this point is an even.
6
+ # @return (Boolean) if even, return true.
7
+ def has_even_y?
8
+ y.even?
9
9
  end
10
10
 
11
- # Check whether +x+ is a quadratic residue modulo p.
12
- # @param x (Integer)
13
- # @return (Boolean)
14
- def square?(x)
15
- x.pow((group.field.prime - 1) / 2, group.field.prime) == 1
11
+ # Encode this point into a binary string.
12
+ # @param (Boolean) only_x whether or not to encode only X-coordinate. default is false.
13
+ def encode(only_x = false)
14
+ if only_x
15
+ ECDSA::Format::FieldElementOctetString.encode(x, group.field)
16
+ else
17
+ ECDSA::Format::PointOctetString.encode(self, {compression: true})
18
+ end
16
19
  end
17
20
 
18
21
  end
@@ -26,29 +29,35 @@ module ECDSA
26
29
 
27
30
  raise DecodeError, 'Point octet string is empty.' if string.empty?
28
31
 
29
- case string[0].ord
30
- when 0
31
- check_length string, 1
32
- return group.infinity
33
- when 2
34
- decode_compressed string, group, 0
35
- when 3
36
- decode_compressed string, group, 1
37
- when 4
38
- decode_uncompressed string, group
32
+ if string.bytesize == 32
33
+ decode_from_x(string, group)
39
34
  else
40
- return decode_from_x(string, group) if string.bytesize == 32
41
- raise DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord
35
+ case string[0].ord
36
+ when 0
37
+ check_length string, 1
38
+ return group.infinity
39
+ when 2
40
+ decode_compressed string, group, 0
41
+ when 3
42
+ decode_compressed string, group, 1
43
+ when 4
44
+ decode_uncompressed string, group
45
+ else
46
+ raise DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord
47
+ end
42
48
  end
43
49
  end
44
50
 
45
51
  # decode from x coordinate.
52
+ # @param (String) x_string X-coordinate binary string
53
+ # @param (ECDSA::Group) group A group of elliptic curves to use.
54
+ # @return (ECDSA::Point) decoded point.
46
55
  def self.decode_from_x(x_string, group)
47
56
  x = ECDSA::Format::FieldElementOctetString.decode(x_string, group.field)
48
57
  y_sq = group.field.mod(x.pow(3, group.field.prime) + 7)
49
58
  y = y_sq.pow((group.field.prime + 1)/4, group.field.prime)
50
59
  raise DecodeError, 'Public key not on the curve.' unless y.pow(2, group.field.prime) == y_sq
51
- finish_decode(x, y, group)
60
+ finish_decode(x, y.even? ? y : group.field.prime - y, group)
52
61
  end
53
62
 
54
63
  end
@@ -22,8 +22,8 @@ module Schnorr
22
22
  # @return (Signature) signature instance.
23
23
  def self.decode(string)
24
24
  raise InvalidSignatureError, 'Invalid schnorr signature length.' unless string.bytesize == 64
25
- r = string[0...32].unpack('H*').first.to_i(16)
26
- s = string[32..-1].unpack('H*').first.to_i(16)
25
+ r = string[0...32].unpack1('H*').to_i(16)
26
+ s = string[32..-1].unpack1('H*').to_i(16)
27
27
  new(r, s)
28
28
  end
29
29
 
@@ -1,3 +1,3 @@
1
1
  module Schnorr
2
- VERSION = "0.1.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bip-schnorr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-22 00:00:00.000000000 Z
11
+ date: 2021-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: 12.3.3
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: 12.3.3
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -73,11 +73,11 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - ".github/workflows/ruby.yml"
76
77
  - ".gitignore"
77
78
  - ".rspec"
78
79
  - ".ruby-gemset"
79
80
  - ".ruby-version"
80
- - ".travis.yml"
81
81
  - CODE_OF_CONDUCT.md
82
82
  - Gemfile
83
83
  - LICENSE.txt
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubygems_version: 3.0.3
112
+ rubygems_version: 3.2.3
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: The ruby implementation of bip-schnorr.
data/.travis.yml DELETED
@@ -1,11 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.7.0
4
- - 2.6.3
5
- - 2.5.5
6
- - 2.4.6
7
-
8
- bundler_args: --jobs=2
9
-
10
- script:
11
- - bundle exec rake spec