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 +4 -4
- data/.github/workflows/ruby.yml +35 -0
- data/.ruby-version +1 -1
- data/README.md +11 -8
- data/bip-schnorrrb.gemspec +1 -1
- data/lib/schnorr.rb +34 -26
- data/lib/schnorr/ec_point_ext.rb +31 -22
- data/lib/schnorr/signature.rb +2 -2
- data/lib/schnorr/version.rb +1 -1
- metadata +8 -8
- data/.travis.yml +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 726ee90533a30264534d3ba2fff1e967334c56e870a25e778d86688001e6a5e2
|
4
|
+
data.tar.gz: 50b8e0df0e3c5bacb276b5dc77b088e2b59dddf6485c8f62ba95f68f4a4a4e3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
-
###
|
26
|
+
### Signing
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
require 'schnorr'
|
30
30
|
|
31
|
-
private_key =
|
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 = ['
|
61
|
+
signature = ['6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A'].pack('H*')
|
59
62
|
|
60
|
-
message = ['
|
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
|
-
* `#
|
75
|
-
* `#
|
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.
|
data/bip-schnorrrb.gemspec
CHANGED
@@ -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", "
|
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 (
|
15
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
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.
|
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
|
80
|
-
r_x = ECDSA::Format::IntegerOctetString.encode(x,
|
81
|
-
|
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
|
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
|
data/lib/schnorr/ec_point_ext.rb
CHANGED
@@ -2,17 +2,20 @@
|
|
2
2
|
module ECDSA
|
3
3
|
class Point
|
4
4
|
|
5
|
-
# Check
|
6
|
-
# @return (Boolean)
|
7
|
-
def
|
8
|
-
|
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
|
-
#
|
12
|
-
# @param
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
30
|
-
|
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
|
-
|
41
|
-
|
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
|
data/lib/schnorr/signature.rb
CHANGED
@@ -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].
|
26
|
-
s = string[32..-1].
|
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
|
|
data/lib/schnorr/version.rb
CHANGED
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.
|
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:
|
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:
|
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:
|
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.
|
112
|
+
rubygems_version: 3.2.3
|
113
113
|
signing_key:
|
114
114
|
specification_version: 4
|
115
115
|
summary: The ruby implementation of bip-schnorr.
|