kpqc 0.1.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.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +174 -0
  5. data/Rakefile +57 -0
  6. data/THIRD_PARTY_NOTICES.md +53 -0
  7. data/ext/kpqc/aimer128f/extconf.rb +4 -0
  8. data/ext/kpqc/aimer128s/extconf.rb +4 -0
  9. data/ext/kpqc/aimer192f/extconf.rb +4 -0
  10. data/ext/kpqc/aimer192s/extconf.rb +4 -0
  11. data/ext/kpqc/aimer256f/extconf.rb +4 -0
  12. data/ext/kpqc/aimer256s/extconf.rb +4 -0
  13. data/ext/kpqc/extconf_helper.rb +93 -0
  14. data/ext/kpqc/haetae2/extconf.rb +4 -0
  15. data/ext/kpqc/haetae3/extconf.rb +4 -0
  16. data/ext/kpqc/haetae5/extconf.rb +4 -0
  17. data/ext/kpqc/module.c +195 -0
  18. data/ext/kpqc/ntruplus1152/extconf.rb +4 -0
  19. data/ext/kpqc/ntruplus768/extconf.rb +4 -0
  20. data/ext/kpqc/ntruplus864/extconf.rb +4 -0
  21. data/ext/kpqc/randombytes.c +107 -0
  22. data/ext/kpqc/smaugt128/extconf.rb +4 -0
  23. data/ext/kpqc/smaugt192/extconf.rb +4 -0
  24. data/ext/kpqc/smaugt256/extconf.rb +4 -0
  25. data/ext/kpqc/timer/extconf.rb +4 -0
  26. data/kpqc.gemspec +41 -0
  27. data/lib/kpqc/aimer.rb +38 -0
  28. data/lib/kpqc/api.rb +129 -0
  29. data/lib/kpqc/haetae.rb +25 -0
  30. data/lib/kpqc/ntruplus.rb +25 -0
  31. data/lib/kpqc/smaugt.rb +29 -0
  32. data/lib/kpqc/types.rb +103 -0
  33. data/lib/kpqc/version.rb +6 -0
  34. data/lib/kpqc.rb +33 -0
  35. data/test/algorithm_test.rb +111 -0
  36. data/test/api_test.rb +52 -0
  37. data/test/interop.mjs +34 -0
  38. data/test/interop_test.rb +82 -0
  39. data/test/kat/aimer128f.rsp +10 -0
  40. data/test/kat/aimer128s.rsp +10 -0
  41. data/test/kat/aimer192f.rsp +10 -0
  42. data/test/kat/aimer192s.rsp +10 -0
  43. data/test/kat/aimer256f.rsp +10 -0
  44. data/test/kat/aimer256s.rsp +10 -0
  45. data/test/kat/haetae2.rsp +10 -0
  46. data/test/kat/haetae3.rsp +10 -0
  47. data/test/kat/haetae5.rsp +10 -0
  48. data/test/kat/ntruplus1152.rsp +8 -0
  49. data/test/kat/ntruplus768.rsp +8 -0
  50. data/test/kat/ntruplus864.rsp +8 -0
  51. data/test/kat/smaugt128.rsp +8 -0
  52. data/test/kat/smaugt192.rsp +8 -0
  53. data/test/kat/smaugt256.rsp +8 -0
  54. data/test/kat/timer.rsp +8 -0
  55. data/test/kat_drbg.rb +155 -0
  56. data/test/kat_test.rb +99 -0
  57. data/test/test_helper.rb +21 -0
  58. data/vendor/AIMer/LICENSE +21 -0
  59. data/vendor/AIMer/aim2.c +163 -0
  60. data/vendor/AIMer/aim2.h +36 -0
  61. data/vendor/AIMer/aim2_constant.h +74 -0
  62. data/vendor/AIMer/api.h +42 -0
  63. data/vendor/AIMer/common/crypto_declassify.h +7 -0
  64. data/vendor/AIMer/common/fips202.c +878 -0
  65. data/vendor/AIMer/common/fips202.h +166 -0
  66. data/vendor/AIMer/common/rng.h +56 -0
  67. data/vendor/AIMer/field.h +43 -0
  68. data/vendor/AIMer/field128.c +454 -0
  69. data/vendor/AIMer/field192.c +485 -0
  70. data/vendor/AIMer/field256.c +564 -0
  71. data/vendor/AIMer/hash.c +50 -0
  72. data/vendor/AIMer/hash.h +37 -0
  73. data/vendor/AIMer/params/params-aimer-128f.h +34 -0
  74. data/vendor/AIMer/params/params-aimer-128s.h +34 -0
  75. data/vendor/AIMer/params/params-aimer-192f.h +34 -0
  76. data/vendor/AIMer/params/params-aimer-192s.h +34 -0
  77. data/vendor/AIMer/params/params-aimer-256f.h +34 -0
  78. data/vendor/AIMer/params/params-aimer-256s.h +34 -0
  79. data/vendor/AIMer/params.h +5 -0
  80. data/vendor/AIMer/sign.c +631 -0
  81. data/vendor/AIMer/sign.h +40 -0
  82. data/vendor/AIMer/tree.c +75 -0
  83. data/vendor/AIMer/tree.h +26 -0
  84. data/vendor/HAETAE/LICENSE +22 -0
  85. data/vendor/HAETAE/include/api.h +54 -0
  86. data/vendor/HAETAE/include/common.h +12 -0
  87. data/vendor/HAETAE/include/config.h +42 -0
  88. data/vendor/HAETAE/include/decompose.h +19 -0
  89. data/vendor/HAETAE/include/encoding.h +20 -0
  90. data/vendor/HAETAE/include/fft.h +26 -0
  91. data/vendor/HAETAE/include/fips202.h +40 -0
  92. data/vendor/HAETAE/include/fixpoint.h +125 -0
  93. data/vendor/HAETAE/include/ntt.h +16 -0
  94. data/vendor/HAETAE/include/packing.h +58 -0
  95. data/vendor/HAETAE/include/params.h +117 -0
  96. data/vendor/HAETAE/include/poly.h +65 -0
  97. data/vendor/HAETAE/include/polyfix.h +65 -0
  98. data/vendor/HAETAE/include/polymat.h +32 -0
  99. data/vendor/HAETAE/include/polyvec.h +131 -0
  100. data/vendor/HAETAE/include/randombytes.h +39 -0
  101. data/vendor/HAETAE/include/rans_byte.h +335 -0
  102. data/vendor/HAETAE/include/reduce.h +31 -0
  103. data/vendor/HAETAE/include/sampler.h +23 -0
  104. data/vendor/HAETAE/include/symmetric.h +52 -0
  105. data/vendor/HAETAE/src/decompose.c +73 -0
  106. data/vendor/HAETAE/src/encoding.c +700 -0
  107. data/vendor/HAETAE/src/fft.c +235 -0
  108. data/vendor/HAETAE/src/fips202.c +745 -0
  109. data/vendor/HAETAE/src/fixpoint.c +143 -0
  110. data/vendor/HAETAE/src/ntt.c +98 -0
  111. data/vendor/HAETAE/src/packing.c +595 -0
  112. data/vendor/HAETAE/src/poly.c +371 -0
  113. data/vendor/HAETAE/src/polyfix.c +298 -0
  114. data/vendor/HAETAE/src/polymat.c +82 -0
  115. data/vendor/HAETAE/src/polyvec.c +534 -0
  116. data/vendor/HAETAE/src/reduce.c +96 -0
  117. data/vendor/HAETAE/src/sampler.c +310 -0
  118. data/vendor/HAETAE/src/sign.c +558 -0
  119. data/vendor/HAETAE/src/symmetric-shake.c +50 -0
  120. data/vendor/NTRUplus/LICENSE +21 -0
  121. data/vendor/NTRUplus/NTRU+1152/api.h +24 -0
  122. data/vendor/NTRUplus/NTRU+1152/fips202/fips202.c +878 -0
  123. data/vendor/NTRUplus/NTRU+1152/fips202/fips202.h +166 -0
  124. data/vendor/NTRUplus/NTRU+1152/kem.c +286 -0
  125. data/vendor/NTRUplus/NTRU+1152/ntt.c +394 -0
  126. data/vendor/NTRUplus/NTRU+1152/ntt.h +16 -0
  127. data/vendor/NTRUplus/NTRU+1152/params.h +18 -0
  128. data/vendor/NTRUplus/NTRU+1152/poly.c +318 -0
  129. data/vendor/NTRUplus/NTRU+1152/poly.h +31 -0
  130. data/vendor/NTRUplus/NTRU+1152/randombytes.h +9 -0
  131. data/vendor/NTRUplus/NTRU+1152/symmetric.c +39 -0
  132. data/vendor/NTRUplus/NTRU+1152/symmetric.h +12 -0
  133. data/vendor/NTRUplus/NTRU+768/api.h +24 -0
  134. data/vendor/NTRUplus/NTRU+768/fips202/fips202.c +878 -0
  135. data/vendor/NTRUplus/NTRU+768/fips202/fips202.h +166 -0
  136. data/vendor/NTRUplus/NTRU+768/kem.c +286 -0
  137. data/vendor/NTRUplus/NTRU+768/ntt.c +379 -0
  138. data/vendor/NTRUplus/NTRU+768/ntt.h +16 -0
  139. data/vendor/NTRUplus/NTRU+768/params.h +18 -0
  140. data/vendor/NTRUplus/NTRU+768/poly.c +320 -0
  141. data/vendor/NTRUplus/NTRU+768/poly.h +31 -0
  142. data/vendor/NTRUplus/NTRU+768/randombytes.h +9 -0
  143. data/vendor/NTRUplus/NTRU+768/symmetric.c +39 -0
  144. data/vendor/NTRUplus/NTRU+768/symmetric.h +12 -0
  145. data/vendor/NTRUplus/NTRU+864/api.h +24 -0
  146. data/vendor/NTRUplus/NTRU+864/fips202/fips202.c +878 -0
  147. data/vendor/NTRUplus/NTRU+864/fips202/fips202.h +166 -0
  148. data/vendor/NTRUplus/NTRU+864/kem.c +286 -0
  149. data/vendor/NTRUplus/NTRU+864/ntt.c +383 -0
  150. data/vendor/NTRUplus/NTRU+864/ntt.h +16 -0
  151. data/vendor/NTRUplus/NTRU+864/params.h +18 -0
  152. data/vendor/NTRUplus/NTRU+864/poly.c +314 -0
  153. data/vendor/NTRUplus/NTRU+864/poly.h +31 -0
  154. data/vendor/NTRUplus/NTRU+864/randombytes.h +9 -0
  155. data/vendor/NTRUplus/NTRU+864/symmetric.c +39 -0
  156. data/vendor/NTRUplus/NTRU+864/symmetric.h +12 -0
  157. data/vendor/SMAUG-T/LICENSE +22 -0
  158. data/vendor/SMAUG-T/include/api.h +37 -0
  159. data/vendor/SMAUG-T/include/cbd.h +15 -0
  160. data/vendor/SMAUG-T/include/ciphertext.h +45 -0
  161. data/vendor/SMAUG-T/include/common.h +12 -0
  162. data/vendor/SMAUG-T/include/config.h +48 -0
  163. data/vendor/SMAUG-T/include/dg.h +28 -0
  164. data/vendor/SMAUG-T/include/fips202.h +41 -0
  165. data/vendor/SMAUG-T/include/hash.h +21 -0
  166. data/vendor/SMAUG-T/include/hwt.h +28 -0
  167. data/vendor/SMAUG-T/include/indcpa.h +33 -0
  168. data/vendor/SMAUG-T/include/key.h +34 -0
  169. data/vendor/SMAUG-T/include/pack.h +78 -0
  170. data/vendor/SMAUG-T/include/packring.h +50 -0
  171. data/vendor/SMAUG-T/include/params.h +84 -0
  172. data/vendor/SMAUG-T/include/poly.h +41 -0
  173. data/vendor/SMAUG-T/include/randombytes.h +11 -0
  174. data/vendor/SMAUG-T/include/toomcook.h +14 -0
  175. data/vendor/SMAUG-T/include/verify.h +16 -0
  176. data/vendor/SMAUG-T/src/cbd.c +159 -0
  177. data/vendor/SMAUG-T/src/ciphertext.c +95 -0
  178. data/vendor/SMAUG-T/src/dg.c +112 -0
  179. data/vendor/SMAUG-T/src/fips202.c +745 -0
  180. data/vendor/SMAUG-T/src/hash.c +15 -0
  181. data/vendor/SMAUG-T/src/hwt.c +121 -0
  182. data/vendor/SMAUG-T/src/indcpa.c +187 -0
  183. data/vendor/SMAUG-T/src/kem.c +194 -0
  184. data/vendor/SMAUG-T/src/key.c +102 -0
  185. data/vendor/SMAUG-T/src/pack.c +349 -0
  186. data/vendor/SMAUG-T/src/packring.c +503 -0
  187. data/vendor/SMAUG-T/src/poly.c +200 -0
  188. data/vendor/SMAUG-T/src/toomcook.c +256 -0
  189. data/vendor/SMAUG-T/src/verify.c +48 -0
  190. data/vendor/SOURCES.json +130 -0
  191. metadata +280 -0
