h2c 0.3.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/.rubocop.yml +6 -2
- data/.ruby-version +1 -1
- data/CHANGELOG.md +46 -2
- data/Gemfile +3 -3
- data/README.md +1 -3
- data/h2c.gemspec +12 -7
- data/lib/{ext → h2c}/curve/bls12381_g1.rb +4 -2
- data/lib/{ext → h2c}/curve/bls12381_g1_11iso.rb +4 -2
- data/lib/{ext → h2c}/curve/secp256k1_3iso.rb +5 -3
- data/lib/h2c/curve.rb +12 -0
- data/lib/h2c/expander/xmd.rb +29 -33
- data/lib/h2c/expander.rb +4 -3
- data/lib/h2c/hash_to_point.rb +14 -3
- data/lib/h2c/m2c/isogeny/bls12381_g1.rb +8 -8
- data/lib/h2c/m2c/isogeny/secp256k1.rb +7 -7
- data/lib/h2c/m2c/isogeny.rb +2 -2
- data/lib/h2c/m2c/sswu.rb +13 -6
- data/lib/h2c/m2c/sswuab0.rb +3 -3
- data/lib/h2c/m2c.rb +2 -2
- data/lib/h2c/suite.rb +8 -8
- data/lib/h2c/version.rb +1 -1
- data/lib/h2c.rb +6 -5
- data/sig/h2c.rbs +124 -1
- metadata +7 -8
- data/.prettierignore +0 -2
- data/lib/ext/curve.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9dcc054ebdb4c4b8e5526bc34cbc81c4fc19b1f20a703e410b580abcbc79bc87
|
|
4
|
+
data.tar.gz: 7564fca3ea320b66f3c6404a08d03dfda8e01b40a09cb56746aeabe6f9188f46
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45e3d5f5b868bd7ca68da8831a3f3f1ef76315145374ee494ec255ec8b47098151fb4766c6b6e9180018b462cd61f80ca598b57179692c37b7d460df863862a3
|
|
7
|
+
data.tar.gz: 596e7f33544c8a48b337cd4867d4af5377911452a56426edc6c81616635658cca7e36b031ce1a15d44514b5532fad0000815eac1df84ecdbaaeccbe866ef4029
|
data/.rubocop.yml
CHANGED
|
@@ -2,9 +2,13 @@ plugins:
|
|
|
2
2
|
- rubocop-rake
|
|
3
3
|
- rubocop-rspec
|
|
4
4
|
inherit_gem:
|
|
5
|
-
|
|
5
|
+
syntax_tree: config/rubocop.yml
|
|
6
6
|
AllCops:
|
|
7
|
-
TargetRubyVersion:
|
|
7
|
+
TargetRubyVersion: 3.3
|
|
8
|
+
NewCops: enable
|
|
9
|
+
# Requiring MFA on rubygems.org is a release-policy decision, not a lint one.
|
|
10
|
+
Gemspec/RequireMFA:
|
|
11
|
+
Enabled: false
|
|
8
12
|
RSpec/ExampleLength:
|
|
9
13
|
Enabled: false
|
|
10
14
|
Metrics:
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby-3.
|
|
1
|
+
ruby-3.4.8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.4.0] - 2026-11-01
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
- Move the curve definitions from `ECDSA::Group::Secp256k1_3ISO`, `BLS::Group::BLS12381G1` and `BLS::Group::BLS12381G1_11ISO` to `H2C::Curve::Secp256k1_3ISO`, `H2C::Curve::BLS12381G1` and `H2C::Curve::BLS12381G1_11ISO`, so that h2c no longer defines constants in the ecdsa gem's namespace or a top-level `BLS` module.
|
|
8
|
+
- Require Ruby 3.3 or later, matching the versions tested in CI.
|
|
9
|
+
- Rename `H2C::M2C::ISOGeny` to `H2C::M2C::Isogeny`.
|
|
10
|
+
- Remove the unused third argument (`k`) of `H2C::Expander.get`.
|
|
11
|
+
- `H2C::Expander::XMD#digest` now returns the `Digest` class rather than a shared instance, and `#dst` returns a binary (ASCII-8BIT) copy of the tag.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `expand_message_xmd` raised `Encoding::CompatibilityError` for a UTF-8 DST containing non-ASCII characters whose length byte is 0x80 or above.
|
|
16
|
+
- `expand_message_xmd` rejected valid inputs: `ell == 255`, `len_in_bytes == 65535` and DSTs of 254 or 255 bytes. The abort conditions now follow RFC 9380 Section 5.3.1 exactly.
|
|
17
|
+
- `H2C::M2C::SSWU#map` reduces `u` modulo p before computing `sgn0`, so an unreduced `u` maps to the same point as `u mod p`.
|
|
18
|
+
- `H2C::M2C::SSWU#map` raises `H2C::Error` instead of `NoMethodError` when no square root is found.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `H2C::Expander::XMD` holds no mutable digest state, so an expander (and `H2C::HashToPoint`) can be shared across threads.
|
|
23
|
+
- Skip the cofactor multiplication when the cofactor is 1.
|
|
24
|
+
- Documentation now references RFC 9380 instead of draft-irtf-cfrg-hash-to-curve-16.
|
|
25
|
+
- Replace the unmaintained prettier gem with syntax_tree for formatting checks, since prettier 4.0.3 fails on Ruby 4.0 (`JSON.fast_generate` was removed).
|
|
26
|
+
|
|
27
|
+
## [0.3.0] - 2026-08-09
|
|
28
|
+
|
|
29
|
+
- Fix the SSWU exceptional case (`u == 0` or `Z * u^2 == -1`) by using `inv0` semantics instead of raising.
|
|
30
|
+
- Fix the byte range used by `hash_to_field` when splitting the expanded message, which was wrong for extension degree `m > 1`.
|
|
31
|
+
- Reject an empty domain separation tag, as required by RFC 9380 Section 3.1.
|
|
32
|
+
- Document that the library is not constant-time and must not be used with secret inputs.
|
|
33
|
+
- Test against Ruby 3.3, 3.4 and 4.0.
|
|
34
|
+
|
|
35
|
+
## [0.2.1] - 2024-02-06
|
|
36
|
+
|
|
37
|
+
- Add a `modulo` argument to `H2C::HashToPoint#hash_to_field` so that protocols such as FROST can reduce by the group order.
|
|
38
|
+
- Move the BLS12-381 group definitions from `ECDSA::Group` to `BLS::Group`.
|
|
39
|
+
|
|
40
|
+
## [0.2.0] - 2023-01-13
|
|
41
|
+
|
|
42
|
+
- Support `BLS12381G1_XMD:SHA-256_SSWU_NU_` and `BLS12381G1_XMD:SHA-256_SSWU_RO_`.
|
|
43
|
+
- Support `P256_XMD:SHA-256_SSWU_NU_` and `P256_XMD:SHA-256_SSWU_RO_`.
|
|
44
|
+
- Support `P384_XMD:SHA-384_SSWU_NU_` and `P384_XMD:SHA-384_SSWU_RO_`.
|
|
45
|
+
- Support `P521_XMD:SHA-512_SSWU_NU_` and `P521_XMD:SHA-512_SSWU_RO_`.
|
|
46
|
+
|
|
47
|
+
## [0.1.0] - 2023-01-12
|
|
48
|
+
|
|
49
|
+
- Initial release with `secp256k1_XMD:SHA-256_SSWU_NU_` and `secp256k1_XMD:SHA-256_SSWU_RO_`.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hashing to Elliptic Curves for Ruby
|
|
2
2
|
|
|
3
|
-
This is a Ruby implementation of
|
|
3
|
+
This is a Ruby implementation of [RFC 9380: Hashing to Elliptic Curves](https://www.rfc-editor.org/rfc/rfc9380.html).
|
|
4
4
|
|
|
5
5
|
It has been tested using the Test Vector provided, but the code has not been audited to ensure functional specification and safety. Also It is under development and is subject to change without backward compatibility.
|
|
6
6
|
|
|
@@ -18,8 +18,6 @@ An attacker able to measure timing or cache behaviour may therefore be able to r
|
|
|
18
18
|
|
|
19
19
|
In particular, do not use this library to derive nonces or key material directly from secret values — for example, FROST's `nonce_generate`, which hashes a secret share. Such protocols require a constant-time implementation.
|
|
20
20
|
|
|
21
|
-
Additionally, `H2C::HashToPoint` and the `H2C::Expander` instance it holds keep mutable digest state, so a single instance must not be shared across threads. Create one instance per thread instead.
|
|
22
|
-
|
|
23
21
|
The following cipher suites are currently supported:
|
|
24
22
|
|
|
25
23
|
* secp256k1_XMD:SHA-256_SSWU_NU_
|
data/h2c.gemspec
CHANGED
|
@@ -12,19 +12,24 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = "Ruby implementation of hash to curve."
|
|
13
13
|
spec.homepage = "https://github.com/azuchi/h2c"
|
|
14
14
|
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = ">=
|
|
15
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
16
16
|
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
|
19
|
-
spec.metadata["changelog_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
|
20
20
|
|
|
21
21
|
# Specify which files should be added to the gem when it is released.
|
|
22
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
-
spec.files =
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
spec.files =
|
|
24
|
+
Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
`git ls-files -z`.split("\x0")
|
|
26
|
+
.reject do |f|
|
|
27
|
+
(f == __FILE__) ||
|
|
28
|
+
f.match(
|
|
29
|
+
%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}
|
|
30
|
+
)
|
|
31
|
+
end
|
|
26
32
|
end
|
|
27
|
-
end
|
|
28
33
|
spec.bindir = "exe"
|
|
29
34
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
35
|
spec.require_paths = ["lib"]
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
module
|
|
3
|
-
|
|
2
|
+
module H2C
|
|
3
|
+
module Curve
|
|
4
|
+
# 3-isogenous curve to secp256k1.
|
|
5
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-8.7
|
|
4
6
|
Secp256k1_3ISO =
|
|
5
|
-
new(
|
|
7
|
+
ECDSA::Group.new(
|
|
6
8
|
name: "secp256k1_3ISO",
|
|
7
9
|
p:
|
|
8
10
|
0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFC2F,
|
data/lib/h2c/curve.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module H2C
|
|
4
|
+
# Curve definitions used by the suites that are not provided by the ecdsa gem,
|
|
5
|
+
# including the isogenous curves required by the SSWU map for AB == 0 curves.
|
|
6
|
+
module Curve
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative "curve/secp256k1_3iso"
|
|
11
|
+
require_relative "curve/bls12381_g1"
|
|
12
|
+
require_relative "curve/bls12381_g1_11iso"
|
data/lib/h2c/expander/xmd.rb
CHANGED
|
@@ -6,59 +6,55 @@ module H2C
|
|
|
6
6
|
# Expander::XMD produces a uniformly random byte string using a cryptographic hash function H that outputs b bits.
|
|
7
7
|
class XMD
|
|
8
8
|
attr_reader :dst, :digest
|
|
9
|
+
|
|
9
10
|
# Constructor
|
|
10
|
-
# @param [String] func Hash function name. Currently supported by 'SHA256' and 'SHA512'
|
|
11
|
+
# @param [String] func Hash function name. Currently supported by 'SHA256', 'SHA384' and 'SHA512'
|
|
11
12
|
# @param [String] dst Domain separation tag with binary format.
|
|
12
13
|
def initialize(func, dst)
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
# Concatenating with the length byte (and the hashed prefix for long
|
|
15
|
+
# tags) requires a binary string. A UTF-8 tag with non-ASCII characters
|
|
16
|
+
# would otherwise raise Encoding::CompatibilityError once the length
|
|
17
|
+
# byte is 0x80 or above.
|
|
18
|
+
@dst = dst.b
|
|
19
|
+
# Keep the Digest class rather than an instance so that expand holds no
|
|
20
|
+
# mutable state and a single expander can be shared across threads.
|
|
21
|
+
@digest = Digest(func)
|
|
15
22
|
end
|
|
16
23
|
|
|
17
24
|
# Expand message.
|
|
18
|
-
# https://www.
|
|
25
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.1
|
|
19
26
|
# @param [String] msg The message to be expanded with binary format.
|
|
20
27
|
# @param [Integer] len The length of the requested output in bytes.
|
|
21
28
|
# @return [String] Expanded message.
|
|
22
29
|
# @raise [H2C::Error]
|
|
23
30
|
def expand(msg, len)
|
|
24
|
-
b_len = digest.digest_length
|
|
31
|
+
b_len = digest.new.digest_length
|
|
25
32
|
ell = (len + b_len - 1) / b_len
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
# RFC 9380 5.3.1 step 2. A dst longer than 255 bytes has already been
|
|
34
|
+
# hashed down by construct_dst_prime, so only the output length needs
|
|
35
|
+
# checking here.
|
|
36
|
+
if ell > 255 || len > 65_535
|
|
37
|
+
raise H2C::Error,
|
|
38
|
+
"requested too many bytes: len must be at most 65535 and " \
|
|
39
|
+
"ceil(len / #{b_len}) at most 255."
|
|
30
40
|
end
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
digest.reset
|
|
35
|
-
digest.update(z_pad.pack("C*"))
|
|
36
|
-
digest.update(msg)
|
|
37
|
-
digest.update(lib_str)
|
|
38
|
-
digest.update([0].pack("C"))
|
|
39
|
-
digest.update(dst_prime)
|
|
40
|
-
|
|
41
|
-
b0 = digest.digest
|
|
42
|
-
digest.reset
|
|
43
|
-
digest.update(b0)
|
|
44
|
-
digest.update([1].pack("C"))
|
|
45
|
-
digest.update(dst_prime)
|
|
41
|
+
dst_prime = construct_dst_prime
|
|
42
|
+
lib_str = [len].pack("n")
|
|
43
|
+
z_pad = "\x00".b * digest.new.block_length
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
b0 = digest.digest(z_pad + msg.b + lib_str + "\x00".b + dst_prime)
|
|
46
|
+
bi = digest.digest(b0 + "\x01".b + dst_prime)
|
|
47
|
+
pseudo = bi.dup
|
|
49
48
|
# b_1 is already computed above, so this yields b_1, ..., b_ell in total.
|
|
50
49
|
(2..ell).each do |i|
|
|
51
|
-
digest.
|
|
52
|
-
|
|
53
|
-
digest.update([i].pack("C"))
|
|
54
|
-
digest.update(dst_prime)
|
|
55
|
-
bi = digest.digest
|
|
56
|
-
pseudo += bi
|
|
50
|
+
bi = digest.digest(Expander.xor(b0, bi) + [i].pack("C") + dst_prime)
|
|
51
|
+
pseudo << bi
|
|
57
52
|
end
|
|
58
|
-
pseudo[0
|
|
53
|
+
pseudo[0, len]
|
|
59
54
|
end
|
|
60
55
|
|
|
61
56
|
# Construct DST prime.
|
|
57
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.3
|
|
62
58
|
# @return [String] DST prime
|
|
63
59
|
def construct_dst_prime
|
|
64
60
|
dst_prime =
|
data/lib/h2c/expander.rb
CHANGED
|
@@ -31,18 +31,19 @@ module H2C
|
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
33
|
# Get expander implementation
|
|
34
|
-
# @param [String] func Hash function name.
|
|
34
|
+
# @param [String] func Hash function name. One of H2C::HashFunc::XMD_FUNCS
|
|
35
|
+
# ("SHA256", "SHA384" or "SHA512").
|
|
35
36
|
# @param [String] dst Domain separation tag with binary format. Must not be empty.
|
|
36
37
|
# @raise [H2C::Error] If invalid func or empty dst specified.
|
|
37
38
|
# @return [XMD] expander implementation, currently only XMD is supported.
|
|
38
|
-
def get(func, dst
|
|
39
|
+
def get(func, dst)
|
|
39
40
|
unless HashFunc::XMD_FUNCS.include?(func)
|
|
40
41
|
raise H2C::Error, "func #{func} is unsupported."
|
|
41
42
|
end
|
|
42
43
|
# RFC 9380 3.1 requires domain separation tags to have nonzero length.
|
|
43
44
|
raise H2C::Error, "dst must not be empty." if dst.nil? || dst.empty?
|
|
44
45
|
XMD.new(func, dst)
|
|
45
|
-
# TODO:
|
|
46
|
+
# TODO: expand_message_xof for HashFunc::XOF_FUNCS is not implemented yet.
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
# XOR two byte(+x+ and +y+) string.
|
data/lib/h2c/hash_to_point.rb
CHANGED
|
@@ -24,11 +24,22 @@ module H2C
|
|
|
24
24
|
u = hash_to_field(msg, 1)
|
|
25
25
|
suite.map.map(u[0])
|
|
26
26
|
end
|
|
27
|
-
|
|
27
|
+
clear_cofactor(p)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Clear the cofactor of the point.
|
|
31
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-7
|
|
32
|
+
# The ecdsa gem leaves the cofactor nil for the NIST curves and set to 1 for
|
|
33
|
+
# secp256k1, both of which are prime-order groups with nothing to clear.
|
|
34
|
+
# @param [ECDSA::Point] p point
|
|
35
|
+
# @return [ECDSA::Point] point in the prime-order subgroup
|
|
36
|
+
def clear_cofactor(p)
|
|
37
|
+
h = suite.curve.cofactor
|
|
38
|
+
h && h > 1 ? p.multiply_by_scalar(h) : p
|
|
28
39
|
end
|
|
29
40
|
|
|
30
41
|
# Hashes a msg of any length into an element of a finite field.
|
|
31
|
-
# https://www.
|
|
42
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-5.2
|
|
32
43
|
# @param [String] msg A byte string containing the message to hash.
|
|
33
44
|
# @param [Integer] count The number of elements of Field to output.
|
|
34
45
|
# @param [Integer] modulo (Optional) This value is a finite field of characteristic p in the
|
|
@@ -41,7 +52,7 @@ module H2C
|
|
|
41
52
|
count.times do |i|
|
|
42
53
|
v = []
|
|
43
54
|
suite.m.times do |j|
|
|
44
|
-
offset = suite.l * (j + i * suite.m)
|
|
55
|
+
offset = suite.l * (j + (i * suite.m))
|
|
45
56
|
t = pseudo[offset, suite.l]
|
|
46
57
|
vj = t.unpack1("H*").to_i(16)
|
|
47
58
|
v[j] = vj % modulo
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module H2C
|
|
4
4
|
module M2C
|
|
5
|
-
module
|
|
5
|
+
module Isogeny
|
|
6
6
|
# 11-isogeny map for BLS12381
|
|
7
|
-
# https://www.
|
|
7
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#appendix-E.2
|
|
8
8
|
class BLS12381G1
|
|
9
9
|
attr_reader :e0, :e1
|
|
10
10
|
|
|
@@ -76,8 +76,8 @@ module H2C
|
|
|
76
76
|
1
|
|
77
77
|
].freeze
|
|
78
78
|
def initialize
|
|
79
|
-
@e0 =
|
|
80
|
-
@e1 =
|
|
79
|
+
@e0 = Curve::BLS12381G1_11ISO
|
|
80
|
+
@e1 = Curve::BLS12381G1
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def map(x, y)
|
|
@@ -87,12 +87,12 @@ module H2C
|
|
|
87
87
|
y_num = 0
|
|
88
88
|
y_den = 0
|
|
89
89
|
(X_NUM.length - 1).step(0, -1) do |i|
|
|
90
|
-
x_num = f.mod(x_num * x + X_NUM[i])
|
|
91
|
-
x_den = f.mod(x_den * x + X_DEN[i])
|
|
90
|
+
x_num = f.mod((x_num * x) + X_NUM[i])
|
|
91
|
+
x_den = f.mod((x_den * x) + X_DEN[i])
|
|
92
92
|
end
|
|
93
93
|
(Y_NUM.length - 1).step(0, -1) do |i|
|
|
94
|
-
y_num = f.mod(y_num * x + Y_NUM[i])
|
|
95
|
-
y_den = f.mod(y_den * x + Y_DEN[i])
|
|
94
|
+
y_num = f.mod((y_num * x) + Y_NUM[i])
|
|
95
|
+
y_den = f.mod((y_den * x) + Y_DEN[i])
|
|
96
96
|
end
|
|
97
97
|
xx = f.mod(x_num * f.inverse(x_den))
|
|
98
98
|
yy = f.mod(y * (y_num * f.inverse(y_den)))
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module H2C
|
|
3
3
|
module M2C
|
|
4
|
-
module
|
|
4
|
+
module Isogeny
|
|
5
5
|
# 3-isogeny map for secp256k1
|
|
6
|
-
# https://www.
|
|
6
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#appendix-E.1
|
|
7
7
|
class Secp256k1
|
|
8
8
|
attr_reader :e0, :e1
|
|
9
9
|
|
|
@@ -33,7 +33,7 @@ module H2C
|
|
|
33
33
|
].freeze
|
|
34
34
|
|
|
35
35
|
def initialize
|
|
36
|
-
@e0 =
|
|
36
|
+
@e0 = Curve::Secp256k1_3ISO
|
|
37
37
|
@e1 = ECDSA::Group::Secp256k1
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -45,10 +45,10 @@ module H2C
|
|
|
45
45
|
y_num = 0
|
|
46
46
|
y_den = 0
|
|
47
47
|
3.step(0, -1) do |i|
|
|
48
|
-
x_num = f.mod(x_num * x + X_NUM[i])
|
|
49
|
-
x_den = f.mod(x_den * x + X_DEN[i])
|
|
50
|
-
y_num = f.mod(y_num * x + Y_NUM[i])
|
|
51
|
-
y_den = f.mod(y_den * x + Y_DEN[i])
|
|
48
|
+
x_num = f.mod((x_num * x) + X_NUM[i])
|
|
49
|
+
x_den = f.mod((x_den * x) + X_DEN[i])
|
|
50
|
+
y_num = f.mod((y_num * x) + Y_NUM[i])
|
|
51
|
+
y_den = f.mod((y_den * x) + Y_DEN[i])
|
|
52
52
|
end
|
|
53
53
|
xx = f.mod(x_num * f.inverse(x_den))
|
|
54
54
|
yy = f.mod(y * (y_num * f.inverse(y_den)))
|
data/lib/h2c/m2c/isogeny.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module H2C
|
|
3
3
|
module M2C
|
|
4
|
-
# https://www.
|
|
5
|
-
module
|
|
4
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#appendix-E
|
|
5
|
+
module Isogeny
|
|
6
6
|
autoload :Secp256k1, "h2c/m2c/isogeny/secp256k1"
|
|
7
7
|
autoload :BLS12381G1, "h2c/m2c/isogeny/bls12381_g1"
|
|
8
8
|
end
|
data/lib/h2c/m2c/sswu.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module H2C
|
|
4
4
|
module M2C
|
|
5
|
-
# https://www.
|
|
5
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-6.6.2
|
|
6
6
|
class SSWU
|
|
7
7
|
attr_reader :curve, :c1, :c2, :z
|
|
8
8
|
|
|
@@ -14,14 +14,18 @@ module H2C
|
|
|
14
14
|
@z = z
|
|
15
15
|
f = curve.field
|
|
16
16
|
@c1 = f.mod(-curve.param_b * f.inverse(curve.param_a))
|
|
17
|
-
@c2 = f.mod(-f.inverse(z))
|
|
17
|
+
@c2 = f.mod(-f.inverse(f.mod(z)))
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# Outputs x and y are elements of the field F.
|
|
21
21
|
# @param [Integer] u
|
|
22
|
-
# @return [
|
|
22
|
+
# @return [ECDSA::Point] point on the curve
|
|
23
|
+
# @raise [H2C::Error] if no square root is found, which cannot happen for
|
|
24
|
+
# a correct Z but guards against a misconfigured curve.
|
|
23
25
|
def map(u)
|
|
24
26
|
f = curve.field
|
|
27
|
+
# sgn0 below inspects the parity of u, so u must be reduced first.
|
|
28
|
+
u = f.mod(u)
|
|
25
29
|
t1 = f.mod(f.power(u, 2) * f.mod(z))
|
|
26
30
|
t2 = f.power(t1, 2)
|
|
27
31
|
x1 = f.mod(t1 + t2)
|
|
@@ -29,7 +33,7 @@ module H2C
|
|
|
29
33
|
# inv0(x) is the multiplicative inverse of x, with inv0(0) = 0.
|
|
30
34
|
x1 = e1 ? 0 : f.inverse(x1)
|
|
31
35
|
x1 = f.mod(x1 + 1)
|
|
32
|
-
x1 =
|
|
36
|
+
x1 = c2 if e1
|
|
33
37
|
x1 = f.mod(x1 * c1)
|
|
34
38
|
gx1 = f.power(x1, 2)
|
|
35
39
|
gx1 = f.mod(gx1 + curve.param_a)
|
|
@@ -41,7 +45,10 @@ module H2C
|
|
|
41
45
|
e2 = square?(gx1)
|
|
42
46
|
x = e2 ? x1 : x2
|
|
43
47
|
y2 = e2 ? gx1 : gx2
|
|
44
|
-
y = f.square_roots(y2)
|
|
48
|
+
y = f.square_roots(y2).first
|
|
49
|
+
if y.nil?
|
|
50
|
+
raise H2C::Error, "no square root found for #{y2} on #{curve.name}."
|
|
51
|
+
end
|
|
45
52
|
e3 = sgn0(u) == sgn0(y)
|
|
46
53
|
y = f.mod(e3 ? y : -y)
|
|
47
54
|
curve.new_point([x, y])
|
|
@@ -54,7 +61,7 @@ module H2C
|
|
|
54
61
|
|
|
55
62
|
def sgn0(x)
|
|
56
63
|
res = x % 2
|
|
57
|
-
curve.field.mod(1 - 2 * res)
|
|
64
|
+
curve.field.mod(1 - (2 * res))
|
|
58
65
|
end
|
|
59
66
|
end
|
|
60
67
|
end
|
data/lib/h2c/m2c/sswuab0.rb
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module H2C
|
|
3
3
|
module M2C
|
|
4
|
-
# https://www.
|
|
4
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-6.6.3
|
|
5
5
|
class SSWUAB0
|
|
6
6
|
attr_reader :sswu, :iso
|
|
7
7
|
|
|
8
8
|
# Constructor
|
|
9
|
-
# @param [H2C::M2C::
|
|
9
|
+
# @param [H2C::M2C::Isogeny] iso
|
|
10
10
|
# @param [Integer] z
|
|
11
11
|
def initialize(iso, z)
|
|
12
12
|
@sswu = SSWU.new(iso.e0, z)
|
|
13
13
|
@iso = iso
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# https://www.
|
|
16
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-6.6.3
|
|
17
17
|
# @param [Integer] u
|
|
18
18
|
# @return [ECDSA::Point]
|
|
19
19
|
def map(u)
|
data/lib/h2c/m2c.rb
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module H2C
|
|
4
4
|
# Map to Curve functions.
|
|
5
|
-
# https://www.
|
|
5
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html#section-6
|
|
6
6
|
module M2C
|
|
7
|
-
autoload :
|
|
7
|
+
autoload :Isogeny, "h2c/m2c/isogeny"
|
|
8
8
|
autoload :SSWU, "h2c/m2c/sswu"
|
|
9
9
|
autoload :SSWUAB0, "h2c/m2c/sswuab0"
|
|
10
10
|
end
|
data/lib/h2c/suite.rb
CHANGED
|
@@ -25,33 +25,33 @@ module H2C
|
|
|
25
25
|
case id
|
|
26
26
|
when SECP256K1_XMDSHA256_SSWU_NU_, SECP256K1_XMDSHA256_SSWU_RO_
|
|
27
27
|
@curve = ECDSA::Group::Secp256k1
|
|
28
|
-
@exp = Expander.get(HashFunc::SHA256, dst
|
|
28
|
+
@exp = Expander.get(HashFunc::SHA256, dst)
|
|
29
29
|
@l = 48
|
|
30
|
-
@map = M2C::SSWUAB0.new(H2C::M2C::
|
|
30
|
+
@map = M2C::SSWUAB0.new(H2C::M2C::Isogeny::Secp256k1.new, -11)
|
|
31
31
|
@ro = (id == SECP256K1_XMDSHA256_SSWU_RO_)
|
|
32
32
|
when BLS12381G1_XMDSHA256_SSWU_NU_, BLS12381G1_XMDSHA256_SSWU_RO_
|
|
33
|
-
@curve =
|
|
34
|
-
@exp = Expander.get(HashFunc::SHA256, dst
|
|
33
|
+
@curve = Curve::BLS12381G1
|
|
34
|
+
@exp = Expander.get(HashFunc::SHA256, dst)
|
|
35
35
|
@l = 64
|
|
36
|
-
@map = M2C::SSWUAB0.new(H2C::M2C::
|
|
36
|
+
@map = M2C::SSWUAB0.new(H2C::M2C::Isogeny::BLS12381G1.new, 11)
|
|
37
37
|
@ro = (id == BLS12381G1_XMDSHA256_SSWU_RO_)
|
|
38
38
|
when P256_XMDSHA256_SSWU_NU_, P256_XMDSHA256_SSWU_RO_
|
|
39
39
|
@curve = ECDSA::Group::Nistp256
|
|
40
|
-
@exp = Expander.get(HashFunc::SHA256, dst
|
|
40
|
+
@exp = Expander.get(HashFunc::SHA256, dst)
|
|
41
41
|
@l = 48
|
|
42
42
|
@map = M2C::SSWU.new(ECDSA::Group::Nistp256, -10)
|
|
43
43
|
@ro = (id == P256_XMDSHA256_SSWU_RO_)
|
|
44
44
|
when P384_XMDSHA384_SSWU_NU_, P384_XMDSHA384_SSWU_RO_
|
|
45
45
|
@k = 192
|
|
46
46
|
@curve = ECDSA::Group::Nistp384
|
|
47
|
-
@exp = Expander.get(HashFunc::SHA384, dst
|
|
47
|
+
@exp = Expander.get(HashFunc::SHA384, dst)
|
|
48
48
|
@l = 72
|
|
49
49
|
@map = M2C::SSWU.new(ECDSA::Group::Nistp384, -12)
|
|
50
50
|
@ro = (id == P384_XMDSHA384_SSWU_RO_)
|
|
51
51
|
when P521_XMDSHA512_SSWU_NU_, P521_XMDSHA512_SSWU_RO_
|
|
52
52
|
@k = 256
|
|
53
53
|
@curve = ECDSA::Group::Nistp521
|
|
54
|
-
@exp = Expander.get(HashFunc::SHA512, dst
|
|
54
|
+
@exp = Expander.get(HashFunc::SHA512, dst)
|
|
55
55
|
@l = 98
|
|
56
56
|
@map = M2C::SSWU.new(ECDSA::Group::Nistp521, -4)
|
|
57
57
|
@ro = (id == P521_XMDSHA512_SSWU_RO_)
|
data/lib/h2c/version.rb
CHANGED
data/lib/h2c.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "h2c/version"
|
|
4
4
|
require "ecdsa"
|
|
5
|
-
require_relative "
|
|
5
|
+
require_relative "h2c/curve"
|
|
6
6
|
|
|
7
7
|
# Hash to Curves library.
|
|
8
|
-
# https://www.
|
|
8
|
+
# https://www.rfc-editor.org/rfc/rfc9380.html
|
|
9
9
|
module H2C
|
|
10
10
|
class Error < StandardError
|
|
11
11
|
end
|
|
@@ -31,9 +31,10 @@ module H2C
|
|
|
31
31
|
|
|
32
32
|
# Get hash to curve corresponding to +suite+.
|
|
33
33
|
#
|
|
34
|
-
# @param [String] suite Suite
|
|
35
|
-
#
|
|
36
|
-
# @param [String] dst Domain separation tag.
|
|
34
|
+
# @param [String] suite Suite id for hashing. See the constants in H2C::Suite
|
|
35
|
+
# for the supported suites.
|
|
36
|
+
# @param [String] dst Domain separation tag. Must not be empty.
|
|
37
|
+
# @raise [H2C::Error] If the suite is unsupported or dst is empty.
|
|
37
38
|
# @return [H2C::HashToPoint]
|
|
38
39
|
def get(suite, dst)
|
|
39
40
|
suite = Suite.new(suite, dst)
|
data/sig/h2c.rbs
CHANGED
|
@@ -1,4 +1,127 @@
|
|
|
1
|
+
# The ecdsa gem ships no type signatures, so its classes (ECDSA::Group,
|
|
2
|
+
# ECDSA::Point and ECDSA::PrimeField) appear here as untyped.
|
|
1
3
|
module H2C
|
|
2
4
|
VERSION: String
|
|
3
|
-
|
|
5
|
+
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module HashFunc
|
|
10
|
+
SHA256: String
|
|
11
|
+
SHA384: String
|
|
12
|
+
SHA512: String
|
|
13
|
+
SHAKE128: String
|
|
14
|
+
SHAKE256: String
|
|
15
|
+
XMD_FUNCS: Array[String]
|
|
16
|
+
XOF_FUNCS: Array[String]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns a hash-to-curve function for +suite+ and +dst+.
|
|
20
|
+
def self.get: (String suite, String dst) -> HashToPoint
|
|
21
|
+
|
|
22
|
+
module Curve
|
|
23
|
+
Secp256k1_3ISO: untyped
|
|
24
|
+
BLS12381G1: untyped
|
|
25
|
+
BLS12381G1_11ISO: untyped
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Suite
|
|
29
|
+
SECP256K1_XMDSHA256_SSWU_NU_: String
|
|
30
|
+
SECP256K1_XMDSHA256_SSWU_RO_: String
|
|
31
|
+
BLS12381G1_XMDSHA256_SSWU_NU_: String
|
|
32
|
+
BLS12381G1_XMDSHA256_SSWU_RO_: String
|
|
33
|
+
P256_XMDSHA256_SSWU_NU_: String
|
|
34
|
+
P256_XMDSHA256_SSWU_RO_: String
|
|
35
|
+
P384_XMDSHA384_SSWU_NU_: String
|
|
36
|
+
P384_XMDSHA384_SSWU_RO_: String
|
|
37
|
+
P521_XMDSHA512_SSWU_NU_: String
|
|
38
|
+
P521_XMDSHA512_SSWU_RO_: String
|
|
39
|
+
|
|
40
|
+
attr_reader id: String
|
|
41
|
+
attr_reader curve: untyped
|
|
42
|
+
attr_reader k: Integer
|
|
43
|
+
attr_reader exp: Expander::XMD
|
|
44
|
+
attr_reader m: Integer
|
|
45
|
+
attr_reader l: Integer
|
|
46
|
+
attr_reader ro: bool
|
|
47
|
+
attr_reader map: M2C::SSWU | M2C::SSWUAB0
|
|
48
|
+
|
|
49
|
+
def initialize: (String id, String dst) -> void
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class HashToPoint
|
|
53
|
+
attr_reader suite: Suite
|
|
54
|
+
|
|
55
|
+
def initialize: (Suite suite) -> void
|
|
56
|
+
def digest: (String msg) -> untyped
|
|
57
|
+
def clear_cofactor: (untyped p) -> untyped
|
|
58
|
+
def hash_to_field: (String msg, Integer count, ?Integer modulo) -> Array[Integer]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
module Expander
|
|
62
|
+
MAX_DST_LENGTH: Integer
|
|
63
|
+
LONG_DST_PREFIX: String
|
|
64
|
+
|
|
65
|
+
def self.get: (String func, String? dst) -> XMD
|
|
66
|
+
def self.xor: (String x, String y) -> String
|
|
67
|
+
|
|
68
|
+
class XMD
|
|
69
|
+
attr_reader dst: String
|
|
70
|
+
attr_reader digest: singleton(Digest::Base)
|
|
71
|
+
|
|
72
|
+
def initialize: (String func, String dst) -> void
|
|
73
|
+
def expand: (String msg, Integer len) -> String
|
|
74
|
+
def construct_dst_prime: () -> String
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
module M2C
|
|
79
|
+
class SSWU
|
|
80
|
+
attr_reader curve: untyped
|
|
81
|
+
attr_reader c1: Integer
|
|
82
|
+
attr_reader c2: Integer
|
|
83
|
+
attr_reader z: Integer
|
|
84
|
+
|
|
85
|
+
def initialize: (untyped curve, Integer z) -> void
|
|
86
|
+
def map: (Integer u) -> untyped
|
|
87
|
+
def square?: (Integer x) -> bool
|
|
88
|
+
def sgn0: (Integer x) -> Integer
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class SSWUAB0
|
|
92
|
+
attr_reader sswu: SSWU
|
|
93
|
+
attr_reader iso: Isogeny::Secp256k1 | Isogeny::BLS12381G1
|
|
94
|
+
|
|
95
|
+
def initialize: (Isogeny::Secp256k1 | Isogeny::BLS12381G1 iso, Integer z) -> void
|
|
96
|
+
def map: (Integer u) -> untyped
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
module Isogeny
|
|
100
|
+
class Secp256k1
|
|
101
|
+
X_NUM: Array[Integer]
|
|
102
|
+
X_DEN: Array[Integer]
|
|
103
|
+
Y_NUM: Array[Integer]
|
|
104
|
+
Y_DEN: Array[Integer]
|
|
105
|
+
|
|
106
|
+
attr_reader e0: untyped
|
|
107
|
+
attr_reader e1: untyped
|
|
108
|
+
|
|
109
|
+
def initialize: () -> void
|
|
110
|
+
def map: (Integer x, Integer y) -> [Integer, Integer]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class BLS12381G1
|
|
114
|
+
X_NUM: Array[Integer]
|
|
115
|
+
X_DEN: Array[Integer]
|
|
116
|
+
Y_NUM: Array[Integer]
|
|
117
|
+
Y_DEN: Array[Integer]
|
|
118
|
+
|
|
119
|
+
attr_reader e0: untyped
|
|
120
|
+
attr_reader e1: untyped
|
|
121
|
+
|
|
122
|
+
def initialize: () -> void
|
|
123
|
+
def map: (Integer x, Integer y) -> [Integer, Integer]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
4
127
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: h2c
|
|
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
|
|
@@ -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"
|
|
@@ -44,11 +43,11 @@ files:
|
|
|
44
43
|
- bin/console
|
|
45
44
|
- bin/setup
|
|
46
45
|
- h2c.gemspec
|
|
47
|
-
- lib/ext/curve.rb
|
|
48
|
-
- lib/ext/curve/bls12381_g1.rb
|
|
49
|
-
- lib/ext/curve/bls12381_g1_11iso.rb
|
|
50
|
-
- lib/ext/curve/secp256k1_3iso.rb
|
|
51
46
|
- lib/h2c.rb
|
|
47
|
+
- lib/h2c/curve.rb
|
|
48
|
+
- lib/h2c/curve/bls12381_g1.rb
|
|
49
|
+
- lib/h2c/curve/bls12381_g1_11iso.rb
|
|
50
|
+
- lib/h2c/curve/secp256k1_3iso.rb
|
|
52
51
|
- lib/h2c/expander.rb
|
|
53
52
|
- lib/h2c/expander/xmd.rb
|
|
54
53
|
- lib/h2c/hash_to_point.rb
|
|
@@ -67,7 +66,7 @@ licenses:
|
|
|
67
66
|
metadata:
|
|
68
67
|
homepage_uri: https://github.com/azuchi/h2c
|
|
69
68
|
source_code_uri: https://github.com/azuchi/h2c
|
|
70
|
-
changelog_uri: https://github.com/azuchi/h2c
|
|
69
|
+
changelog_uri: https://github.com/azuchi/h2c/blob/master/CHANGELOG.md
|
|
71
70
|
rdoc_options: []
|
|
72
71
|
require_paths:
|
|
73
72
|
- lib
|
|
@@ -75,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
75
74
|
requirements:
|
|
76
75
|
- - ">="
|
|
77
76
|
- !ruby/object:Gem::Version
|
|
78
|
-
version:
|
|
77
|
+
version: 3.3.0
|
|
79
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
79
|
requirements:
|
|
81
80
|
- - ">="
|
data/.prettierignore
DELETED