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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +174 -0
- data/Rakefile +57 -0
- data/THIRD_PARTY_NOTICES.md +53 -0
- data/ext/kpqc/aimer128f/extconf.rb +4 -0
- data/ext/kpqc/aimer128s/extconf.rb +4 -0
- data/ext/kpqc/aimer192f/extconf.rb +4 -0
- data/ext/kpqc/aimer192s/extconf.rb +4 -0
- data/ext/kpqc/aimer256f/extconf.rb +4 -0
- data/ext/kpqc/aimer256s/extconf.rb +4 -0
- data/ext/kpqc/extconf_helper.rb +93 -0
- data/ext/kpqc/haetae2/extconf.rb +4 -0
- data/ext/kpqc/haetae3/extconf.rb +4 -0
- data/ext/kpqc/haetae5/extconf.rb +4 -0
- data/ext/kpqc/module.c +195 -0
- data/ext/kpqc/ntruplus1152/extconf.rb +4 -0
- data/ext/kpqc/ntruplus768/extconf.rb +4 -0
- data/ext/kpqc/ntruplus864/extconf.rb +4 -0
- data/ext/kpqc/randombytes.c +107 -0
- data/ext/kpqc/smaugt128/extconf.rb +4 -0
- data/ext/kpqc/smaugt192/extconf.rb +4 -0
- data/ext/kpqc/smaugt256/extconf.rb +4 -0
- data/ext/kpqc/timer/extconf.rb +4 -0
- data/kpqc.gemspec +41 -0
- data/lib/kpqc/aimer.rb +38 -0
- data/lib/kpqc/api.rb +129 -0
- data/lib/kpqc/haetae.rb +25 -0
- data/lib/kpqc/ntruplus.rb +25 -0
- data/lib/kpqc/smaugt.rb +29 -0
- data/lib/kpqc/types.rb +103 -0
- data/lib/kpqc/version.rb +6 -0
- data/lib/kpqc.rb +33 -0
- data/test/algorithm_test.rb +111 -0
- data/test/api_test.rb +52 -0
- data/test/interop.mjs +34 -0
- data/test/interop_test.rb +82 -0
- data/test/kat/aimer128f.rsp +10 -0
- data/test/kat/aimer128s.rsp +10 -0
- data/test/kat/aimer192f.rsp +10 -0
- data/test/kat/aimer192s.rsp +10 -0
- data/test/kat/aimer256f.rsp +10 -0
- data/test/kat/aimer256s.rsp +10 -0
- data/test/kat/haetae2.rsp +10 -0
- data/test/kat/haetae3.rsp +10 -0
- data/test/kat/haetae5.rsp +10 -0
- data/test/kat/ntruplus1152.rsp +8 -0
- data/test/kat/ntruplus768.rsp +8 -0
- data/test/kat/ntruplus864.rsp +8 -0
- data/test/kat/smaugt128.rsp +8 -0
- data/test/kat/smaugt192.rsp +8 -0
- data/test/kat/smaugt256.rsp +8 -0
- data/test/kat/timer.rsp +8 -0
- data/test/kat_drbg.rb +155 -0
- data/test/kat_test.rb +99 -0
- data/test/test_helper.rb +21 -0
- data/vendor/AIMer/LICENSE +21 -0
- data/vendor/AIMer/aim2.c +163 -0
- data/vendor/AIMer/aim2.h +36 -0
- data/vendor/AIMer/aim2_constant.h +74 -0
- data/vendor/AIMer/api.h +42 -0
- data/vendor/AIMer/common/crypto_declassify.h +7 -0
- data/vendor/AIMer/common/fips202.c +878 -0
- data/vendor/AIMer/common/fips202.h +166 -0
- data/vendor/AIMer/common/rng.h +56 -0
- data/vendor/AIMer/field.h +43 -0
- data/vendor/AIMer/field128.c +454 -0
- data/vendor/AIMer/field192.c +485 -0
- data/vendor/AIMer/field256.c +564 -0
- data/vendor/AIMer/hash.c +50 -0
- data/vendor/AIMer/hash.h +37 -0
- data/vendor/AIMer/params/params-aimer-128f.h +34 -0
- data/vendor/AIMer/params/params-aimer-128s.h +34 -0
- data/vendor/AIMer/params/params-aimer-192f.h +34 -0
- data/vendor/AIMer/params/params-aimer-192s.h +34 -0
- data/vendor/AIMer/params/params-aimer-256f.h +34 -0
- data/vendor/AIMer/params/params-aimer-256s.h +34 -0
- data/vendor/AIMer/params.h +5 -0
- data/vendor/AIMer/sign.c +631 -0
- data/vendor/AIMer/sign.h +40 -0
- data/vendor/AIMer/tree.c +75 -0
- data/vendor/AIMer/tree.h +26 -0
- data/vendor/HAETAE/LICENSE +22 -0
- data/vendor/HAETAE/include/api.h +54 -0
- data/vendor/HAETAE/include/common.h +12 -0
- data/vendor/HAETAE/include/config.h +42 -0
- data/vendor/HAETAE/include/decompose.h +19 -0
- data/vendor/HAETAE/include/encoding.h +20 -0
- data/vendor/HAETAE/include/fft.h +26 -0
- data/vendor/HAETAE/include/fips202.h +40 -0
- data/vendor/HAETAE/include/fixpoint.h +125 -0
- data/vendor/HAETAE/include/ntt.h +16 -0
- data/vendor/HAETAE/include/packing.h +58 -0
- data/vendor/HAETAE/include/params.h +117 -0
- data/vendor/HAETAE/include/poly.h +65 -0
- data/vendor/HAETAE/include/polyfix.h +65 -0
- data/vendor/HAETAE/include/polymat.h +32 -0
- data/vendor/HAETAE/include/polyvec.h +131 -0
- data/vendor/HAETAE/include/randombytes.h +39 -0
- data/vendor/HAETAE/include/rans_byte.h +335 -0
- data/vendor/HAETAE/include/reduce.h +31 -0
- data/vendor/HAETAE/include/sampler.h +23 -0
- data/vendor/HAETAE/include/symmetric.h +52 -0
- data/vendor/HAETAE/src/decompose.c +73 -0
- data/vendor/HAETAE/src/encoding.c +700 -0
- data/vendor/HAETAE/src/fft.c +235 -0
- data/vendor/HAETAE/src/fips202.c +745 -0
- data/vendor/HAETAE/src/fixpoint.c +143 -0
- data/vendor/HAETAE/src/ntt.c +98 -0
- data/vendor/HAETAE/src/packing.c +595 -0
- data/vendor/HAETAE/src/poly.c +371 -0
- data/vendor/HAETAE/src/polyfix.c +298 -0
- data/vendor/HAETAE/src/polymat.c +82 -0
- data/vendor/HAETAE/src/polyvec.c +534 -0
- data/vendor/HAETAE/src/reduce.c +96 -0
- data/vendor/HAETAE/src/sampler.c +310 -0
- data/vendor/HAETAE/src/sign.c +558 -0
- data/vendor/HAETAE/src/symmetric-shake.c +50 -0
- data/vendor/NTRUplus/LICENSE +21 -0
- data/vendor/NTRUplus/NTRU+1152/api.h +24 -0
- data/vendor/NTRUplus/NTRU+1152/fips202/fips202.c +878 -0
- data/vendor/NTRUplus/NTRU+1152/fips202/fips202.h +166 -0
- data/vendor/NTRUplus/NTRU+1152/kem.c +286 -0
- data/vendor/NTRUplus/NTRU+1152/ntt.c +394 -0
- data/vendor/NTRUplus/NTRU+1152/ntt.h +16 -0
- data/vendor/NTRUplus/NTRU+1152/params.h +18 -0
- data/vendor/NTRUplus/NTRU+1152/poly.c +318 -0
- data/vendor/NTRUplus/NTRU+1152/poly.h +31 -0
- data/vendor/NTRUplus/NTRU+1152/randombytes.h +9 -0
- data/vendor/NTRUplus/NTRU+1152/symmetric.c +39 -0
- data/vendor/NTRUplus/NTRU+1152/symmetric.h +12 -0
- data/vendor/NTRUplus/NTRU+768/api.h +24 -0
- data/vendor/NTRUplus/NTRU+768/fips202/fips202.c +878 -0
- data/vendor/NTRUplus/NTRU+768/fips202/fips202.h +166 -0
- data/vendor/NTRUplus/NTRU+768/kem.c +286 -0
- data/vendor/NTRUplus/NTRU+768/ntt.c +379 -0
- data/vendor/NTRUplus/NTRU+768/ntt.h +16 -0
- data/vendor/NTRUplus/NTRU+768/params.h +18 -0
- data/vendor/NTRUplus/NTRU+768/poly.c +320 -0
- data/vendor/NTRUplus/NTRU+768/poly.h +31 -0
- data/vendor/NTRUplus/NTRU+768/randombytes.h +9 -0
- data/vendor/NTRUplus/NTRU+768/symmetric.c +39 -0
- data/vendor/NTRUplus/NTRU+768/symmetric.h +12 -0
- data/vendor/NTRUplus/NTRU+864/api.h +24 -0
- data/vendor/NTRUplus/NTRU+864/fips202/fips202.c +878 -0
- data/vendor/NTRUplus/NTRU+864/fips202/fips202.h +166 -0
- data/vendor/NTRUplus/NTRU+864/kem.c +286 -0
- data/vendor/NTRUplus/NTRU+864/ntt.c +383 -0
- data/vendor/NTRUplus/NTRU+864/ntt.h +16 -0
- data/vendor/NTRUplus/NTRU+864/params.h +18 -0
- data/vendor/NTRUplus/NTRU+864/poly.c +314 -0
- data/vendor/NTRUplus/NTRU+864/poly.h +31 -0
- data/vendor/NTRUplus/NTRU+864/randombytes.h +9 -0
- data/vendor/NTRUplus/NTRU+864/symmetric.c +39 -0
- data/vendor/NTRUplus/NTRU+864/symmetric.h +12 -0
- data/vendor/SMAUG-T/LICENSE +22 -0
- data/vendor/SMAUG-T/include/api.h +37 -0
- data/vendor/SMAUG-T/include/cbd.h +15 -0
- data/vendor/SMAUG-T/include/ciphertext.h +45 -0
- data/vendor/SMAUG-T/include/common.h +12 -0
- data/vendor/SMAUG-T/include/config.h +48 -0
- data/vendor/SMAUG-T/include/dg.h +28 -0
- data/vendor/SMAUG-T/include/fips202.h +41 -0
- data/vendor/SMAUG-T/include/hash.h +21 -0
- data/vendor/SMAUG-T/include/hwt.h +28 -0
- data/vendor/SMAUG-T/include/indcpa.h +33 -0
- data/vendor/SMAUG-T/include/key.h +34 -0
- data/vendor/SMAUG-T/include/pack.h +78 -0
- data/vendor/SMAUG-T/include/packring.h +50 -0
- data/vendor/SMAUG-T/include/params.h +84 -0
- data/vendor/SMAUG-T/include/poly.h +41 -0
- data/vendor/SMAUG-T/include/randombytes.h +11 -0
- data/vendor/SMAUG-T/include/toomcook.h +14 -0
- data/vendor/SMAUG-T/include/verify.h +16 -0
- data/vendor/SMAUG-T/src/cbd.c +159 -0
- data/vendor/SMAUG-T/src/ciphertext.c +95 -0
- data/vendor/SMAUG-T/src/dg.c +112 -0
- data/vendor/SMAUG-T/src/fips202.c +745 -0
- data/vendor/SMAUG-T/src/hash.c +15 -0
- data/vendor/SMAUG-T/src/hwt.c +121 -0
- data/vendor/SMAUG-T/src/indcpa.c +187 -0
- data/vendor/SMAUG-T/src/kem.c +194 -0
- data/vendor/SMAUG-T/src/key.c +102 -0
- data/vendor/SMAUG-T/src/pack.c +349 -0
- data/vendor/SMAUG-T/src/packring.c +503 -0
- data/vendor/SMAUG-T/src/poly.c +200 -0
- data/vendor/SMAUG-T/src/toomcook.c +256 -0
- data/vendor/SMAUG-T/src/verify.c +48 -0
- data/vendor/SOURCES.json +130 -0
- 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
|
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
|
data/lib/kpqc/haetae.rb
ADDED
|
@@ -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
|
data/lib/kpqc/smaugt.rb
ADDED
|
@@ -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
|
+
|
data/lib/kpqc/version.rb
ADDED
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
|
+
|