@@ -0,0 +1,107 @@
1
+ /* SPDX-License-Identifier: MIT */
2
+ #include <stddef.h>
3
+ #include <stdint.h>
4
+ #include <string.h>
5
+
6
+ #if defined(KPQC_TEST_ENTROPY)
7
+ #include <ruby.h>
8
+
9
+ static VALUE kpqc_entropy_callback = Qnil;
10
+ static ID kpqc_call;
11
+
12
+ static VALUE kpqc_set_randombytes(VALUE self, VALUE callback) {
13
+ (void)self;
14
+ if (!rb_respond_to(callback, kpqc_call)) {
15
+ rb_raise(rb_eTypeError, "entropy provider must respond to call");
16
+ }
17
+ kpqc_entropy_callback = callback;
18
+ return callback;
19
+ }
20
+
21
+ static VALUE kpqc_reset_randombytes(VALUE self) {
22
+ (void)self;
23
+ kpqc_entropy_callback = Qnil;
24
+ return Qnil;
25
+ }
26
+
27
+ void kpqc_randombytes_test_init(VALUE algorithm) {
28
+ kpqc_call = rb_intern("call");
29
+ rb_global_variable(&kpqc_entropy_callback);
30
+ rb_define_singleton_method(
31
+ algorithm, "__set_randombytes", kpqc_set_randombytes, 1);
32
+ rb_define_singleton_method(
33
+ algorithm, "__reset_randombytes", kpqc_reset_randombytes, 0);
34
+ }
35
+ #endif
36
+
37
+ #if defined(_WIN32)
38
+ #include <limits.h>
39
+ #include <windows.h>
40
+ #include <bcrypt.h>
41
+ #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
42
+ defined(__NetBSD__) || defined(__DragonFly__)
43
+ #include <stdlib.h>
44
+ #elif defined(__linux__) || defined(__ANDROID__)
45
+ #include <errno.h>
46
+ #include <sys/random.h>
47
+ #else
48
+ #error "kpqc currently supports operating-system entropy on Windows, Linux, Android, macOS, and BSD"
49
+ #endif
50
+
51
+ static int kpqc_fill_random(uint8_t *output, size_t length) {
52
+ #if defined(KPQC_TEST_ENTROPY)
53
+ if (kpqc_entropy_callback != Qnil) {
54
+ VALUE entropy = rb_funcall(
55
+ kpqc_entropy_callback, kpqc_call, 1, SIZET2NUM(length));
56
+ if (!RB_TYPE_P(entropy, T_STRING)) {
57
+ rb_raise(rb_eTypeError, "entropy provider must return a String");
58
+ }
59
+ if ((size_t)RSTRING_LEN(entropy) != length) {
60
+ rb_raise(rb_eRuntimeError,
61
+ "entropy provider returned %ld bytes, expected %lu",
62
+ RSTRING_LEN(entropy), (unsigned long)length);
63
+ }
64
+ memcpy(output, RSTRING_PTR(entropy), length);
65
+ return 0;
66
+ }
67
+ #endif
68
+ #if defined(_WIN32)
69
+ while (length != 0) {
70
+ ULONG chunk = length > (size_t)ULONG_MAX ? ULONG_MAX : (ULONG)length;
71
+ if (BCryptGenRandom(NULL, output, chunk,
72
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG) != 0) {
73
+ return -1;
74
+ }
75
+ output += chunk;
76
+ length -= chunk;
77
+ }
78
+ return 0;
79
+ #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
80
+ defined(__NetBSD__) || defined(__DragonFly__)
81
+ arc4random_buf(output, length);
82
+ return 0;
83
+ #else
84
+ while (length != 0) {
85
+ ssize_t count = getrandom(output, length, 0);
86
+ if (count < 0) {
87
+ if (errno == EINTR) continue;
88
+ return -1;
89
+ }
90
+ if (count == 0) return -1;
91
+ output += (size_t)count;
92
+ length -= (size_t)count;
93
+ }
94
+ return 0;
95
+ #endif
96
+ }
97
+
98
+ #if defined(KPQC_AIMER)
99
+ int randombytes(unsigned char *output, unsigned long long length) {
100
+ if (length > (unsigned long long)SIZE_MAX) return -1;
101
+ return kpqc_fill_random(output, (size_t)length);
102
+ }
103
+ #else
104
+ int randombytes(uint8_t *output, size_t length) {
105
+ return kpqc_fill_random(output, length);
106
+ }
107
+ #endif
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../extconf_helper"
3
+ KpqCBuild.smaugt("smaugt128", 0)
4
+
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../extconf_helper"
3
+ KpqCBuild.smaugt("smaugt192", 1)
4
+
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../extconf_helper"
3
+ KpqCBuild.smaugt("smaugt256", 2)
4
+
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../extconf_helper"
3
+ KpqCBuild.smaugt("timer", 3)
4
+
data/kpqc.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/kpqc/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "kpqc"
7
+ spec.version = KpqC::VERSION
8
+ spec.authors = ["osuolfou"]
9
+ spec.email = ["osuolfou@naver.com"]
10
+
11
+ spec.summary = "Ruby APIs for AIMer, HAETAE, NTRU+, and SMAUG-T."
12
+ spec.description =
13
+ "Synchronous Ruby bindings for the bundled AIMer and HAETAE signature " \
14
+ "schemes and NTRU+ and SMAUG-T key-encapsulation mechanisms."
15
+ spec.homepage = "https://github.com/osuolfou/kpqc-rb"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
18
+
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
21
+ spec.metadata["rubygems_mfa_required"] = "true"
22
+
23
+ spec.files = Dir[
24
+ "lib/**/*.rb",
25
+ "ext/**/*.{rb,c,h}",
26
+ "vendor/**/*.{c,h,json}",
27
+ "vendor/**/LICENSE",
28
+ "test/**/*.{rb,mjs,rsp}",
29
+ "Gemfile",
30
+ "kpqc.gemspec",
31
+ "Rakefile",
32
+ "README.md",
33
+ "LICENSE",
34
+ "THIRD_PARTY_NOTICES.md"
35
+ ]
36
+ spec.require_paths = ["lib"]
37
+ spec.extensions = Dir["ext/kpqc/*/extconf.rb"].sort
38
+
39
+ spec.add_development_dependency "rake", ">= 12.3"
40
+ spec.add_development_dependency "minitest", ">= 5.10"
41
+ end
data/lib/kpqc/aimer.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "api"
4
+
5
+ module KpqC
6
+ # AIMer signature parameter sets.
7
+ module Aimer
8
+ AIMER128F = SignatureAlgorithm.new(
9
+ "aimer-128f", SignatureSizes.new(32, 48, 5_888), "aimer128f"
10
+ )
11
+ AIMER128S = SignatureAlgorithm.new(
12
+ "aimer-128s", SignatureSizes.new(32, 48, 4_160), "aimer128s"
13
+ )
14
+ AIMER192F = SignatureAlgorithm.new(
15
+ "aimer-192f", SignatureSizes.new(48, 72, 13_056), "aimer192f"
16
+ )
17
+ AIMER192S = SignatureAlgorithm.new(
18
+ "aimer-192s", SignatureSizes.new(48, 72, 9_120), "aimer192s"
19
+ )
20
+ AIMER256F = SignatureAlgorithm.new(
21
+ "aimer-256f", SignatureSizes.new(64, 96, 25_120), "aimer256f"
22
+ )
23
+ AIMER256S = SignatureAlgorithm.new(
24
+ "aimer-256s", SignatureSizes.new(64, 96, 17_056), "aimer256s"
25
+ )
26
+ private_constant :AIMER128F, :AIMER128S, :AIMER192F, :AIMER192S,
27
+ :AIMER256F, :AIMER256S
28
+
29
+ class << self
30
+ def aimer128f; AIMER128F; end
31
+ def aimer128s; AIMER128S; end
32
+ def aimer192f; AIMER192F; end
33
+ def aimer192s; AIMER192S; end
34
+ def aimer256f; AIMER256F; end
35
+ def aimer256s; AIMER256S; end
36
+ end
37
+ end
38
+ end
data/lib/kpqc/api.rb ADDED
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types"
4
+
5
+ module KpqC
6
+ # An error reported by a bundled cryptographic core.
7
+ class Error < StandardError
8
+ end
9
+
10
+ EMPTY_CONTEXT = "".b.freeze
11
+ private_constant :EMPTY_CONTEXT
12
+
13
+ module AlgorithmSupport
14
+ def generate_key_pair
15
+ KeyPair.new(*native.keygen)
16
+ end
17
+
18
+ def inspect
19
+ "#<#{self.class} id=#{id.inspect} sizes=#{sizes.inspect}>"
20
+ end
21
+
22
+ def to_s
23
+ id
24
+ end
25
+
26
+ private
27
+
28
+ def initialize_algorithm(id, sizes, native_name)
29
+ @id = id.freeze
30
+ @sizes = sizes
31
+ @native_name = native_name.freeze
32
+ @native_constant = native_name.sub(/\A([a-z])/) do
33
+ Regexp.last_match(1).upcase
34
+ end.freeze
35
+ end
36
+
37
+ def native
38
+ require_native_extension
39
+ Native.const_get(@native_constant, false)
40
+ end
41
+
42
+ def require_native_extension
43
+ require "kpqc/#{@native_name}_native"
44
+ rescue LoadError => installed_error
45
+ begin
46
+ require "#{@native_name}_native"
47
+ rescue LoadError
48
+ raise installed_error
49
+ end
50
+ end
51
+
52
+ def require_string(label, value)
53
+ return value if value.is_a?(String)
54
+
55
+ raise TypeError, "#{label} must be a String"
56
+ end
57
+
58
+ def require_size(label, value, expected)
59
+ require_string(label, value)
60
+ return value if value.bytesize == expected
61
+
62
+ raise ArgumentError,
63
+ "#{label} must be #{expected} bytes, received #{value.bytesize}"
64
+ end
65
+
66
+ def require_context(context)
67
+ require_string("context", context)
68
+ return context if context.bytesize <= 255
69
+
70
+ raise ArgumentError,
71
+ "context cannot exceed 255 bytes, received #{context.bytesize}"
72
+ end
73
+ end
74
+ private_constant :AlgorithmSupport
75
+
76
+ # A signature algorithm and parameter set.
77
+ class SignatureAlgorithm
78
+ include AlgorithmSupport
79
+
80
+ attr_reader :id, :sizes
81
+
82
+ def initialize(id, sizes, native_name)
83
+ initialize_algorithm(id, sizes, native_name)
84
+ freeze
85
+ end
86
+
87
+ def sign(message, secret_key, context: EMPTY_CONTEXT)
88
+ require_string("message", message)
89
+ require_size("secret_key", secret_key, sizes.secret_key)
90
+ require_context(context)
91
+ native.sign(message, secret_key, context)
92
+ end
93
+
94
+ def verify(message, signature, public_key, context: EMPTY_CONTEXT)
95
+ require_string("message", message)
96
+ require_string("signature", signature)
97
+ require_size("public_key", public_key, sizes.public_key)
98
+ require_context(context)
99
+ return false unless signature.bytesize == sizes.signature
100
+
101
+ native.verify(message, signature, public_key, context)
102
+ end
103
+
104
+ end
105
+
106
+ # A key-encapsulation algorithm and parameter set.
107
+ class KeyEncapsulationAlgorithm
108
+ include AlgorithmSupport
109
+
110
+ attr_reader :id, :sizes
111
+
112
+ def initialize(id, sizes, native_name)
113
+ initialize_algorithm(id, sizes, native_name)
114
+ freeze
115
+ end
116
+
117
+ def encapsulate(public_key)
118
+ require_size("public_key", public_key, sizes.public_key)
119
+ EncapsulatedSecret.new(*native.encapsulate(public_key))
120
+ end
121
+
122
+ def decapsulate(ciphertext, secret_key)
123
+ require_size("ciphertext", ciphertext, sizes.ciphertext)
124
+ require_size("secret_key", secret_key, sizes.secret_key)
125
+ native.decapsulate(ciphertext, secret_key)
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "api"
4
+
5
+ module KpqC
6
+ # HAETAE signature parameter sets.
7
+ module Haetae
8
+ HAETAE2 = SignatureAlgorithm.new(
9
+ "haetae-mode2", SignatureSizes.new(992, 1_408, 1_474), "haetae2"
10
+ )
11
+ HAETAE3 = SignatureAlgorithm.new(
12
+ "haetae-mode3", SignatureSizes.new(1_472, 2_112, 2_349), "haetae3"
13
+ )
14
+ HAETAE5 = SignatureAlgorithm.new(
15
+ "haetae-mode5", SignatureSizes.new(2_080, 2_752, 2_948), "haetae5"
16
+ )
17
+ private_constant :HAETAE2, :HAETAE3, :HAETAE5
18
+
19
+ class << self
20
+ def haetae2; HAETAE2; end
21
+ def haetae3; HAETAE3; end
22
+ def haetae5; HAETAE5; end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "api"
4
+
5
+ module KpqC
6
+ # NTRU+ key-encapsulation parameter sets.
7
+ module NtruPlus
8
+ NTRUPLUS768 = KeyEncapsulationAlgorithm.new(
9
+ "NTRU+768", KemSizes.new(1_152, 2_336, 1_152, 32), "ntruplus768"
10
+ )
11
+ NTRUPLUS864 = KeyEncapsulationAlgorithm.new(
12
+ "NTRU+864", KemSizes.new(1_296, 2_624, 1_296, 32), "ntruplus864"
13
+ )
14
+ NTRUPLUS1152 = KeyEncapsulationAlgorithm.new(
15
+ "NTRU+1152", KemSizes.new(1_728, 3_488, 1_728, 32), "ntruplus1152"
16
+ )
17
+ private_constant :NTRUPLUS768, :NTRUPLUS864, :NTRUPLUS1152
18
+
19
+ class << self
20
+ def ntruplus768; NTRUPLUS768; end
21
+ def ntruplus864; NTRUPLUS864; end
22
+ def ntruplus1152; NTRUPLUS1152; end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "api"
4
+
5
+ module KpqC
6
+ # SMAUG-T and TiMER key-encapsulation parameter sets.
7
+ module SmaugT
8
+ SMAUGT128 = KeyEncapsulationAlgorithm.new(
9
+ "SMAUG-T128", KemSizes.new(672, 832, 672, 32), "smaugt128"
10
+ )
11
+ SMAUGT192 = KeyEncapsulationAlgorithm.new(
12
+ "SMAUG-T192", KemSizes.new(1_088, 1_312, 992, 32), "smaugt192"
13
+ )
14
+ SMAUGT256 = KeyEncapsulationAlgorithm.new(
15
+ "SMAUG-T256", KemSizes.new(1_440, 1_728, 1_376, 32), "smaugt256"
16
+ )
17
+ TIMER = KeyEncapsulationAlgorithm.new(
18
+ "TiMER", KemSizes.new(672, 832, 608, 32), "timer"
19
+ )
20
+ private_constant :SMAUGT128, :SMAUGT192, :SMAUGT256, :TIMER
21
+
22
+ class << self
23
+ def smaugt128; SMAUGT128; end
24
+ def smaugt192; SMAUGT192; end
25
+ def smaugt256; SMAUGT256; end
26
+ def timer; TIMER; end
27
+ end
28
+ end
29
+ end
data/lib/kpqc/types.rb ADDED
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KpqC
4
+ # A generated public and secret key pair.
5
+ class KeyPair
6
+ attr_reader :public_key, :secret_key
7
+
8
+ def initialize(public_key, secret_key)
9
+ @public_key = public_key
10
+ @secret_key = secret_key
11
+ end
12
+
13
+ # Wipe the secret key in place on a best-effort basis.
14
+ def clear!
15
+ KpqC.send(:wipe!, @secret_key)
16
+ self
17
+ end
18
+
19
+ def inspect
20
+ "#<#{self.class} public_key=#{@public_key.bytesize} bytes secret_key=[REDACTED]>"
21
+ end
22
+
23
+ alias to_s inspect
24
+ end
25
+
26
+ # The result of a key-encapsulation operation.
27
+ class EncapsulatedSecret
28
+ attr_reader :ciphertext, :shared_secret
29
+
30
+ def initialize(ciphertext, shared_secret)
31
+ @ciphertext = ciphertext
32
+ @shared_secret = shared_secret
33
+ end
34
+
35
+ # Wipe the shared secret in place on a best-effort basis.
36
+ def clear!
37
+ KpqC.send(:wipe!, @shared_secret)
38
+ self
39
+ end
40
+
41
+ def inspect
42
+ "#<#{self.class} ciphertext=#{@ciphertext.bytesize} bytes shared_secret=[REDACTED]>"
43
+ end
44
+
45
+ alias to_s inspect
46
+ end
47
+
48
+ # Byte sizes for a signature parameter set.
49
+ class SignatureSizes
50
+ attr_reader :public_key, :secret_key, :signature
51
+
52
+ def initialize(public_key, secret_key, signature)
53
+ @public_key = public_key
54
+ @secret_key = secret_key
55
+ @signature = signature
56
+ freeze
57
+ end
58
+
59
+ def ==(other)
60
+ other.is_a?(SignatureSizes) &&
61
+ [public_key, secret_key, signature] ==
62
+ [other.public_key, other.secret_key, other.signature]
63
+ end
64
+
65
+ alias eql? ==
66
+
67
+ def hash
68
+ [self.class, public_key, secret_key, signature].hash
69
+ end
70
+ end
71
+
72
+ # Byte sizes for a key-encapsulation parameter set.
73
+ class KemSizes
74
+ attr_reader :public_key, :secret_key, :ciphertext, :shared_secret
75
+
76
+ def initialize(public_key, secret_key, ciphertext, shared_secret)
77
+ @public_key = public_key
78
+ @secret_key = secret_key
79
+ @ciphertext = ciphertext
80
+ @shared_secret = shared_secret
81
+ freeze
82
+ end
83
+
84
+ def ==(other)
85
+ other.is_a?(KemSizes) &&
86
+ [public_key, secret_key, ciphertext, shared_secret] ==
87
+ [other.public_key, other.secret_key, other.ciphertext,
88
+ other.shared_secret]
89
+ end
90
+
91
+ alias eql? ==
92
+
93
+ def hash
94
+ [self.class, public_key, secret_key, ciphertext, shared_secret].hash
95
+ end
96
+ end
97
+
98
+ def self.wipe!(value)
99
+ value.bytesize.times { |index| value.setbyte(index, 0) }
100
+ end
101
+ private_class_method :wipe!
102
+ end
103
+
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KpqC
4
+ VERSION = "0.1.0"
5
+ end
6
+
data/lib/kpqc.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "kpqc/version"
4
+ require_relative "kpqc/aimer"
5
+ require_relative "kpqc/haetae"
6
+ require_relative "kpqc/ntruplus"
7
+ require_relative "kpqc/smaugt"
8
+
9
+ # Synchronous Ruby APIs for the KpqC post-quantum cryptographic schemes.
10
+ module KpqC
11
+ class << self
12
+ def aimer128f; Aimer.aimer128f; end
13
+ def aimer128s; Aimer.aimer128s; end
14
+ def aimer192f; Aimer.aimer192f; end
15
+ def aimer192s; Aimer.aimer192s; end
16
+ def aimer256f; Aimer.aimer256f; end
17
+ def aimer256s; Aimer.aimer256s; end
18
+
19
+ def haetae2; Haetae.haetae2; end
20
+ def haetae3; Haetae.haetae3; end
21
+ def haetae5; Haetae.haetae5; end
22
+
23
+ def ntruplus768; NtruPlus.ntruplus768; end
24
+ def ntruplus864; NtruPlus.ntruplus864; end
25
+ def ntruplus1152; NtruPlus.ntruplus1152; end
26
+
27
+ def smaugt128; SmaugT.smaugt128; end
28
+ def smaugt192; SmaugT.smaugt192; end
29
+ def smaugt256; SmaugT.smaugt256; end
30
+ def timer; SmaugT.timer; end
31
+ end
32
+ end
33
+
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class SignatureAlgorithmTest < Minitest::Test
6
+ def test_all_parameter_sets_round_trip_and_reject_changes
7
+ SIGNATURE_ALGORITHMS.each do |algorithm|
8
+ keys = algorithm.generate_key_pair
9
+ other = algorithm.generate_key_pair
10
+
11
+ assert_equal algorithm.sizes.public_key, keys.public_key.bytesize,
12
+ algorithm.id
13
+ assert_equal algorithm.sizes.secret_key, keys.secret_key.bytesize,
14
+ algorithm.id
15
+ refute_equal keys.public_key, other.public_key, algorithm.id
16
+
17
+ [["test\x00\xff".b, "domain".b], ["".b, "".b],
18
+ ["boundary".b, "x".b * 255]].each do |message, context|
19
+ signature = algorithm.sign(message, keys.secret_key, context: context)
20
+ assert_instance_of String, signature
21
+ assert_equal algorithm.sizes.signature, signature.bytesize
22
+ assert algorithm.verify(
23
+ message, signature, keys.public_key, context: context
24
+ ), algorithm.id
25
+ refute algorithm.verify(
26
+ message + "!", signature, keys.public_key, context: context
27
+ ), algorithm.id
28
+ refute algorithm.verify(
29
+ message, altered(signature), keys.public_key, context: context
30
+ ), algorithm.id
31
+ refute algorithm.verify(
32
+ message, signature, other.public_key, context: context
33
+ ), algorithm.id
34
+ refute algorithm.verify(
35
+ message, signature, keys.public_key, context: "wrong".b
36
+ ), algorithm.id
37
+ end
38
+
39
+ refute algorithm.verify("".b, "".b, keys.public_key), algorithm.id
40
+ assert_raises(ArgumentError) do
41
+ algorithm.sign("".b, keys.secret_key, context: "x".b * 256)
42
+ end
43
+ assert_raises(TypeError) { algorithm.sign(:text, keys.secret_key) }
44
+ assert_raises(ArgumentError) do
45
+ algorithm.sign("".b, keys.secret_key.byteslice(1..-1))
46
+ end
47
+ assert_raises(ArgumentError) do
48
+ algorithm.verify("".b, "".b, keys.public_key.byteslice(1..-1))
49
+ end
50
+ ensure
51
+ keys.clear! if keys
52
+ other.clear! if other
53
+ end
54
+ end
55
+ end
56
+
57
+ class KeyEncapsulationAlgorithmTest < Minitest::Test
58
+ def test_all_parameter_sets_round_trip_and_reject_changes
59
+ KEM_ALGORITHMS.each do |algorithm|
60
+ keys = algorithm.generate_key_pair
61
+ first = algorithm.encapsulate(keys.public_key)
62
+ second = algorithm.encapsulate(keys.public_key)
63
+
64
+ assert_equal algorithm.sizes.public_key, keys.public_key.bytesize,
65
+ algorithm.id
66
+ assert_equal algorithm.sizes.secret_key, keys.secret_key.bytesize,
67
+ algorithm.id
68
+ assert_equal algorithm.sizes.ciphertext, first.ciphertext.bytesize,
69
+ algorithm.id
70
+ assert_equal algorithm.sizes.shared_secret, first.shared_secret.bytesize,
71
+ algorithm.id
72
+ assert_equal first.shared_secret,
73
+ algorithm.decapsulate(first.ciphertext, keys.secret_key),
74
+ algorithm.id
75
+ refute_equal first.ciphertext, second.ciphertext, algorithm.id
76
+ refute_equal first.shared_secret, second.shared_secret, algorithm.id
77
+
78
+ if algorithm.id.start_with?("NTRU+")
79
+ assert_raises(KpqC::Error) do
80
+ algorithm.decapsulate(altered(first.ciphertext), keys.secret_key)
81
+ end
82
+ else
83
+ replacement = algorithm.decapsulate(
84
+ altered(first.ciphertext), keys.secret_key
85
+ )
86
+ assert_equal algorithm.sizes.shared_secret, replacement.bytesize
87
+ refute_equal first.shared_secret, replacement
88
+ end
89
+
90
+ assert_raises(ArgumentError) do
91
+ algorithm.encapsulate(keys.public_key.byteslice(1..-1))
92
+ end
93
+ assert_raises(TypeError) { algorithm.encapsulate([]) }
94
+ assert_raises(ArgumentError) do
95
+ algorithm.decapsulate(
96
+ first.ciphertext.byteslice(1..-1), keys.secret_key
97
+ )
98
+ end
99
+ assert_raises(ArgumentError) do
100
+ algorithm.decapsulate(
101
+ first.ciphertext, keys.secret_key.byteslice(1..-1)
102
+ )
103
+ end
104
+ ensure
105
+ keys.clear! if keys
106
+ first.clear! if first
107
+ second.clear! if second
108
+ end
109
+ end
110
+ end
111
+