bip-schnorr 0.1.0 → 0.4.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: 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