aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/BenchTest.mc70
-rw-r--r--test/TestSupport.mc53
-rw-r--r--test/crypto/HashHmacTest.mc54
-rw-r--r--test/crypto/KdfAesTest.mc116
-rw-r--r--test/crypto/SealedBoxTest.mc96
-rw-r--r--test/data/Vectors.mc256
-rw-r--r--test/golden/fr255s.pngbin1082 -> 1106 bytes
-rw-r--r--test/golden/fr70.pngbin5158 -> 5383 bytes
-rw-r--r--test/golden/fr965.pngbin7362 -> 7487 bytes
-rw-r--r--test/golden/venusq2.pngbin1653 -> 1684 bytes
-rw-r--r--test/otp/OtpTest.mc118
-rw-r--r--test/util/UtilTest.mc66
-rw-r--r--tools/gen_vectors.py361
-rw-r--r--tools/sim_screenshot.py5
14 files changed, 1193 insertions, 2 deletions
diff --git a/test/BenchTest.mc b/test/BenchTest.mc
new file mode 100644
index 0000000..7e1f612
--- /dev/null
+++ b/test/BenchTest.mc
@@ -0,0 +1,70 @@
+// Author: cirakg & claude ai
+// Summary: Benchmarks of the crypto and OTP primitives; logs BENCH lines for docs/PERFORMANCE.md, never fails.
+//
+// Simulator timings run on the PC's CPU, so they only compare primitives with
+// each other (for example how much SHA-512 costs against native SHA-1).
+// Watch timings come from the on-watch benchmark in M4.
+
+import Toybox.Lang;
+import Toybox.System;
+import Toybox.Test;
+
+(:test)
+module Bench {
+ // Run fn `times` times and log the average in microseconds.
+ function measure(logger as Test.Logger, name as String, times as Number, fn as Method() as Void) as Void {
+ var start = System.getTimer();
+ for (var i = 0; i < times; i++) {
+ fn.invoke();
+ }
+ var elapsed = System.getTimer() - start;
+ logger.debug("BENCH " + name + " us_per_call=" + (elapsed * 1000 / times));
+ }
+
+ class Cases {
+ var secret20 as ByteArray = new [20]b;
+ var secret64 as ByteArray = new [64]b;
+ var keys as SealedBox.Keys = new SealedBox.Keys(new [32]b);
+ var box as ByteArray = []b;
+
+ function initialize() {
+ box = SealedBox.seal(keys, []b, new [20]b);
+ }
+
+ function totpSha1() as Void {
+ Otp.totp(Hashes.SHA1, secret20, 1700000000l, 30, 6);
+ }
+
+ function totpSha256() as Void {
+ Otp.totp(Hashes.SHA256, secret20, 1700000000l, 30, 6);
+ }
+
+ function totpSha512() as Void {
+ Otp.totp(Hashes.SHA512, secret64, 1700000000l, 30, 6);
+ }
+
+ function pbkdf2Thousand() as Void {
+ Kdf.pbkdf2([1, 2, 3, 4, 5, 6]b, new [16]b, 1000, 32);
+ }
+
+ function sealOpen() as Void {
+ SealedBox.open(keys, []b, SealedBox.seal(keys, []b, new [20]b));
+ }
+
+ function selfTest() as Void {
+ SelfTest.run();
+ }
+ }
+}
+
+(:test)
+function benchmarkPrimitives(logger as Test.Logger) as Boolean {
+ var c = new Bench.Cases();
+ Bench.measure(logger, "totp_sha1", 20, c.method(:totpSha1));
+ Bench.measure(logger, "totp_sha256", 20, c.method(:totpSha256));
+ Bench.measure(logger, "totp_sha512", 20, c.method(:totpSha512));
+ Bench.measure(logger, "pbkdf2_1000_iterations", 3, c.method(:pbkdf2Thousand));
+ Bench.measure(logger, "sealedbox_seal_open", 20, c.method(:sealOpen));
+ Bench.measure(logger, "self_test", 3, c.method(:selfTest));
+ return true;
+}
diff --git a/test/TestSupport.mc b/test/TestSupport.mc
new file mode 100644
index 0000000..f6fa4b7
--- /dev/null
+++ b/test/TestSupport.mc
@@ -0,0 +1,53 @@
+// Author: cirakg & claude ai
+// Summary: Small helpers shared by the Monkey C unit tests (reading vector rows, reporting mismatches).
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module TestSupport {
+
+ // One row of a TestVectors table.
+ function row(table as Array, index as Number) as Array {
+ return table[index] as Array;
+ }
+
+ function str(r as Array, column as Number) as String {
+ return r[column] as String;
+ }
+
+ function bytes(r as Array, column as Number) as ByteArray {
+ return Bytes.fromHex(r[column] as String);
+ }
+
+ function num(r as Array, column as Number) as Number {
+ return r[column] as Number;
+ }
+
+ // Numbers above 2^31 are stored as Long literals in the tables.
+ function long(r as Array, column as Number) as Long {
+ return (r[column] as Number or Long).toLong();
+ }
+
+ function alg(r as Array, column as Number) as Hashes.Algorithm {
+ return r[column] as Hashes.Algorithm;
+ }
+
+ // Compare bytes; log both sides on mismatch. Returns true when equal.
+ function sameBytes(logger as Test.Logger, what as String, actual as ByteArray, expected as ByteArray) as Boolean {
+ if (Bytes.equal(actual, expected)) {
+ return true;
+ }
+ logger.error(what + ": got " + Bytes.toHex(actual) + ", expected " + Bytes.toHex(expected));
+ return false;
+ }
+
+ // Compare strings; log both sides on mismatch.
+ function sameString(logger as Test.Logger, what as String, actual as String, expected as String) as Boolean {
+ if (actual.equals(expected)) {
+ return true;
+ }
+ logger.error(what + ": got '" + actual + "', expected '" + expected + "'");
+ return false;
+ }
+}
diff --git a/test/crypto/HashHmacTest.mc b/test/crypto/HashHmacTest.mc
new file mode 100644
index 0000000..15f166c
--- /dev/null
+++ b/test/crypto/HashHmacTest.mc
@@ -0,0 +1,54 @@
+// Author: cirakg & claude ai
+// Summary: SHA-1/256/512 and HMAC tests against FIPS 180-4, RFC 2202, RFC 4231 and seeded padding-boundary vectors.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+// Every hash vector (FIPS 180-4 examples + messages around the padding boundaries).
+(:test)
+function hashMatchesVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.HASH.size(); i++) {
+ var r = TestSupport.row(TestVectors.HASH, i);
+ var actual = Hashes.digest(TestSupport.alg(r, 0), TestSupport.bytes(r, 1));
+ ok = TestSupport.sameBytes(logger, "hash vector " + i, actual, TestSupport.bytes(r, 2)) && ok;
+ }
+ logger.debug(TestVectors.HASH.size() + " hash vectors");
+ return ok;
+}
+
+// Generic RFC 2104 HMAC for all three hashes, and the native HMAC-SHA256.
+(:test)
+function hmacMatchesRfcVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.HMAC.size(); i++) {
+ var r = TestVectors.HMAC[i] as Array;
+ var alg = TestSupport.alg(r, 0);
+ var key = TestSupport.bytes(r, 1);
+ var data = TestSupport.bytes(r, 2);
+ var expected = TestSupport.bytes(r, 3);
+ ok = TestSupport.sameBytes(logger, "generic HMAC " + i, Hmac.generic(alg, key, data), expected) && ok;
+ ok = TestSupport.sameBytes(logger, "HMAC " + i, Hmac.compute(alg, key, data), expected) && ok;
+ }
+ return ok;
+}
+
+// HMAC must not change the caller's key, even for keys longer than a block.
+(:test)
+function hmacLeavesKeyUntouched(logger as Test.Logger) as Boolean {
+ var key = new [131]b;
+ for (var i = 0; i < key.size(); i++) {
+ key[i] = 0xaa;
+ }
+ var before = Bytes.copy(key);
+ Hmac.generic(Hashes.SHA512, key, [1, 2, 3]b);
+ Hmac.generic(Hashes.SHA1, key.slice(0, 20), [1, 2, 3]b);
+ return Bytes.equal(key, before);
+}
+
+(:test)
+function hashSizesAndValidity(logger as Test.Logger) as Boolean {
+ return Hashes.digestSize(Hashes.SHA1) == 20 && Hashes.digestSize(Hashes.SHA256) == 32
+ && Hashes.digestSize(Hashes.SHA512) == 64 && Hashes.blockSize(Hashes.SHA512) == 128
+ && Hashes.blockSize(Hashes.SHA1) == 64 && Hashes.isValid(2) && !Hashes.isValid(3) && !Hashes.isValid(-1);
+}
diff --git a/test/crypto/KdfAesTest.mc b/test/crypto/KdfAesTest.mc
new file mode 100644
index 0000000..6587bc9
--- /dev/null
+++ b/test/crypto/KdfAesTest.mc
@@ -0,0 +1,116 @@
+// Author: cirakg & claude ai
+// Summary: PBKDF2, HKDF and AES-CBC tests (RFC 7914, RFC 5869, NIST SP 800-38A) plus padding and job edge cases.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+function pbkdf2MatchesVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.PBKDF2.size(); i++) {
+ var r = TestSupport.row(TestVectors.PBKDF2, i);
+ var key = Kdf.pbkdf2(TestSupport.bytes(r, 0), TestSupport.bytes(r, 1), TestSupport.num(r, 2), TestSupport.num(r, 3));
+ ok = TestSupport.sameBytes(logger, "PBKDF2 vector " + i, key, TestSupport.bytes(r, 4)) && ok;
+ }
+ return ok;
+}
+
+// The result must not depend on how the work is split into steps.
+(:test)
+function pbkdf2StepSizeDoesNotMatter(logger as Test.Logger) as Boolean {
+ var password = Bytes.fromUtf8("123456");
+ var salt = new [16]b;
+ var oneByOne = new Kdf.Pbkdf2(password, salt, 37, 40);
+ var steps = 0;
+ while (!oneByOne.step(1)) {
+ steps++;
+ }
+ // 2 output blocks x 37 iterations, the last step returns true.
+ return Bytes.equal(oneByOne.result(), Kdf.pbkdf2(password, salt, 37, 40)) && steps == 73;
+}
+
+(:test)
+function pbkdf2RejectsBadParameters(logger as Test.Logger) as Boolean {
+ var rejected = 0;
+ var cases = [[0, 32], [1, 0], [1, 255 * 32 + 1]];
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i] as Array<Number>;
+ try {
+ new Kdf.Pbkdf2([1]b, [2]b, c[0], c[1]);
+ } catch (e instanceof Lang.InvalidValueException) {
+ rejected++;
+ }
+ }
+ return rejected == cases.size();
+}
+
+(:test)
+function pbkdf2CalibrationStaysInBounds(logger as Test.Logger) as Boolean {
+ var iterations = Kdf.calibrate(1000, 500);
+ logger.debug("calibrated iterations for 1 s in this simulator: " + iterations);
+ return iterations >= Kdf.MIN_ITERATIONS && iterations <= Kdf.MAX_ITERATIONS;
+}
+
+(:test)
+function hkdfMatchesRfcVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.HKDF.size(); i++) {
+ var r = TestSupport.row(TestVectors.HKDF, i);
+ var okm = Kdf.hkdf(TestSupport.bytes(r, 0), TestSupport.bytes(r, 1), TestSupport.bytes(r, 2), TestSupport.num(r, 3));
+ ok = TestSupport.sameBytes(logger, "HKDF vector " + i, okm, TestSupport.bytes(r, 4)) && ok;
+ }
+ return ok;
+}
+
+(:test)
+function aesCbcMatchesNistVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.AES_CBC.size(); i++) {
+ var r = TestSupport.row(TestVectors.AES_CBC, i);
+ var key = TestSupport.bytes(r, 0);
+ var iv = TestSupport.bytes(r, 1);
+ var plain = TestSupport.bytes(r, 2);
+ var cipher = TestSupport.bytes(r, 3);
+ ok = TestSupport.sameBytes(logger, "AES encrypt " + i, Aes.cbcRaw(key, iv, plain, true), cipher) && ok;
+ ok = TestSupport.sameBytes(logger, "AES decrypt " + i, Aes.cbcRaw(key, iv, cipher, false), plain) && ok;
+ }
+ return ok;
+}
+
+// PKCS#7 round trips for every length from 0 to 40 bytes, both key sizes.
+(:test)
+function aesPaddingRoundTrips(logger as Test.Logger) as Boolean {
+ var iv = new [16]b;
+ var keys = [new [16]b, new [32]b];
+ for (var k = 0; k < keys.size(); k++) {
+ for (var length = 0; length <= 40; length++) {
+ var plain = new [length]b;
+ for (var i = 0; i < length; i++) {
+ plain[i] = (i * 7 + length) & 0xFF;
+ }
+ var cipher = Aes.cbcEncrypt(keys[k] as ByteArray, iv, plain);
+ var back = Aes.cbcDecrypt(keys[k] as ByteArray, iv, cipher);
+ if (cipher.size() != (length / 16 + 1) * 16 || back == null || !Bytes.equal(back, plain)) {
+ logger.error("padding round trip failed at length " + length);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+(:test)
+function aesRejectsBadPaddingAndLength(logger as Test.Logger) as Boolean {
+ var key = new [32]b;
+ var iv = new [16]b;
+ // A block whose decryption ends in 0x00, which is never valid padding.
+ var badPadding = Aes.cbcRaw(key, iv, new [16]b, true);
+ var badKey = false;
+ try {
+ Aes.cbcEncrypt(new [24]b, iv, [1]b);
+ } catch (e instanceof Lang.InvalidValueException) {
+ badKey = true;
+ }
+ return Aes.cbcDecrypt(key, iv, badPadding) == null && Aes.cbcDecrypt(key, iv, new [15]b) == null
+ && Aes.cbcDecrypt(key, iv, []b) == null && badKey;
+}
diff --git a/test/crypto/SealedBoxTest.mc b/test/crypto/SealedBoxTest.mc
new file mode 100644
index 0000000..810e512
--- /dev/null
+++ b/test/crypto/SealedBoxTest.mc
@@ -0,0 +1,96 @@
+// Author: cirakg & claude ai
+// Summary: Tests for SealedBox (encrypt-then-MAC records): round trips, tamper detection, wrong key or aad, and the self-test.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module SealedBoxFixtures {
+ function keys(seed as Number) as SealedBox.Keys {
+ var master = new [32]b;
+ for (var i = 0; i < 32; i++) {
+ master[i] = (i * 13 + seed) & 0xFF;
+ }
+ return new SealedBox.Keys(master);
+ }
+}
+
+(:test)
+function sealedBoxRoundTrips(logger as Test.Logger) as Boolean {
+ var keys = SealedBoxFixtures.keys(1);
+ var aad = Bytes.fromUtf8("K7F2|header");
+ for (var length = 0; length <= 70; length += 7) {
+ var plain = new [length]b;
+ for (var i = 0; i < length; i++) {
+ plain[i] = (i * 31) & 0xFF;
+ }
+ var box = SealedBox.seal(keys, aad, plain);
+ var back = SealedBox.open(keys, aad, box);
+ if (back == null || !Bytes.equal(back, plain) || box[0] != SealedBox.VERSION) {
+ logger.error("round trip failed at length " + length);
+ return false;
+ }
+ }
+ return true;
+}
+
+// Flipping any single byte of the box (version, IV, ciphertext or tag) must
+// make open() return null.
+(:test)
+function sealedBoxDetectsEveryTamperedByte(logger as Test.Logger) as Boolean {
+ var keys = SealedBoxFixtures.keys(2);
+ var aad = [9, 9]b;
+ var box = SealedBox.seal(keys, aad, Bytes.fromUtf8("JBSWY3DPEHPK3PXP fake secret"));
+ for (var i = 0; i < box.size(); i++) {
+ var tampered = Bytes.copy(box);
+ tampered[i] = tampered[i] ^ 0x01;
+ if (SealedBox.open(keys, aad, tampered) != null) {
+ logger.error("tampering byte " + i + " was not detected");
+ return false;
+ }
+ }
+ return true;
+}
+
+(:test)
+function sealedBoxRejectsWrongKeyAadAndTruncation(logger as Test.Logger) as Boolean {
+ var keys = SealedBoxFixtures.keys(3);
+ var aad = [1, 2, 3]b;
+ var box = SealedBox.seal(keys, aad, [42]b);
+ // Moving a byte from the aad into the box must not verify either.
+ var shifted = Bytes.concat([3]b, box);
+ return SealedBox.open(SealedBoxFixtures.keys(4), aad, box) == null
+ && SealedBox.open(keys, [1, 2]b, box) == null
+ && SealedBox.open(keys, [1, 2]b, shifted) == null
+ && SealedBox.open(keys, aad, box.slice(0, box.size() - 1)) == null
+ && SealedBox.open(keys, aad, []b) == null
+ && SealedBox.open(keys, aad, box) != null;
+}
+
+// The same IV gives the same box; random IVs make every seal different.
+(:test)
+function sealedBoxIvHandling(logger as Test.Logger) as Boolean {
+ var keys = SealedBoxFixtures.keys(5);
+ var iv = new [16]b;
+ var a = SealedBox.sealWithIv(keys, []b, [7]b, iv);
+ var b = SealedBox.sealWithIv(keys, []b, [7]b, iv);
+ var c = SealedBox.seal(keys, []b, [7]b);
+ var d = SealedBox.seal(keys, []b, [7]b);
+ return Bytes.equal(a, b) && !Bytes.equal(c, d);
+}
+
+(:test)
+function sealedBoxKeysWipe(logger as Test.Logger) as Boolean {
+ var keys = SealedBoxFixtures.keys(6);
+ keys.wipe();
+ return Bytes.equal(keys.encKey, new [32]b) && Bytes.equal(keys.macKey, new [32]b);
+}
+
+(:test)
+function selfTestPasses(logger as Test.Logger) as Boolean {
+ var failed = SelfTest.run();
+ if (failed != null) {
+ logger.error("self-test failed: " + failed);
+ }
+ return failed == null;
+}
diff --git a/test/data/Vectors.mc b/test/data/Vectors.mc
new file mode 100644
index 0000000..3fa6418
--- /dev/null
+++ b/test/data/Vectors.mc
@@ -0,0 +1,256 @@
+// Author: cirakg & claude ai
+// Summary: Crypto and OTP test vectors (official RFC/FIPS/NIST values plus seeded cases); generated, do not edit.
+//
+// Generated by tools/gen_vectors.py. Every published vector was recomputed with
+// Python's standard library (or openssl for AES) before being written here.
+// Algorithm IDs: 0 = SHA-1, 1 = SHA-256, 2 = SHA-512. All data is fake.
+
+import Toybox.Lang;
+
+(:test)
+module TestVectors {
+
+ // [alg, message hex, digest hex]
+ const HASH = [
+ [0, "", "da39a3ee5e6b4b0d3255bfef95601890afd80709"],
+ [0, "616263", "a9993e364706816aba3e25717850c26c9cd0d89d"],
+ [0, "6162636462636465636465666465666765666768666768696768696a68696a6b696a6b6c6a6b6c6d6b6c6d6e6c6d6e6f6d6e6f706e6f7071", "84983e441c3bd26ebaae4aa1f95129e5e54670f1"],
+ [0, "61626364656667686263646566676869636465666768696a6465666768696a6b65666768696a6b6c666768696a6b6c6d6768696a6b6c6d6e68696a6b6c6d6e6f696a6b6c6d6e6f706a6b6c6d6e6f70716b6c6d6e6f7071726c6d6e6f707172736d6e6f70717273746e6f707172737475", "a49b2446a02c645bf419f995b67091253a04a259"],
+ [0, "6d0d8c3c1ce808451a34469d6624cd700a4fa6d15b876e7557c8bd17cf5600a9c413a505bc854e507d32a411aa9f551f952cf38009c4c6", "1c869e923c782ee8d9ab2d4f85d9a204be1db7c3"],
+ [0, "7731b02cd0337ae020f31e9bc4a14517f85c5df2538ea53213e69b948123f9d258d83556eef48e977b6681c5524e18e4bc511f3a5f7622bf", "5cd4a7d0da88584202738e80727c97a26a0c127c"],
+ [0, "131a46ef9295c0b758a8ebcdadf4daf8835727011ad74bdfedb7cf63e0533ab00b5d8e9eaa2e2dfd37b26a61429eafac75e75626563561e2fd258dc4386354", "2ac31dafc392f987f9651e352d2ceed6a93cd17b"],
+ [0, "38fdef0b935ff5b7c4829ac4464d7875691b6cdbbb77f5bd444ab3f7073eb944f447affe25ff0324dd7088fa27aa46648b62683c1208d3a6e68d303a848b0c00", "7ed0cc4b1632e258d5523e4cd610e56972148b13"],
+ [0, "3e9adb68be6a7cfbe04fa0a38f66398d5f13e3968de9025e2c2c0f5e9348f8060eac04c18d270ff9720515e933984c423fc4ecb266f98af0c24a7e6ffaea357438", "c9d2086acb73ac21167d4ada0372e666ef755f03"],
+ [0, "13f31c99adac92c7f26cd932169db6cfcff78af291b86a3af01e4d5228057a60fd1e1e191fc12ee569b5a54699d0a5f2861e1664cb451614957e9537cb21312c633d6881d83bdad64f51b1e6561efe6c5412fde7e26427da71ed4f7bd57cf37556dcaebd4d95ccf819eca1a215348f", "4c45cc84adbf739241369de057624bfaa504342f"],
+ [0, "e1763744bd7820171b3ef27d194b5dce4dbf0ea28995979c3a24f97aaba8d06806f78d6af1a0c6f36a691e35d04a4137ee28deb30d256b2d5580b2ea0588f3fad41e4b645514babe5657a00b70fcd883f1d9ff3c1de979eaa6ed08e0fa7ceb6444a0ed409a26d2d4ca9185b51dabaa44", "d8c47c71a062bf583b0f60f1aab90b58306743fe"],
+ [0, "392d835246b8b7df9e4dfb13bf8b39d061096e22efe3cc006f226a241802930259bfa221a2fa0cada80abef651e3004af4aedf2f196f282687877678118c05b96e1346408829f88e305727cd01406fe5e30a1ca1fc0baeadd04054c4a5cdbe20e569ecd958cc87e055c92d01707e321417a99cf2aa0ee5", "81b8b131a25aa2e7b413c76be0b8a5a8ef8b2bd4"],
+ [0, "0a59941f0f6248d528970e0d4f7f067486f0d658e00216fc22858e99b5c97d1896e7d4860ce2a330306961bd36fbbd32bb69880eb739b647ea0ee87027d04738b767420e1bb88c27a7e6abc5f0b85b65193f095691b87d6a336d085aff85bb3f6e1c61ed51258b7ed2f2660c7e1ac61e5c5c4355a155c1bb", "9c9243f3dda2afa93394a5301d00a8d1f156cfef"],
+ [0, "bea111b3d92efc8c75ead3f37878211b487a565b837eb409a96ad2ec0ad622ed85a6578f1863ad3e6d1dbb2224b57ba6937fa97510a2832b28f675551642b33dabda0b67ccc04f9b5114689098ccfc0c31841291ef986966eb6848bbac7e0e26d667083200f3fb42039a9caa8562416af1b991f645263e3984f1850a0fa334", "27ea91250bdb027af68c050b90e56c75eb3cd9d3"],
+ [0, "27a0b7d68901642b6313f800eee48c21eef8a15c59dd10b6762de05339a542912d41441ff4167dded377c179d3f174dd2266f9c44a2f74ca256e2a840b71215a16d49e47362f518f4feb37fe95034c4916f0ce20d845e2bd513a055aeb967be5519406efc4a26c032a484be2324fd75e06f00ff980c67a33b23dc470fdbdd93f", "6b6ec44a0921c56255c39709ebe4eb6f8fb906c7"],
+ [0, "fd164b1b27fea36e2c61866648bafa348d9f350e562764b83f84761084a3cb47c4f63c6c1ed6e415871cc0aaba2a6b7cd32f4fa62603bde8f0d6cf6a0d5641616368c908bac82676696a613df52ce0a6f7106e8c93d1f03360afa5e206cd3f9d937de830d015092688bcebc2a33178e921068591bd6cd27232613d888f423c8bca", "fd587b4527835de6de2747a28c6f20462fe379f6"],
+ [0, "3975342b2653b1eeefa2d13abf192812bc0ca4a64f9974da2eb3692ba049fce011f41b3b0bf5f2afbf35d1838371b7fbe16b2878ac84072e1740be20f9fd0ef9c207bac63c5f9591ab2be34e0a9c01d5da04a59b9a24b2eeca6402267664a7e7cd8e9502132adf3810560bb09d79d9e1bec02e5c6e844323a31d0719f415d2bbf6b422d8bad5f051f92cd421a0c524c4c323ed3bb00ed38073086afe5ef7d72bc0de6cce36a3e657e7e79491c1e9774a49e28783cfa9b9df05906437458b3be9be1641eafa0aa6dd", "7be3138fdf6400ea9fb1419d16a3d312713f862d"],
+ [0, "c0d0cfea92651a5d920b275142ceb89a75bfcafc58f95b49b838df35a2b1b3820b88ac3dd88fb3ada473cee9fe02335c422082f7e9534e206de970c71bb0e82f54ab3364a68d1c047dcce6e6407ff2cd9f90e8f11fc78784941dad27c21cf0e5adddb3e4e21e39347382be63a73de8da60e6788056edb3c9c1e2cc72dfc7ae7ea3a7b77fa594fa716b3c836acf8fff9667ae9bda70ae0e89fadef31701f1bcf9ce458670af7107099dae8f8a63040058bb2189b3f553fde96fb9cae10a6f4e8ea3c761b8be8c68d0e95f1ca108ab744f285235238626b20a31ae5d797e57f8cd21fd61979e9236a3623c3fae53b0ebc6dd5b7a8c6fe5f94a2c80c50f8c356a", "85c732ab558ea9d468435f1bbb9d7efc85650a33"],
+ [0, "31c1fb7e167b34c36fda54fe80e078d55c209aeab1c75a48d32223aee136ab1648ebad4448dd7d42a8d3a8c6b9aad8683d818e0d48b0ccfb6c5023cb7dc3bf6f9b8acaa79cc4627b0d983babfa7d3b382016feedc47f588a272844d9095aa08328b6f3bc2bd84dbc3e448a527597ff2828116b5ea11e8fac2eb1f74e90b33e6408df66c03048377e21fc96bcd8b333576650ef8c2ab95eb09494165317bf5636d4281d248d8bbb085373c7fa2f9cc16e422c9e111bebd654d8f4f15dacc084b127412483e5121b1e2df5b655f0d61d76fa8446ca6ad4d4a377d445fcbb985f7234e86751a5ee31cd165e67f6ddd46d571cc49fe9db65f436f4ecf1eb13d4600e", "004a9403ae34d067bc413c0654b47eec220b92aa"],
+ [1, "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"],
+ [1, "616263", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"],
+ [1, "6162636462636465636465666465666765666768666768696768696a68696a6b696a6b6c6a6b6c6d6b6c6d6e6c6d6e6f6d6e6f706e6f7071", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"],
+ [1, "61626364656667686263646566676869636465666768696a6465666768696a6b65666768696a6b6c666768696a6b6c6d6768696a6b6c6d6e68696a6b6c6d6e6f696a6b6c6d6e6f706a6b6c6d6e6f70716b6c6d6e6f7071726c6d6e6f707172736d6e6f70717273746e6f707172737475", "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"],
+ [1, "6d0d8c3c1ce808451a34469d6624cd700a4fa6d15b876e7557c8bd17cf5600a9c413a505bc854e507d32a411aa9f551f952cf38009c4c6", "cd521b248f7ba3da9d6cc73bc9c6d2e8a03c577093518a4bfbc43d1d3401ecf1"],
+ [1, "7731b02cd0337ae020f31e9bc4a14517f85c5df2538ea53213e69b948123f9d258d83556eef48e977b6681c5524e18e4bc511f3a5f7622bf", "4b292348851062ae121598d26fca16c3a7720e5dd72f863d5943a014e1de4b38"],
+ [1, "131a46ef9295c0b758a8ebcdadf4daf8835727011ad74bdfedb7cf63e0533ab00b5d8e9eaa2e2dfd37b26a61429eafac75e75626563561e2fd258dc4386354", "ad45017da98a41870e502895f506b7b0e321dece0cef5c0234c0d0b04859ecd5"],
+ [1, "38fdef0b935ff5b7c4829ac4464d7875691b6cdbbb77f5bd444ab3f7073eb944f447affe25ff0324dd7088fa27aa46648b62683c1208d3a6e68d303a848b0c00", "a5f6d88d0bff0e0838ebd323396f31f6d173d570306077a48416a266da1024f4"],
+ [1, "3e9adb68be6a7cfbe04fa0a38f66398d5f13e3968de9025e2c2c0f5e9348f8060eac04c18d270ff9720515e933984c423fc4ecb266f98af0c24a7e6ffaea357438", "7b6c6ccaa8fdbdeeb982cdc88c7d0bb722ae02a7653164870b25c8179d3e2f59"],
+ [1, "13f31c99adac92c7f26cd932169db6cfcff78af291b86a3af01e4d5228057a60fd1e1e191fc12ee569b5a54699d0a5f2861e1664cb451614957e9537cb21312c633d6881d83bdad64f51b1e6561efe6c5412fde7e26427da71ed4f7bd57cf37556dcaebd4d95ccf819eca1a215348f", "ef25ca1677776a0e141dd7d08a69c14b9ec3c264de52f6cb13bb878b64ea774f"],
+ [1, "e1763744bd7820171b3ef27d194b5dce4dbf0ea28995979c3a24f97aaba8d06806f78d6af1a0c6f36a691e35d04a4137ee28deb30d256b2d5580b2ea0588f3fad41e4b645514babe5657a00b70fcd883f1d9ff3c1de979eaa6ed08e0fa7ceb6444a0ed409a26d2d4ca9185b51dabaa44", "1ff578a01347102520a62129dfc57880a97ada0daf3a6761a0a38e7f06f29c1d"],
+ [1, "392d835246b8b7df9e4dfb13bf8b39d061096e22efe3cc006f226a241802930259bfa221a2fa0cada80abef651e3004af4aedf2f196f282687877678118c05b96e1346408829f88e305727cd01406fe5e30a1ca1fc0baeadd04054c4a5cdbe20e569ecd958cc87e055c92d01707e321417a99cf2aa0ee5", "628dd6b90dca5602ab123e01e17147eaa9e22b16f82a6a8793d3ad95c55df233"],
+ [1, "0a59941f0f6248d528970e0d4f7f067486f0d658e00216fc22858e99b5c97d1896e7d4860ce2a330306961bd36fbbd32bb69880eb739b647ea0ee87027d04738b767420e1bb88c27a7e6abc5f0b85b65193f095691b87d6a336d085aff85bb3f6e1c61ed51258b7ed2f2660c7e1ac61e5c5c4355a155c1bb", "5ddd4e441c0af889afb7776e08d0f0e6b8cc18c121ee816c146be2141384116f"],
+ [1, "bea111b3d92efc8c75ead3f37878211b487a565b837eb409a96ad2ec0ad622ed85a6578f1863ad3e6d1dbb2224b57ba6937fa97510a2832b28f675551642b33dabda0b67ccc04f9b5114689098ccfc0c31841291ef986966eb6848bbac7e0e26d667083200f3fb42039a9caa8562416af1b991f645263e3984f1850a0fa334", "8ccfa60304794bd9aef2fccbe26a06ba1616170351b1cdab4a45b0ae6e5cbf78"],
+ [1, "27a0b7d68901642b6313f800eee48c21eef8a15c59dd10b6762de05339a542912d41441ff4167dded377c179d3f174dd2266f9c44a2f74ca256e2a840b71215a16d49e47362f518f4feb37fe95034c4916f0ce20d845e2bd513a055aeb967be5519406efc4a26c032a484be2324fd75e06f00ff980c67a33b23dc470fdbdd93f", "75284fd4f70a41c9a89e0b590942bfac17b54fc4021ea41a2f6bef0cac09d644"],
+ [1, "fd164b1b27fea36e2c61866648bafa348d9f350e562764b83f84761084a3cb47c4f63c6c1ed6e415871cc0aaba2a6b7cd32f4fa62603bde8f0d6cf6a0d5641616368c908bac82676696a613df52ce0a6f7106e8c93d1f03360afa5e206cd3f9d937de830d015092688bcebc2a33178e921068591bd6cd27232613d888f423c8bca", "f58ba766306d84a4c124289aed4b06e6c42fda8afe5aa3b0a73529ce3311159f"],
+ [1, "3975342b2653b1eeefa2d13abf192812bc0ca4a64f9974da2eb3692ba049fce011f41b3b0bf5f2afbf35d1838371b7fbe16b2878ac84072e1740be20f9fd0ef9c207bac63c5f9591ab2be34e0a9c01d5da04a59b9a24b2eeca6402267664a7e7cd8e9502132adf3810560bb09d79d9e1bec02e5c6e844323a31d0719f415d2bbf6b422d8bad5f051f92cd421a0c524c4c323ed3bb00ed38073086afe5ef7d72bc0de6cce36a3e657e7e79491c1e9774a49e28783cfa9b9df05906437458b3be9be1641eafa0aa6dd", "7b5ee7140f4426da3849dba94fa129d177ae479928797ae61f72ecfb8922ddf0"],
+ [1, "c0d0cfea92651a5d920b275142ceb89a75bfcafc58f95b49b838df35a2b1b3820b88ac3dd88fb3ada473cee9fe02335c422082f7e9534e206de970c71bb0e82f54ab3364a68d1c047dcce6e6407ff2cd9f90e8f11fc78784941dad27c21cf0e5adddb3e4e21e39347382be63a73de8da60e6788056edb3c9c1e2cc72dfc7ae7ea3a7b77fa594fa716b3c836acf8fff9667ae9bda70ae0e89fadef31701f1bcf9ce458670af7107099dae8f8a63040058bb2189b3f553fde96fb9cae10a6f4e8ea3c761b8be8c68d0e95f1ca108ab744f285235238626b20a31ae5d797e57f8cd21fd61979e9236a3623c3fae53b0ebc6dd5b7a8c6fe5f94a2c80c50f8c356a", "81692a67280a7c614a0243d3657d5088227a72ddeaee5e51c8ce630c275ec193"],
+ [1, "31c1fb7e167b34c36fda54fe80e078d55c209aeab1c75a48d32223aee136ab1648ebad4448dd7d42a8d3a8c6b9aad8683d818e0d48b0ccfb6c5023cb7dc3bf6f9b8acaa79cc4627b0d983babfa7d3b382016feedc47f588a272844d9095aa08328b6f3bc2bd84dbc3e448a527597ff2828116b5ea11e8fac2eb1f74e90b33e6408df66c03048377e21fc96bcd8b333576650ef8c2ab95eb09494165317bf5636d4281d248d8bbb085373c7fa2f9cc16e422c9e111bebd654d8f4f15dacc084b127412483e5121b1e2df5b655f0d61d76fa8446ca6ad4d4a377d445fcbb985f7234e86751a5ee31cd165e67f6ddd46d571cc49fe9db65f436f4ecf1eb13d4600e", "b0134708be68019bddb81d1c4b34bab1cec31e51bc2ec2f247c5aa4c4a6d086d"],
+ [2, "", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"],
+ [2, "616263", "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"],
+ [2, "6162636462636465636465666465666765666768666768696768696a68696a6b696a6b6c6a6b6c6d6b6c6d6e6c6d6e6f6d6e6f706e6f7071", "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445"],
+ [2, "61626364656667686263646566676869636465666768696a6465666768696a6b65666768696a6b6c666768696a6b6c6d6768696a6b6c6d6e68696a6b6c6d6e6f696a6b6c6d6e6f706a6b6c6d6e6f70716b6c6d6e6f7071726c6d6e6f707172736d6e6f70717273746e6f707172737475", "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"],
+ [2, "6d0d8c3c1ce808451a34469d6624cd700a4fa6d15b876e7557c8bd17cf5600a9c413a505bc854e507d32a411aa9f551f952cf38009c4c6", "34a13900da1e7144741d0435dc36c9493417a6796ce6d2157b6a7d5d90f58facfd981457061e16480a7ce547e61aa3cc6a35d33eb450237dcec7b6d1b371dfdb"],
+ [2, "7731b02cd0337ae020f31e9bc4a14517f85c5df2538ea53213e69b948123f9d258d83556eef48e977b6681c5524e18e4bc511f3a5f7622bf", "2c25fde731494bd45e9cb0f929616c1d8d6ba93de01067adacd0df6146eff9e809562bdf7662990a851a148a84377936bf0689debd46871e86228524ea74374b"],
+ [2, "131a46ef9295c0b758a8ebcdadf4daf8835727011ad74bdfedb7cf63e0533ab00b5d8e9eaa2e2dfd37b26a61429eafac75e75626563561e2fd258dc4386354", "50ecb1de97633d82a6247221f277b4f5c3dc65e58a72d64ff0cddc2b8ecc2fd18037ad4dbc0401db0b2db54eecb45f55c993dd697a3621b13f2f64cf249956ea"],
+ [2, "38fdef0b935ff5b7c4829ac4464d7875691b6cdbbb77f5bd444ab3f7073eb944f447affe25ff0324dd7088fa27aa46648b62683c1208d3a6e68d303a848b0c00", "0fa45233a7a3c90f4a73df2da9223f067c1ed78b6345d43449da482c2bf23529359d9dcdf036b40a945466ad15291ae0f6094e9c97e06be3fc5f8278d7186087"],
+ [2, "3e9adb68be6a7cfbe04fa0a38f66398d5f13e3968de9025e2c2c0f5e9348f8060eac04c18d270ff9720515e933984c423fc4ecb266f98af0c24a7e6ffaea357438", "2578a20981b007efb3d29bc4b86acdb09b908ae02bec6a5f4cd7b04f9a2ab9718fcea73827e3a54f1d73ab0d7f192d677c415ad94c82b71828fcd364a0baa0f7"],
+ [2, "13f31c99adac92c7f26cd932169db6cfcff78af291b86a3af01e4d5228057a60fd1e1e191fc12ee569b5a54699d0a5f2861e1664cb451614957e9537cb21312c633d6881d83bdad64f51b1e6561efe6c5412fde7e26427da71ed4f7bd57cf37556dcaebd4d95ccf819eca1a215348f", "c37c880d8075821ddf8e2e983ba92817ab2168a97fe78b60ea6bb5c4213bb643a2bc7f68f8c122f173b5d1fe6ce5ad2c7362466eca58302a0ddb6ff544fe120a"],
+ [2, "e1763744bd7820171b3ef27d194b5dce4dbf0ea28995979c3a24f97aaba8d06806f78d6af1a0c6f36a691e35d04a4137ee28deb30d256b2d5580b2ea0588f3fad41e4b645514babe5657a00b70fcd883f1d9ff3c1de979eaa6ed08e0fa7ceb6444a0ed409a26d2d4ca9185b51dabaa44", "a2e1380ce72e1a763496614d542975162cdb3eb170b031656d1e4fc94337e6dd01b8816335e54b4c3bff01b78ddd8f17f9c9a1921df9dd7a074e291c4d244ca9"],
+ [2, "392d835246b8b7df9e4dfb13bf8b39d061096e22efe3cc006f226a241802930259bfa221a2fa0cada80abef651e3004af4aedf2f196f282687877678118c05b96e1346408829f88e305727cd01406fe5e30a1ca1fc0baeadd04054c4a5cdbe20e569ecd958cc87e055c92d01707e321417a99cf2aa0ee5", "ac6299ac16f071485693509e1808f2ee2280ac300bd763e22304b2ab7910a5eb876a6f1c2715f8bfd6fd4aeab6ff3a48346042db99d6424d2a5139e25d55159b"],
+ [2, "0a59941f0f6248d528970e0d4f7f067486f0d658e00216fc22858e99b5c97d1896e7d4860ce2a330306961bd36fbbd32bb69880eb739b647ea0ee87027d04738b767420e1bb88c27a7e6abc5f0b85b65193f095691b87d6a336d085aff85bb3f6e1c61ed51258b7ed2f2660c7e1ac61e5c5c4355a155c1bb", "e0a3df4be63b3ffe49b4dbc8557ea1ee7c9021e17786f511850d58694cad26011ad8da47d715837d6ea488d7047a827ebc0f1549ff986bfc06571c6ef5a74a6f"],
+ [2, "bea111b3d92efc8c75ead3f37878211b487a565b837eb409a96ad2ec0ad622ed85a6578f1863ad3e6d1dbb2224b57ba6937fa97510a2832b28f675551642b33dabda0b67ccc04f9b5114689098ccfc0c31841291ef986966eb6848bbac7e0e26d667083200f3fb42039a9caa8562416af1b991f645263e3984f1850a0fa334", "4112bbe7a623cf3e449faa73dbc29383330b53891260a103d29cfd79ee799da5534c61be905e9a0f915c5a98a3e5c3c7be08f007d657f6c494682d845847d68c"],
+ [2, "27a0b7d68901642b6313f800eee48c21eef8a15c59dd10b6762de05339a542912d41441ff4167dded377c179d3f174dd2266f9c44a2f74ca256e2a840b71215a16d49e47362f518f4feb37fe95034c4916f0ce20d845e2bd513a055aeb967be5519406efc4a26c032a484be2324fd75e06f00ff980c67a33b23dc470fdbdd93f", "160068376630101582bb84b7020606c95c671784d922157047351e942261c668c83e7758fd7ae72144af304371c5610b283bbbe94d9c5042df0c0e635d417a4e"],
+ [2, "fd164b1b27fea36e2c61866648bafa348d9f350e562764b83f84761084a3cb47c4f63c6c1ed6e415871cc0aaba2a6b7cd32f4fa62603bde8f0d6cf6a0d5641616368c908bac82676696a613df52ce0a6f7106e8c93d1f03360afa5e206cd3f9d937de830d015092688bcebc2a33178e921068591bd6cd27232613d888f423c8bca", "fda0bb148c3a87ca00656afefbbda126a1cdcbf8c16d0cb02183fb8b08566f76931087976ce8328407b9eb1ef11b7ddc6f73bbff6f04e55af1fe157d8f617fd6"],
+ [2, "3975342b2653b1eeefa2d13abf192812bc0ca4a64f9974da2eb3692ba049fce011f41b3b0bf5f2afbf35d1838371b7fbe16b2878ac84072e1740be20f9fd0ef9c207bac63c5f9591ab2be34e0a9c01d5da04a59b9a24b2eeca6402267664a7e7cd8e9502132adf3810560bb09d79d9e1bec02e5c6e844323a31d0719f415d2bbf6b422d8bad5f051f92cd421a0c524c4c323ed3bb00ed38073086afe5ef7d72bc0de6cce36a3e657e7e79491c1e9774a49e28783cfa9b9df05906437458b3be9be1641eafa0aa6dd", "506d260b0036564100a4854ba8fcdfbc596ea21d331f4bd64bb5981aae2e8245464e550148b6b4851fa9a03aeb29950de4b7b1bd5df714bdc76629e8579d1a5e"],
+ [2, "c0d0cfea92651a5d920b275142ceb89a75bfcafc58f95b49b838df35a2b1b3820b88ac3dd88fb3ada473cee9fe02335c422082f7e9534e206de970c71bb0e82f54ab3364a68d1c047dcce6e6407ff2cd9f90e8f11fc78784941dad27c21cf0e5adddb3e4e21e39347382be63a73de8da60e6788056edb3c9c1e2cc72dfc7ae7ea3a7b77fa594fa716b3c836acf8fff9667ae9bda70ae0e89fadef31701f1bcf9ce458670af7107099dae8f8a63040058bb2189b3f553fde96fb9cae10a6f4e8ea3c761b8be8c68d0e95f1ca108ab744f285235238626b20a31ae5d797e57f8cd21fd61979e9236a3623c3fae53b0ebc6dd5b7a8c6fe5f94a2c80c50f8c356a", "cc3f30b6e1c87c491abad65e1bb85fcc039d19001f6e5b4f3c0ffa6332cc8f0735e466915dae91b9801a2a9c323c5d609dfaa7283236e9552f121390a1c31d90"],
+ [2, "31c1fb7e167b34c36fda54fe80e078d55c209aeab1c75a48d32223aee136ab1648ebad4448dd7d42a8d3a8c6b9aad8683d818e0d48b0ccfb6c5023cb7dc3bf6f9b8acaa79cc4627b0d983babfa7d3b382016feedc47f588a272844d9095aa08328b6f3bc2bd84dbc3e448a527597ff2828116b5ea11e8fac2eb1f74e90b33e6408df66c03048377e21fc96bcd8b333576650ef8c2ab95eb09494165317bf5636d4281d248d8bbb085373c7fa2f9cc16e422c9e111bebd654d8f4f15dacc084b127412483e5121b1e2df5b655f0d61d76fa8446ca6ad4d4a377d445fcbb985f7234e86751a5ee31cd165e67f6ddd46d571cc49fe9db65f436f4ecf1eb13d4600e", "70a3d62f90104a4b3c041eb4c80bb215201175ce1f9f52a6e012c8e88b61745967f929ae8775339321ac982f29ff4a5afbf83792180fc7b668762908effd87f2"]
+ ];
+
+ // [alg, key hex, data hex, mac hex] (RFC 2202, RFC 4231)
+ const HMAC = [
+ [0, "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "4869205468657265", "b617318655057264e28bc0b6fb378c8ef146be00"],
+ [0, "4a656665", "7768617420646f2079612077616e7420666f72206e6f7468696e673f", "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"],
+ [0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "125d7342b9ac11cd91a39af48aa17b4f63f175d3"],
+ [0, "0102030405060708090a0b0c0d0e0f10111213141516171819", "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", "4c9007f4026250c6bc8414f9bf50c86c2d7235da"],
+ [0, "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "546573742057697468205472756e636174696f6e", "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"],
+ [0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374", "aa4ae5e15272d00e95705637ce8a3b55ed402112"],
+ [0, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b657920616e64204c6172676572205468616e204f6e6520426c6f636b2d53697a652044617461", "e8e99d0f45237d786d6bbaa7965c7808bbff1a91"],
+ [1, "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "4869205468657265", "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"],
+ [1, "4a656665", "7768617420646f2079612077616e7420666f72206e6f7468696e673f", "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"],
+ [1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"],
+ [1, "0102030405060708090a0b0c0d0e0f10111213141516171819", "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"],
+ [1, "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "546573742057697468205472756e636174696f6e", "a3b6167473100ee06e0c796c2955552bfa6f7c0a6a8aef8b93f860aab0cd20c5"],
+ [1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374", "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"],
+ [1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e", "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"],
+ [2, "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "4869205468657265", "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"],
+ [2, "4a656665", "7768617420646f2079612077616e7420666f72206e6f7468696e673f", "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"],
+ [2, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"],
+ [2, "0102030405060708090a0b0c0d0e0f10111213141516171819", "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"],
+ [2, "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "546573742057697468205472756e636174696f6e", "415fad6271580a531d4179bc891d87a650188707922a4fbb36663a1eb16da008711c5b50ddd0fc235084eb9d3364a1454fb2ef67cd1d29fe6773068ea266e96b"],
+ [2, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374", "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"],
+ [2, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e", "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"]
+ ];
+
+ // [password hex, salt hex, iterations, length, key hex] (RFC 7914 + seeded)
+ const PBKDF2 = [
+ ["706173737764", "73616c74", 1, 64, "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783"],
+ ["70617373776f7264", "73616c74", 2, 32, "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"],
+ ["70617373776f7264", "73616c74", 100, 32, "07e6997180cf7f12904f04100d405d34888fdf62af6d506a0ecc23b196fe99d8"],
+ ["313233343536", "00000000000000000000000000000000", 1000, 32, "c4b2f911865b1a55abf7deb663563bef104af632bfc246b91581ea0eac11630f"],
+ ["70617373776f726450415353574f524470617373776f7264", "73616c7453414c5473616c7453414c5473616c7453414c5473616c7453414c5473616c74", 300, 40, "d4de84cdb20a7916ca23be9303f66df58d1b2407247b2212263e0589701d86a95fc04e7e893c4d93"]
+ ];
+
+ // [ikm hex, salt hex, info hex, length, okm hex] (RFC 5869 A.1-A.3)
+ const HKDF = [
+ ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "000102030405060708090a0b0c", "f0f1f2f3f4f5f6f7f8f9", 42, "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"],
+ ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f", "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf", "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", 82, "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87"],
+ ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "", 42, "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"]
+ ];
+
+ // [key hex, iv hex, plaintext hex, ciphertext hex] (SP 800-38A F.2.1, F.2.5)
+ const AES_CBC = [
+ ["2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090a0b0c0d0e0f", "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b273bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7"],
+ ["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "000102030405060708090a0b0c0d0e0f", "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", "f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b"]
+ ];
+
+ // [bytes hex, encoded] (RFC 4648 section 10 + seeded)
+ const BASE32 = [
+ ["", ""],
+ ["66", "MY======"],
+ ["666f", "MZXQ===="],
+ ["666f6f", "MZXW6==="],
+ ["666f6f62", "MZXW6YQ="],
+ ["666f6f6261", "MZXW6YTB"],
+ ["666f6f626172", "MZXW6YTBOI======"],
+ ["a1634835dd01edefddaa", "UFRUQNO5AHW67XNK"],
+ ["4747f06e580fb0c74d252628124e67a2", "I5D7A3SYB6YMOTJFEYUBETTHUI======"],
+ ["ebe276b504dbc13cffbe351846f00ded08164c5c", "5PRHNNIE3PATZ756GUMEN4AN5UEBMTC4"],
+ ["fbcaebfc120620a3b9f6f1b5cbfa090b7d573ee9836db4b8fff9b773d81a94e4", "7PFOX7ASAYQKHOPW6G24X6QJBN6VOPXJQNW3JOH77G3XHWA2STSA===="],
+ ["a1a74ca6ac9f7d8479009a6380b70af193bfb1a470cbfc7fafe02481559379905890316f66ef402e12a493513c61c0df667e8264f1573a537a293323329ba99f", "UGTUZJVMT56YI6IATJRYBNYK6GJ37MNEODF7Y75P4ASICVMTPGIFREBRN5TO6QBOCKSJGUJ4MHAN6ZT6QJSPCVZ2KN5CSMZDGKN2THY="]
+ ];
+
+ // [alg, secret hex, time or counter, period (0 = HOTP), digits, code]
+ const OTP = [
+ [0, "3132333435363738393031323334353637383930", 0, 0, 6, "755224"],
+ [0, "3132333435363738393031323334353637383930", 1, 0, 6, "287082"],
+ [0, "3132333435363738393031323334353637383930", 2, 0, 6, "359152"],
+ [0, "3132333435363738393031323334353637383930", 3, 0, 6, "969429"],
+ [0, "3132333435363738393031323334353637383930", 4, 0, 6, "338314"],
+ [0, "3132333435363738393031323334353637383930", 5, 0, 6, "254676"],
+ [0, "3132333435363738393031323334353637383930", 6, 0, 6, "287922"],
+ [0, "3132333435363738393031323334353637383930", 7, 0, 6, "162583"],
+ [0, "3132333435363738393031323334353637383930", 8, 0, 6, "399871"],
+ [0, "3132333435363738393031323334353637383930", 9, 0, 6, "520489"],
+ [0, "3132333435363738393031323334353637383930", 59, 30, 8, "94287082"],
+ [0, "3132333435363738393031323334353637383930", 1111111109, 30, 8, "07081804"],
+ [0, "3132333435363738393031323334353637383930", 1111111111, 30, 8, "14050471"],
+ [0, "3132333435363738393031323334353637383930", 1234567890, 30, 8, "89005924"],
+ [0, "3132333435363738393031323334353637383930", 2000000000, 30, 8, "69279037"],
+ [0, "3132333435363738393031323334353637383930", 20000000000l, 30, 8, "65353130"],
+ [1, "3132333435363738393031323334353637383930313233343536373839303132", 59, 30, 8, "46119246"],
+ [1, "3132333435363738393031323334353637383930313233343536373839303132", 1111111109, 30, 8, "68084774"],
+ [1, "3132333435363738393031323334353637383930313233343536373839303132", 1111111111, 30, 8, "67062674"],
+ [1, "3132333435363738393031323334353637383930313233343536373839303132", 1234567890, 30, 8, "91819424"],
+ [1, "3132333435363738393031323334353637383930313233343536373839303132", 2000000000, 30, 8, "90698825"],
+ [1, "3132333435363738393031323334353637383930313233343536373839303132", 20000000000l, 30, 8, "77737706"],
+ [2, "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334", 59, 30, 8, "90693936"],
+ [2, "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334", 1111111109, 30, 8, "25091201"],
+ [2, "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334", 1111111111, 30, 8, "99943326"],
+ [2, "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334", 1234567890, 30, 8, "93441116"],
+ [2, "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334", 2000000000, 30, 8, "38618901"],
+ [2, "31323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334", 20000000000l, 30, 8, "47863826"],
+ [0, "a820ea3b711c8b835f197a403826716c", 29, 37, 6, "852264"],
+ [0, "a820ea3b711c8b835f197a403826716c", 2083839650, 37, 6, "744326"],
+ [0, "a820ea3b711c8b835f197a403826716c", 29, 15, 7, "6095944"],
+ [0, "a820ea3b711c8b835f197a403826716c", 2959118287l, 15, 7, "1680553"],
+ [0, "a820ea3b711c8b835f197a403826716c", 0, 15, 8, "40852264"],
+ [0, "a820ea3b711c8b835f197a403826716c", 138345305, 15, 8, "20434868"],
+ [0, "3a96311d28eb74f2fbdc650ffe66650b443c9ccf661304bfbfe4683b27764d6028b76fd1cc5ae9e46083c2f0a3270b767915a4448ac417ffcad0c177b236e472", 30, 30, 6, "454281"],
+ [0, "3a96311d28eb74f2fbdc650ffe66650b443c9ccf661304bfbfe4683b27764d6028b76fd1cc5ae9e46083c2f0a3270b767915a4448ac417ffcad0c177b236e472", 441580728, 30, 6, "654983"],
+ [0, "3a96311d28eb74f2fbdc650ffe66650b443c9ccf661304bfbfe4683b27764d6028b76fd1cc5ae9e46083c2f0a3270b767915a4448ac417ffcad0c177b236e472", 30, 30, 7, "8454281"],
+ [0, "3a96311d28eb74f2fbdc650ffe66650b443c9ccf661304bfbfe4683b27764d6028b76fd1cc5ae9e46083c2f0a3270b767915a4448ac417ffcad0c177b236e472", 3797986681l, 30, 7, "4647824"],
+ [0, "3a96311d28eb74f2fbdc650ffe66650b443c9ccf661304bfbfe4683b27764d6028b76fd1cc5ae9e46083c2f0a3270b767915a4448ac417ffcad0c177b236e472", 0, 37, 8, "31451272"],
+ [0, "3a96311d28eb74f2fbdc650ffe66650b443c9ccf661304bfbfe4683b27764d6028b76fd1cc5ae9e46083c2f0a3270b767915a4448ac417ffcad0c177b236e472", 1703416463, 37, 8, "41455495"],
+ [0, "80c38fc3aec4671c5aa29614a41ae9676b4077b6", 0, 90, 6, "026259"],
+ [0, "80c38fc3aec4671c5aa29614a41ae9676b4077b6", 1106401343, 90, 6, "175499"],
+ [0, "80c38fc3aec4671c5aa29614a41ae9676b4077b6", 2147483647, 60, 7, "6973904"],
+ [0, "80c38fc3aec4671c5aa29614a41ae9676b4077b6", 1927359805, 60, 7, "8420158"],
+ [0, "80c38fc3aec4671c5aa29614a41ae9676b4077b6", 0, 60, 8, "01026259"],
+ [0, "80c38fc3aec4671c5aa29614a41ae9676b4077b6", 955574268, 60, 8, "55067388"],
+ [0, "1c112c028b824331c2cacd584e6ec75e44440ae435f9143eb95838b79e7638bd32e1dd08f9635480324894a51d525e0231c51453894de3305f77cd370abc6d06", 2147483647, 15, 6, "335148"],
+ [0, "1c112c028b824331c2cacd584e6ec75e44440ae435f9143eb95838b79e7638bd32e1dd08f9635480324894a51d525e0231c51453894de3305f77cd370abc6d06", 1882823122, 15, 6, "176153"],
+ [0, "1c112c028b824331c2cacd584e6ec75e44440ae435f9143eb95838b79e7638bd32e1dd08f9635480324894a51d525e0231c51453894de3305f77cd370abc6d06", 4102444800l, 37, 7, "8239134"],
+ [0, "1c112c028b824331c2cacd584e6ec75e44440ae435f9143eb95838b79e7638bd32e1dd08f9635480324894a51d525e0231c51453894de3305f77cd370abc6d06", 46566660, 37, 7, "0305254"],
+ [0, "1c112c028b824331c2cacd584e6ec75e44440ae435f9143eb95838b79e7638bd32e1dd08f9635480324894a51d525e0231c51453894de3305f77cd370abc6d06", 4102444800l, 37, 8, "38239134"],
+ [0, "1c112c028b824331c2cacd584e6ec75e44440ae435f9143eb95838b79e7638bd32e1dd08f9635480324894a51d525e0231c51453894de3305f77cd370abc6d06", 681863666, 37, 8, "99992227"],
+ [1, "519314e78dc41fc39c2f", 2147483647, 90, 6, "679650"],
+ [1, "519314e78dc41fc39c2f", 1155113653, 90, 6, "848006"],
+ [1, "519314e78dc41fc39c2f", 0, 37, 7, "6382936"],
+ [1, "519314e78dc41fc39c2f", 4134525533l, 37, 7, "4817746"],
+ [1, "519314e78dc41fc39c2f", 4294967295l, 30, 8, "74552574"],
+ [1, "519314e78dc41fc39c2f", 3560961279l, 30, 8, "41338585"],
+ [1, "4ce06b24ffc273f2d7924adf5f77e4a7ba98781409d3c442c9ef5f89b701ff6a", 30, 30, 6, "452063"],
+ [1, "4ce06b24ffc273f2d7924adf5f77e4a7ba98781409d3c442c9ef5f89b701ff6a", 1051421492, 30, 6, "661571"],
+ [1, "4ce06b24ffc273f2d7924adf5f77e4a7ba98781409d3c442c9ef5f89b701ff6a", 29, 15, 7, "5452063"],
+ [1, "4ce06b24ffc273f2d7924adf5f77e4a7ba98781409d3c442c9ef5f89b701ff6a", 3486007904l, 15, 7, "2258474"],
+ [1, "4ce06b24ffc273f2d7924adf5f77e4a7ba98781409d3c442c9ef5f89b701ff6a", 0, 60, 8, "92054694"],
+ [1, "4ce06b24ffc273f2d7924adf5f77e4a7ba98781409d3c442c9ef5f89b701ff6a", 794956808, 60, 8, "40048967"],
+ [1, "a470c80d0501397b0a254487cea5bf7ce09c5063", 31, 15, 6, "080620"],
+ [1, "a470c80d0501397b0a254487cea5bf7ce09c5063", 2513780014l, 15, 6, "548759"],
+ [1, "a470c80d0501397b0a254487cea5bf7ce09c5063", 4102444800l, 37, 7, "2460729"],
+ [1, "a470c80d0501397b0a254487cea5bf7ce09c5063", 1610930926, 37, 7, "1892807"],
+ [1, "a470c80d0501397b0a254487cea5bf7ce09c5063", 2147483647, 37, 8, "05015463"],
+ [1, "a470c80d0501397b0a254487cea5bf7ce09c5063", 922725035, 37, 8, "16504598"],
+ [1, "4712530205347c29d8ac0aba1125b453a7e34ee3282e110771700f6aed8beda4a414a330377ecdf188e9c446f5bf04379a3b0955733c4e65eabb95e99665f8e7", 4294967295l, 15, 6, "175226"],
+ [1, "4712530205347c29d8ac0aba1125b453a7e34ee3282e110771700f6aed8beda4a414a330377ecdf188e9c446f5bf04379a3b0955733c4e65eabb95e99665f8e7", 840189972, 15, 6, "080722"],
+ [1, "4712530205347c29d8ac0aba1125b453a7e34ee3282e110771700f6aed8beda4a414a330377ecdf188e9c446f5bf04379a3b0955733c4e65eabb95e99665f8e7", 29, 15, 7, "5709807"],
+ [1, "4712530205347c29d8ac0aba1125b453a7e34ee3282e110771700f6aed8beda4a414a330377ecdf188e9c446f5bf04379a3b0955733c4e65eabb95e99665f8e7", 1926118406, 15, 7, "7795831"],
+ [1, "4712530205347c29d8ac0aba1125b453a7e34ee3282e110771700f6aed8beda4a414a330377ecdf188e9c446f5bf04379a3b0955733c4e65eabb95e99665f8e7", 4102444800l, 37, 8, "22291178"],
+ [1, "4712530205347c29d8ac0aba1125b453a7e34ee3282e110771700f6aed8beda4a414a330377ecdf188e9c446f5bf04379a3b0955733c4e65eabb95e99665f8e7", 2909254729l, 37, 8, "43207193"],
+ [2, "1d2a42826bd81decc2f1", 4102444800l, 30, 6, "392493"],
+ [2, "1d2a42826bd81decc2f1", 1095680325, 30, 6, "344980"],
+ [2, "1d2a42826bd81decc2f1", 2147483648l, 90, 7, "7697857"],
+ [2, "1d2a42826bd81decc2f1", 702510575, 90, 7, "2542324"],
+ [2, "1d2a42826bd81decc2f1", 2147483648l, 30, 8, "94001313"],
+ [2, "1d2a42826bd81decc2f1", 895022827, 30, 8, "05212166"],
+ [2, "2c6f1a4e8c1fb8bf07d7", 2147483648l, 37, 6, "532042"],
+ [2, "2c6f1a4e8c1fb8bf07d7", 1957071305, 37, 6, "474640"],
+ [2, "2c6f1a4e8c1fb8bf07d7", 29, 15, 7, "8726799"],
+ [2, "2c6f1a4e8c1fb8bf07d7", 2962371382l, 15, 7, "0945077"],
+ [2, "2c6f1a4e8c1fb8bf07d7", 31, 37, 8, "56225279"],
+ [2, "2c6f1a4e8c1fb8bf07d7", 3377259681l, 37, 8, "34126316"],
+ [2, "f212e4e1faa7be995420bb3a059b1d6ad8d5dd88", 30, 90, 6, "463723"],
+ [2, "f212e4e1faa7be995420bb3a059b1d6ad8d5dd88", 642771092, 90, 6, "837542"],
+ [2, "f212e4e1faa7be995420bb3a059b1d6ad8d5dd88", 4294967295l, 90, 7, "0873390"],
+ [2, "f212e4e1faa7be995420bb3a059b1d6ad8d5dd88", 2418690488l, 90, 7, "7218343"],
+ [2, "f212e4e1faa7be995420bb3a059b1d6ad8d5dd88", 2147483648l, 15, 8, "63029657"],
+ [2, "f212e4e1faa7be995420bb3a059b1d6ad8d5dd88", 4294362063l, 15, 8, "57908285"],
+ [2, "5aa908958300f5fc2100", 2147483647, 37, 6, "038840"],
+ [2, "5aa908958300f5fc2100", 3153426934l, 37, 6, "577891"],
+ [2, "5aa908958300f5fc2100", 0, 37, 7, "3308928"],
+ [2, "5aa908958300f5fc2100", 3886732662l, 37, 7, "9401464"],
+ [2, "5aa908958300f5fc2100", 30, 90, 8, "23308928"],
+ [2, "5aa908958300f5fc2100", 3103524056l, 90, 8, "59171280"]
+ ];
+
+ // [secret hex, unix time, code]
+ const STEAM = [
+ ["ad14ca150424f2feeba3f5e6ff708c9030b12d5a", 3186721893l, "N5QR5"],
+ ["31e1ceef26a37ee6640c65942ca8dcb14b55805f", 3883557096l, "YBTGV"],
+ ["48e12ed5e83e5cf329074cba3f2a9d4fed05653c", 2707715549l, "7F4TV"],
+ ["157f497ef5067b2a696c7016aaca5af7a7fe5896", 221299866, "33J34"],
+ ["f1617555c89e90cd41fd73c89c67c2d1467bd31e", 241887401, "Y97TC"],
+ ["ad56d2d648e76e622f0f0e143a3ed5d1e6266dc2", 2177274001l, "D7MVH"],
+ ["8bd218cdfc5589f1d5fa819686a1e8c136946d81", 1719254088, "P8KW2"],
+ ["3fae4a911e0d3e5591e441d6500768cc91cf1cde", 2517674608l, "KTHQH"],
+ ["b0d1821bb80147d6d5551bbb33a2b69a1bc55617", 3710479172l, "GVTT5"],
+ ["ce9808f9ff17018a2b67d3f6de83c0574967c124", 2476233033l, "3C7K7"],
+ ["50bb20b7cdc8ce84436f136163363740a9070d61", 774300921, "7DD2N"],
+ ["90ff1bdd0cb8e7f8239f51d74485672b2e892301", 1104091982, "JVJ9N"]
+ ];
+}
diff --git a/test/golden/fr255s.png b/test/golden/fr255s.png
index 8619268..fa5b0e5 100644
--- a/test/golden/fr255s.png
+++ b/test/golden/fr255s.png
Binary files differ
diff --git a/test/golden/fr70.png b/test/golden/fr70.png
index a0e8fb7..f629cf6 100644
--- a/test/golden/fr70.png
+++ b/test/golden/fr70.png
Binary files differ
diff --git a/test/golden/fr965.png b/test/golden/fr965.png
index 81423f7..5b49651 100644
--- a/test/golden/fr965.png
+++ b/test/golden/fr965.png
Binary files differ
diff --git a/test/golden/venusq2.png b/test/golden/venusq2.png
index 18a2614..028e185 100644
--- a/test/golden/venusq2.png
+++ b/test/golden/venusq2.png
Binary files differ
diff --git a/test/otp/OtpTest.mc b/test/otp/OtpTest.mc
new file mode 100644
index 0000000..598dd89
--- /dev/null
+++ b/test/otp/OtpTest.mc
@@ -0,0 +1,118 @@
+// Author: cirakg & claude ai
+// Summary: Base32, HOTP, TOTP and Steam tests (RFC 4648, RFC 4226, RFC 6238, seeded vectors incl. 2038 and 2100).
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+function base32MatchesVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.BASE32.size(); i++) {
+ var r = TestSupport.row(TestVectors.BASE32, i);
+ var data = TestSupport.bytes(r, 0);
+ var text = TestSupport.str(r, 1);
+ ok = TestSupport.sameString(logger, "encode " + i, Base32.encode(data), text) && ok;
+ var decoded = Base32.decode(text);
+ if (!(decoded instanceof Lang.ByteArray) || !Bytes.equal(decoded, data)) {
+ logger.error("decode " + i + " failed");
+ ok = false;
+ }
+ }
+ return ok;
+}
+
+// Lower case, spaces, tabs, hyphens and missing padding are all accepted.
+(:test)
+function base32IsLenient(logger as Test.Logger) as Boolean {
+ var expected = Bytes.fromUtf8("foobar");
+ var inputs = ["mzxw6ytboi", "MZXW 6YTB OI", "mzxw-6ytb-oi======", "MZXW6YTB\tOI"];
+ for (var i = 0; i < inputs.size(); i++) {
+ var decoded = Base32.decode(inputs[i] as String);
+ if (!(decoded instanceof Lang.ByteArray) || !Bytes.equal(decoded, expected)) {
+ logger.error("lenient input " + i + " rejected");
+ return false;
+ }
+ }
+ return true;
+}
+
+// Errors name the right position (1-based) or say it's a length problem (0).
+(:test)
+function base32ReportsErrors(logger as Test.Logger) as Boolean {
+ var cases = [["MZX1W6", 4], ["MZ X0", 5], ["MZ=X", 4], ["M", 0], ["MZX", 0], ["MY=", 0], ["MZÄW", 3]];
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i] as Array;
+ var input = c[0] as String;
+ var result = Base32.decode(input);
+ if (!(result instanceof Base32.DecodeError) || result.position != (c[1] as Number)) {
+ logger.error("case " + i + " (" + input + ") not reported as expected");
+ return false;
+ }
+ logger.debug(input + " -> " + result.message);
+ }
+ return true;
+}
+
+// RFC 4226, RFC 6238 and seeded HOTP/TOTP vectors (period 0 means HOTP).
+(:test)
+function otpMatchesVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.OTP.size(); i++) {
+ var r = TestSupport.row(TestVectors.OTP, i);
+ var alg = TestSupport.alg(r, 0);
+ var secret = TestSupport.bytes(r, 1);
+ var time = TestSupport.long(r, 2);
+ var period = TestSupport.num(r, 3);
+ var digits = TestSupport.num(r, 4);
+ var code = period == 0 ? Otp.hotp(alg, secret, time, digits) : Otp.totp(alg, secret, time, period, digits);
+ ok = TestSupport.sameString(logger, "OTP vector " + i, code, TestSupport.str(r, 5)) && ok;
+ }
+ logger.debug(TestVectors.OTP.size() + " OTP vectors");
+ return ok;
+}
+
+(:test)
+function steamMatchesVectors(logger as Test.Logger) as Boolean {
+ var ok = true;
+ for (var i = 0; i < TestVectors.STEAM.size(); i++) {
+ var r = TestSupport.row(TestVectors.STEAM, i);
+ var code = Otp.steam(TestSupport.bytes(r, 0), TestSupport.long(r, 1));
+ ok = TestSupport.sameString(logger, "Steam vector " + i, code, TestSupport.str(r, 2)) && ok;
+ }
+ return ok;
+}
+
+// Period boundaries: the counter changes exactly at k * period.
+(:test)
+function totpPeriodBoundaries(logger as Test.Logger) as Boolean {
+ return Otp.totpCounter(29l, 30) == 0l && Otp.totpCounter(30l, 30) == 1l && Otp.totpCounter(31l, 30) == 1l
+ && Otp.secondsRemaining(29l, 30) == 1 && Otp.secondsRemaining(30l, 30) == 30
+ && Otp.totpCounter(-5l, 30) == 0l && Otp.secondsRemaining(-5l, 30) == 30
+ && Otp.totpCounter(4294967295l, 30) == 143165576l;
+}
+
+(:test)
+function otpRejectsBadParameters(logger as Test.Logger) as Boolean {
+ var rejected = 0;
+ try {
+ Otp.hotp(Hashes.SHA1, [1]b, 0l, 5);
+ } catch (e instanceof Lang.InvalidValueException) {
+ rejected++;
+ }
+ try {
+ Otp.hotp(Hashes.SHA1, [1]b, 0l, 9);
+ } catch (e instanceof Lang.InvalidValueException) {
+ rejected++;
+ }
+ try {
+ Otp.totp(Hashes.SHA1, [1]b, 0l, 0, 6);
+ } catch (e instanceof Lang.InvalidValueException) {
+ rejected++;
+ }
+ try {
+ Otp.totp(Hashes.SHA1, [1]b, 0l, 301, 6);
+ } catch (e instanceof Lang.InvalidValueException) {
+ rejected++;
+ }
+ return rejected == 4;
+}
diff --git a/test/util/UtilTest.mc b/test/util/UtilTest.mc
new file mode 100644
index 0000000..ea689a6
--- /dev/null
+++ b/test/util/UtilTest.mc
@@ -0,0 +1,66 @@
+// Author: cirakg & claude ai
+// Summary: Tests for the byte helpers, the 2038-safe clock widening and the job runner.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+function bytesConversions(logger as Test.Logger) as Boolean {
+ return Bytes.toHex(Bytes.fromHex("00FFa5")).equals("00ffa5")
+ && Bytes.equal(Bytes.fromUtf8("é"), [0xc3, 0xa9]b)
+ && Bytes.fromHex("").size() == 0 && Bytes.toHex([]b).equals("")
+ && Bytes.equal(Bytes.concat([1]b, [2, 3]b), [1, 2, 3]b)
+ && Bytes.equal(Bytes.uint64BigEndian(1l), [0, 0, 0, 0, 0, 0, 0, 1]b)
+ && Bytes.equal(Bytes.uint64BigEndian(-1l), [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]b)
+ && Bytes.equal(Bytes.uint64BigEndian(20000000000l / 30), [0, 0, 0, 0, 0x27, 0xbc, 0x86, 0xaa]b)
+ && Bytes.equal(Bytes.uint32BigEndian(0x01020304), [1, 2, 3, 4]b);
+}
+
+(:test)
+function bytesEqualWipeAndXor(logger as Test.Logger) as Boolean {
+ var a = [1, 2, 3]b;
+ var copy = Bytes.copy(a);
+ Bytes.xorInto(copy, [1, 2, 3]b);
+ var wiped = [5, 6]b;
+ Bytes.wipe(wiped);
+ Bytes.wipe(null);
+ return Bytes.equal(a, [1, 2, 3]b) && !Bytes.equal(a, [1, 2]b) && !Bytes.equal(a, [1, 2, 4]b)
+ && Bytes.equal(copy, [0, 0, 0]b) && Bytes.equal(wiped, [0, 0]b);
+}
+
+// After 2038-01-19 the watch's 32-bit clock value is negative; read as
+// unsigned it keeps counting up to 2106.
+(:test)
+function clockWidensTo64Bits(logger as Test.Logger) as Boolean {
+ return Clock.toUnsigned(0x7FFFFFFF) == 2147483647l
+ && Clock.toUnsigned(0x7FFFFFFF + 1) == 2147483648l
+ && Clock.toUnsigned(-1) == 4294967295l
+ && Clock.toUnsigned(0) == 0l
+ && Clock.nowUnix(0) > 1700000000l
+ && Clock.nowUnix(60) - Clock.nowUnix(0) >= 59l;
+}
+
+(:test)
+module JobFixtures {
+ class CountingJob extends Jobs.Job {
+ var done as Number = 0;
+ var calls as Number = 0;
+
+ function initialize() {
+ Job.initialize();
+ }
+
+ function step(budget as Number) as Boolean {
+ calls++;
+ done += budget;
+ return done >= 10;
+ }
+ }
+}
+
+(:test)
+function jobsRunToCompletion(logger as Test.Logger) as Boolean {
+ var job = new JobFixtures.CountingJob();
+ Jobs.runToCompletion(job, 3);
+ return job.calls == 4 && job.done == 12;
+}
diff --git a/tools/gen_vectors.py b/tools/gen_vectors.py
new file mode 100644
index 0000000..fb7053a
--- /dev/null
+++ b/tools/gen_vectors.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python3
+# Author: cirakg & claude ai
+# Summary: Generate test/data/Vectors.mc: official crypto/OTP test vectors, cross-checked, plus seeded extra cases.
+"""Generate the Monkey C test vectors in test/data/Vectors.mc.
+
+Sources, and how each is double-checked before it is written:
+
+* Published vectors (FIPS 180-4 examples, RFC 2202, RFC 4231, RFC 7914 §11,
+ RFC 5869, RFC 4648 §10, RFC 4226 Appendix D, RFC 6238 Appendix B, NIST SP
+ 800-38A F.2.1/F.2.5) are written into this file from the documents. Python's
+ standard library (hashlib, hmac, base64) recomputes each one, and the
+ generator stops if the two disagree. AES isn't in the standard library, so
+ the NIST AES vectors are checked with `openssl enc` instead.
+* Extra cases (random messages around the SHA padding boundaries, random OTP
+ secrets, digit counts, periods, times around 2038, Steam codes) come from a
+ fixed seed and are computed with the standard library only, so they are
+ reproducible and independent of the Monkey C code under test.
+
+Usage: gen_vectors.py [--out test/data/Vectors.mc]
+The output is committed; rerun after changing this file. All data is fake.
+"""
+
+from __future__ import annotations
+
+import argparse
+import base64
+import hashlib
+import hmac
+import random
+import struct
+import subprocess
+from collections.abc import Sequence
+from pathlib import Path
+
+SEED = 20261018
+ALGS = {"SHA1": 0, "SHA256": 1, "SHA512": 2}
+STEAM_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY"
+
+
+def h(data: bytes) -> str:
+ return data.hex()
+
+
+# --------------------------------------------------------------------- hashes
+
+FIPS_MESSAGES = [
+ b"",
+ b"abc",
+ b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+]
+# FIPS 180-4 example digests of "abc", as printed in the standard's examples.
+FIPS_ABC = {
+ "SHA1": "a9993e364706816aba3e25717850c26c9cd0d89d",
+ "SHA256": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
+ "SHA512": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
+ "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
+}
+
+
+def hash_vectors(rng: random.Random) -> list[tuple[int, str, str]]:
+ for alg, expected in FIPS_ABC.items():
+ assert hashlib.new(alg.lower(), b"abc").hexdigest() == expected, alg
+ messages = list(FIPS_MESSAGES)
+ # Lengths around the padding boundaries of 64-byte (SHA-1/256) and
+ # 128-byte (SHA-512) blocks, where implementations usually break.
+ for length in (55, 56, 63, 64, 65, 111, 112, 119, 120, 127, 128, 129, 200, 255, 256):
+ messages.append(bytes(rng.randrange(256) for _ in range(length)))
+ return [(ALGS[a], h(m), hashlib.new(a.lower(), m).hexdigest()) for a in ALGS for m in messages]
+
+
+# ----------------------------------------------------------------------- HMAC
+
+KEY_20 = b"\x0b" * 20
+KEY_AA131 = b"\xaa" * 131
+RFC4231_CASES = [ # (key, data); case 5's output is truncated to 128 bits in the RFC
+ (KEY_20, b"Hi There"),
+ (b"Jefe", b"what do ya want for nothing?"),
+ (b"\xaa" * 20, b"\xdd" * 50),
+ (bytes(range(1, 26)), b"\xcd" * 50),
+ (b"\x0c" * 20, b"Test With Truncation"),
+ (KEY_AA131, b"Test Using Larger Than Block-Size Key - Hash Key First"),
+ (
+ KEY_AA131,
+ b"This is a test using a larger than block-size key and a larger than block-size data."
+ b" The key needs to be hashed before being used by the HMAC algorithm.",
+ ),
+]
+RFC2202_SHA1_CASES = [
+ (KEY_20, b"Hi There"),
+ (b"Jefe", b"what do ya want for nothing?"),
+ (b"\xaa" * 20, b"\xdd" * 50),
+ (bytes(range(1, 26)), b"\xcd" * 50),
+ (b"\x0c" * 20, b"Test With Truncation"),
+ (b"\xaa" * 80, b"Test Using Larger Than Block-Size Key - Hash Key First"),
+ (b"\xaa" * 80, b"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"),
+]
+# Spot checks copied from the RFCs.
+RFC_HMAC_CHECKS = [
+ ("SHA1", RFC2202_SHA1_CASES[0], "b617318655057264e28bc0b6fb378c8ef146be00"),
+ ("SHA256", RFC4231_CASES[0], "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"),
+ (
+ "SHA512",
+ RFC4231_CASES[0],
+ "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde"
+ "daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
+ ),
+ ("SHA256", RFC4231_CASES[5], "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"),
+]
+
+
+def hmac_vectors() -> list[tuple[int, str, str, str]]:
+ for alg, (key, data), expected in RFC_HMAC_CHECKS:
+ assert hmac.new(key, data, alg.lower()).hexdigest() == expected, alg
+ out = [(ALGS["SHA1"], h(k), h(d), hmac.new(k, d, "sha1").hexdigest()) for k, d in RFC2202_SHA1_CASES]
+ out += [
+ (ALGS[a], h(k), h(d), hmac.new(k, d, a.lower()).hexdigest())
+ for a in ("SHA256", "SHA512")
+ for k, d in RFC4231_CASES
+ ]
+ return out
+
+
+# --------------------------------------------------------------- PBKDF2, HKDF
+
+RFC7914_PBKDF2 = (
+ b"passwd",
+ b"salt",
+ 1,
+ 64,
+ "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc"
+ "49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783",
+)
+
+
+def pbkdf2_vectors() -> list[tuple[str, str, int, int, str]]:
+ p, s, c, n, expected = RFC7914_PBKDF2
+ assert hashlib.pbkdf2_hmac("sha256", p, s, c, n).hex() == expected
+ cases = [
+ (p, s, c, n),
+ (b"password", b"salt", 2, 32),
+ (b"password", b"salt", 100, 32),
+ (b"123456", b"\x00" * 16, 1000, 32),
+ ]
+ cases.append((b"passwordPASSWORDpassword", b"saltSALTsaltSALTsaltSALTsaltSALTsalt", 300, 40))
+ return [(h(p), h(s), c, n, hashlib.pbkdf2_hmac("sha256", p, s, c, n).hex()) for p, s, c, n in cases]
+
+
+def hkdf(ikm: bytes, salt: bytes, info: bytes, length: int) -> bytes:
+ prk = hmac.new(salt or b"\x00" * 32, ikm, "sha256").digest()
+ okm, block = b"", b""
+ for counter in range(1, -(-length // 32) + 1):
+ block = hmac.new(prk, block + info + bytes([counter]), "sha256").digest()
+ okm += block
+ return okm[:length]
+
+
+RFC5869_CASES = [
+ (KEY_20 + b"\x0b" * 2, bytes(range(13)), bytes(range(0xF0, 0xFA)), 42),
+ (bytes(range(0x50)), bytes(range(0x60, 0xB0)), bytes(range(0xB0, 0x100)), 82),
+ (KEY_20 + b"\x0b" * 2, b"", b"", 42),
+]
+RFC5869_OKM = {
+ 0: "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
+ 2: "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8",
+}
+
+
+def hkdf_vectors() -> list[tuple[str, str, str, int, str]]:
+ for index, expected in RFC5869_OKM.items():
+ ikm, salt, info, n = RFC5869_CASES[index]
+ assert hkdf(ikm, salt, info, n).hex() == expected, index
+ return [(h(i), h(s), h(f), n, hkdf(i, s, f, n).hex()) for i, s, f, n in RFC5869_CASES]
+
+
+# ------------------------------------------------------------------------ AES
+
+NIST_IV = "000102030405060708090a0b0c0d0e0f"
+NIST_PT = (
+ "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"
+ "30c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"
+)
+NIST_CBC = [ # SP 800-38A F.2.1 (AES-128) and F.2.5 (AES-256)
+ (
+ "2b7e151628aed2a6abf7158809cf4f3c",
+ "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"
+ "73bed6b8e3c1743b7116e69e222295163ff1caa1681fac09120eca307586e1a7",
+ ),
+ (
+ "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
+ "f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d"
+ "39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b",
+ ),
+]
+
+
+def openssl_cbc(key: str, iv: str, plaintext: str) -> str:
+ cipher = "aes-128-cbc" if len(key) == 32 else "aes-256-cbc"
+ result = subprocess.run(
+ ["openssl", "enc", f"-{cipher}", "-K", key, "-iv", iv, "-nopad"],
+ input=bytes.fromhex(plaintext),
+ capture_output=True,
+ check=True,
+ )
+ return result.stdout.hex()
+
+
+def aes_vectors() -> list[tuple[str, str, str, str]]:
+ for key, ciphertext in NIST_CBC:
+ assert openssl_cbc(key, NIST_IV, NIST_PT) == ciphertext, key
+ return [(key, NIST_IV, NIST_PT, ct) for key, ct in NIST_CBC]
+
+
+# --------------------------------------------------------------------- Base32
+
+RFC4648_BASE32 = [
+ (b"", ""),
+ (b"f", "MY======"),
+ (b"fo", "MZXQ===="),
+ (b"foo", "MZXW6==="),
+ (b"foob", "MZXW6YQ="),
+ (b"fooba", "MZXW6YTB"),
+ (b"foobar", "MZXW6YTBOI======"),
+]
+
+
+def base32_vectors(rng: random.Random) -> list[tuple[str, str]]:
+ cases = list(RFC4648_BASE32)
+ for data, encoded in cases:
+ assert base64.b32encode(data).decode() == encoded
+ for length in (10, 16, 20, 32, 64):
+ data = bytes(rng.randrange(256) for _ in range(length))
+ cases.append((data, base64.b32encode(data).decode()))
+ return [(h(d), e) for d, e in cases]
+
+
+# ------------------------------------------------------------------------ OTP
+
+RFC4226_SECRET = b"12345678901234567890"
+RFC4226_CODES = ["755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", "520489"]
+RFC6238_SEEDS = {
+ "SHA1": b"12345678901234567890",
+ "SHA256": b"12345678901234567890123456789012",
+ "SHA512": b"1234567890" * 6 + b"1234",
+}
+RFC6238_TIMES = [59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000]
+RFC6238_CODES = {
+ "SHA1": ["94287082", "07081804", "14050471", "89005924", "69279037", "65353130"],
+ "SHA256": ["46119246", "68084774", "67062674", "91819424", "90698825", "77737706"],
+ "SHA512": ["90693936", "25091201", "99943326", "93441116", "38618901", "47863826"],
+}
+
+
+def truncated(alg: str, secret: bytes, counter: int) -> int:
+ mac = hmac.new(secret, struct.pack(">Q", counter), alg.lower()).digest()
+ offset = mac[-1] & 0x0F
+ return int.from_bytes(mac[offset : offset + 4], "big") & 0x7FFFFFFF
+
+
+def hotp(alg: str, secret: bytes, counter: int, digits: int) -> str:
+ return str(truncated(alg, secret, counter) % 10**digits).zfill(digits)
+
+
+def steam(secret: bytes, counter: int) -> str:
+ value = truncated("SHA1", secret, counter)
+ chars = []
+ for _ in range(5):
+ chars.append(STEAM_ALPHABET[value % len(STEAM_ALPHABET)])
+ value //= len(STEAM_ALPHABET)
+ return "".join(chars)
+
+
+def otp_vectors(rng: random.Random) -> tuple[list[tuple[int, str, int, int, int, str]], list[tuple[str, int, str]]]:
+ """(alg, secret hex, unix time, period, digits, code) and Steam (secret hex, unix time, code).
+
+ HOTP cases use period 0, meaning "time is the counter".
+ """
+ assert [hotp("SHA1", RFC4226_SECRET, c, 6) for c in range(10)] == RFC4226_CODES
+ for alg, codes in RFC6238_CODES.items():
+ assert [hotp(alg, RFC6238_SEEDS[alg], t // 30, 8) for t in RFC6238_TIMES] == codes, alg
+
+ cases = [(ALGS["SHA1"], h(RFC4226_SECRET), c, 0, 6, code) for c, code in enumerate(RFC4226_CODES)]
+ for alg, codes in RFC6238_CODES.items():
+ cases += [
+ (ALGS[alg], h(RFC6238_SEEDS[alg]), t, 30, 8, code) for t, code in zip(RFC6238_TIMES, codes, strict=True)
+ ]
+ boundary_times = [0, 29, 30, 31, 2**31 - 1, 2**31, 2**32 - 1, 4102444800] # incl. 2038 and the year 2100
+ for alg in list(ALGS):
+ for _ in range(4):
+ secret = bytes(rng.randrange(256) for _ in range(rng.choice((10, 16, 20, 32, 64))))
+ for digits in (6, 7, 8):
+ period = rng.choice((15, 30, 60, 90, 37))
+ for t in (rng.choice(boundary_times), rng.randrange(0, 2**32)):
+ cases.append((ALGS[alg], h(secret), t, period, digits, hotp(alg, secret, t // period, digits)))
+ steam_cases = []
+ for _ in range(12):
+ secret = bytes(rng.randrange(256) for _ in range(20))
+ t = rng.randrange(0, 2**32)
+ steam_cases.append((h(secret), t, steam(secret, t // 30)))
+ return cases, steam_cases
+
+
+# --------------------------------------------------------------------- output
+
+
+def mc_value(value: object) -> str:
+ if isinstance(value, str):
+ return f'"{value}"'
+ if isinstance(value, int):
+ return f"{value}l" if value > 2**31 - 1 or value < -(2**31) else str(value)
+ raise TypeError(value)
+
+
+def mc_table(name: str, comment: str, rows: Sequence[Sequence[object]]) -> str:
+ body = ",\n".join(" [" + ", ".join(mc_value(v) for v in row) + "]" for row in rows)
+ return f" // {comment}\n const {name} = [\n{body}\n ];\n"
+
+
+HEADER = """\
+// Author: cirakg & claude ai
+// Summary: Crypto and OTP test vectors (official RFC/FIPS/NIST values plus seeded cases); generated, do not edit.
+//
+// Generated by tools/gen_vectors.py. Every published vector was recomputed with
+// Python's standard library (or openssl for AES) before being written here.
+// Algorithm IDs: 0 = SHA-1, 1 = SHA-256, 2 = SHA-512. All data is fake.
+
+import Toybox.Lang;
+
+(:test)
+module TestVectors {
+
+"""
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description="Generate test/data/Vectors.mc")
+ parser.add_argument("--out", type=Path, default=Path("test/data/Vectors.mc"))
+ args = parser.parse_args()
+ rng = random.Random(SEED)
+ otp, steam_cases = otp_vectors(rng)
+ parts = [
+ mc_table("HASH", "[alg, message hex, digest hex]", list(hash_vectors(rng))),
+ mc_table("HMAC", "[alg, key hex, data hex, mac hex] (RFC 2202, RFC 4231)", list(hmac_vectors())),
+ mc_table(
+ "PBKDF2", "[password hex, salt hex, iterations, length, key hex] (RFC 7914 + seeded)", pbkdf2_vectors()
+ ),
+ mc_table("HKDF", "[ikm hex, salt hex, info hex, length, okm hex] (RFC 5869 A.1-A.3)", hkdf_vectors()),
+ mc_table(
+ "AES_CBC", "[key hex, iv hex, plaintext hex, ciphertext hex] (SP 800-38A F.2.1, F.2.5)", aes_vectors()
+ ),
+ mc_table("BASE32", "[bytes hex, encoded] (RFC 4648 section 10 + seeded)", base32_vectors(rng)),
+ mc_table("OTP", "[alg, secret hex, time or counter, period (0 = HOTP), digits, code]", list(otp)),
+ mc_table("STEAM", "[secret hex, unix time, code]", steam_cases),
+ ]
+ args.out.parent.mkdir(parents=True, exist_ok=True)
+ args.out.write_text(HEADER + "\n".join(parts) + "}\n", encoding="utf-8")
+ print(f"wrote {args.out}: {len(otp)} OTP, {len(steam_cases)} Steam vectors")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/sim_screenshot.py b/tools/sim_screenshot.py
index 81b41b6..5517b78 100644
--- a/tools/sim_screenshot.py
+++ b/tools/sim_screenshot.py
@@ -16,7 +16,8 @@ Steps:
black out the corners of round displays, which show bezel, not screen.
3. Fail if the screen is blank (the app drew nothing).
4. With --golden, compare with the approved image: the check fails when more
- than 0.5 % of the pixels differ by more than 24 levels in any channel. A
+ than 0.01 % of the pixels differ by more than 24 levels in any channel (less
+ than one changed character; rendering is deterministic). A
diff image (differences in red) is written next to --out.
5. With --approve, save the screen as the new golden image instead.
@@ -40,7 +41,7 @@ MAX_OFFSET_X, MAX_OFFSET_Y = 16, 64 # search range for the device drawing
MIN_OVERLAP = 0.9 # the window may clip the drawing's edge, but not more than this
MAX_BEZEL_DIFF = 6.0 # mean absolute difference per channel at the best offset
PIXEL_TOLERANCE = 24 # per-channel difference that still counts as equal
-MAX_DIFF_FRACTION = 0.005 # share of pixels allowed to differ
+MAX_DIFF_FRACTION = 0.0001 # share of pixels allowed to differ: below one changed character
MIN_SCREEN_STDDEV = 2.0 # below this the screen counts as blank