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 +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.
|