ecdsa_ext 0.5.2 → 0.6.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/.rubocop.yml +1 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile +8 -8
- data/README.md +9 -0
- data/lib/ecdsa/ext/abstract_point.rb +25 -1
- data/lib/ecdsa/ext/jacobian_point.rb +1 -0
- data/lib/ecdsa/ext/projective_point.rb +1 -0
- data/lib/ecdsa/ext/sign_verify.rb +15 -2
- data/lib/ecdsa/ext/version.rb +1 -1
- data/sig/ecdsa_ext.rbs +56 -3
- metadata +1 -2
- data/.prettierignore +0 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8ae635cb030065845eb7e0e08d33adb64c96789e75f8e9b6eac37566e36e4d0
|
|
4
|
+
data.tar.gz: e4d993a63966e99d63df34b586dbc28f05e6ad093e0aa1d96134eefa93b7bad3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8ac2e8f9cc036bede6914415e81526fd82788c476ed4694e38841ea0c91176df0148b39fee7ded35c8be4fa520d0f4e9f6c53fd515397a44a14d24bc1ccef8ed
|
|
7
|
+
data.tar.gz: 5f3e6b6fd617492d8b8f7b8510341ffdc6fea4f16b2bac040fca0c6064d34201cba346474a78db663757d2e909a6095abb75ce3022d13da0145ca3838a302eae
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-11-01
|
|
4
|
+
|
|
5
|
+
- Fix `==` of `ProjectivePoint`/`JacobianPoint` to return false for points on different groups
|
|
6
|
+
- Add `eql?` and `hash` to `AbstractPoint` so that points can be used as Hash keys consistently with `==`
|
|
7
|
+
- Fix the number of iterations in `multiply_by_scalar` to depend only on the bit length of the scalar, and document that signing is still not constant time
|
|
8
|
+
- Raise `ArgumentError` in `ECDSA.sign` when `temporary_key` is not in the range [1, n-1]
|
|
9
|
+
- Raise `ArgumentError` when constructing a point with z = 0 (use `infinity_point` instead)
|
|
10
|
+
- Make `ecdsa/ext/sign_verify` require `ecdsa_ext` itself so it can be required standalone
|
|
11
|
+
- Rewrite `sig/ecdsa_ext.rbs` to describe the actual API
|
|
12
|
+
|
|
3
13
|
## [0.5.2] - 2026-08-07
|
|
4
14
|
|
|
5
15
|
- Fix a timing leak of the ephemeral key's bit length in `ECDSA.sign` by padding the scalar with the group order
|
data/Gemfile
CHANGED
|
@@ -9,15 +9,15 @@ gem "rake", "~> 13.0"
|
|
|
9
9
|
|
|
10
10
|
gem "rspec", "~> 3.0"
|
|
11
11
|
|
|
12
|
-
gem
|
|
12
|
+
gem "syntax_tree"
|
|
13
13
|
|
|
14
|
-
gem
|
|
15
|
-
gem
|
|
14
|
+
gem "rubocop-rake"
|
|
15
|
+
gem "rubocop-rspec"
|
|
16
16
|
|
|
17
|
-
gem
|
|
17
|
+
gem "benchmark-ips"
|
|
18
18
|
|
|
19
19
|
# Bundled gems removed from default gems in Ruby 3.5/4.0.
|
|
20
|
-
gem
|
|
21
|
-
gem
|
|
22
|
-
gem
|
|
23
|
-
gem
|
|
20
|
+
gem "base64"
|
|
21
|
+
gem "benchmark"
|
|
22
|
+
gem "ostruct"
|
|
23
|
+
gem "racc"
|
data/README.md
CHANGED
|
@@ -6,6 +6,11 @@ which mainly speeds up the computation of points on elliptic curves by using pro
|
|
|
6
6
|
This gem was not written by a cryptography expert and has not been carefully checked as with the original gem.
|
|
7
7
|
It is provided "as is" and it is the user's responsibility to make sure it will be suitable for the desired purpose.
|
|
8
8
|
|
|
9
|
+
In particular, this gem does **not** provide resistance against timing or other side-channel attacks.
|
|
10
|
+
Scalar multiplication uses a data-dependent NAF add/subtract pattern, the modular inverse is computed with
|
|
11
|
+
a variable-time algorithm, and Ruby's `Integer` arithmetic itself is not constant time.
|
|
12
|
+
Do not use this gem in an environment where an attacker can observe the timing of signing operations.
|
|
13
|
+
|
|
9
14
|
## Installation
|
|
10
15
|
|
|
11
16
|
Add this line to your application's Gemfile:
|
|
@@ -92,3 +97,7 @@ add the following code. This code is a monkey patch to do the existing process i
|
|
|
92
97
|
```ruby
|
|
93
98
|
require 'ecdsa/ext/sign_verify'
|
|
94
99
|
```
|
|
100
|
+
|
|
101
|
+
Note that `ECDSA.sign` provided by this patch is not constant time (see the disclaimer at the top of this README).
|
|
102
|
+
It only pads the ephemeral key so that the number of loop iterations in scalar multiplication is fixed,
|
|
103
|
+
which is a partial mitigation and not a substitute for a constant-time implementation.
|
|
@@ -20,6 +20,10 @@ module ECDSA
|
|
|
20
20
|
raise ArgumentError, "Invalid x: #{x.inspect}" unless x.is_a?(Integer)
|
|
21
21
|
raise ArgumentError, "Invalid y: #{y.inspect}" unless y.is_a?(Integer)
|
|
22
22
|
raise ArgumentError, "Invalid z: #{z.inspect}" unless z.is_a?(Integer)
|
|
23
|
+
if z.zero?
|
|
24
|
+
raise ArgumentError,
|
|
25
|
+
"z must not be zero. Use infinity_point for the point at infinity."
|
|
26
|
+
end
|
|
23
27
|
end
|
|
24
28
|
end
|
|
25
29
|
|
|
@@ -79,7 +83,10 @@ module ECDSA
|
|
|
79
83
|
q = self.class.infinity_point(group)
|
|
80
84
|
v = self
|
|
81
85
|
p, n = to_naf(x)
|
|
82
|
-
|
|
86
|
+
# The NAF of x has at most x.bit_length + 1 digits. Using that bound
|
|
87
|
+
# instead of the actual NAF length keeps the number of iterations
|
|
88
|
+
# dependent only on the bit length of x.
|
|
89
|
+
len = x.bit_length + 1
|
|
83
90
|
len.times do
|
|
84
91
|
q += v if p.odd?
|
|
85
92
|
q += v.negate if n.odd?
|
|
@@ -107,6 +114,23 @@ module ECDSA
|
|
|
107
114
|
raise NotImplementedError
|
|
108
115
|
end
|
|
109
116
|
|
|
117
|
+
# Check whether same point or not.
|
|
118
|
+
# @param [Object] other
|
|
119
|
+
# @return [Boolean]
|
|
120
|
+
def eql?(other)
|
|
121
|
+
self == other
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Return hash value consistent with #==.
|
|
125
|
+
# Projective representations are not unique, so the hash is computed
|
|
126
|
+
# from the affine coordinates.
|
|
127
|
+
# @return [Integer]
|
|
128
|
+
def hash
|
|
129
|
+
return [self.class, group, :infinity].hash if infinity?
|
|
130
|
+
affine = to_affine
|
|
131
|
+
[self.class, group, affine.x, affine.y].hash
|
|
132
|
+
end
|
|
133
|
+
|
|
110
134
|
private
|
|
111
135
|
|
|
112
136
|
# Convert Integer to NAF
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../ecdsa_ext"
|
|
4
|
+
|
|
3
5
|
# A monkey patch to allow signature generation and verification of existing ECDSA with Jacobian coordinates.
|
|
4
6
|
module ECDSA
|
|
5
7
|
def self.sign(group, private_key, digest, temporary_key)
|
|
8
|
+
unless temporary_key.is_a?(Integer)
|
|
9
|
+
raise ArgumentError, "temporary_key is not an integer."
|
|
10
|
+
end
|
|
11
|
+
unless temporary_key.positive? && temporary_key < group.order
|
|
12
|
+
raise ArgumentError, "temporary_key must be in the range [1, n-1]."
|
|
13
|
+
end
|
|
14
|
+
|
|
6
15
|
# Second part of step 1: Select ephemeral elliptic curve key pair
|
|
7
16
|
# temporary_key was already selected for us by the caller
|
|
8
|
-
# Pad the scalar with the group order so that its bit length
|
|
9
|
-
#
|
|
17
|
+
# Pad the scalar with the group order so that its bit length, and thus
|
|
18
|
+
# the number of iterations in multiply_by_scalar, is fixed.
|
|
10
19
|
# Since (k + i * n)G = kG, this does not change the result.
|
|
20
|
+
# NOTE: This is only a partial mitigation of timing leaks. The NAF
|
|
21
|
+
# add/subtract pattern, the modular inverse of temporary_key and Ruby's
|
|
22
|
+
# Integer arithmetic are still data-dependent, so signing is not
|
|
23
|
+
# constant time.
|
|
11
24
|
k = temporary_key + group.order
|
|
12
25
|
k += group.order if k.bit_length == group.order.bit_length
|
|
13
26
|
r_point = (group.generator.to_jacobian * k).to_affine
|
data/lib/ecdsa/ext/version.rb
CHANGED
data/sig/ecdsa_ext.rbs
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Type signatures for ecdsa_ext.
|
|
2
|
+
# The ecdsa gem does not ship signatures, so the classes it defines that are
|
|
3
|
+
# referenced here (Group, Point, PrimeField) are declared minimally.
|
|
4
|
+
module ECDSA
|
|
5
|
+
class Group
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class PrimeField
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Point
|
|
12
|
+
def to_projective: () -> Ext::ProjectivePoint
|
|
13
|
+
def to_jacobian: () -> Ext::JacobianPoint
|
|
14
|
+
def to_hex: (?bool compression) -> String
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Ext
|
|
18
|
+
VERSION: String
|
|
19
|
+
|
|
20
|
+
class AbstractPoint
|
|
21
|
+
attr_reader group: Group
|
|
22
|
+
attr_reader x: Integer?
|
|
23
|
+
attr_reader y: Integer?
|
|
24
|
+
attr_reader z: Integer?
|
|
25
|
+
attr_reader infinity: bool
|
|
26
|
+
|
|
27
|
+
def initialize: (Group group, *untyped args) -> void
|
|
28
|
+
def self.from_affine: (Point point) -> instance
|
|
29
|
+
def self.infinity_point: (Group group) -> instance
|
|
30
|
+
def field: () -> PrimeField
|
|
31
|
+
def infinity_point: () -> self
|
|
32
|
+
def infinity?: () -> bool
|
|
33
|
+
def negate: () -> self
|
|
34
|
+
def coords: () -> [Integer?, Integer?, Integer?]
|
|
35
|
+
def multiply_by_scalar: (Integer x) -> self
|
|
36
|
+
def *: (Integer x) -> self
|
|
37
|
+
def add_to_point: (self other) -> self
|
|
38
|
+
def double: () -> self
|
|
39
|
+
def to_affine: () -> Point
|
|
40
|
+
def ==: (untyped other) -> bool
|
|
41
|
+
def eql?: (untyped other) -> bool
|
|
42
|
+
def hash: () -> Integer
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def to_naf: (Integer x) -> [Integer, Integer]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class ProjectivePoint < AbstractPoint
|
|
50
|
+
def +: (ProjectivePoint other) -> ProjectivePoint
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class JacobianPoint < AbstractPoint
|
|
54
|
+
def +: (JacobianPoint other) -> JacobianPoint
|
|
55
|
+
end
|
|
56
|
+
end
|
|
4
57
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ecdsa_ext
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- azuchi
|
|
@@ -30,7 +30,6 @@ executables: []
|
|
|
30
30
|
extensions: []
|
|
31
31
|
extra_rdoc_files: []
|
|
32
32
|
files:
|
|
33
|
-
- ".prettierignore"
|
|
34
33
|
- ".rspec"
|
|
35
34
|
- ".rubocop.yml"
|
|
36
35
|
- ".ruby-gemset"
|
data/.prettierignore
DELETED