bip-schnorr 0.1.0 → 0.2.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: e68ad5d5ccf6213171279052ddef947229a8eab7597cc93ca2b8cc312d0e7254
4
+ data.tar.gz: b77fed1e8cc5dba386dcf6ce81bfe3d36c20985bd246b5c5d5e6f9ce45db6177
5
5
  SHA512:
6
- metadata.gz: aa3263695c27d8453971bb881c19f5b05c5d69bea52770823de956fc5cfe8d65fdd5393790fbd282eb8a78a13f56fbde3046f0222d9bbfefc668fedb099d150d
7
- data.tar.gz: f828993f5d2c149aebc61dbd51c3569bf3ba97fe71aaab5e5aafbf1c7742d880e55b2db6dfa698e7dae521c50eea5d43c7ace1eb1e750b389ab11e8c2a0d9991
6
+ metadata.gz: 0d2a1ffd174074375af26fe2e4dfae18d6a72e9e44e76861d09e3724d3d13c4b90e182b2dc7533cefb93fbc44317cd92f4941716ae3e0fe549dc4e950e09b710
7
+ data.tar.gz: ebeba83783283f328b321ab19a5c3cebf4f74aa0d56c9f491283a502367a4baf29ea78f905b335f547f1cd8b6215395260c72981776d9e2743d3b4d03d408d7f
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
 
@@ -28,7 +28,7 @@ Or install it yourself as:
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
 
@@ -55,9 +55,9 @@ require 'schnorr'
55
55
  # public key does not start with 02 or 03.
56
56
  public_key = ['DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659'].pack('H*')
57
57
 
58
- signature = ['e7758e20e67a0607433cf8a1f0383a02c7b56792aab71763173ab7085cab8768082aae167787bc3572d15176dc26c073d9b03726aed0b59c728aa6538dc03d57'].pack('H*')
58
+ signature = ['0E12B8C520948A776753A96F21ABD7FDC2D7D0C0DDC90851BE17B04E75EF86A47EF0DA46C4DC4D0D1BCB8668C2CE16C54C7C23A6716EDE303AF86774917CF928'].pack('H*')
59
59
 
60
- message = ['5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C'].pack('H*')
60
+ message = ['243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89'].pack('H*')
61
61
 
62
62
  # verify signature.(result is true or false)
63
63
  result = Schnorr.valid_sig?(message, public_key, signature)
@@ -73,4 +73,6 @@ This library changes the following functions of `ecdsa` gem in `lib/schnorr/ec_p
73
73
  * `ECDSA::Point` class has following two instance methods.
74
74
  * `#has_square_y?` check this point does not infinity and square?(y coordinate)
75
75
  * `#square?(x)` check whether `x` is a quadratic residue modulo p.
76
+ * `#has_even_y?` check the y-coordinate of this point is an even.
77
+ * `#encode(only_x = false)` encode this point into a binary string.
76
78
  * `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
@@ -11,25 +11,32 @@ module Schnorr
11
11
 
12
12
  # Generate schnorr signature.
13
13
  # @param message (String) A message to be signed with binary format.
14
- # @param private_key (Integer) The private key.
14
+ # @param private_key (String) The private key with binary format.
15
+ # @param aux_rand (String) The auxiliary random data with binary format. If not specified, SecureRandom is used to generate a random value.
15
16
  # (The number of times to add the generator point to itself to get the public key.)
16
17
  # @return (Schnorr::Signature)
17
- def sign(message, private_key)
18
+ def sign(message, private_key, aux_rand = SecureRandom.bytes(32))
18
19
  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)
20
+ d0 = private_key.unpack('H*').first.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.' unless aux_rand.bytesize == 32
22
23
 
23
- k0 = ECDSA::Format::IntegerOctetString.decode(tagged_hash('BIPSchnorrDerive', secret + message)) % GROUP.order
24
+ p = GROUP.new_point(d0)
25
+ d = p.has_even_y? ? d0 : GROUP.order - d0
26
+
27
+ t = d ^ tagged_hash('BIP340/aux', aux_rand).unpack('H*').first.to_i(16)
28
+ t = ECDSA::Format::IntegerOctetString.encode(t, GROUP.byte_length)
29
+
30
+ k0 = ECDSA::Format::IntegerOctetString.decode(tagged_hash('BIP340/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)
27
-
28
34
  k = r.has_square_y? ? k0 : GROUP.order - k0
35
+ e = create_challenge(r.x, p, message)
29
36
 
30
- e = create_challenge(r.x, p, message, GROUP)
31
-
32
- Schnorr::Signature.new(r.x, (k + e * seckey) % GROUP.order)
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)
39
+ sig
33
40
  end
34
41
 
35
42
  # Verifies the given {Signature} and returns true if it is valid.
@@ -49,18 +56,20 @@ module Schnorr
49
56
  # @param signature (String) The signature with binary format.
50
57
  # @return (Boolean)
51
58
  def check_sig!(message, public_key, signature)
59
+ raise InvalidSignatureError, 'The message must be a 32-byte array.' unless message.bytesize == 32
60
+ raise InvalidSignatureError, 'The public key must be a 32-byte array.' unless public_key.bytesize == 32
61
+
62
+
52
63
  sig = Schnorr::Signature.decode(signature)
53
64
  pubkey = ECDSA::Format::PointOctetString.decode(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
 
@@ -74,15 +83,16 @@ 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('BIP340/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)
@@ -8,6 +8,12 @@ module ECDSA
8
8
  !infinity? && square?(y)
9
9
  end
10
10
 
11
+ # Check the y-coordinate of this point is an even.
12
+ # @return (Boolean) if even, return true.
13
+ def has_even_y?
14
+ y.even?
15
+ end
16
+
11
17
  # Check whether +x+ is a quadratic residue modulo p.
12
18
  # @param x (Integer)
13
19
  # @return (Boolean)
@@ -15,6 +21,16 @@ module ECDSA
15
21
  x.pow((group.field.prime - 1) / 2, group.field.prime) == 1
16
22
  end
17
23
 
24
+ # Encode this point into a binary string.
25
+ # @param (Boolean) only_x whether or not to encode only X-coordinate. default is false.
26
+ def encode(only_x = false)
27
+ if only_x
28
+ ECDSA::Format::FieldElementOctetString.encode(x, group.field)
29
+ else
30
+ ECDSA::Format::PointOctetString.encode(self, {compression: true})
31
+ end
32
+ end
33
+
18
34
  end
19
35
 
20
36
  module Format
@@ -43,12 +59,15 @@ module ECDSA
43
59
  end
44
60
 
45
61
  # decode from x coordinate.
62
+ # @param (String) x_string X-coordinate binary string
63
+ # @param (ECDSA::Group) group A group of elliptic curves to use.
64
+ # @return (ECDSA::Point) decoded point.
46
65
  def self.decode_from_x(x_string, group)
47
66
  x = ECDSA::Format::FieldElementOctetString.decode(x_string, group.field)
48
67
  y_sq = group.field.mod(x.pow(3, group.field.prime) + 7)
49
68
  y = y_sq.pow((group.field.prime + 1)/4, group.field.prime)
50
69
  raise DecodeError, 'Public key not on the curve.' unless y.pow(2, group.field.prime) == y_sq
51
- finish_decode(x, y, group)
70
+ finish_decode(x, y.even? ? y : group.field.prime - y, group)
52
71
  end
53
72
 
54
73
  end
@@ -1,3 +1,3 @@
1
1
  module Schnorr
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.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.2.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: 2020-05-08 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
@@ -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.1.2
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: The ruby implementation of bip-schnorr.