diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index b5ac906..aea6c49 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -417,4 +417,4 @@ jobs: TRINITY_REQUIRE_MLDSA87_RUNTIME: "1" run: | rm -rf tmp trinity_test.db* trinity_test_receipts.db* - mix test test/trinity/crypto/preflight_test.exs test/trinity/toolchain_node_test.exs test/trinity/receipts/signer_test.exs test/trinity/n6/cose_signing_test.exs test/trinity/n6/cose_test.exs test/trinity/receipts/foreign_algorithm_test.exs test/trinity/receipts/v4_test.exs test/trinity/n6/esp384_test.exs + mix test test/trinity/crypto/preflight_test.exs test/trinity/toolchain_node_test.exs test/trinity/receipts/signer_test.exs test/trinity/n6/cose_signing_test.exs test/trinity/n6/cose_test.exs test/trinity/receipts/foreign_algorithm_test.exs test/trinity/receipts/v4_test.exs test/trinity/n6/esp384_test.exs test/trinity/crypto/boot_check_test.exs test/trinity/crypto/boot_check_node_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 984b5f7..decb0f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,23 @@ built and read back; a chain crossing from the earlier form, and every tampering or a checkpoint refused; attachments missing or altered reported without changing the verdict; and the two verifiers agreeing on each case. +### `slice/143`: every node checks its ML-DSA-87 verifier at boot + +Every node now checks at boot that its cryptography library verifies ML-DSA-87 signatures +correctly, against nine published cases: two that must verify and seven that must not, so a +verifier that accepts everything is caught. A wrong answer stops the boot on any node. A runtime +that cannot do ML-DSA-87 at all still boots and says so on its boot record, except on the national +security profile, where it is refused. On that profile the node also refuses to start unless FIPS +mode is on and the FIPS provider reports the build its validation names. A node that signs receipts +with ML-DSA-87 additionally signs with a published test key at boot, and no longer generates a key +of its own to test with. None of this is presented as FIPS evidence: the provider tests itself. + +Verified by the test suite: every case's expected answer on a runtime that does ML-DSA-87; a planted +wrong answer at each case and each step refused by name; real boots refusing a wrong verifier in +both profiles and refusing on the national security profile by mode or by build string; and the +pass on that profile shown by the decision's own tests, since no CI runtime carries the validated +provider. + ## 2026-10-08 ### `slice/136`: the web pages ask who is there diff --git a/REUSE.toml b/REUSE.toml index 00f57ac..994116b 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -69,10 +69,12 @@ SPDX-FileCopyrightText = "National Institute of Standards and Technology" SPDX-License-Identifier = "CC0-1.0" # Slice 137. **Not ours.** The ML-DSA-87 known-answer vectors the boot preflight checks against. -# `priv/kat/acvp/` is one case from NIST's ACVP-Server (`usnistgov/ACVP-Server`), a work of the US -# Government, annotated CC0-1.0 as the NIST catalogues above are. `priv/kat/wycheproof/` is three -# cases from C2SP's Wycheproof, Apache-2.0. Each file names its source commit and the SHA-256 of the -# file it was cut from. The `**/*.json` line above would otherwise assign them to this project. +# `priv/kat/acvp/` is three cases from NIST's ACVP-Server (`usnistgov/ACVP-Server`), a work of the +# US Government, annotated CC0-1.0 as the NIST catalogues above are: slice 137's keyGen case and +# slice 143's sigGen and sigVer cases. `priv/kat/wycheproof/` is five cases from C2SP's Wycheproof, +# Apache-2.0. Each file names its source commit and the SHA-256 of the file it was cut from. The +# `**/*.json` line above would otherwise assign them to this project. `priv/kat/mldsa87_boot_check.json`, +# the table of which case must answer what (slice 143), is this project's and takes that line. [[annotations]] path = ["priv/kat/acvp/**"] precedence = "override" diff --git a/docs/07-security-model.md b/docs/07-security-model.md index d84b401..60a0a6d 100644 --- a/docs/07-security-model.md +++ b/docs/07-security-model.md @@ -539,7 +539,7 @@ the row names must be at or above its version there. Elixir outside 1.20.x is re `crypto:supports/0` cannot say whether ML-DSA-87 is usable in FIPS mode: OTP's `build_pkey_type_list/3` omits every ML-DSA and SLH-DSA type whenever the mode is on, whatever the -loaded provider can do. `Trinity.Crypto.Preflight.mldsa87/1` asks the provider by doing the +loaded provider can do. `Trinity.Crypto.Preflight` (by role since slice 143, below) asks the provider by doing the operation: NIST's ACVP keyGen case (the published secret key must yield the published public key), Wycheproof's valid, bit-flipped and malformed-hints verify cases, then a fresh key. The ML-DSA-87 receipts signer reports itself available exactly when that passes. @@ -549,6 +549,34 @@ fail-open. Where it signs today: a runtime linked to OpenSSL 3.5 or later with t mode, on the image's current FIPS provider, it does not; a provider validated with ML-DSA-87 (CMVP #5523) is what would change that. +### The check by role, and the provider's identity on `nss` (Slice 143, as built) + +Slice 137's six steps are now two checks, chosen by what the node does. + +- **The verifier check runs on every node, at boot, right after the floor.** It verifies nine + published cases and requires each to give exactly its expected answer: two that must be true + (NIST ACVP's ML-DSA-87 sigGen case and Wycheproof's valid signature) and seven that must be false + (ACVP's modified signature, Wycheproof's modified signature, hint encoding, infinity norm and + wrong length, and the valid signature with one bit of its message or its signature flipped). The + table and its measured answers are `priv/kat/mldsa87_boot_check.json`. A verifier that says true + to everything fails at the first negative. +- **A case answered wrongly refuses the boot in every profile**, naming the case. A runtime that + cannot run ML-DSA-87 at all (OpenSSL 3.0, or FIPS mode on a provider without it) boots, and the + boot receipt says `unavailable`, except on `nss`. +- **On the `nss` receipts profile** the boot also refuses, each by name, when FIPS mode is not + enabled, when no FIPS provider is loaded, when the provider's build string is not "3.6.0-r4" + (the string CMVP #5523's security policy, section 11.2, names), or when the verifier check cannot + run. This is the node checking itself, not the Crypto Officer's check, and it is never cited as + one. The policy's other string, the provider's name, cannot be checked: OTP's `crypto:info/0` does + not expose it. +- **The signer check runs where receipts sign with ML-DSA-87.** It is the verifier check, ACVP's + keyGen case, and a signature made with ACVP's published test key that verifies and fails on a + flipped message. It generates no key: the provider's own self-tests cover generation, and a key + made at boot cannot be wiped from the BEAM's memory. +- **None of this is FIPS evidence.** Self-tests are the module's duty, and outside the approved + mode the provider owes none, so on `commercial` and on development runtimes this check is the only + ML-DSA-87 test that runs. + ## The headless release's floor, and the decoder asked directly (Slice 141, as built) The headless release, the one deployed as a server, is held to OTP 28.5.0.8 and to the OTP 28 line diff --git a/docs/fips-leg.md b/docs/fips-leg.md index 46fae55..6ebebf1 100644 --- a/docs/fips-leg.md +++ b/docs/fips-leg.md @@ -103,6 +103,12 @@ OpenSSL was a 3.0 line; as of 9.8 it is not. own clients (Req, req_llm, the web tools); a FIPS deployment that fetches from such hosts hits this at run time. 3. **The provider's version string is not the page's.** See the section above; owner: the deployment's assessor, named in `docs/09-standards-register.md`. +4. **The leg can show an `nss` node refuse, never one boot (slice 143).** On `nss` a node refuses to boot unless its + FIPS provider reports the build string CMVP #5523's security policy names. The image's provider is Red Hat's and + reports another, and it refuses ML-DSA-87 in the mode, so the `fips` job shows the refusal naming the build string; + the boot that passes needs #5523's provider and is shown only by `Trinity.Crypto.BootCheck`'s pure tests. Lift + condition: the provider the headless release is fielded with, in a CI image. The provider's name, which the policy also names, cannot be checked at + all: OTP's `crypto:info/0` does not expose it. ## Reds on the leg, by test name diff --git a/docs/regulated/control-mapping.md b/docs/regulated/control-mapping.md index 6a56fe7..80b9053 100644 --- a/docs/regulated/control-mapping.md +++ b/docs/regulated/control-mapping.md @@ -117,7 +117,7 @@ register does not claim is never Trinity's. | `SC-12` | Keys pass through one behaviour, so a KMS or HSM adapter is a module and a configuration line; none ships in this tree | `lib/trinity/keys.ex` | `test/trinity/keys/signer_seam_test.exs` "a sealed key still signs, which is the point of sealing it" | shared | A KMS or HSM adapter, where one is required | CRM "A KMS or HSM adapter" | | `SC-12` | Custody of the signing key is not addressed: a key in a file shows the records were not altered by anything without read access to it, and nothing about who held it | `docs/10-assurance-case.md` | none | the deployment's | Custody of the signing key | CRM "Custody of the signing key" | | `SC-13` | Signing picks its algorithm by the runtime's mode: Ed25519 by default, ECDSA P-384 with SHA-384 in FIPS mode, where Ed25519 reports itself unavailable. The gate and the suite run in FIPS mode on every push | `lib/trinity/receipts/signer.ex`, `docs/fips-leg.md`, `docs/regulated/crypto-inventory.md` | `test/trinity/receipts/signer_test.exs` "outside FIPS mode the default is Ed25519; in FIPS mode (the fips leg) it is P-384"; `test/fips/receipts_test.exs` "the mode is on, Ed25519 reports unavailable, P-384 is selected and the boot receipt names it" | shared | A validated cryptographic module on the host, established from its certificate and the running binary | Register "FIPS 140-3 validated cryptography in FIPS mode": :unknown; Register "FIPS 140-2 certificates on the Historical list from 2026-09-22": real-world dependency | -| `SC-13` | ML-DSA-87 is offered for receipts only where a known-answer check passes through `:crypto` in the mode the node is in: NIST's ACVP keyGen case (the published secret key yields the published public key), Wycheproof's verify cases (a valid signature, a bit-flipped one and one with malformed hints), then a fresh key. Not where the algorithm is merely listed: OTP lists no ML-DSA type in FIPS mode whatever the provider can do. A regulated node configured for ML-DSA-87 that fails the check refuses to boot naming the step; the boot receipt records the check's answer | `lib/trinity/crypto/preflight.ex`, `priv/kat/`, `lib/trinity/receipts/signer/mldsa87.ex`, `lib/trinity/receipts/key_custody.ex` | `Trinity.Crypto.Preflight.mldsa87/1`; `test/trinity/crypto/preflight_test.exs` "a provider that derives the wrong public key fails at :keygen_kat"; `test/trinity/crypto/preflight_test.exs` "a verifier that accepts Wycheproof's bit-flipped signature fails at :sigver_kat, naming the case"; `test/trinity/crypto/preflight_test.exs` "the ML-DSA-87 signer is available exactly when the preflight passes (F-137-1)"; `test/trinity/toolchain_node_test.exs` "a failing preflight refuses the boot, naming the step" | Trinity's | none | none | +| `SC-13` | Every node checks at boot that `:crypto` verifies ML-DSA-87 rightly, by doing it: nine published cases (NIST ACVP sigGen and sigVer, five Wycheproof cases, and flips of a valid signature), each of which must give exactly its expected answer, so a verifier that says yes to everything is refused. A case answered wrongly refuses the boot in every profile; on the `nss` profile a runtime that cannot run the check refuses too, and so does FIPS mode off, no FIPS provider, or a provider whose build string is not the one CMVP #5523's security policy names. Where receipts sign with ML-DSA-87 the signer check adds ACVP's keyGen case and a signature made with a published test key, and generates no key at boot. Not where the algorithm is merely listed: OTP lists no ML-DSA type in FIPS mode whatever the provider can do. The check is defence in depth, never FIPS evidence; the boot receipt records its answer | `lib/trinity/crypto/preflight.ex`, `lib/trinity/crypto/boot_check.ex`, `priv/kat/`, `lib/trinity/receipts/signer/mldsa87.ex`, `lib/trinity/receipts/key_custody.ex` | `Trinity.Crypto.Preflight.verifier/1`; `Trinity.Crypto.Preflight.signer/1`; `Trinity.Crypto.BootCheck.decide/3`; `test/trinity/crypto/preflight_test.exs` "a verifier that says true to everything fails at the first negative"; `test/trinity/crypto/preflight_test.exs` "a provider that derives the wrong public key fails at :keygen_kat"; `test/trinity/crypto/preflight_test.exs` "the ML-DSA-87 signer is available exactly when the signer check passes (F-137-1)"; `test/trinity/crypto/boot_check_test.exs` "another provider's build string refuses, naming both"; `test/trinity/crypto/boot_check_node_test.exs` "a node whose verifier answers a case wrongly refuses, naming the case, in every profile"; `test/trinity/crypto/boot_check_node_test.exs` "an nss node refuses, naming the mode or the provider's build string"; `test/trinity/toolchain_node_test.exs` "a failing preflight refuses the boot, naming the step" | Trinity's | none | none | | `SC-13` | Signed statements have one byte form: deterministic CBOR inside COSE_Sign1 with ML-DSA-87, or on the `commercial` profile only Ed25519 (slice 139) or, for a node in FIPS mode, ECDSA P-384 as ESP384 with its 96-byte signature (slice 140). Receipts and checkpoints are written in it since slice 140. The decoder refuses any input that is not exactly the deterministic encoding of an allowed value, naming the first defect; the header admits only `alg` -50 (and -19 or -51 at `commercial`), the key's RFC 9679 thumbprint as `kid`, and `typ`; the key's registration names its algorithm and a header naming another is refused; one `typ` per kind, and a body under another kind's is refused; a single-signer carrier refuses a `typ` that needs two signers and a body without an issuer; a verifier that cannot run the header's algorithm answers no trust, never a pass, and so does the receipt verifier for a row whose algorithm the runtime refuses. A census classifies every hashing and signing call in the tree and names the sites that still re-encode data from elsewhere before hashing it | `lib/trinity/n6/`, `lib/trinity/receipts/v4.ex`, `test/fixtures/n6/`, `scripts/n6_vectors.exs` | `test/trinity/n6/cbor_test.exs` "every negative vector is refused with its code"; `test/trinity/n6/cose_test.exs` "every negative header is refused with its code, at either entry point"; `test/trinity/n6/carrier_test.exs` "AC5: every sample gives its expected outcome under its context"; `test/trinity/n6/cose_test.exs` "kid for ACVP's published key is SHA-384 over exactly the RFC 9679 input"; `test/trinity/n6/cose_signing_test.exs` "one flipped byte in the protected header, the payload or the signature is invalid"; `test/trinity/n6/esp384_test.exs` "DER, or 95 or 97 bytes, is signature_encoding"; `test/trinity/n6/esp384_test.exs` "a compressed point, a P-256 key and a point off the curve are key_invalid"; `test/fips/receipts_test.exs` "no effect is denied for want of a signer: an effect runs, its receipts are P-384 and verify"; `test/trinity/n6/census_test.exs` "every hashing and signing call in lib/ is in the census, with its count and a class" | Trinity's | none | none | | `SC-23` | A request whose `Host` is not the loopback or the configured host is refused before routing, and the websocket origin check is an explicit list in every environment; under `TRINITY_PROFILE=regulated` an origin check of `false` or `:conn` refuses to boot, because a DNS rebinding page controls both | `lib/trinity_web/plugs/host_allow_list.ex`, `lib/trinity/profile.ex` | `Trinity.Profile.check_origin/2`; `test/trinity_web/auth/host_and_origin_test.exs` "a foreign Host is 403 before routing, wherever it points"; `test/trinity_web/auth/host_and_origin_test.exs` "check_origin is an explicit list in dev, test and prod" | shared | Setting `PHX_HOST` to the host users reach | CRM "Refusing a request whose Host is not this deployment's" | | `SC-23` | The state an MCP client carries between rounds is sealed with AES-256-GCM: a replay, one tampered byte, an expired envelope and a state bound to other arguments are refused | `lib/trinity/mcp/server/envelope.ex` | `test/trinity/mcp/server_mrtr_test.exs` "a replay inside the partition, one tampered byte, an expired envelope and a state bound to other arguments are refused; a missing state is a first call" | Trinity's | none | Register "Envelope MAC in FIPS mode is an approved algorithm": :unknown | diff --git a/docs/regulated/crypto-inventory.md b/docs/regulated/crypto-inventory.md index 7e4c391..5db8988 100644 --- a/docs/regulated/crypto-inventory.md +++ b/docs/regulated/crypto-inventory.md @@ -15,7 +15,8 @@ to that provider, on that host, not to this software. **Anything not established |---|---|---|---|---| | Receipt signing, default | `lib/trinity/receipts/signer/ed25519.ex` | `:crypto.sign(:eddsa, :none, …, :ed25519)`, line 21 | **UNKNOWN** | Ed25519, RFC 8032. `available?/0` line 14 returns false when `:crypto.info_fips() == :enabled`, so it is not selected in FIPS mode | | Receipt signing, FIPS mode | `lib/trinity/receipts/signer/p384.ex` | `:crypto.sign(:ecdsa, :sha384, …, :secp384r1)`, line 25 | **UNKNOWN** | ECDSA P-384 with SHA-384. Selected when the mode is enabled. Since slice 140 its DER signature is carried as ESP384's 96-byte `r \|\| s` (`lib/trinity/n6/ec2.ex`) | -| Receipt signing, opt-in | `lib/trinity/receipts/signer/mldsa87.ex` | `:crypto` ML-DSA-87, FIPS 204 | **UNKNOWN** | Never the default. `available?/0` asks the runtime by generating, signing and verifying a known answer (`Trinity.Crypto.Preflight`, slice 137); it was a `:crypto.supports/1` lookup before, which never holds in FIPS mode | +| Receipt signing, opt-in | `lib/trinity/receipts/signer/mldsa87.ex` | `:crypto` ML-DSA-87, FIPS 204 | **UNKNOWN** | Never the default. `available?/0` asks the runtime by its signer check (`Trinity.Crypto.Preflight.signer/1`, slice 143): published verify cases, ACVP's keyGen case and a signature made with a published test key, with no key generated; it was slice 137's six steps before, and a `:crypto.supports/1` lookup before those, which never holds in FIPS mode | +| ML-DSA-87 verifier check at boot (slice 143) | `lib/trinity/crypto/preflight.ex`, `lib/trinity/crypto/boot_check.ex` | `:crypto.verify(:mldsa87, :none, …)` over nine published cases | Not applicable | Defence in depth, never FIPS evidence. On `nss` the boot also asserts the FIPS provider's build string (`fips_provider_buildinfo`) against CMVP #5523's security policy | | Receipt hashing, v2 and v3 rows | `lib/trinity/receipts/envelope.ex:36` | `:crypto.hash(:sha256, …)` | **UNKNOWN** | Over the DSSE pre-authentication encoding, not the bare payload. Rows written before slice 140; still verified | | Receipt identity and link, v4 rows (slice 140) | `lib/trinity/receipts/v4.ex` | `:crypto.hash(:sha384, …)` | **UNKNOWN** | `statement_id` over the kind's `typ` and the N6 body; the link to a v3 predecessor over its PAE bytes | | Merkle tree over a chain | `lib/trinity/receipts/merkle.ex` | `:crypto.hash(:sha256, …)` | **UNKNOWN** | RFC 6962 hashing: distinct leaf and node prefixes, odd nodes promoted not duplicated | diff --git a/lib/trinity.ex b/lib/trinity.ex index 95b52a9..fd88991 100644 --- a/lib/trinity.ex +++ b/lib/trinity.ex @@ -48,6 +48,8 @@ defmodule Trinity do # the runtime, asked before anything starts. Toolchain, Crypto.Preflight, + # Slice 143: what a boot does with the ML-DSA-87 verifier check and the provider's identity. + Crypto.BootCheck, # Slice 136: the web authentication mode, its rules and the mode in force. WebAuth, UUID, diff --git a/lib/trinity/application.ex b/lib/trinity/application.ex index 1052d01..699c31a 100644 --- a/lib/trinity/application.ex +++ b/lib/trinity/application.ex @@ -38,6 +38,12 @@ defmodule Trinity.Application do # the OTP 28 line only, and every node asks its decoder directly as well. verify_toolchain!() + # Slice 143, in every profile, right after the floor: the ML-DSA-87 verifier check. A case + # answered wrongly refuses everywhere; on the `nss` receipts profile so does a runtime that + # cannot run the check, and a FIPS provider that is not the validated module by its build + # string (`Trinity.Crypto.BootCheck`). + verify_crypto!() + # Slice 135, in every profile: a filesystem root that is, contains or sits inside the data # directory or the secrets directory refuses the boot, before any child (and before the # regulated refusals, so the reason printed is this one when both apply). @@ -200,6 +206,14 @@ defmodule Trinity.Application do end end + # Slice 143. The decision is `Trinity.Crypto.BootCheck.decide/3`'s, pure; raised as the floor is. + defp verify_crypto! do + case Trinity.Crypto.BootCheck.run(Trinity.Receipts.KeyCustody.profile()) do + :ok -> :ok + {:error, reason} -> raise "Trinity refuses to boot: #{inspect(reason)}" + end + end + # Slice 136 AC4, AC6 and AC13. The decisions are `Trinity.WebAuth`'s and `Trinity.Profile`'s, # both pure; this reads the endpoint's configuration and hands them the values. defp verify_web_auth! do diff --git a/lib/trinity/crypto/boot_check.ex b/lib/trinity/crypto/boot_check.ex new file mode 100644 index 0000000..0af3b9f --- /dev/null +++ b/lib/trinity/crypto/boot_check.ex @@ -0,0 +1,108 @@ +# SPDX-FileCopyrightText: Sudo Apt Holdings LLC +# SPDX-License-Identifier: Apache-2.0 +defmodule Trinity.Crypto.BootCheck do + @moduledoc """ + What a boot does with the ML-DSA-87 verifier check, and, on `nss`, with the FIPS provider's + identity (slice 143, the PM's Decisions of 2026-10-20 19:35Z and 19:42Z). + + `Trinity.Application` runs `Trinity.Crypto.Preflight.verifier/1` once, in every release and + profile, right after the toolchain floor, keeps its answer (`verifier_answer/0`) for the boot + receipt, and refuses the boot when `decide/3` says so: + + * **A case answered wrongly refuses in every profile**, naming the case: a runtime whose + verifier says the wrong thing about a published case cannot be trusted for anything it says + yes to. + * **On the `nss` receipts profile** (`Trinity.Receipts.KeyCustody.profile/0`), the node writes + and verifies only ML-DSA-87, so the boot also refuses, each by name, when `crypto:info_fips/0` + is not `enabled`, when `fips_provider_available` is not `true`, when `fips_provider_buildinfo` + is not "3.6.0-r4", or when the verifier check cannot run at all. + * Elsewhere a runtime that cannot run ML-DSA-87 (OpenSSL 3.0, or FIPS mode on a provider + without it) boots, and the boot receipt says `unavailable`. + + The build string is the one CMVP #5523's security policy (v1.2, 2026-08-21, section 11.2) has the + Crypto Officer check, pinned here and not configurable. This is the node checking itself at boot, + not the Crypto Officer's check, and it is never cited as one. The policy's other string, the + provider's name, cannot be asserted: OTP 28.5.0.8's `crypto:info/0` does not expose it (its keys, + measured 2026-10-20: `otp_crypto_version`, `compile_type`, `link_type`, + `cryptolib_version_compiled`, `cryptolib_version_linked`, `fips_provider_available`, and + `fips_provider_buildinfo` when a FIPS provider is loaded). That is a recorded gap. + """ + + @nss_buildinfo "3.6.0-r4" + @key {__MODULE__, :verifier} + + @typedoc "What the boot knows of the crypto library: its mode, and `:crypto.info/0`." + @type runtime :: %{fips: atom(), info: map()} + + @doc "The FIPS provider build string the `nss` profile requires." + @spec nss_buildinfo() :: String.t() + def nss_buildinfo, do: @nss_buildinfo + + @doc "This runtime's mode and crypto information, as `decide/3` reads them." + @spec runtime() :: runtime() + def runtime, do: %{fips: :crypto.info_fips(), info: :crypto.info()} + + @doc """ + Whether a boot under `profile` (`:commercial` or `:nss`) may proceed, given the verifier check's + answer and the runtime. Pure. + """ + @spec decide(:commercial | :nss, Trinity.Crypto.Preflight.verifier_result(), runtime()) :: + :ok | {:error, term()} + def decide(profile, verifier, runtime) do + with :ok <- identity(profile, runtime) do + verdict(profile, verifier) + end + end + + defp identity(:nss, %{fips: fips}) when fips != :enabled, + do: {:error, {:nss_requires_fips_mode, fips}} + + defp identity(:nss, %{info: info}) do + cond do + Map.get(info, :fips_provider_available) != true -> + {:error, {:nss_requires_fips_provider, Map.get(info, :fips_provider_available)}} + + to_string(Map.get(info, :fips_provider_buildinfo, "")) != @nss_buildinfo -> + {:error, + {:nss_fips_provider_buildinfo, to_string(Map.get(info, :fips_provider_buildinfo, "")), + @nss_buildinfo}} + + true -> + :ok + end + end + + defp identity(_profile, _runtime), do: :ok + + defp verdict(_profile, :ok), do: :ok + + defp verdict(:nss, {:error, {:mldsa87_verifier_failed, :unavailable, reason}}), + do: {:error, {:nss_requires_mldsa87, reason}} + + defp verdict(_profile, {:error, {:mldsa87_verifier_failed, :unavailable, _reason}}), do: :ok + + defp verdict(_profile, {:error, {:mldsa87_verifier_failed, case_id, got}}), + do: {:error, {:mldsa87_verifier_wrong, case_id, got}} + + @doc "Runs the verifier check, keeps its answer for the boot receipt, and decides." + @spec run(:commercial | :nss) :: :ok | {:error, term()} + def run(profile) do + answer = Trinity.Crypto.Preflight.verifier() + :persistent_term.put(@key, answer) + decide(profile, answer, runtime()) + end + + @doc """ + The verifier check's answer at this boot, as the boot receipt names it: `"ok"`, `"unavailable"`, + `"wrong:"`, or `"not_run"` before `run/1`. + """ + @spec verifier_answer() :: String.t() + def verifier_answer do + case :persistent_term.get(@key, nil) do + :ok -> "ok" + {:error, {:mldsa87_verifier_failed, :unavailable, _}} -> "unavailable" + {:error, {:mldsa87_verifier_failed, case_id, _}} -> "wrong:#{case_id}" + nil -> "not_run" + end + end +end diff --git a/lib/trinity/crypto/preflight.ex b/lib/trinity/crypto/preflight.ex index 9b315b5..8b65bdd 100644 --- a/lib/trinity/crypto/preflight.ex +++ b/lib/trinity/crypto/preflight.ex @@ -2,108 +2,222 @@ # SPDX-License-Identifier: Apache-2.0 defmodule Trinity.Crypto.Preflight do @moduledoc """ - Whether this runtime can do ML-DSA-87, answered by doing it against published answers (slice 137). + Whether this runtime does ML-DSA-87 right, answered by doing it against published answers + (slice 137), chosen by role since slice 143 (the PM's Decisions of 2026-10-20, 19:35Z and 19:42Z). - Six steps through `:crypto`, in order, the answer being `:ok` or - `{:error, {:mldsa87_preflight_failed, step, reason}}` for the first that failed. Nothing is - raised out of it. + ## The verifier check, on every node - 1. `:keygen_kat`: the NIST ACVP ML-DSA-87 keyGen case (`priv/kat/acvp/`): the published - secret key regenerates exactly the published public key, and signing with the published seed - verifies under it. - 2. `:sigver_kat`: Wycheproof's ML-DSA-87 verify cases (`priv/kat/wycheproof/`): a valid - signature verifies, a signature with one bit flipped does not, and one whose hints are out of - order does not (the malleability class of CVE-2026-24850). - 3. to 6. `:keygen`, `:sign`, `:verify`, `:negative`: a fresh key signs a fixed message, the - signature verifies, and the message with one bit changed does not. + `verifier/1` verifies the cases `priv/kat/mldsa87_boot_check.json` lists, in its order, and each + must give exactly the answer the table names: - The published cases catch a provider that is consistently wrong, which a fresh key alone would - not. Signing is hedged, so no signature-bytes answer is compared. + * must be `true`: NIST ACVP's ML-DSA-87 sigGen tgId 5 tcId 63 (external interface, pure, + deterministic, empty context) and Wycheproof tcId 1; + * must be `false`: ACVP sigVer tgId 5 tcId 64 (a modified commitment); Wycheproof's modified + signature (tcId 8), hint encoding (18), infinity norm (77) and wrong length (6); and tcId 1 + with one bit of its message, then of its signature, flipped. - ## Why not `crypto:supports/0` + Its answer is `:ok`; `{:error, {:mldsa87_verifier_failed, :unavailable, reason}}` when the first + case raises the way `:crypto` refuses an algorithm it does not have (OpenSSL 3.0; FIPS mode on a + provider without ML-DSA-87); or `{:error, {:mldsa87_verifier_failed, case_id, got}}` when a case + answers anything but its expected answer, a raise included. A verifier that says `true` to + everything fails at the first negative, and one that says `false` to everything at the first + positive. The negatives go beyond what the IG asks of a module (one positive); they are the + check a verify-only release most needs. - OTP's `build_pkey_type_list/3` (`lib/crypto/c_src/pkey.c`) lists the pkey types, every ML-DSA - and SLH-DSA set among them, only when FIPS mode is off. It is the same at `OTP-28.5.0.7`, - `OTP-29.0.6` and `master`. In the mode the listing says no whatever the provider can do, and a - provider that signs ML-DSA-87 inside its validated boundary (CMVP #5523) would be reported as - unable. Doing the operation asks the provider itself. + ## The signer check, where Trinity holds an ML-DSA-87 signing key + + `signer/1` is the verifier check, then three steps, each answering + `{:error, {:mldsa87_preflight_failed, step, reason}}` for the first that fails: + + 1. `:keygen_kat`: ACVP keyGen tgId 3 tcId 51 (slice 137): the published secret key regenerates + the published public key, and signing with the published seed verifies under it; + 2. `:sign_kat`: tcId 63's message, signed with tcId 63's published secret key, verifies under + its published public key; + 3. `:sign_negative`: that signature does not verify the message with one bit flipped. - ## Where it signs + No key is generated: the provider's own keygen self-test and pairwise test cover generation, and + a key made at boot cannot be zeroised on the BEAM. Signing is hedged, so no signature bytes are + compared. A verifier failure inside it is `{:mldsa87_preflight_failed, :verifier, reason}`. + + Neither check is FIPS evidence. Self-tests are the module's duty (ISO/IEC 19790, IG 10.3.A), and + outside the approved mode the provider owes none (IG 10.3.A GeneralNote8), so on `commercial` and on + development runtimes this is the only ML-DSA-87 test that runs. `Trinity.Crypto.BootCheck` decides + what a boot does with the answers. + + ## Why not `crypto:supports/0` - Where the linked OpenSSL is 3.5 or later: on the FIPS image with the mode off (slice 003), and on - an OTP built against OpenSSL 3.5.9. Not on a runtime linked to OpenSSL 3.0 (hacktuah's asdf - OTPs, the `gate` runners), and not in the mode on Red Hat's provider (slice 003). + OTP's `build_pkey_type_list/3` (`lib/crypto/c_src/pkey.c`) lists the pkey types, every ML-DSA + and SLH-DSA set among them, only when FIPS mode is off. In the mode the listing says no whatever + the provider can do. Doing the operation asks the provider itself. ## The fault seam - `config :trinity, :mldsa87_preflight_fault, step` makes that step fail as if the runtime had - refused it, so a test can plant a failure at each step on a runtime that signs. It can only turn - an answer into a refusal, never the other way. + `config :trinity, :mldsa87_preflight_fault, x` makes the case or step `x` (a case id from the + table, or one of `signer_steps/0`) fail as if the runtime had answered wrongly, so a test can + plant a failure on a runtime that does ML-DSA-87. It can only turn an answer into a refusal, + never the other way. """ - @message "Trinity ML-DSA-87 known-answer preflight (slice 137)" - @steps [:keygen_kat, :sigver_kat, :keygen, :sign, :verify, :negative] - @priv Path.join([__DIR__, "..", "..", "..", "priv", "kat"]) |> Path.expand() - @acvp_path Path.join(@priv, "acvp/mldsa87_keygen.json") - @wycheproof_path Path.join(@priv, "wycheproof/mldsa87_verify.json") - @external_resource @acvp_path - @external_resource @wycheproof_path + @table_path Path.join(@priv, "mldsa87_boot_check.json") + @keygen_path Path.join(@priv, "acvp/mldsa87_keygen.json") + @siggen_path Path.join(@priv, "acvp/mldsa87_siggen.json") + @external_resource @table_path + @external_resource @keygen_path + @external_resource @siggen_path + @external_resource Path.join(@priv, "acvp/mldsa87_sigver.json") + @external_resource Path.join(@priv, "wycheproof/mldsa87_verify.json") hex = &Base.decode16!(&1, case: :mixed) - acvp = @acvp_path |> File.read!() |> JSON.decode!() - wy = @wycheproof_path |> File.read!() |> JSON.decode!() + read = fn rel -> @priv |> Path.join(rel) |> File.read!() |> JSON.decode!() end + flip = fn <> -> <> end + + table = @table_path |> File.read!() |> JSON.decode!() + + @cases (for c <- table["cases"] do + src = read.(c["from"]) + src = if c["entry"], do: src[c["entry"]], else: src + msg = hex.(src["msg"] || src["message"]) + sig = hex.(src["sig"] || src["signature"]) + {msg, sig} = + case c["flip"] do + "message" -> {flip.(msg), sig} + "signature" -> {msg, flip.(sig)} + nil -> {msg, sig} + end + + %{id: c["id"], msg: msg, sig: sig, pk: hex.(src["pk"]), expect: c["expect"]} + end) + + acvp = @keygen_path |> File.read!() |> JSON.decode!() @keygen %{seed: hex.(acvp["seed"]), pk: hex.(acvp["pk"]), sk: hex.(acvp["sk"])} - @sigver for name <- ["valid", "invalid", "invalid_hints"], - into: %{}, - do: - {name, - %{ - tc: wy[name]["tcId"], - pk: hex.(wy[name]["pk"]), - msg: hex.(wy[name]["msg"]), - sig: hex.(wy[name]["sig"]) - }} - - @type step :: :keygen_kat | :sigver_kat | :keygen | :sign | :verify | :negative - @type result :: :ok | {:error, {:mldsa87_preflight_failed, step(), term()}} - - @doc "The steps, in the order they run." - @spec steps() :: [step()] - def steps, do: @steps + + siggen = @siggen_path |> File.read!() |> JSON.decode!() + @signing %{msg: hex.(siggen["message"]), pk: hex.(siggen["pk"]), sk: hex.(siggen["sk"])} + + @signer_steps [:verifier, :keygen_kat, :sign_kat, :sign_negative] + + @type verifier_result :: + :ok | {:error, {:mldsa87_verifier_failed, :unavailable | String.t(), term()}} + @type step :: :verifier | :keygen_kat | :sign_kat | :sign_negative + @type signer_result :: :ok | {:error, {:mldsa87_preflight_failed, step(), term()}} + + @doc "The verifier check's cases, decoded, in the order they run." + @spec cases() :: [map()] + def cases, do: @cases + + @doc "The signer check's steps, in the order they run." + @spec signer_steps() :: [step()] + def signer_steps, do: @signer_steps @doc false - # The published cases, decoded, for the tests' stand-in. - def vectors, do: %{keygen: @keygen, sigver: @sigver} + # The published key material the signer check uses, for the tests' stand-in. + def vectors, do: %{keygen: @keygen, signing: @signing} @doc """ - Runs the six steps against `crypto` (`:crypto` unless a test passes a stand-in exposing - `generate_key/2`, `generate_key/3`, `sign/4` and `verify/5`). + The verifier check against `crypto` (`:crypto` unless a test passes a stand-in exposing + `verify/5`). """ - @spec mldsa87(module()) :: result() - def mldsa87(crypto \\ :crypto) do + @spec verifier(module()) :: verifier_result() + def verifier(crypto \\ :crypto) do fault = Application.get_env(:trinity, :mldsa87_preflight_fault) - with :ok <- step(:keygen_kat, fault, fn -> keygen_kat(crypto) end), - :ok <- step(:sigver_kat, fault, fn -> sigver_kat(crypto) end), - {:ok, {pub, priv}} <- run(:keygen, fault, fn -> crypto.generate_key(:mldsa87, []) end), - {:ok, sig} <- run(:sign, fault, fn -> crypto.sign(:mldsa87, :none, @message, priv) end), - :ok <- step(:verify, fault, fn -> expect(verify(crypto, @message, sig, pub), true) end) do - step(:negative, fault, fn -> expect(verify(crypto, flip(@message), sig, pub), false) end) + @cases + |> Enum.with_index() + |> Enum.reduce_while(:ok, fn {c, i}, :ok -> + case check_case(crypto, c, i, fault) do + :ok -> {:cont, :ok} + error -> {:halt, error} + end + end) + end + + defp check_case(_crypto, %{id: id}, _i, id), + do: {:error, {:mldsa87_verifier_failed, id, :planted}} + + defp check_case(crypto, c, i, _fault), do: judge(c, i, answer(crypto, c)) + + # The first case raising the way :crypto refuses an algorithm is a runtime without ML-DSA-87; any + # other answer that is not the case's own, a raise included, is a wrong answer. + defp judge(%{expect: want}, _i, want), do: :ok + defp judge(%{expect: "raises"}, _i, {:raised, _reason}), do: :ok + + defp judge(_c, 0, {:raised, reason}) do + if refusal?(reason), + do: {:error, {:mldsa87_verifier_failed, :unavailable, reason}}, + else: {:error, {:mldsa87_verifier_failed, hd(@cases).id, {:raised, reason}}} + end + + defp judge(c, _i, got), do: {:error, {:mldsa87_verifier_failed, c.id, got}} + + @doc """ + The signer check against `crypto` (a stand-in exposing `generate_key/3`, `sign/4` and `verify/5` + in tests): the verifier check, then the steps of `signer_steps/0`. + """ + @spec signer(module()) :: signer_result() + def signer(crypto \\ :crypto) do + fault = Application.get_env(:trinity, :mldsa87_preflight_fault) + + with :ok <- verifier_step(crypto, fault), + :ok <- step(:keygen_kat, fault, fn -> keygen_kat(crypto) end), + {:ok, sig} <- signed(crypto, fault), + :ok <- + step(:sign_kat, fault, fn -> + expect(verify(crypto, @signing.msg, sig, @signing.pk), true) + end) do + step(:sign_negative, fault, fn -> + expect(verify(crypto, flip(@signing.msg), sig, @signing.pk), false) + end) end end - @doc "True when `mldsa87/1` answers `:ok`." + @doc "True when `signer/1` answers `:ok`." @spec ok?(module()) :: boolean() - def ok?(crypto \\ :crypto), do: mldsa87(crypto) == :ok + def ok?(crypto \\ :crypto), do: signer(crypto) == :ok + + defp verifier_step(_crypto, :verifier), do: failed(:verifier, :planted) + + defp verifier_step(crypto, _fault) do + case verifier(crypto) do + :ok -> :ok + {:error, reason} -> failed(:verifier, reason) + end + end + + # The signature the `:sign_kat` step checks, made with tcId 63's published secret key. + defp signed(_crypto, :sign_kat), do: failed(:sign_kat, :planted) + + defp signed(crypto, _fault) do + case attempt(fn -> + crypto.sign(:mldsa87, :none, @signing.msg, {:expandedkey, @signing.sk}) + end) do + {:ok, sig} when is_binary(sig) -> {:ok, sig} + {:ok, other} -> failed(:sign_kat, {:unexpected, summarize(other)}) + {:raised, reason} -> failed(:sign_kat, reason) + end + end + + defp answer(crypto, c) do + case attempt(fn -> verify(crypto, c.msg, c.sig, c.pk) end) do + {:ok, got} -> got + {:raised, reason} -> {:raised, reason} + end + end + + # How :crypto says the runtime cannot do the algorithm, each measured: `badarg` "Bad digest type" + # where the linked OpenSSL has no ML-DSA (3.0.13, 2026-10-20); `error` "Can't create PKEY_CTX from + # id" in FIPS mode on Red Hat's provider; `notsup` for an algorithm the mode refuses. + defp refusal?({:erlang, {reason, _, _}}) when reason in [:badarg, :error, :notsup], do: true + defp refusal?(_), do: false defp keygen_kat(crypto) do %{seed: seed, pk: pk, sk: sk} = @keygen + msg = @signing.msg with {:pk, {^pk, _}} <- {:pk, crypto.generate_key(:mldsa87, [], sk)}, - sig = crypto.sign(:mldsa87, :none, @message, {:seed, seed}), - {:seed_signs, true} <- {:seed_signs, verify(crypto, @message, sig, pk)} do + sig = crypto.sign(:mldsa87, :none, msg, {:seed, seed}), + {:seed_signs, true} <- {:seed_signs, verify(crypto, msg, sig, pk)} do :ok else {:pk, other} -> {:wrong, {:public_key_from_secret_key, summarize(other)}} @@ -111,18 +225,6 @@ defmodule Trinity.Crypto.Preflight do end end - defp sigver_kat(crypto) do - Enum.reduce_while([{"valid", true}, {"invalid", false}, {"invalid_hints", false}], :ok, fn - {name, want}, :ok -> - %{tc: tc, pk: pk, msg: msg, sig: sig} = @sigver[name] - - case verify(crypto, msg, sig, pk) do - ^want -> {:cont, :ok} - got -> {:halt, {:wrong, {:wycheproof, tc, want, got}}} - end - end) - end - defp expect(want, want), do: :ok defp expect(got, _want), do: {:wrong, {:answered, got}} @@ -140,21 +242,10 @@ defmodule Trinity.Crypto.Preflight do end end - # A step that produces a value the next step needs. - defp run(name, name, _fun), do: failed(name, :planted) - - defp run(name, _fault, fun) do - case attempt(fun) do - {:ok, {pub, priv}} when name == :keygen -> {:ok, {pub, priv}} - {:ok, sig} when name == :sign and is_binary(sig) -> {:ok, sig} - {:ok, other} -> failed(name, {:unexpected, summarize(other)}) - {:raised, reason} -> failed(name, reason) - end - end - defp attempt(fun) do {:ok, fun.()} rescue + e in ErlangError -> {:raised, {:erlang, e.original}} e -> {:raised, Exception.message(e)} catch kind, reason -> {:raised, {kind, reason}} diff --git a/lib/trinity/effects/boot.ex b/lib/trinity/effects/boot.ex index 32aa14f..18f874c 100644 --- a/lib/trinity/effects/boot.ex +++ b/lib/trinity/effects/boot.ex @@ -15,10 +15,15 @@ defmodule Trinity.Effects.Boot do def start_link(_), do: Task.start_link(__MODULE__, :run, []) - defp preflight_subject do - case Trinity.Crypto.Preflight.mldsa87() do - :ok -> "ok" - {:error, {:mldsa87_preflight_failed, step, _reason}} -> "failed:#{step}" + # Slice 143: the verifier check's answer at this boot, which `Trinity.Application` ran before any + # child; and, where receipts sign with ML-DSA-87, the signer check, which selection ran and passed + # (a failed one leaves no signer and so no boot receipt). + defp mldsa87_subject(subject) do + subject = Map.put(subject, "mldsa87_verifier", Trinity.Crypto.BootCheck.verifier_answer()) + + case Trinity.Receipts.KeyCustody.selected() do + %{algorithm: :mldsa87} -> Map.put(subject, "mldsa87_signer", "ok") + _ -> subject end end @@ -30,11 +35,10 @@ defmodule Trinity.Effects.Boot do "signer" => signer_subject(), "key_custody" => key_custody_subject(), "otp_release" => List.to_string(:erlang.system_info(:otp_release)), - # Slice 137: the runtime the floor was checked against, and whether ML-DSA-87 can be done - # here, as the known-answer check found it at this boot. + # Slice 137: the runtime the floor was checked against. Whether ML-DSA-87 is done right here + # is added below (slice 143). "erts" => Trinity.Toolchain.current().erts, "elixir" => Trinity.Toolchain.current().elixir, - "mldsa87_preflight" => preflight_subject(), "fips" => Atom.to_string(:crypto.info_fips()), "node" => Atom.to_string(node()), # Slice 136: who the web pages answer on this run, and on what address, so a reader of the @@ -49,6 +53,7 @@ defmodule Trinity.Effects.Boot do nil -> subject report -> Map.put(subject, "secrets_migration", report) end + |> mldsa87_subject() meta = %{ "core_policy_hash" => Trinity.CorePolicy.hash(), diff --git a/lib/trinity/receipts/key_custody.ex b/lib/trinity/receipts/key_custody.ex index d412774..d95b40d 100644 --- a/lib/trinity/receipts/key_custody.ex +++ b/lib/trinity/receipts/key_custody.ex @@ -194,9 +194,10 @@ defmodule Trinity.Receipts.KeyCustody do defp available(algorithm) do cond do # Slice 137: for ML-DSA-87 the reason names the known-answer step that failed, so a refused - # regulated boot says which operation the runtime could not do. + # regulated boot says which operation the runtime could not do. Slice 143: the signer check, + # which signs with a published key and generates none. algorithm == :mldsa87 -> - case Trinity.Crypto.Preflight.mldsa87() do + case Trinity.Crypto.Preflight.signer() do :ok -> {:ok, algorithm} {:error, reason} -> {:error, reason} end diff --git a/lib/trinity_web/live/receipts_live.ex b/lib/trinity_web/live/receipts_live.ex index be2c47c..9eae809 100644 --- a/lib/trinity_web/live/receipts_live.ex +++ b/lib/trinity_web/live/receipts_live.ex @@ -106,7 +106,7 @@ defmodule TrinityWeb.ReceiptsLive do
{gettext("elixir")}
{@boot.subject["elixir"]}
{gettext("ML-DSA-87 check")}
-
{@boot.subject["mldsa87_preflight"]}
+
{mldsa87_check(@boot.subject)}
{gettext("core policy hash")}
{@boot.meta["core_policy_hash"]}
{gettext("receipt hash")}
@@ -254,6 +254,16 @@ defmodule TrinityWeb.ReceiptsLive do # Slice 140: a v4 row's decision is the stored bytes its ForeignRef names, shown as they are; a # display, never a verdict, which the verifier gives. + # Slice 143: the verifier check's answer, and the signer check's where it ran; a boot receipt + # written before slice 143 names slice 137's preflight instead. + defp mldsa87_check(subject) do + case {subject["mldsa87_verifier"], subject["mldsa87_signer"]} do + {nil, _} -> subject["mldsa87_preflight"] + {verifier, nil} -> verifier + {verifier, signer} -> "#{verifier} · signer #{signer}" + end + end + defp decision(%{body: body, decision_bytes: bytes}) when is_binary(body) do case bytes && JSON.decode(bytes) do {:ok, d} when is_map(d) -> d |> Enum.map_join(" ", fn {k, v} -> "#{k}=#{value(v)}" end) diff --git a/priv/kat/acvp/mldsa87_siggen.json b/priv/kat/acvp/mldsa87_siggen.json new file mode 100644 index 0000000..f0d6cbb --- /dev/null +++ b/priv/kat/acvp/mldsa87_siggen.json @@ -0,0 +1,17 @@ +{ + "source": "https://raw.githubusercontent.com/usnistgov/ACVP-Server/975de31eb83d87039ec88934fdc47d8c312b892d/gen-val/json-files/ML-DSA-sigGen-FIPS204/internalProjection.json", + "source_sha256": "72dcaf5f69853ca267ccd16af9cb40949786aca0fcfbf05d1ebeba132b93af22", + "licence": "NIST work, not subject to copyright in the United States; annotated CC0-1.0 in REUSE.toml", + "parameterSet": "ML-DSA-87", + "tgId": 5, + "tcId": 63, + "signatureInterface": "external", + "preHash": "pure", + "externalMu": false, + "deterministic": true, + "context": "", + "message": "3358C3FCC173399DCF812F91324AA63EBE64A3028935493B940D0D9D3DEE82D889CF35EB549CC68BC99D707E9BF4AC5054F1E29FAD8B646944189887FE033A3F00CA86DE9BFA30A003518EED9F8E532D950CA0772E82A210ADD1E6843DC9DD578B0F0CEF22415CF5691021509C123A2A9CD550DC5BC54E0DB9A820F8EF99B125A22AFFD9B8F003E5E8FB3D7D8F5CB8CEC9D61DEEEE80828AE9C5DF252745F0A68DB1AC55E00401BAC43F5CDD5038D399CD483EE03F3ED64F7FE0DEBAABC72545D88DB562F5861607B75D2CC5CF016BC7808BEC4C8AC9334214A2DBF70CB818F0042BDFBBFF5324BA95C8A97381EBF15BD7EA73C3B82971B749B4B61A41ABD060D1ADB0C05C42B2F34EA576FB817DF7CABC1DA3DD19EA9EB4EC3BFEB246735F0BC42AF178B6AE787BC88CAE56DE5DF34767FD8DA1B7958BC226666ED5BE7A7FFCFB545974C45EC849BE5A72A5ADC41B5B11BC2CC6705B5F1E9FB5D77EF737D53A315B1BF9B9050494C4FDFD16B32B78FDE5660C93B5B158174D127AC9F28C47BC7026D025C200C21D4FE278C873AC31197EC90B3FF346610280DEF97B2354763CC570C3E545F7C7F0A500D917B3D9F6478E9D1D69C90D2D330B161D89F0671E29F92CA169F4BFB69C33D44C20796D189E68EAE3417D706867DAB7A4FC93FAC4F2205384CB775E3171DE9BD9317407EBB012B184B55A3E7C7EF818C7253194370C06C028651D2A4FEC9F3D7FFAEE607FA2DDD591CB07CAB248B8A2F6C64AD1AE5D95C892F3AAC886A37C8B800C873520EEDE12F7DFF2CCD498B4D22DA4CECB78204A072F0E4F5EB4D4B58497DC63661BE646C491C3F1F965C5D6E1515F9D3C271467E373A21B66B370D5E9D95892AB7A0631CAE0F5DFE004BEAA4B7502CF8ABE8EDC2A87DE6C4CB126BA71A3F6B70F104CE420E95BCA043EEFC2E0EFCC8D6DE178DF9067A3F85E261B26ABF91DCFC69E9A180370B0039D1F44CD7F3D9368D7D2B48E72BC56A9294F54D1ECF9EA480B6FFF7457A45F2270EB9CE611CC47B3F8081F9542283EC513F2BD639221EE3A055699C7F08C70F767F6B7B182699CE6B3E7F3DB33EF9EA436EF01C79BC719289D1E4FADB2FB9AEBE35285E0CD75B514BA582AAB498C5855044CC38B785349ECD78527387D563FDE923C688B45A2F1FA7B791BE5B4F0ADC556CD04F1D92D284BDECA82BF6F308ED53EDF9CF651AF22083E9CB8A19F28B7594497690B2C7DBD2E33B93B68AA2F96341BBB0E6C4AF15DC0D9306F975C5C051A332DEF0665A9914C4BE6E8AE6FFB38AD3415FDA543EA87339ECEB5BDC909151C68AB63E3DDCB4918615157D125FB8E8EEE38A171786EC59610CCD2AEC1F29DA3B70EAAFE8926E5D587751409819700707C76B066ACCD2A13A9ED0AC3A1D18E755AD969256C0B059F9F4E3EA2816F841A8F737F4F68F77C213316098EC68524C618967806B28FE4FCC6195E29EE5E29B37CD048C597A60CD3DF1BB55008CCB8FD772D01AB2301EAD83917F674CA5C3DE967115A7E839BF319429C089D681E13BD8E29852B4791C5D74ECB618ABF4E6FE7B91D6F1C894727FD58493DE8D939A49BA1B6C33A44F607D62DE4C6AA994B627F85E1D70CB9B5735163CA214E68F3BC1476FBD622CDF4C9902928D1FD988AC77997A5DC954E1A9678D8902E0D8730FFFFE15E3460C5FA24617626264F2E4CE292D7A97F67F39D73AA5B9B1CF2553FB7507800497B1C9F22170DCF33C2C4F214F583844C3E8177002BDA51FFE8123230A9E9EB424242651624E701DFF3C94EFC5B832F2485E8A03BBA428CE032C30A18ECC2FC11C14D9824A70935DFBC3242B7BB84C3F22B1EF2A5FAAC8FDA670D1F5AC6BD11358109801F765A43788E9AE84A1CF3ED521ED1D6FDF87D1A416D0E7FEA82C47AA57C5FABA9D6E1C4011CEB2315EEFDE8E6F7A21E41638D952CB257003A5C5F63B2AFD74705D9E5F53C123A8B8DAACD996D49DB49E6762A7CC365858ECC1ED63CA778CEE2876A32C14188956BDF51FAB241231F119806DC80925EB2389B277ED27D19DC885C0EBC8D5A7350C2F7BCDB759D20B64AF1545583002AF044F78C34914B8D27BAC798D2F76D1065ED8ADDAB82A2AAC4D0B9C960A5D4A3202F05299DBF61DA9FA33E7E2062F06F1351F92A27215AF66E954C956CEB306638E7FAC36780615F18B1FE0679103F116625B4900B0D8A1AF26DAAFF1F798B9DA9B78E5F735695C8E20537B23B4D6318946B0FF889D91EC7476D1AFC49436450633953DC29D47D751E3D6A29D5D2880D69F34283C9EFD7FC94B834F0CCB3054C2E0C84C45ACD0CD7EED9E760D6B2807B5EE6B04E90925CE7FDAEAA36AB6C4E059E41DC554DE7F31942D31526D6D5AB3EEEDF5C1AB9A48E0B62D2AB4EE74EAA04FFBE3582F16F5BBB0169F21E9EBEB9A9FC918556704DC4D3C3CEC4A8A3F58335F2B4E5907EA8CF7335CD687D7852D9267B4E436A849CB8E44FB42AC1E291896AE76A7177E534F840FB09F229321388EE8FA7392D5F6AA0632BCBC576E8CB7D28F79B91004068CEB47BA3D42400E4AF142A138C5FB98105786BB1DFB3E974E3D02EF20DE228B3011B07F3AE64E20B140B7EAFEACC9BAB1B057EB9B34184D5A82A08B63014EF2EC415DF280BD58683311EF26489929EEB875E07FD11ECC8E04412C935E8132A0770084188B8220A23038FA57B22C8E8725FF0CA5A1B5F36059E984A16F88912D79A152D7104FDDC51B9235D48F266788E28C2BFFBEAC41C9D7946869574C44660D797E29B5711341EECB51F37FDB04C242DBEDA3FEAEA6E7CB08482381CC807D85B5AF83E8BA64D0301A9E55941CC22FB7FA35D7B9DA1BBD0D56E41A41435C8D20F60041FACCBC88B3977E1A5BA44EAF129966EE49B16395F79A4095331877AE24FA09E2CBE4B92EE87F991D6013474DFC1722C8E181772AD8CC26A3EB95084CB4DE2DA2F24AC63552587FC0DC3C522E4E3C3A3B552C8C529CFC2D821A1B7758407559C231958A265277F57DE41427065E40CF62FD48B20E906F387C7128BD57C663E50342A06F1D9F2784073B2CC1D578AEBAD7734DCC25789C72A0A3295DE68F209AA5F99FA5091832A0B97E2AAB131FEB95DF0F3C886109DEA2E7B987504FE0F231E63FD1C11E7DBA13FBAEC6041B842C3803694EE8C116DA87BAAE12B33C3E5F8F72462F3F57EF0F1DE7FD9F3B9199410A7CC38EAF607488056F29B01D8BA2118AEA679D6C9E98FB8218AF5D491C49B16ACBAE654DB949E70FB2BDC8437127BD4546BB7542F41CB8D20147F957843AC3377D213548E758366FBA9189477B0CCA0206E173EF813E9C00E74C95FFB9A1A13C0543504B29C426CAAAF671902D901EA737A2F92566C87ACD7C6F1D0E12E1663C21A6E789A401B50016EAB7FC0C187FF45AB8D12FDDC2D2ECCCF45CDED43DB552F28DA89D94696018C2C6F849B547C1C94DE14BD83468B9DA347BF3142A77D70F346D43CDD1BDE59691158887AD1758C0087756AB4CE9C0464B8A4B7A0FF13E5B5EA5610E17DA40949C88C204BEF5648FF6030558C325B4957B5BA82252722F531A378D01A74EC31DD029D0F18F4BA1F3ACADEC3F444882DBB7085CEAF810BE6D8A414050F9013DD23259A155F16B9DD846903310DFB9A46A987612D5210567717FD25D2D13E893DFBECBE8F8B31B41C9DD8DE05F823E3FE77E54398CE0EC9E1B42547143357B5626FFC54F1F9112CEC1F303C1CF20B9C5E11582987A0F83950DDB7A1B71ED44DD9F4D0119582F557A16DE490085FA79955DC65E88030AA338091D6E750B399D1B0399BC48C6179C4FAD95A31D479CB27C4085450F0858774B95C720E46F031FB9EB80AD4866CA2AA2C556E08A56390F3A01DD782AF378ABB0E22302E0BA61A69E1FF95646D6C459C8B03887A409596E98B7EB5615912FB331D7740CB082DAEB5D0AF9D3B4D1C72557F3095AA6BA7334EED395CBA5BEB187F3BE280E0C1C4CC20AEF32274E38423E839104C719236169E580290D54D5DA8F32381C07004AC65CBA227E181468B507E0EAA1704DE3B00FB52858C27906E478F7568FF3402319350142AAD92326823E429204495EC1E5B23B5C3A297FC7AF64FB913EA696F7A9E523F0422395EB6BFF41D89A186BD00A35FD26FAEA084D25537640B48F15D406DCBA70195907E2D926464276A52B15B5A020547AADD454554D595A167F11B231B87DB52E21803A9A77C4CF4E6D9A8EAD8CCFC641579C8481D891A993A26AADE594A00133C892757661AC1722563CE6F6FCFC711CF746E1C9A17DC096F6AC469EF7257B54817B9A0DECCD883966360FA938A03FE8F9951B100492AF52905233F9D57D5865D62F973CD57AB21256E66C50D050F93AA62C0C4F8B517428A87F789EDFEF1FC286E00A31DAE60505E4966188CB57D218F113E8F54FEF45D0517212EA7096A8B86F27A8A279EA5E8F115C3EC121D59ACB59F92588A77EFA3F82741C296526521357935E66132C02169C80643A1EC8447DAB7800D2AA005BBD8DCBDE7C00EB1F85964D99764346336ADBDF88F33C87FB60F02D1474ABE860E109A1AC5696DDE61583A0C20A39349ACFFCBCCB0AE1556F3A127559E70093365857D2BAEE06301796DA7A58ABC20DE64A142517D240B8B3C49FB296BE74C09FDC4C84182F8EAEE5149F0BC3A529FEECA923DEB7907F72DC6FF2499CEB44B6A29E2F829AAEB70A28CBC587551D28B60E2C9DDB65275EBF5EE722CF29A76AAA9BF428EC5B381B8A8498F6165FDE6A0F3F1BE10795E0AACB8A8ABAB6393B931ED21555CF1C57C1FF2423CC1FE2E93A199D6BFACCC63CBFF1B335342D274FBE40A8E8FF89E8076B29E8B73445D25E00BEA7E1F5E77C4C07C19F4DC68781BAB668799E6DF397E1FB93437708DE0E5F3F79CCB5E7EDA827EF06EC877BD39AAEA0F18542A511C7CD749FCFAE1F6B52E4591B11513622AC3BAB2EB035C11C71BB3152E25BB2576A50CAF2E8841943469F4CADA3ED9EDC01834D479A1F6292C06D13E8E165FA9F91FCF8B02D37B5F983353E12D8817995A422B8DCFB54546300F82A9E8DAC8884A2253F1D7342FEBA60489BEE72739AEAFB918579EAACB4C11A7BF32BF216322F23A086637C653739AC9389B0156EDF04141CA4BFCBBC253888AB82AA7C9B5DF9F8B15D4187AF07A05151360FE54D7CAD167FC740003AD080E20E128415888AD2A37EFD821BE583DB6CB4FAAC3D1746AB2FA832F77DF168F64369370B8A64655BFD63172BE89C4559773541792123EA19099CE87AE48845BA9AE62E614EFB10AE2E2BC0273040381823178FB3BBF3EC71DA75F69B128377CD8A477C20B47860F6F8FD6D0D95C49F00280B0E5C45C0020706D2D9AFE2099B9B3965EBCF5060132173C583BE0AE354A425E894BE2EA1237606CC80D799D4BC7CAB77F0EA68B4F8CCB93D0D794618D397BA7384AE7647032A505484493CDC6E713EC9C6EE8B0F27BA971AF531DB73CA9B15BDA3F845FB4672567BE9906F8FD7AE6ED8FD10A3E9555E7BDAA9BCB959A639E4E3C9147ACD4A3BEA6AC7D05D938530D7AE9B81BDBF7EEED21EC5902720DD2E5C4723F4C0BF5CFA5ABAC022C8277D316AD0DD9677F28118E899C3C7B7A4E6BA8D9FF7CAD4F011B53513743ED8677895E68D351DDB6ABE440208E4D264920DDEF9A88A40BD53BEAFEEB8F136468311CD6C2869C573D05317D0B52E9390006E3649CAC1EA24D4BE5042A8EE2E729F09209C84E2C2D85799BB82BF157BA40158DCD773D54FB3BE425EEB321D1D8BE5C6F620D294E8DD45BA2F1A75A87733A9825FB67A04399529F3316DB0088276A4313B26591536611F46EBBD6A79A6FFB01EF3B483C1BD10ED4F4608F5F824217789942C9837429D46E378272526E4700C3765E429FA00385DC36D9BB27D44877E00E44EF9A32725F31DBE840A1AE45AEAF8864A8F92225BD88DDFBF6D35BAF57782420981CF788271F5765C55D8C62D82CAC16D72BC19B00A661E6235A92B697A046CC0387725AC65F90699523CC1E6E0056888AF2509D4427A70A77BDE7B889EC88917CFB622EB5360927C905C3A8B1D8A40A121BA31F31C2C766B62791CF865A6F58CC7DF6130A68A42D1281600C8E5D32088D6E12F845540E730E6D9759641BA92A115206E3E031792514E9744035D6D44D81F5EB308480AAD261D744848EB81687C296E262ABB3FE789E9779F0276F4A53F225117AA36BC0F3B4B297DEAA18BC8DD99CB08029C67393708E9B45745F85F8B5EF9C4A45F02472160B291F466E073ADCEE50AC6800428E70576BC7DDD7FFDED2DE4DB152987B294918806C55E4EADB55781C743D57D602A0EEE3C0196D8A97777E04314A5CB2F8628D043E03E37D54188534676E775CD60F970E0591DDB74D57834FD23802608AE08AEB9A2DE59AAFF4759ED1BBE11AC7513C33ABE793834E2731249D9271C9B756F049883EAD134342F598152CDF7F7B21E14402B9B77A29132A474029DDA58D8ACBB03A5D3CA8953FED6262BC97796F714D7B37FF5482062AA6C4E7435230A55755ACA54EF0C99C1943573CBDA5CFD8128A935A82FCE23770FF2B26D8C1D997291F078C6F8BFA04A8FD34E18CF30556CD9DA91CEA5AC55FE11C3048F5719A4D25E42FC3FB9C76BBA77DB4475CC453A1472198730FFE7B59E30229E546A1825C4590A6DF954D776510B91B66B4A8610B0177EA78CEE0DECBC3C41072028674C2E368EFBDCF551932996753EECA3F1EF3752B6CBB6466C5354E7535AE7F10CC63A7E5A5D29BD7376E324EDE93152D41F8739083CFEA1BA8893B8B9506C34B8077FB691B0B389183F8FB56D70CEF00C1E37CF8FA0088C8CDC2F0DE641FCF32C5095BCD94D710BDF16D900C97B7DE99679FAB65398855DC458F402CC929FE1405930EB5A656A06C48B6814FDCCE580DEEBA086234D5C6D3BF01243544597472BD6DDAEFC5CD536F02DF3AFF5C15DE69C584853D9CBC82DF83D482F9C5A24C82B6DD65BA381D9274F7A41EB6B449A842FBE9D890DC2A502228130D58414A132195EE480557D9539D156267EDDEAAF2902DC444D9C4E271FEE2BC948CBAA7D75E91FD60A7D9D5073FC39C724E2933DFBC9FC70018A9A51EF69622DA5F19173987B09020BD6691582DDDED7FE2D80821BD1BADA3F42F87EEB1BAC7393102E42D427F4A574AF0FA6A42BC2FA09857D9BDD054DD44465AB9D207DA10B8D1D4145C7E85D6BC96D9759A5FF4FDAD018FC9C273980E465F63B3193BE030BF8B5A5F406A313062194C56DE9A3DA7FA73F4B2389A9B1330157F8EAFE8F008ECB710D807EF71A2B4C7645E86C923C6ED129B0A44CDE7D063624EB20DE5F8C677D21F02602BBA967D39801F6AFF757EC78C73A7AE68DB0CB97103C185EFDC34E7CF5DFB47EA5361BA4A721FD9A1416723D3A1E91AE095D5CE67ECB38C74E0F05959C1AB66995268768D88443DA973A624903666DF358EA97BC3959A53C9EF428EAFC94C4766F7593A811FA889109863013F7D7BB76F9F22A609115DF38CEA95361155D60D74E6A20CEDD57F5735B28D2188263D5F2554E485D6C12E0803470F0076DD3C9DBAEB287DDDA10C5325F967371A613A9CEB0C144DF390F99C637FADF928A5519DE9A782C2C2286C3AA2D716D05E9288B14038A5C77A25453DAEEAA649DBAE", + "pk": "DAB85733AA12D33FA70F198A1C67F64257631538EB2DD03F770D3E0EEEEA72ADCB3C177C64C1CFADCDF979FE586D19ABED735F01EDF2CB192CCB7599FEDE6E8B0F073114C85DB4B67B2729F9D435657FCBF01D73991872EFFA8680A5C46E0DAF8D05BF246429CCD6D5A372825BD31F0DC24E78AFA023197D055C856BE4AF4F39F8C8ABBE94C2E1D9B69E9063B4A0E22E448FE2A5E61D09D66D30C619D3B751BB8F3E16490E85CD69C348F9A2A1DD5F12773A6E5138BC8725993C7D58EFF781D32E96704FC18C8FE3D8C956D4843FD1D5CBFDF1E58D8574E2AF9909D26B6EEE0E2BBC6B6A4CA8470998052CFB633A86FD7C8A12C2BD374084D69B98801C87A4B98C8960C6CF3C3B72CC5C1245F4C0EB49188C7C5829733E455682BDB7C0931059C338403D31DAAE271546FCA90BDEB5900A30680AC9DB04EC12C133B41ED813B0A052FD007111943534DCAD1EF83E69F62E49BB37A2230724B9ECD7BEA44EA6903DB57899568D8E4C30FB39D19521F315B04EA15942D5BD93649BE3635597A14BFEE2A419A04C618A1D2F8F6001F3E818A294AFFF7E14E460FE6EBBFC2EC93E68CCCD44B7C4545762415341A1CD766E8448638E8B3383DA604D91FC34041D598FBD177E349CF3376DE33E533C18D79BB6BDAF22A0D25ECB6D177847B5CD02C55F1F58AA473EDE7F1587AB0F2EECEF86AAE8A982FF8EFC7A1F5575E4DAE8AF4D788B5D502F350A0BB4B61BF5C903977EF40ABA5CE9DCD58EA266558EFE4C1F375F3DE08C353C0B72ACD16678FB48177DCD66ED39B4EC6A4B903B5A71A20A07FB7BA8D70894004009211EE1450E29863429C2869C6C3187C108BE156E8E023797B52B957DD64AF1638A8612B3253355178ADBF928E21617E6EFACD1DD2020FBBE28DE48AD66AE0A879CF3F5AE5861EADC060E172CEFCC2EB604F1053B4644E705502AC230ACA0E38167235D3E3B9784294E9126E1B5C14CD26075DF6B5E68878E7C69ADB7AB7CCD9DDD5F9CBB4BB2AE09B6FE9930C50A7AB29E0F11584A5537FEC5BE4FC1307AE00BDD1F90C1862E72601015A157283B0B0A821D7719801ED997DCB6A89CEA7596E159C554B74844D8250B6E9A7B58FCA66BA5A405DC1DE08B20ACFFD0AA488ADEBCEBE0675E4D90D03FF70860E840F1C266201A88288DA1740C13C68BEF92AD4A35065AB1336D1BDF3F533922A6B14C5E668327E11C89D092DF74AE08F4C59C9646F4A7423C729917B31081030237357E3FE204CFC5DCEA4D3B02DEF706E2DF05664C09291931C375D1F8D0F35FAA9BADE0A42C161F3D77348CA9D8A155535E4D88D9972F44F9B60D662F4265920BD276DB1FF2D7E1C8AA584207591CA3E363DAAD86DEE4C0EB30A7F72BB62BF80C5986F16F8C940EF3D8AAF04194C65E6BEB2A9A5BADC3ABC1B3949FB280466D31366453C914C720EE69C8B1CEF9B8EA58425F0B806B7F72803B55E7C401BC2F7317AD2E4C355010DB06C37F21DD2329D9908FE0CAEDF70B9704FFE5C66A52B1A91FEE4EC36639726A5B18C96D0B18F944B9B262F1BB1DAF1B6D5C8A3283A19B9A145BC12B180A3B37323429F32265DC65A9290CEAF7A3B4166A69512C6E4E8729567E8445961C97E3FDA54324219E1D7D4532A0DC856F0D749E960BCF837C9471383E92D45811EA6969E648E57AF44ADC20C23885559E597FE345E12E0E588BEEFCA0B9EF64470EB977BECED6F753C7C20DED1CE80D7AD2D67A4FD1794AB252DEC3DA0B13B9826572B365ED05BA350EE4BC74F3F5DD460DFAADAE1C091AE0C9C1B8AB8A4C6602EE169FF0A09B7341F95A061FC443CBDC44C111573C2C82A8373B0C369BA3FC3285BF8B8152B635CBEAD46AB720559F15BCFA6DBCFD912C9F9478244ACC1020AC83142D37BCC9399FF696688655A042CCD7BC001A9B638541E80350AE4E0F4F3D81EFE9770AAB800D3CD76527C706A4FA33E1B6CCF890DE7B38753FD9FD79EBBB6C1E3C92EC3124B1F5B211FD51A1C582A4907806CF11BBC5EB2579C6711E1AC8C5DC30222E5CEBD825427FBC79B4985B0D891FD6BFBC7BF509891CE8E1B1A44AA4F8B69A6437D0535E63B8F6DBFBB70EF95417AC1678D153B621E04A9E97704B4DF8B30D590148B7CE0AF7A7698C5475D7CEFF842A978BDB1F1533F02BA6FEA4ED2898F157D04FCEF799EFB69BB547CF2C2C0C9C318CFED5BC17DE3A28A4A7E5E196DDAB58C551619661F9938437DDF1ABA98515B0C31BE69E003B8A154CDC3EC471AE28290F0E1B307B6AE834C230C227C831741FB4F2ECEFE953B328F80189A8ABA1873F615869778E16AD858E97D2F2BBDDBACAB493A847D23AAD5244193671FFD1C6C40B171664F706C85D06C9477A7425140706C82111893DD73F16A1596A8C2D3299976B6969358DFFFE4C8DE14196ECC269605ED8D785AC45CEF1C4971AA33BB43CF4795919823316E806E0646FB528F836422BCBF2E4FCD4A4207EB8F5A5991D8CCD5398F5C62AE269F60F1647194765BB558B5B64E33E8736E154B44365FCFACF507D0CA5D831E985359A3EDCF47E6DDB666156C1F8917740164AB0DFD1F8E2146D6FD639B7E41811F5BDCD1CD7D8595D3BC26DF17BF8A94813A769FCAA466F71B3685302507C056E87925A79235BAF57218A9031962AF3F52236899905FBF5ED642B4D6A69D9FA88857D8890DD9D576E34D6220521ED56F2C100D5382F46AD0340A2689B830DC448C963FB22EE945F3A763FD09000270322766A4D75E2EA7382B7FCE1EAB87338BFF3F9D81F7B3907EFB0D09FA57B047319C3ED88FFBC824F465E151054FBA863C5CF2958B4DBE2DED7F46B3BD2C923E8634A7955299C01651E4E6F02CDA36ABD4654B71BDEF566B1E9F393DA952BE2AC28E95D9D8579C6B9503D44AB3D616C349E808666DAB7C051900576DBE1000F577F1DE6F07FC353B3DBD9BEFB3A5A70020A2469DE2A59AAD36E58C54C3C580944A77D92DD4724F9682614E2BD0458729763E9C58037DBE758D430A9B8682E0DB02405C94D73646344AC28A7F644BCC75896F066A923A4C36399833730513F59206CA81A7D6DE53EA18940213E8DDA0A296236B0E3A795A3DD219C1E1494904A02E8593AE2CF4012099A92051542FABF24AF53711170BE6221A0830D46BFD7704414351506C13AB24EA3183C616E6FADE738A52787DC77E649276C6D9D653D29E76C3E8829E72CFC60C230743C7AD8065861A52332AC9863F1AE93E37134E1AB9C5AFABF7ADE457B7015AA127A1C49A932728B90573B713382F14779F129424A81E4D60A4E562E03BE3629401FE1C32EF83D09E62CA8F690DBFDDEEDBD115627CDF2CEDC75A865E8B3524951853ED0A8DEBBFE5196A4A918A8492F66C094305C0B268B9F4FEA86A80DAB859C268EE6CE65CA4773F6201B62F7D038E11885DB260FE27127729532900DF30B1451C07E375541230DEBF1F0FE73A236B2CDC21FAEDFDC097DF0460CD24DD2BBBDB79EFCDC8FB49D46504FE6B047103DC2D886D851F5D59EC4CA17CD37D628B86274F3BE098ADFC2F6DA486EA81D6F9E093E052908181DC93F3BE65C547CA0600D3B2B83882F15C1B908FDF775517E9843CB160716DBB56E691806115B1C3C6B3C9B427FE2121E1B20F6D12CA7C576E80F83DB2351FDC930E3B10940B981502E", + "sk": "DAB85733AA12D33FA70F198A1C67F64257631538EB2DD03F770D3E0EEEEA72AD6A5460801686C1CC5E2CE89AB05DCF4E9E7E6395F4DE0B8297693C28256DD5F4A1CD574689DD4EBBF387AEA85542E6DF3EB885F35EEC378326983F6E5DC42DA67C3367E76CADA9D292EE20D7D4262FB05C8D0C87F5BC269BA42B10FCB9E611924332401B8741D30482010562D13201A03042CA1469D8926CDC26112119908A104203B131A14868DC866403B44513C568103466CC202AC020094C900C01434A00A97120232A58084222078254248288C800D8A49003B291CBB24DCC108C0481095B848101B4294CA2219B3644C0282A1BC3888C3684101522A302259A486202272E2244311AA80483C48DE13201110661DB28221B2760E0C66160346A20A88810C21152C828C3300DD4B08C583286E02424C430244C8405E2C00842C88861200423434DCC143214125299867058386E5C987141288589B281C2800C1CA3101A466191940591229248122AA408060C37715BB80D803446C9C2700A973143C82100851124078ADA06515B384912A268A3C06C11818D98846521488E130462619851CA267003338D89926501A92C1BB4285A1622E0C825E486045B406C0B278E43002A032528181849644690CCB8811908928A482242A0710C850058240D18367141A46589440494002D83882162B82DE14810533266082610A3C48D98107202B54D14322441204A8C186E984246C80492002804DB164A9826004B026600C3000A2691E4109112874CA0060D1312495C00851A2165C4A220A224464294106384914346854B488CE0B228141506D92891D2405098B2715002620CB14D11286024458C149708181524831000CA4209A184219CB0605A2201120820880886C0100412B88C11460E0B01098A3011C89208D1180240C84C59468A22B98823A0701B996058444C5CB86090C06D5834050C458511A220624662A232044B12841B1672A108521A4232D9C84CCA0644DA46918A465199A604111388618600D8964443064994B48DD9C86C9946011C9149E41042D2205084B8650380292091854338484B16698A22005C1024D4868098A41119A8891B234903C62491869008211189164E219245933286C2060E1B1700E0302DA1A420A0A05040868CD22824C192059B4052CC307091460D0BA56CA03846C31880C20821620206E1860003A920C3C89080462A14016EE118061A384C0C140E9B0048E08851D4088D08B265CB18401BC2605B488E21870D81A2689B320604B69103374124484652161202A40059320C002451A3B4004C84315A383099808450C005D2142501496A51B4500C82909A1022538605243005E000010A02628144128A160D88A43063060A20331088A6519B468524170859C06C0496814C04419438250BB3288B446CC420809C342A54960C0891709C142A01448A00C26804366C6194814002710BB98859848CE304308092051B9609012190DB2626044645A22831C824061A3821920805009945994080DC422094C08908106852C08109994C10428962B20C800846202688088084E1C84463A811020360230429D9244819336A10102163C431C010260913429C221102998C20B43023254A59480581C084D1383223096AD9C4284040448B22121BA361E3486E1B0370194549098229D9C64042A889D0A0008AB22DCC080D122721884446023644D8944840366ED94084E13690D312651A2108DC822914260653929148360552C8919C342CA1308010A711883442C9862CCCC60521980409A64910346818276E21366CE3B84D90262904B749C0364218482023346CCC8631C0002E93309200A62D81A630DB286A4BA65143486CA436002016099CC4898B484640446A52C6105B4641084061D340318CB2602234801A860CCC44421C334A8036240B9691D41850CB84002425900CC32049100254244A90A0451B05518BA831DC12009AA631A224511AC99144A489502646C2202020148E03126DD0A6699C0804A4146E48422C18840013B08D4B463158C60D8A120C134902438661DC249221906853B230D32264E2B6410C1121C0803111A9508306111B46815C06694C424A5314681AA81020990D5306260BA16400B611CC424A21A92508308A5CA291CB224620C684D3100A2232861C120E1BB9881B841144A22922A52D4AB825C98084C1362E1BA4250210920A370A4663A1326B6A156E9CAA037C820F743EDA3CDAEBE009E2E916A32B048BD82DCB2722D58DD4CE20BB90D470ECCA047AC3644D78467D0F9A9254BFC085D80217849BD1EA5BC1A48F60DF57AD8B26F85C4520D0C59B45393F4EFCAE2419D0178A75715B68CDD30FE97F09DCDB551381267EA216C5C5DD52DAEE3ABDCE19CC1A48ECD39E6364087AA5782D2BB33D30D604C0ADF9B08617DCCAAD1D71C89AC0DE1662839A7CD50CF1F90714B69DC50F3579CDD0C0850D2009A1D9F581D4778487D21E79EB44A62802697A6A2AA4219740D77FDF37E2F0781989797F354C39B95B79763FA33651A97AD487A4CA89304F4F4E189848E6B69F899673722A1F1DA21818928DD626807981344EFADA9860EB74A0B1C34CDEEC66569C4F643C737D235E41B6C3714535A459C88EE5D23B0EE6437CA9C9F25496983759504444AEACBDB9515B8527C30270C141F227C44272A0BB4215D79E30EC5D68976C9334EB6C12EC4FE241165BBD760F0DC473C0A6E2293D050D7DB38E8244842AC4FA2E86B0A07313D04E26722FE111F4E407EFAD5BCABDC3BCD21EE9774F3A0947D7D62B6912AEE8B9F3AD2FA40834668DAE10510EEE78D7867C6A71A7CF1B0D28CF4EE93EA879C9C940ADBC1556DD8B5E0D0D5E38197C04E4FC9CADB03C8E013B17E3727C3A3DF41ACD604125F067708B42F2BBD2E4F938ECCB13DD9DE1175E66FC47CFDCD12B6DEA3F42DE4532E05435427F9B54F72F0A3E73802C74C2316F97F9102EC2767A9E7D53D9B9B74243BEF8DEBF49E738650784A0F4DDCED875A5A52B811748B9E6A842AEBDDCD39673097F4B10E5C24F65E581C3A08A46B09CF2B31502F4506CB58C198BBDD739A0E31CE6984688281428DA0484B1DE3AFBA14B9B78B10B78BDA6D743812C4B01B7C44762971C8C2F8AFECC9917B1B18A1F151298996A52A8DC7D2E2E1F67EECA0DB3D409111155563B9A0EC2928E1D07786C74D964019C0725DE21802FB5DD0717CBF665D1FE0413F2C7B93033C1A16BEDBB8B19B48D905E6ACEFD79071B151DAA00A4F71D984D4C3E712D3C90A4ACF924C1A2C0CE3260ECA06B38C10E2FB80A79E61ED055170955016B5D13F2F74DA80C5344B0181CC7F30264C6ABCE09DF9B0C0686F79E52CCF1D5EF0ED66944656250938D5332A96C497087E9F676A2F16A2264F9FB3F033C6D506996D2B4C0372FE543E627D937466BBFABD21F74ED76BC7B1D3CB3BB2540766E37BE52913D6F10A41F2459F064D1A857A39110348E9BA3ECD4BD6F49B417408AA2444047A3D34EAD3F524B46147FB8AC829B682DA02BCA9EA51289C33ECA6AB5F18F2DFC56B8D1A100907FD524115397A070D96CCCE2127DDBF8A56429BB2C59F1AC65089949787642C52E1B0E789732F4EDB071CC7FE4523F236B1B78F475EB1C4AC1CA0F54722B18B78B07185314E64B139765800CB267DF85911810D5F3388BC5A91DDFDB59D348D770B2614343265ED253C7DA168CF7F53865C7D2CF85F78B94B78D58F415BE8DB0C2DA275485AD6FB3C75AA51EE83CDECBBDBA4693A6AF55A08C7B8A3D633DC8C415690E20A10C59CCFDE1B41F7A196DADBD816E1F1429B832A3513B30D0AB57FC1AE0D20644637F787A92A2A93DFB4DAFA814FAC85F7EB419E994EEEE6262586EF7D04F72F65936087D62FBA175FE0331D9A195CDB3E545CA673B9C0106CC92BFEEA595B5BB562EEE506F4801DAB92303279CC782EE285D30FAA14DB201D082F46A6441867A3531D9F77C24B3CD4C23A2072B3B1470CEDB2686CD136C88DFFCB094720D9442B0A67020854C99F5A166F7272456EB6557E7AC6A13AA81C5159DF6309CE81B89819358FB1547580315297ADADB8485D62098A3BB32492405F110C11D7D613FBD3901628214D13873310D478A0C34A2FA00E2DBE09FD7CD81AB2BE947EEFB4D3E17AFAA8839F235765D12C3D4D9FD7C55D4E1EC3F7DE3F0B3EAB94FCAF9E28D7D08E5E1CCD87CF17E4B4899C2BD7FA7101BFA96333556D30D0177F8924D63C5F819BCFDAF019A60D79C99D8812E1ACB9557A5BC0AA94401B9B5C06CB0FFF0A90BEE88292B9D7EBCD34C3E47FFA52AB39C8A504CE27B91CD61DA97BC20BEA06136FF9CA2023A10A4FBC3B034C5F44F590017EF6688721A85DC985666E2456FE1BA5E0B201AF10B07EC48065669D576F34B2D36E012FE41AF5206FB99AF557416A839632880554BB6EF5C4BCF46D442FBAA52EDAA4DFAB3170F32BAE5543737DFE36FDA4C9B8F6EFB255A7929CB4ED0C892070473DEF215AC6BEC121A0ADB1DC3B5225C0AD7B2F75217F99432347260FDFD25BBC7A6C3C1E6425326097A80C09A28C0902B86C782BAD17CC5DDD3748ABAF97EB543C164B6A209E447256BB8BB1DC645C0C17EF0081B4424138B8BFB2EABDB769F121AB2DB488298EDE59336848F25137D9A45D656E04600ACBBEA0AE03A1969E6FF6E54ED2E2A7EFEE6AFEB3D20F848739B450B9CD06E2E4936D360BE358B75C261A8DEDB16D87B16534F3A181E6C23C04F3CBB78E9FBC1C0DBEFA987E3DAD1B51935F567B9A4F22F2AFDDE3EF3FFD8E41312F2B06F524F65B28EC1F94274EB5F430BD01CBF25CC2738955AC79B5E375DEFA83F3F3700D4C0C886DC6D840770406947945C3BFC217CCC081A3158EA5743EB4B230F6E9B6178821B22F80909B11B2D509B63689F46AFD16A946F37BF072B803E0F8BE7F183B0EF6C5EA9282CEEA01265AB5DA3F2E0048F182035579C4B546FFB7C9BB3D25937D4FF19FF938B74ACA2E84C87E8CC2A07025078961B511F3DE70BD6E985FEBC66BAC2B36F624E8B1C0F9AEFFA3578797A77F6026A3A7D42FE3E930E91A121644E8897EDF168FBB3E751F8DA51B7B2E8004D7C1D721284F42361D47400AA1B362C122352685AF7F9735E51D4D354ECAB1C37F04815AD77E9039DA36D49D5D3E5679BBFE55B0AEBB04D0A42DBEA1817DD5A6D4692AF3DF0432538271957691DCBFA56DD87DB7670FB1CE24404C0DEE4B1A4035295419EA3978C7413F79AD1F4B56BE354F641296C0A9B719F9B3CBC9F86CDD8898A677143B67A717ED57696BEDB455C01E4BF322E213DCAADE6643B91CE57FBE1A8A8F27F6101515B039B37A0F8CE668521CB1A90E4BEE3E1EEDB3F660D21B2877A1542C385E71241C702513CD80CEF406524853EEDFE297ED4CDC3148D656E385757591BC55B926266BB43939647B6F6B4CB2DAE5BD669BF9B921A2AE07AC222A1BBF12D8A6D4586B1C1733C5716366EA95080148ACA85CD12769CF7FC6722BAFAB043A86DAEFC9BFC540C4D5F9212047F3FB51A7BBF857526D48B3240E12692C08C2E8E1A102B50C35B40F6D30521CA5DE8D9B1D773646A17DDC0FABFD0BD7340A97AB7450AD9A8D34815633959A32753F407F8E84759A1BC84D49E7C8866C6B5C4B9B1A903EDC24F402E5DE8D0124E7B51D553B558007B4623F68240DFE4969A075727E5EFE7F5E4EE7FA4B5E97569D6ACC3EC417CED78230277ACC44D7892EBD7A05944CE9FCD36C4816643CE1CFDBD6667749ABDD7872F7CDEEEACB8BB1C1E49D56E45E3C6D40B7E0124CF1B4A70213566BD2D136195216EAE2DA5FEA6ED7174C3095B3DA9DD53295381C4F1EE2C08E0FC988BEA7E320BC297ED38B9E85AD7702CED177775DE91484DC695F6BE4CB544BE4E9CCBCDA4D3584318E14438403B1FFF1FD1A4CD0C3C347B40D0F849E4BE26A0AA91AA424FE862D11A8AB7658A491AF0444A77700B583F2CC68B7C2E7D66ABBCBC7476D8ABDDF17721249094DD17AEA884E8797F2A9D62353038B56584EB369C8C98C8F77D7B51C6BE5E82539E9EFA5F2B69B36EAA74D58C34CC05C2C94BCD6E00D6FACE45C031BF331C57B16A93E72B21556C14B54E1179876AC32A08708E790A7AD870CCA3750B4DF94342A72109E9B7A227DEFA13BC9036BC22CE2BD597F1BD1A161AECEB147E6EE3EABB97E46381B6763F186464A0A86832C001B6EEBE8322B672D69672F3D81B5CC447215EFE95FD41F754A47FCE29BA003835C99D106EC796526AE776CD7765FFB6ED9FF2E7217567DAE9B064867E61BEBB2CB3DE429B2EAA0A9FBF53329FF75F5A18EFCBC58C25407AB4A58524F90FE1A9BD23460823D398ED40228D2072EDB4210D146A51F4EB11F7E6F00BB2BD8F19F77F813628222DA76E4CA9E4EE5B595A1F78D76FD176FB58AF172A32358B99D0219BBA9864E11AEEBBBE367623B55BE9FEF16274E73CBF361C600C230CD9DDFB1264E35818B26EB13BC971AE65C497266E6617ED77891B1454344487954D9BC0615E5A78C2E4373D095767D838FEE941EDCAD121C70C030457A93842B6C4CDB3D278C416903286B6715D1CA534FAF2726C36B92F5143C1AF8CFE2A7D27A412D90ABDCDC517280A39BDD97811D49910731384F0CA7ED90FDE71073581E0A0245E0D198552DE9459D3ADCED1183331E3B8CE0F6F9A8D946B8320D7BBEEE18004C9B022C3A443E4CA8541C0E95984FC5556DEF7C6548189BDCEEB219478FA703CD63E5CDCC2F91C8C5E489BFA0C84D90120A281D6F7E8F562A256A0031D6CCF4D3EF36999E6AD452AB8FCAEF0C641AD08B6FD24899CC057105565EF7AE05EB6DA998F6B1C9B7BBE525A35EE8898BFAF48209A20671DB22ACC214308B82810B18A51B4D3B98FA765D1951A18A5B9D1498B743F163C353C772DF632A792", + "signature": "6AC43640F574999C1F542DCBE562864318F511D07327398A18666A22310FF1075CD07279A3AA0F283A902F0FFEE76D4E953B34DCAAC0796F037BB6C41C9F4D3710EB01902B56AD27E5781B0AF6953E3C07DD4ACE2A9F5E2FF573255F61D542A7D58ED22ACE4A51899A1BC0B2C048D6B8262950CE4BA67C3FB032CDD8C2E154893F62EF5BA66523CB2274BB71D11A0AC9C87CD05D42ED5C06EF39639FD15E01E9F7CB8DB03081AFED2320C23D3BE8EC33FA9ECE48B0983BA85F14E5577D662C0C220152B70C9A22E4359422F0A32B310BD37F25AFFFD6AC75DDFA83AB3115818E0E130FCF7A02CF7819E2D673F1C2616D295A1254C4660AF910C472FE0B656FDBF5E3C9901AEB10A47D01045B7C9AEF0BA7DDAF33637C3DC8F9A2CB3789B78FE5805D25FF5FA3FA14338E37C9D5ACCAD080362F33D5A1F1EE9113A61512A6FDB417F7DCB4C0C6379DF58EED1E1CFEE4A080DE8B7029DAC41747A99EE0C9C4F0C990CF938C03D3C4D37979B452DF6F032C08B8F7952EAED14DA3EC3D96B22983AF62354514A320B7E4430FBEDE6791293EB57DDF7F67DBC0423198EEA8E5F28AED1A5DADEBCFAA6FE8C11E42B8AF26177AAC68C66CEAAC6B0D164EA8F9A1229AD105EA064D6F5836E4CC663E2A1B99681AD882EE25514E08D73F16D8B8167F983BE0F1F389AC050D693522D9B30E12E593AF748080ADF3CD20D07A0FF4F9389C51DC59AB9C27ACCC96B0D2B280E57E4E277B650EDDBE7268ED5CA12703AF8118D9FA6181A7ED184B05DD38549CAEF2311F5E8DBC43516BF2F78E43EEB4573C8A8E842BAF7E8723E2BCAA1F6BDCBC40572B88824AC60501977E9F8461806EA461DB057E8AFD8763B6E62E6B72C4FF59EBF2B3BFFF90011F6EE1672580D3BC4AA39808043E8C7BBBD1D3B5966D1B2A19F0F6CC8D1DC9DF812C0BA648B28033E55C218BE25E07B20B27FDA8CEEA88D2A81C2A77262B29631D07D6829EDAFFBEAAD0821270778DEDA54953769825EDF3D87FEF102BDCF5A0A22CA89A540EACAB4820D5E1EB55C9FB116B63531AE1D6FD54940FED1C66320314A5CC25548CE53AB39BB43BF3D87D26A4BAEC7BA626C8B18EDD59F79536E5EAB01906972E72C7FF503420D7EE4A320609C283D59E7949701177E72B77F60657632024CD19B4D007C064EE337C0446EE8247023B9D4B9E051B3773A3AE8DB7526C566F7F40E280DF800F1CCE7D1B819510BEB8DF3A0C6FBCCF734AB8FC0013A769B2EE5B76FC5D1FB3275853B5DDF6E5721E88B235547C8D6A4A1A4159A29A89CBB9643C3FEA0E8BF9F048B95DFA8A3E166DE901521D23EC9702827945016E0DEA2E73F1622A4F039687E16A72450DA85459345CED391947416612388737163E415A14AA5279BA04A0DF09692B999B61F1BE82B96FB089D2F6F685BB46AA9ABC23619AEA3C6F67D1CE7CEB2F636444980E6D9CFEF65CC7850FDF83EAE3FD9484D16A84E1F324812B26AD5E055B2A0FAFAD18117DADECB308458FA1B7C6A109247083E406F070CACB8929FD2E6063AD0013641DF8B2BDEABBBA72CA5F8FEAED0FC85D51CC2D3950FDC10B211AB1D94B81D8FF1EF39EBDDAEE970C5D1945AD94FC24259948A7D8F392B333263C39C1E42FD90FF0B7F3819AC846DF131EF7150D95D55F079313F34C3D84CBBC6A17C47CBC3B64D47216A927BAFEB1B0BAEEED37FEF133BF804066184D435AEDE4777EEA58DFD26DA9C5A6D5BF684607D507F447FA9AE264E836786973376E9890A10DFAD7B33F6873C0126FFA21411E61662A4854B9A4B31C37A8C1D65FBB9789624AAEDC5082B261CC5D900AF0EF31C074A7767F61189AD9F90B4DAA68CA25A50B919603B50DD548D8FA38321BDA2B505CB15C7A6DC9E570DC1E28029852628AD5681C1AE62A7B22A79B05CECA8CBD0B93C2B7B9CD5EDB45D21D09A32E34D03A680066946A64D045F23B5EA81695568CC1E02CA938B80BF92FCAEB22591FB01B3EE477BBD7B38613030EDC2250C834BBFA008CD3AC5B23F93EABA33A0C16F9F0DB6FF58E1EFA1C7E73065055530AC5C8E54A2AA512DF3E2F526FA96962D418151252473C2739E3AD79A62352F5BC9F07ADE34AE76CB68C4BB44EE8B4FD7F2A4E2A0C12165313054BFB0AD5719B508DD6290DC64DBC0395191CB5C3D27EFABAC2CFC81468DB141253C2D1ED8C10106CCE8CC8248A5FB5A8DCB38DBBC69381FB058F1B11A5933F69BD9456B0FB2083E382CEB84518E80D25CE10309F90FD45C758D1687B0BE512B4339585EF74AF9F3350CD1D5C40726753E1B2AC06A1CED3713E2AC1BCC9454B27E5789D3E42BB09C27D5ACD26C3C35816D51BB69D32FBC5FCAE8AAEFCE96ABE8C53936F65C1CA116863FEDE2CA5A574A6A7F6A1540B12F8401001A6C1157B873389C397ACEFBC3FBD5129970738E4A7554D8B1B036D8D7E37972CF1271A402D92FD97680895011EF3FCADD1E8518BA3F0E6ACBD3C060CCD1A1692C6134E88D5D7EFAB726EA0286457784AABD98229EEBF7B0BF39ADFF249117622621CE00181ACA46D2E4BAE1C0B3B9EF4BC2CA340A8D5ED1B2255BEA6C8C31D49FB8F7ABDBB3B2A9ED7372D161DD9B1C3470F434B8C675A7C44896D96EE89EA89CB4D0E933CD7B93CB5A5D2B36315EB865886B7B33E5417866CB75406FE24D2686B6B8D2D5E6F9AF10B1B95476FA455D52FF155746CE28196A9FDE3BABF9AA02023CEFA012DDF9DAFDB1F17D49F87425731455766F69BA23DA320E1CD875F4BA1BE1B3889D888284A0AA747DFAE53198B050AEDB2EF356FD8C9FEF3F272A49A3C177DE655AB301545784485B572F4239549ABCD586291E9D4C70DE6A2D1C70FFFD0A1A8D90C2EC24BFA4D35EFB19D95F0A0D6A25A79C1430D5B964AC46EEE859454CDCDBE699303A6F2B40D8C8BE7B0703B8E98D904976B985423EA9F7447A6215A9129CBE2C56BEC4DBE2D41D718E8FE39991A721866502A888AF47B1ABFDBE517C7992E8E9276D6C56CAFFC137BC4EFA549CE4BD12F62AC887238532B98BE42D5A6EA833A2A62FF639363747C816BDF4F1B40185502E599A8DFF3A1809B1A1352271EA48874E755E3A7B588BD0538BF04A45B2C13ED8BBAFCDDEEB7D133F29ED7F8527608E18DB042E1A30A14B9B952B04909553C429DEE2B3FB4A0D03EB889E460463D36D07A2B76BE4E4BF5D5117A73716F693F7866679AE1263A3BAC3FEEDEDC8E36B8D83409B2928B28EFC368313F0FC6DA3553036DEE1C0F68B16DDC895E228955E362DC306A07B2FBD653F0639FC2055460FB1E965C1204EDCF92BD7273BDF40E664D885E6E50BFCD81D30F10E2CB00277DCEA55106C7B5DD24DE3A5FE47F5FF22DC349C26B936F7CE4A1D8EF9A3B77679D1F54134809EF162704425ABB8C231E84E8DFE0D17E0B237E6A4C8DFB85209809201491390D2B7520C2692F8BBAD970DB8F4DB47D661F3B099E5DEA28B56FC8EB6B386A8E943D373F4EF116356F0110116206B54DE89D701ACFE271E29D1BAD2E8A940EB70BBCA640CB5DD8A8DBC754530F57C9173A5CD4679A0424D8170C4B20191743EB5AA71EEC3F4BE3EFA58EDF478A91E5805AA99AE231B44D2F0D5CC0C72B8E37E83A9FE7857C3B96401430CB3DE4F48EE5EE859970D35D849CA80BE6C568031B7CC5044244C72D4399EBFC37784E3D2E1A12BFB35AEA41726599FF73F8369DA0727873FD22356F0114B20E84D74AC371048206150CDCC84687CB8D4718BBAC8589EA3611A8A45410B51103E5DE3264B4D19A7F946683D5B245FDBD66FE09AF1FF5142C0873C9068A963CAE71805D02E76844E7AFE87E32D70097E508D89E63B77D4E152802A337E49A0B37FC5BEBC723A2070B89503AC62C25CC9E9BCFBA7B608726D1455A4A512696D4A14C5C04EC16CE89E1EB9F3AAC49703751EF1224948465EB054188A3A14EFE557581F23F14F00054DCAA45C0F1D0D604C542A89363341DE096039AC0BFC1F2B7E37A267C1B9915552161A3C7EA4F91FF48184419EA1F8388F4173F1C0759FBB37B2B64CF117DF767520A8E4F151534FCB22EA150FA8A7C2A2BDE08D4049E26E451F881578A083F96DA90FB3612AD20B6E3A17D010D4919BC61FC4E9435C175B70A5B398E98EBDA8B5F2932A619D59DBAFC5AF3FD5700E2DDF912F66A9403C36E087304902ACD271848AB64CB4974980F716D59845D2EC53AB62F8C5EF013EEE06CA8EB419EEF9BDC16FCA8F8A091180095C18A19BFA2DEED2C6D3D546FE532189F1640AEFDC1AC6783566F968B36048025CB6E1A53E98C784A0DC0B55ED1CBF08E0F9D1AE385900F16B3EC88AEF4D0485DC3C38C984F143C399B90D7D418CE04FA03C02CC45316A8C45A92A7B04BA8295A926A7B4D54FAA7452D4BE8DA5D759983A7A9973F7F09B5D1D15183EE2DEEB5774EAF0462DCDEAE280DFCD883F095E32E2DDD5FACDDA24CC9BFD589DFFD612813CB0C30C88FAA2E9498CBAF5C2CBEC2B9478447B04FC9D6E143A1CBE307099278B222C6F11039998947439A559FC446A9427FEAEE0737C2327C23B3EA634A3368B54E9B810185C01DC235602C72B0E49347A96417EEFC356CCF2B9847B8B27A7382ED4A9ABD759377BF3539E0B90E620F0C1AD94FF2B44C7D6967D7EBC9460276D34C53B366DBA51DD595D63835BA52B141B1D6DFA186015A8A68396794EFE6E052B093F147BF7364B3B7E7E877F96BBE7D358AAF957D504EC6EEC1A0406731A0438E32AE40E4F908A6ABD0EB8DA30D1117BF0293ABE037B0EAD5220C83A453439C817404DE2A564D250DE571958177DAC540DBD3203693970EE945931798BEC8A22D1A2531A9477C572759116A77835F31E1CEF8BE3691562E6FC4DBF0597EE851B51AEE49F609BACFB5D72A45CA4E39E3CB5FCCA314D79A5D8187F0AC1147C23B457B96539E79EEE426E5950BC2D6393618F0BFDE289EE5AAA9E344906597858B99A19307FE3458C3599600109024EB8C2772653A65AFCA6F3097DFA2E8FF4F06B51B21C0878E0190426A3A47AB89297324A43BC38367EE1AB7EEB80F5CB147AA3EDDD573CCCB8CF756364CAF89256F7B63786A87FF548A088A1F9D16371DA3F54B553168EA7976C2F8EC55ACDD90CCD25EE06FDE39CD065E061E038D948AE620706DC7240EE12800C928DE01D60987705BCB4AE81573F56ACCA412F979611AD33737D950AC9D142677AF703453F84849FBD85F9852E58E36B8F8D51BAEDCC80855F52500BFF9726BFE5A52F78C2278E148DC583AF54D7CB82DEE9688817B808D810C67825718FAF299565A6A51826C24C8D32DC46AF8798C8BDB484CF3B59FFABB7DE1DB048620726CEF177C670A3912E5B536486B03526AC33A5664267FFAADFD5FE097879141B2BED75AACCE7F64E43B1B51AEA2D6C7A209F644C18F2469C269CB3A40031EAA2C1FE8081D493C228B9F08DAD1DD3CAB95AF2DB210144B40E487630C4EAECC8CC74CE02E710CBC86364DEFDBB4928A929D06F7FF3871AB5F4BB13652E2109D46611EE242E8F5F8ADEBFC254678D5D7D2D321CFFDCEE4C6893041B53A57040E8D5FB4FF4D0528702F65206EE31BB92D736FE08BFD21A19B3FEDDE43082C375E290A526BED209693B19E7E12E597848EFB8FA95CD2D66789FEC1E5D02D0C4AE2482978F080342F2FB4BC2AEDC09867C4523D4E7CB07E62A52EC184A92FEE9F0C7D0D5D807B744B51221189BB07474AA2543276813A3A4B28710F4781D44808690706A2B06997BF09C87C64296F64DA72DEB0D4A49D0589E4EA91D186380228B8D2DD050087F07329E6BBCEF909D6AD7C3F8425BE39ADB9248D49A06D1E3EA440A12B66143B4B9FEE43029388E9DBFBD2E15C6061009BF9FEF728048652FF95128E57C6338CA710AC4D6B98EBA764A20B6E6467D1E42780D2D6AF1BB4857344D11876528A59C06D794F8BF8F632A997A9ED260D853148381E6FC7BD9BD1F5D3F99980422576BE61B92D22201897A2D7DF07138C3FBE42CCFE108E5811DAB9857A383859A2F45684B394F0A48BC231E877BC56A540A9554773D8A12C2BDA96E0B0155A1430F8A701B21E9C03C61D6A3D4C545B1C523915CC1E9E17ED0B6CD04303CB76C59BEA787CBAE075BCDA31EC2DF8D8120B9A79AE25182527968797584BB570AB46706343CB9E6A89647B40550E1D64B5E7418AC69F7FD2B58726BC234F2C5FF6539D6E4CED9B52FC36AD98A90C28DF8C8E98507437126AF6A1C125B01AAC84C8F4497A34C68537F0B86C921EFADBBFD7FF29BD87CAFB9E0CAD46B9F76CA5C6E0F3D7132499C580C6C9054F40BBE1C594AB63C1E45B4E8261E4577AA2EB3CA1C1BCA7B9794D1A7031DA85D0098781F2C4739184C007907029356297502B076CB3A9AEF789D941E79B8F3C741818AB75798199A8B6EFE0651ED2B848D7C9373A7DE2B3072B3A80889AE6FA26303B7BAFB3E3E4F7014C627F999AA8B2EF0243768594A2C5E71E3B4B4E9BFE3D5F9399FA0A4F6DE012163A5B74C6F90000000000000000000000000000000000000008111A22282D3138" +} diff --git a/priv/kat/acvp/mldsa87_sigver.json b/priv/kat/acvp/mldsa87_sigver.json new file mode 100644 index 0000000..a2a4206 --- /dev/null +++ b/priv/kat/acvp/mldsa87_sigver.json @@ -0,0 +1,18 @@ +{ + "source": "https://raw.githubusercontent.com/usnistgov/ACVP-Server/975de31eb83d87039ec88934fdc47d8c312b892d/gen-val/json-files/ML-DSA-sigVer-FIPS204/internalProjection.json", + "source_sha256": "47cdd6314c7f746d02421ffcba89d4dbc7bb875ac49e07a029fdfc26fba55437", + "licence": "NIST work, not subject to copyright in the United States; annotated CC0-1.0 in REUSE.toml", + "parameterSet": "ML-DSA-87", + "tgId": 5, + "tcId": 64, + "signatureInterface": "external", + "preHash": "pure", + "externalMu": false, + "testPassed": false, + "reason": "modified signature - commitment", + "context": "", + "message": "55153ECA774F0033458B85AA1A9BE98D46F7237D479AA5282C3E50799684AD462175AA47F8187098FBC11AEF450BAF233F031ABF7203A4005D37E2E8E7FBF4607BCBB6C9BF299060B79580EC9A035F0F2738758BE42912D73F6A718978272D7CADB4F2A51AD65BB58EFEE884DBE46ACEC05B101577A2146E71D0A01BDE1F5E9F1A5CD004D1206A374DED2DF89783A8DF854FA9D7C748D87EB9C4C6850EA9A2BCF7800884C3EF1E6A32D85D4F20CD333756AEC481DC562022E094F4F08A9C10AB3D4EC6CABBFF8E49D0852DE76BB812ADB550221E54DF0ABE4BC3835239EFEF60C0C808337BE88878CD1960E7146BE663E62DDF7AFDD074458242B343775B63316C4153511CF9DB94CB1FEFC8258D00FE07FAAD9C205CA2131A242D567C5397BCE285741469EE5E93CAFFD69FE48E6AAF982F970E1845B7E156BD3F7A8DA594A1BED4578F3C992E9E09C5E1D73DF8D05A9EBBFC11F556913915B1AD978C6759ABD71497B5E9086A94418C64A97ECC920354CCE1E7516CB656894098A72D6F2B6E6E60A4D0FC252680A24D7DABAC1A79AD59DB07B8D5C70B7BE494DB0F890CE2BE3811C4F879EBDF5C84B7AE5DE89AEA645021B4102E637F32581EBBDB81AB3750AA6545977FCACE0DEB351B55FA649A9F705CBBC05D8C247DA843C248F9CA91A00CA9C1823A5AC10CE2129C53E128761A67B4A6134B44FA6BE87FF141D6B1208888FC7E707F01541670070558F80DFD9A8598FA19521A9D8B1A40838E713213E86A9460DFC2FBB361CDF92EE9CF14AC6B0E6373E9998A1CE9DC416C336222109EF0D56CA4780CFDF88293A46F0379CC8673E6B181D004BDD739D9EBC0F436F4726931629B9B2051BFE443EE566256C2C7016169579479D0537108C5CACCE23C71EA06C6C640CEE8DD3D46FBA0572EDFFEA3B7A331A7DF792FF8A36A83FD8F06E0E446854255667FA59210360DA9C8F6DB8BC259D2C321B76F491C2CC0737AEE7354A4694E0A6BC1FD8A66525E4EC24C2463DDFDEB025A844E948FAE1764090CE56F85D0F5ED59B989AE5C2C476F4BFFF73390C9805F5DF8249A56A8C3F5705AB6AEC2AA4EBA7FB52D5D5D49FB7E1EA53C52A387BEC0BBA71AD38A75A28716E7F758AB661588529BBB4F8A3B0778CBAE0FF92FDADC68B36115D733A0553B04B79864C1C72D2D9E6C2C80F07222D07F4D73DFB0A73EAF4BB920D894B3F47F1BABC1AC0B86C53F8EBFA06E892821D33AAA8B281F70E6C664A4EB8706BCED3FDAB6A811455C7A0A759250966DA3543640269481F2CF35C3BD070985913BD2DF550E11DE5E62FACE2B7EF75534A3CD6E2A7CC2D99B424DEC9407743F2BA6896CE7853A3B117EB303FF2FC440BBCACB3147352C1AA8BA64904A235C1265FAFEF0BEB21FFFD515A356976A93FFD3970FF11AD9392A97471F07840F50B5D54B6D922E98D7CABBE50A8160C361C7C1E89F9AD8325DD28D2D88957DDBE5DF96031AF93254E87232E82377ACC75820DC9DE269EFE6B9EF44A0E7170A09B3DF5DB648B8E33E65A4528E819222A2737444939F31BB4296DEBCFE3798F93B01B37B451A1F3B9CB37BCE9DAC85C575EE7C38E0520CBD2E2577D6835597BA3442FE144608101FAB55979F5C5FE0B96C621196F2CC3B3A15FA835539CB903453D82E7E65F22787846F041C8C21B7B27AB1A11489EB9362C064066D8F948EB07123C114125B6A0E769A81852FFC9E1C6813EAC77B025715EC40D9D0019E2205F433182A3E20C565781C16E0007FA39935080CB19A82E3894E0034D7C0E32B8D7B8205D295D32474C5844A5E8BA515211899B42C2C9D87FA06ADFE63948AEAC37BA18616A1BC33E934384F227CE9374D63C962AAA7A62F0298A68FC8BD58AC14A7C748C2F378EA7A435CD25B9F20A09576BCBC419F918BC364798E8B3777450693EF421D7FAD0A1F0A013B326A937C2732C640035F4004E9E5C870FD98F47C7F8E3CEF1B47D35CB6C01C82A37B708CD4772780B3F6D443830B8AF1DB64CA3B8DA4B6A3F668F34EB27A36D8A3C40F74D6A4D4C8D38121363233570F9685D6ED98BF46B2EF4730A93FFA02D42C0776DB1A2076B25263FBD357A6EA26113249B44F63DF1A9F2A954D56272BCE5579DC1D596942DF3A255178DAE6DD849486D06BC9584E0924D0445C00B1D8A4DBD5BA352A7FADE29D120D9D6D03EAE48795FE2ECB07A310AE079D064BDFF247C3BE7CC5CA641A93B59C726D68FC10C1D78F3CF2F99B63589A85994513627E107F75997DC5C173C388F8C92F5F3E95484CB068C9F072BD5C3C41873E28A38B7FCBD2ABFE445C713F592C64B7F89F9921C3C2E89FE14AE22B74DDDA3601CDF0EDAFC1CEF860590A344F4A13D29D313A1678890B19903B419ACB0C574E91C18A6DFF02E932B611AACFDB78DA22E4AD37CB2C6BF88C3AF0F098F4840A0A28E5AD2A6DF880D5FD67827015460980D3351ED51680343EEBF76580D8E69A3881C016E5F9AE5802834E4C61AAE484A070C87F8EBC8CCB3ECBA6D8C6FB032449C82C5499E77A4852C4DB61E1BFFFE5C4BD6083FC306265D659FD5F41011DDEE5300B6090A84101F4C5AFF12263243B2AE8DAE3D9E8555529BDFDDBE86E0E02B6FDC406FB29CE9EA13A6C51868B22D434E5CD5004D3DBAA37068A5E47E33B6117367D44375921F738DEB7B5938C5CB31B45BF23CF25393341FC5F1C21067D59EFFFDEE23F3D19F3099C753D071DA69723AC64C53C2A60F518BC82514D2A94AE1BBED239237F60800AFC4E301B51F0C44DAD46B6B49917C23902629FBE24EF57CD908FF7E1F4446962195E54EC9AF77B5C40753D81613EAAB5B42E68E9C39968503BCA12CC4C04C3EAC818F344CD76676A4B63578D409297A35C43E6B72F317B0CA222630262C6BF98027E14F9E107F99AE5971A3EA60A739FCD74930C9CC568DC12D54BC7114D0165A1FE97B4326518EF50827D980E8DE2EDE9754F632C61969A32465EFAF64A1D632F29CB88F85D151CD8BF6D33609B2370CF41112FB0318F6D8790114782F0AEBEB6DD3332FEB12EFBA50095BBCF1E2A438EE0EAA408D340635BCA3A2658BA1D6E7A3D518ADFB251BAE61A03F195EA37C2B769029F5F29FB076812E44F6B1415A8F12BA506E0E1AD90804ACD04DBA9763F1AA63B6E630E992044326C8D42D104B41EDE9A0C295268F3D765ADF8493F3AD5A0272170C933878B37CF5849D485F52D421743167E16663B1605A1EA9AD0EF9D7C411238FF47CB61F41822908D7FF8794F47C939DCB616B25F21B794957BA45A60E6C8CDFF45750C5020CC95C9C65DCD9936663E2A4F83450E127D75531B81C1AEEC56EA4D91B0B586F48860FB25C4DD960AAF163D5246B7D1165EAFFAFB00D980C8BA8EA3F3D8484DA3907107555B6DC1787053F4028A697AFADE25DAFD59166590B557928655075EA318C7BBC1B2B03C38AD53126669361F4D15F9FA36EC666B4C32B90597A66FCC83BA5D38AAB78FF4D95CC664C5629849BBEE39F6228A180151727250A68998FF121875FEB2F10E870B1C854FEDB7DDBE83608E30A581E5F8B80F29B83E9B115EABB41BA2211BA9491FDF780782D0117A2BB0ECB8E744B42DBB96195C3B2E668323081FFF32096F6F1876755AAFA18ED26C65E7A41E3466CBB34EAB908AC1C0943927A811A37DE012540AEC4E10677C6726A41DFBC29FABCC19C37C121BC37A04C485C22B08B5768961AA43C900808E2F61C75404C6841AFB53B1C6190641BC98D4EDCE9F97DD2E7AA9E480DD9AFE7DE0DD8354D75C4EF293245909372F206EE287DEC87E606BB3B59EB053B9183F555DE9F63A752343779D6C2F0A4A93CD2AFD9D991CF05B66D617F8781BD3EC5EC22FA49C055A1D0B7BA3CE24E43683AB2EB9834018DA6EFAF9EE5058819FE745C151668EF6C187AE3DB6738BEA5555276F79170219ADB028BD9B5E8D095BC2EE2FB8793A5EF22545BC9CEDC8022C95587B5AB3438D16FE3B8BC4801B06444B2B7E76B87FADD18123BCA51917E93BEC3EB665D155864065E738270BE016E1EAD5B4D703EF6B4CBD4DAF3A84F3FD376396A496FDF11AFF02826623CC6B1E315D4098174E4CD191D5B456EB5589C3CB0A05932A11732283EC0323D5814000143904C347BF083CF6C0B60A207407032657E763CD8058352088571F8B439248DFD4CBF258AF1AB36ED213824794AADA0C8E6767C920D4E02C5C726172760E0A42FD9FCA4F13918DBFB184A160849293E9CA96994ED5457E9E1A06E93CD964F2747307D92894A4A328B53C0F9F73FA3246AF3BD769827E49D7C75FF4B6B943FBC023155AB7D84325A1001CCC1E1D46FE0EBDA37FD5251689CEBEB26BB946A344815B2CE57109F0E92F10FD800C4C088932183A5AFDC557ED098764A8C172369E80281827A779164918A1452C122011336674DEEFE5246D9A83D45F8853088A743D0FA11D7F6FFE11081F7F6A5FDAC16810B7B04CE773AE21469896A58A1F68970CA407D48921EA17C0C5639346B3331979E9A8BBA0961944E32A3F6A86BED66CB3C51E93F43A52B87351E200B8924C86021D64618B1C5D2A38FB0EDBDA5E68A3C61C2692393637DD4A34D8FDC15140F2061AC8EF629D88D95858C92C189DED4B4EA452F98552A1CB0DC6FE14F5B58302272779C8A9B7471915ABE5DA0245A44848CEB24453681A7FE272A6AAB69E843D33CB71BDE6361900E8A6619FD2833EB470B4B37AF3B6CD99E55FFEC3B730BF774BB75E40F915FDFC48A5FA2101FFDEE0365EDA7516D0F5920B6AD3E33262A6E2C8639FB16474124B29C94AE948D903B84230265E1387DBEA69C3B40C6127C5938F43FB05B821115CBDEDFDF1BA5F55C076340AF8F63526C36858D275D1D03827BB05489D9A7AEC4D281C94394A43423C3986DC994E8198D7E0957FA56E397952264735FD9B8E4653CE58798F0D0758E7FD41AF4A225F6FF003E32CE1CDBAA6A1ABA140A32B3F7EE8D0FEF9BD719CBA0D764D6015DB24F367EB603D19771751C716EA2BC714CF355D578B6E15C65A5040FDA3F81F82BC8CD6695473609DD5733F14C018C2DCB8B16F6CE2D268FA5DBB5FC1DAEFEA5666882EAD077FC53FA14029F3697382BA121BEC8E4E860AEB3ADEE91AB202DDFE67A88742D2D4F8A440BDF2EDBDA2D846D612C3B8ED52FEEECB0182FF2405FC942EE69642B959CCD5D4D9E14D74D6ECBCDD0D20AF960BE5FDEB419C9EAACC7E210B67C3A95FB654F7C15AC4D350E930679F2CCBA22707FCF290F5F90D508E2F98AD3137A43D8CE8A9AF47B792FD76F3E44FEC2CDA642B52420C01364760033F79ECC24943CE7F53CC0730CD794A08F3C63D59118F071C5AF67505249058EFD97DC1019BF13D313D607C34F7BE22C74730918F373A81007A88B386740BE0CF01FEEFEF41913985567593939082A50D7DE911046059EEC86D0293D47693A6D1F40CB69CCC9FB15835F2E92603EF020E5D29191BDDD4DCB79BBB8FF2EA6E773F9B9782929102369605EED93C118984FCF47E43EED24168769F479F0336B6470E2FFAAF87EBF78CA117EB5D454B7BFF3ABEC5A9327DA7394B1E6E8A1EA34E82623424B711C343C19EB0C1950C8D87E429654251B8197B33E70C73CAAD963D62ACF7EACD2FA5657D107DC2ADB08E46BEE91B205B06FA7A4C01545D7640533F615E7223524B70004AC1484C33667C874482B41EEDF79BD5D04A353D2FF026C55DB0292A38D2C872070918F6DBCBC4823F844D9D06B0F08A88AAF61ABE780963B82417F7E8BB0B911FBBCFDD812480C664F3B63953DEEA75A811E64364E82DD7E6991B5B922976F5BDA07BF125989C6FEE5B02EC4FF0FCD37FAA215FB1C8329BACF5761D936E2267E0062950B1691FC971970C9783DACC5DB7AEC9B49156FBE7F02D5A44620D06FAF286C61B4EB381495EB2B6ADB022C8063AD92A9A6914EEA622780333B37D7C2745BCBA4CE00636E14833C87FFC1D5FF4600DC2DE354CB7ACD2A7228495C97C2732B3CB0F172914FCB75FE620C0C04D44FD99FC2FA4AEDA10CCD175F502EE412E5389404A615C8E5C3D678D0F5C837E479C2473D7F7D0B173FD2D6CA5855263456F42A43F08EA687071F4005522D83D3877FB476634B170F8C84934CACDFF513DBCAFE799BCF543E48AA78868C979BD31F739A5D78F20F557DC61344BCEF318F95CC9CFDF0934711CD2D78629084C59FD9502BEE5F9AD068C8FF1CE2E1215BB11D191B522775234B51F9E8495AC8448AA8F46618904E98B12EC46323EEA90BB576C32AD51F7E943F68CC2CF85C0F2D3C12FB9BECD6C28437EE3AC0CBB49C178EE2345887F4578634E14CDB248686EBFF8027BFF637FDE9CF5B2F2CA5B3758C43F0B37F37FEAD53C2F1C3EAC081DF671AD0843130E00832617C4735581A7808CC0D6CF3FD91CC73B158900E7A5F66068087471DBC3189B0F4A11104626EBC62F03E4A26985FFE51FE3072BA90FCE37270A994048D78470633F553EBE3BAB4241330FB5DD1E6B0149BC4860347CDA6489D04BF1682CF93EB9C1C1D48748A268443BA88474570773FC026D42153E322413DD8761A3EF7EE9B7830DE5926703B7FF5E9E67A0EE421031EFE1E716B1C2C6A6533C8B00A2C7E2A34872B6683963DDE984F3D805D978506987036BD221322A3A751EF545ED3BF337A340193EAB98345AB30949DBFD3CC54981793C95FE86E361B162016DBE91F3CDCFD5419EC1D2E2C39869580594DB9C768CB4D7E109C20DE5DFDA118E5CCDC40C0FDDB69AB351FD595A45570B6BC23CC2F05D3B829D02AB8570F5EFBE2B3D09F075C4C8D09E8DD4C73674E4B5487DDBCF993CCB66BD39D0D7AE5527881934CD3EFB9F894691D5FFBBC3F9BC6EDA59DB977D09F2F97C048FFE0B5AA69D4998C0F3E3BE0F231558D88B35476B4D38DB3254F27020DA27B1FD0AC39E9F6A6134032F46F44A0DAECD089EE31AAA790F529CABC407D24CC707E72DCCBDF01DF7BBA5E0C17AA0D487F75CC4BCE7E10FDEC9C7B4BB97250614C4A9E7C9D1040A92C485C5298843D6065842EA0EE71070EFB1FED0C883E27C81CE680451A72DC9E16AABCD58D1C0DC9A90379D13A6D57DC9FA8EF69AE6753E92DD96E964DD673EF65591E464D96B7DA798B63DC2745A17B8CDE2B8E5231F734E10A7C34E677450CEF9126EBE8E4AABB37419B959C44F39A0E550D92E353808C44F72AF851EF2C1510F0111AEFBFC09A610ED1D2CE3C2C1C02CE14128C660D00AC069BA97F1BCA092469C1B850F658226799F8E0542C92D0A09A141BF8407EE2B8720147E2282469FF360F510CDCA616DA1C630B48C572BD4E7DD1A385B94F33BADE94F70D323ECAA541A5C6E9E15E823D99C5378439B15D791E8A10F2C9ABA4FCF9425B5EE3A0AF43E7350A12EBCBC89ED46D215E4C2CFD39F3EAADE182A1DA4BC8B2A5B9ED56AACB2373A136EB4153BC6BA3A4A3F76DBF9EFC48B58E6BD2C7986BF5543F883432791128CBFBCBF972A96919B45A4A46311CE22DEE2FDFBBE3EF7F7D205DEABCD62FFDEEEA388A91CCEA68854A5FC237059C534EFC5E543057FB61468D98D3DB3EB36986814FD2846BEE8C425027B755360D06043919704FA68F3EBCCCE670592CC8BD01A4F8341B908C687C9E0ABF96B82E2D0DE200A3EBC51E7EC55B6310C45B2A5D1FCE7F73630AE804ED05355E25449A1FEDBADB7389D87B81285B4EF395696DA1FC200420D80BE7D9658A8C674636DA145F088A320D268D23FF578F4AE45D9F696C8B94FF68F92867ECD8AA070C7AD8B7A6334134C2DA3D912F6D29F073C4A20F9188D21EFE7047A03D5D1C3AB7BC911948031F965D30E24EAA837FA09955B5090351B39D8420BAB5D9AE65B528D824AF1DB35638CDB5615516663FB936214EA3023502DC6BBECC9E3BED5BBE85F8A4EA4CB5D1E7F8C73E163E2F4C50BEF693255CFBBDCDB26154EF9B4F000BCB9F7452B6FEAFC946AB9EB002C53DC09E67ED3BE9275E0A5B5BD8EC7F1883978740DB7BEFEAB8AC328C4CBAF609299B5DD4081ED6875CA70B543D6F9AE245935694C93C509B94A686C3C345CE695E98CB73CEC4ABEA2C408D3C0784813F6FF5C21B1454A1F51F1CA8B3F902C7D9E312D8CEAF0AD817AE6AE96B217FA84D5A8FC69B2FB17C37676A8EA6EC5A4515BBAEBBBFC1EBBF2680F1CC809C096F9DC3950E4E1873889EC2AABA6E3169096AFB8574910D7BD268CDDF0EB8403ED211D8B01FF5B5309B204FC92BA4548D059FAA0CCF7E2B385DD38AC38EA3A3C857FE4F0EFB4708A377BAF39FC96B5F6D3C5C82B280A83D2120C3F718A93CEB8EB009248B0180E998422AB4FC9F5BFC54EC24007A3253D17C79509FD4DF4ADE2CA782EBC5B04B913CC6C5065214A671C098D66377699311ABDED4C692CDA05D587CC993111C952B4FAB6A0D43FFA0513FF35D853CA34AF64A93F86AEB8B3CA6D409060275E6652E1E1306C26FDDB59439B041DF6484665E7F4F89863AB33E566DC9EAF993DA1181C2C209A301CA45C42A331E29E19A70CF0299DC876827E027B6088CA5A1DFC747EA2369E54378F85BC6E1BE2ADAF45EFA7F8CD96B21FBB52CA75D1BFA260CB5D85DE91E3094E04BEC04FE732D549784C202A2B257A82530F8640EC59F550D5A4A0F12C25FC651AD21D4718052E4193B7CFC71077056C4EB1048D4923202206D95516746901471430A23091C833579DCF640D7CFB292ABB155A2D6D1096BC03AF104C5DCB11F7C54CC339A1C081A8DF719DDFE024D11625B2E28560C793D76944F1127113C93AAF44314E124FDC9D9BBAF501E7FF00F4C1825F2606C13CF79AD5887E5E1C63D47385DFE118939885D9E9479DBE2EADF2C24F2ED4E73CC08E264AE2ABD4B586E257B42F94F2C344F0564763C75062427C815D34E4FEBB88A21AD1A45C84EFCD3CB097FF01AA2E4FF6342D9070DFDA60CAA7669BA2F2367957B5DAC341BA781DE6B842A2B02DE9E21CFF261B78123E7CDD125FD5A1D6857F52472CEA8D4B4E1D4FA5A2AE5BB873297D0771287189C184A0670F90D7C8C389679E5A235766EA8E2798A43996DEE3ECA2379E645FEC1DAD6B845177DA76B5F4DF398C3FA516601B6CD1148F362C08F1DFE9068DBDA2C3F3DD0039B88257603C27DA669ED0C2D6A9C45B0723E0FBADA53CD4CE3AB2DDCF45D85C027BBF257674F4F58AE3D244C6AE23B86A8CE79B50E07BF9DEF9CB5432B1BE38E39671F9622FE7EEBE31DB299E1ACD9A8FE48E8D7EBCF3737521555E63B327660C705E9348B29DF91965574453F01A33572E085918CBA94229B9AC0029015416752F1AB94D24C8973EA75827FE1C2C21217F5FB7CA9D95262AACB8104ADCA0728D566080123CB2B7E6D2F6ADF2D3EEE1C9F6DCCA74E16CD6AF2CF600DCAE6A35544AFAECFEF618FE49D3993773455320AC94BFB411F5BD87425F772A3BFC8B7F0A6AF345F39B7689ABEEC03169AF36275731FC672E759F4890ED88757FC3ED090503E7A96CAA51A9FBED2AB13E4D75CAD4F6EE28059082F11460EF44447176D5A0FE7FDE7A0A1E455902F7B066B4A88032CCE6F6998D0885E161B48BEFAAABF39E9065E7CDA897BBED87B50E156453C8EBDFEFF064146606E54D8712C60BCC0F100D15BA7C945F22BD4B0AFF94EED268747A96BCBABC00B44AE27B2DCA5507F78749FCE8CDC743819CA9AD65C8B0E9607B5167CD0254590DF5E382C015FCCC6AB1D3B07D4E4DBADC48991113CAF597CCA3902996BFB8CB37C74B3688A48C1AADBD775B607D8CF1A5E82786CE8B20592B6B264C30A989E10C332E0940F3A304CF679D33232103F3EFC1D320A1C4FF66D08C292BA85B824AF19A7C63717BB37809C94ACDF2F0E55597CFA8B2F47A7B89979F85F36BACCD1A358F2408779AC29873C79B1D4D9705558095D77675B7D915A2F4FE33142640A4E69864F74665EA6E451E69CD901E7D96D3991AAC50B3B299866B6D610908B6DD952B3BCB2DD9EF9A41272A285060811056083CCA04D5D0226392D136002C966F46BA2E122ED39E0F631F8220D6162C58A16D8EA077FD97C33E6A9ECCE853BAC448003F7DB8237EE4A8B3C79C990E8D025FB989BAF1DE708637E635ADB747C32BBB1C2E63188B112B287F7FE783CFAA73185613AC7E75E077645808CA4EA664279B4A6B4482A5587266072FD156D4DE40B184531C6E5F752660B10871FE77325751D5BB610F5CF86C72820BF49C41286CC9074281FEB6FC57AC7BA07A11CBF571FBCB137E41A747F40652D36BB722A2012B1A3CF4E92E236553FC095750FEBE46EEC2163E42538705FB2CB0211B4F54B5D3F179D011E334120A56D1C8AD4220B674EFBB0BD875F2A4EA4DD71D402DBB25C5B69970B43235A9CEEEB435F87153D24AF14FD7751A6C6D612D03468D0200BE8BCA3A00156AA7C4C6302544E254D2ED94642041EC47EFFFB6AFDA5CF7FCD9F4339789CB5FE7376A323C1F6419231C3C8AFA4266F63456159599E74DD4DA01E6953F67FF905CE7D7CB1776435D85999EFCAF36F8E26E339B96192847BE0381A87F7CEAB3C119EBC0F225D52ACFEC2FBEE1A650568381627FF329187C1DDAFCA042452F8C447B444D06B97945EE27C5AF954521BAA4C4EEAF092E2E0667F27A6B6AC351949BC5040360F41A71306CAE21307F6E9746666493DACB51487EA542212BA9B19C48F52E04DD68210857C87A5B76ACACA913A5151C609A8ACAC9E16B82FD723AAAC725CEDC29813D5B83C42E64951B2AE9284094655479C117D258851AC4A5CC862D0F76A096D4E9A70B6A9522B15D234FFC2F27CAF867BA3DC664B1132948C618E6DFC1B86A26194F0A8CCD0FB6887C836CAF638ABDB365EFC8E0C7AF23F434D8794CAA2DE08FFE5FB9E57D5E57831E8D14D5075D203F64900DA2476986D33487EB2492D5159158730C83CA22447392104A4E83587C1FFC0C21C0BE39020BE38408C2D2E99368DB982E837D1D4C8650CCDC4A746A573D4ABD0D263EF12986F78AA116A8953F6E5ABFB8DA95C6DD0A325FB2E7C0FA96849AB0571157EE289472C4495B277EE503B39D440BEFF1EDA1CF651466869B4A41E5C8958E98F30B9FCAA7CC8367AC6F47C538830B3B7B4D2F3FEF04638EF23C3E47B10797C6AB3B37A980863C212D91D3C8A358C2DA92F2ED5F5B3AD32BB41F50E4EDB1F88038EA1EFA5E9E2D68AD8B79C32F18AD43202CF9AA88CFB1CA369647ADEFEAACA75403E1519CE2078C9C752F0D0409C1FCCD40CE99584D84193CBADD652ACADEDD987ECDF1157B83EFC552", + "pk": "E39515BF8F6FEC2293D62B92132F0897FB9EB6A90FC81D0D94CDD5860D5E539CFE09DDEBCB97C678C66110008C204CDC81C943E25374347FCF054408A17B4458C1012DBC5A57BD2731FBE1599B9352242CA6C97AB808B7F9E6375E68CB5D1804959F1C61744D92EA7CB3D3B735EA2E4B6B6D524DCBBA2253C789D7532F694FA8E10A0A0DE5E0807BB997789F9044A559107FAC3229E50E911F60F611738808FA20F4BB9FBEC2D5DB7B09716B8D5CC49287A02211B54456B084B3897211A09388DD5284C02CD9242CAAFD0D80D24B6AD29671A7FCB412BC6A5329755A423D2A5AFDAACF0463AB475D4A3E138F781DED1FE70CD04F10B53B76B116FC2E241D39DC9323BF226BC21C9424981AA9026E3550F73CA877809C67FEE4CE92AB5A85B1D3752BB7FB4D6E8B0C65D881520F455309551DB077CF5AE4A7DC4335F51A2C6A45A12F72583951770E5DA64E187B45B9F6EA9BD5B5F8399731AC8BDC28188CB3510F17060C54C29F4D2D8D8529EB29F90582AE0BB9CB2CF9399D49A82A8A4C3374F94E3DC0C4CD971C603958C6F66E517B65F892AE718641CB0C145CC87A094974DD0BC362C89806FD4DA78EBB1684FAB209E0EBCF6811F0AE7AA174A44B92D4506875580A9F4A6018B1C63FE3796266F87E3101F2838B373659BEF4CF18599F1B944570D4C326C49FE72BC0F048E3868BD5C53C1F4921DEFD346C81F487ABDF5CE9191FB90F0C5D73355FE6D32377CA6BE9F953D640F33F27AD93FFF2C227F86C61922CE6FCBA20722CB12701AEC79BD5F5308B584019C06D830B3472829589A4908504DAE7A7E7AA915B43DF86B629E31AE18F6E50F96BE16FD73082153B1775AB89283C582FA0307A9DF9D773C1D0A3B2907DD69FB2461606DDE21CC07FFCA83AB2FB41CF919B1C07A7802F4C2B54AEAA98BD3A46D69C5CB4205A0D2F8420EAE82B412813026E965B40A08F0104DF62AC17A0E530E38D3BA93F914B198A25D5D5C25D794EE1A2EC91BD4C7F8E057EF610CD001B8F9E292F50C8E519A3A991A9BBAD7E590771217501D6B3AE2CC36D1DC812B134371A9549F5712AB612596DCCDC18ED460DE0D612DBE425CE1EAFDFECB7784BFFB8DF06FCF32354AC1F621AD9EC4020D580FFE8E6017E3C534B37709DE337572DCE092612DF23731877F5DF658BEA1F382CE53010C3C8AD9EB687DD0BE0449D93450FC7C1A25F5F6973E498344D66C9ADC7E57E99A3AED58DAC469BF48337C426232617D215C38DE950DFBA9C5B6654E19CE4425328E26864426159655B60DA42E43E9B8734938B719E9BBBEC6D68A1EA88B12D7002C3E3C67DDB70D758FD9633B8CE42E4EB4BC2F3F7607688818481E10B7B3A8F4BC32BF1A24B0D1E503AC64C62D76785B4DDCA23E0BD9FAF3F7F1AE10889E06A39CADA3567F1C2A00358997B15BFB58BABE475872CBADA97C283F5854CC931A2632005E5C89A117F97811E5C6C40A29EDB984BF302B8A51CD953160208E00F02E4058BA99936DD2739F1B04AE064F9B6CB93D8085044E9AECA7A7252FF2152B514C6A2BD0A9C6A301DCD2587CF326E7B7F7CCCDA2FEFABAF0029EA30BB093EDC977806A36AD4CE942C8AF7701447CB4A5617B05651AAD79422A5E88E83A07F8B75C0F4765FA87080679292A96E370C3499A8668485EB30F1B647BDF0A9DBFD38E92E0A1B0D6B2AAAB9F71480D06B798EBB61DF9177FB9BA70F5F762705F553239347E7E7767035C97ACBD00B0DB2F0FDD5EB46779C5B6C7C79E750CE4F0AB0A4F36B72B78DA61D6172FD8D5A0E6CF1E62B27B8B170BE6AE6E96675E61BDD39CC454554955A09A92743304EA8F4818E27BE1B328193B6C6BB1F37D9B9FC681BA5430BE53C0C9B8270F7E5FE5C9EAE5F89E74CBEFDD8916FAF1AC2DC377F145F83E44794A5F08605B1A7A21BEBDB33C677F97C07029CD7757EEAC7EDD3043BD26C689DC56E5271A72C1A5FE5D3541F1987E7C05CCF2F9B6D7453318544DCE61A928974A63AD6CC4E28667FB49C078636C87D3099BF5E5364A5DD0348EBD72033FB14DA9EB899A3EA99AC4599F774DF27E0DE8B5F123A367C3791A96638FED83EB43BAF6C19A1181BA2370C8F845C045A5C05E0DD099F976E9A8B74D1C6992518CAA6F98EFC4F260F0650E4A81C9C723C9ED5BDD4B1D9BFAF94052E09AE4D934975ACE28B7EA7C39BF815A1C7427F98AF769DBA50958CE111415BB9031605DBCF248DBCA2DB896FB5EEE42A958E4167180388D17D5CAEFA37F60DDDBCED3E2E26FA28AABAC2D2864ED411A782E4844A1C57784C5D113DA5F81DF2BEFC706065157B34A2E1889BE3672DEA7F836CB0BB5B006267966B4C7B18490968A9A2396CF7595DB791875E66705956E0A5050EEE87E980AD48C4EF51EDFD7018879559878BF0428E92EAB36E60F2C6563ED4191FA87D63F7E297E33CBBBB3FE45F2802D7134DDD3C5A9127C374B63FC0671B9B462B6DB48546EF08CEF3E7CD72BBBEFB750CF53F4AB214BB1EF733EC311D606EB0BC4AD4965083CAE9CC16DC248CB56951F349CF3993792B5D10046DE5ED78D7BB960356093EFF1C3ED4B9D360BCCD98FDBEE6D8E9E081F6761595F11E221FB8F072A7E5C3E953994F1E3533C901747DE9589B91F057EAD4024B12C4BB1D9498E801E298F487234A9C55CBE27A54AE70AED882AD06A6CF44FAA447DA7D465A09AEF236C3BF418B58A571D5AF6E001BAD6077974FE471858173909DE4EC3793C995C02905B0F40EC536D9F762DA4A7DBBC72AA58D23E3775C745BA5BC5824AE357CD579090B5651E29739DB2793CF9A433D38FD4456330A6129A71F191CA95C082B8A025DCB8B417E1FA95606DD0129E7B03E4222F78B42FF5319117CD3306C99F5B7A77BAADA1292ADFA149C7C6274A85F6D6C0DB2FFFAFE0DC2983100AE9074EA409780AB5B639615BEE9ECFF3A3F2D60717B847F4B7562CD4B0C32A76CF0829BBA56086E58C5AA2AC983FD8B9D0233B953B86814EDDABB28A7C98AC9F7684A12148A8289B85087D1D1D36AF7B9865305CB2FFA230B701C27A9820894AF26943DD5F5457C12E6B274B287D26E8A4D5758191E81DB282F19B2FD4B17391DEF8FED35F7A9A1530B308442436538804D3D36BB3DC3A6D9E6141EA806EFA58707CB5F3065D21328FD3ADB2E8AB2FC3E2C6E4CCD343501379FB634FDF50D3E566A608C9FA1A6FA418DDDE8E1516E0711F3BC1BF79B53F1EE1794CA6B535D4F9B1CFB878FF957FDB4F93EE5748B65621DB8B5EEC66DBD3AB2E4D69B190DBC9B4FD2BC010581EE542853AE658A8E608B6607D4A260950F5A2CEF686468FF0102F87ED90E9B748D62C4893ED86231C05F499DDEF49925B2C5D4E2A858ADD1109BE4AF026E5A6E4522EF8A9DB450408DF6DC08E23206586CB34AFC8B00CA558B9AC32F0AE464BA1A7AB3E0F550CC1A236267E2DEA121CDAEBC09ADD1FA335E5BF09BABEEB8BF94C5A5D357C48034975294EE604385430F9AE6D3F79B052F1AB226307BB8C05E3107F4B47DE5263CED117DDA65C35387D616385AC4E6468ECA63C35F3CFCC65EDB3AC92AB16F304EABC6FED0888D884300F52E222370569C4FE43986C1C3CBD1C4DB716A060EA870E77F75019F891E6CEDF9F408ACA3A0C9DCA9D8A35A849ECF7EC60038E9BF1ACF50", + "sk": "E39515BF8F6FEC2293D62B92132F0897FB9EB6A90FC81D0D94CDD5860D5E539C75E59EAE1602875B2C9B9218052FA9A218E1D8B3366858B60095D5B3F2E8A6F7220AD0C33F0799DA369C00849A761AC9E21C4F06970CDC9E7B98BD19D756ECBED1D8C472BF62ECA4ECAA9A1235BA3D2A835515F359E060B6142D774FF176C1D4D9C44D21162D94920D5CA0908114900CB860890289C0A08D01366DC89691C4468423C22412368989160963224121306054B008C94291C8A00CA204095A30309C284D01388A498484C32232E20046941490C0B405E41668642282122151249908C2802454466A4CB08C09318A01A90D0C4668113702941084193185129740E4143294826894B22060182A112860800802CB186918138651C030A0B42962B28C14430D203465DCA60C0424696228861A198CA0182800B5644A1022E2006501974109078C082726034551E1A021C2A85160184DC0942461C0690420210B996193307284040C18B365648469C3302409A565D1080A5BA08D0AC1010398811137324C026D42C808832405E2364C8BB4711C294958900588204A61104241803042B048D9A8240B49080B084C1A3331CB448A43C8841C8560A3982808C744A032504A224664306E902428C09205E3008520A64C9A4445423825202265C304801C928810361122166240120E60268823A864108044622820E1A605E3367122142111922922C8701C972DA0005220A39163047140148C10A74D1CA40919361150B24DA4C2910A424ED9882918188DC8924D4BC884D9B4214342410BC08512C051CB0832A2803113437202036A80A469E1124D9818481248241C44086336091A296E4C9061DAB6040C374D21114184C8804C446A1A40222012019B002C24A92859A4704AC228023122080751C4485002262103185221C26013880500896D84B844C0242A21057183180A0B422103060C09083018934C24471119265098464C11038A91B6209B8451A1342E88480013272CD3188C00224DC9B625141441540691939671C112891A408D1C96850C3841182746129731829649D136240C2406C3944453B2705A32700814410C3124240982249950C8C46CDA4230201169521062224401013549024950130132E0406CD1461119206C98368A09A38488080DE11001C03672E4A630542281A2B00CE442320A044D50268C60146AD38440129705589024C886311C1846138091112982C4C66548060961468080460D01116D1B42711CC22019C10109265123B48952446283022102436DE3A26C93908CE2042404B7114214890C3881182010431486A41080A3424600084E61B62410B16D01314A08150A4914640A166EC13468E384450A2532E440440CC74D1334069C2044DC304A032989CA1600D0962C9C025083A84D4AA28CA1080ACA0402182080D9986DD3120C14055223272508401114A90C8C188601432E1242454B2090123008C326290C8304C8864824430EDC9060E4328A13086D002364090871DB044504B885CA3432041621DA306DC12690D8168C5220499AC62950227181C4404C049214364A4940242128624932461009850AA80004B26D232572840401D2822DDC404C143991C8226D24213164248AE4445151C08418255208880D0B34301C4432540640C20292198504A008406036510B322A83148189B41089844001A04D90C865023810649890E428528BA66C0C4586234252D4300510188804432248488EC43271223788D9860D04294109B43122016043B41104870504C60494B25010A46CE4042461164C23C70D0A33529B8825E036440231889396498A086C5026218004609A9084A30052809460C8266682A4440A389281029020A95140300921A86123C32CCBC20CA3408443983000440C11069109B3290B0069002664DC422502290820936DA084095A86481B810C2119500301011CB46060A609CAA4411B2848C8125003B92964C645648429134708CB348513288022A32943C0710905855B32910B30525B22641146080A290CA1C46D91C640A1B08103384C5B487211188EC0A681D2024E00186CA042205B967141488A1A952813478A011584142386E2102D54200E62901004306062A46823268AE1988412864C6322894A409209B6014A20249A364159126664A46C0A04448CC629C3925022A741204121A4086D21118A801671C14809DC808814A469A2404D6CC236DDB141FC1624C1489B0E267A337808A8B208A5F063DB725D76821755ABC2AA881B1B37F4CBF2307222288FE6150581B99A3E6848EAC874842B39E3599FD48B87CEF7214E2EB2472EDCD571EEA90853F9DE33BD9731F5EC1C8062FF779F572F4736138352469896496EF7B36E0BA968BD89377D86D745D5B31D7459BDFD70B2714779051718011680B0C73F565DC3B246F77CC3A9390885D9EEB6885C7FD95A3894F657B1E90F6D747B42396C1370BBD932B9D02B314CB64C09552AEB5C51B4DF9D31875A5C503586BDD71E3A4ECAF8365391E0FF8BAE96FF6071AFDCA95B457A25E2B8E199B4FDD18D4BECA647480600D1EF2EC4EAFA1EA5EBD7D0B3D955A73692431849D0CAB270A5CC2D7AEA105EC15F1B065B3AEDA518E7A802636BC82EED428EB2BF9F991D9C767F98B4630205CB041B683FDD12D12DA05585D9D7AA24B05F97192C5F90DAE4A4696F3C68655ED96ABB0331E8C52552E52A1F156EB2BC5DA11E24651E87967A0DC4A03F47B4C4B7100F9B1E77DB59CB1AC0C5C2B9B6ADC45A93BB256638ABC3A06CFD2E2AB48BF6C74A26AFA90C0665B16D7D884FF0FEBFC2964D66AC72E9F7C34EC25CB8A6E73CF5D5B80939FFCAC59E4680B877B9EB5CFAECCA38201D4B4AB4AE747BC6DF710632F748E2A4C0118948B0B13F0884655E6F21A61BFAA5250E73E838BC81D3B2EA22F7A3ECC02766469FCB64D9DF72C3A67CA9E2FCCBC822D0BE7B6E2202656664D5F5561C21E79F2178261FF62087BB0195507DE47A6B63712E38DA9C39EF5B4DD511D66F63C12B3585D7A716E83E7BD50CF3F94AC410A3028E7642415581B0A9FDA314952B149E4ED5037508538B92BCBF95181202317F3EEF2715321CFEFA9DA7D32A06007EAFF8ADD07D8DB4D3AEC351AD82DB7A31C4C0D4CC713D86608C1E1B4E26B3345BFCC6D0F46974E08ECBC8F4AC24F4CEFD246830044F319E49A32C10ACD0B2BA7C964E7C1DF5AADD3D83779938B4A00913CA44BC75FDFDD8B1C6373857498DAD2A21FBB11D2CF99D23EA8DF9688B100A2398869475E3B8F653F37B757A6023B81A922175D8D2A6FD96E721ED4F750330EFC5154D4EAF379A4A0CA0D4883CDE7ADA98C42AC98881E6A9E0610CE2629E3D1C4AECFCF128010F5F31CF388D15B38C57E12472F7056B036439F17064DDFFF10F5FBF2FAF489B71BAC8BE3394B2689221CA52C7714858F7FDBE7F91E40B2B535FC89733655E199CC9D6A12F451586123D0AC5B5C62DA9BC573C717FF8BD289E4331B1CA9554C77F381BA09FF079A9328825B682FA9322DA2DC65204EBAFB5166379057D0A348C7285D807D40798EADBE6FD0BAE3624AB8DBC9DB52D92CD11A4EB69BE18F3587FEBBEB22541506ED997A29F8A3D1BA0BBF04CF27155EC8C65FB6CAC0F15BFBCAE383875ED1537664AF1E25EBDEABB6214A398A3284176DF43A4778C61C42A3A8A03312F79D6F6F8D283E2F2F8470734D7A1F7402AFA30426DDDEFC99560A79E84B6ECC7062A20D41A8179DD9401EE538179B754D6AAF134920259C3166370D4FBDFF0BAC5A60BF00CEE1DBC8E80A7405FDA4287F3256C136C53A15CAB83EB0F5C11F95F15A70F07C399B2C69C7E5A3AF8180C5A4B8BACA37338FA72A68F9A03D2EE963561BC54F95086823EEE5CF4FC3A8BB2E32B1B7B8A97A9FC8F3CB752A9BC0C3B7FADF864AE5422583571960D237A6487CCE742FF090A502B36E6AAB283FC7CDFA3CCDB8F54CAF4525C854083AF40E15C02E9C55401CB26D92D6839F481098C3C78B69456854C17BBD301486538F7CA884C932B394B702823B14B50A7C1E7307FEC549A6309E5FD0F1963720902783FEEF7504CD5DBB4DDCB10391A3D522FF4D832EB97E00E3826DA31C7E1C062170B1F27CF6B592F81EE1AEE3379A2ACF29F002CE9F8A7D69BB108B0CC856821947F7F87EE5C9FB9F92C2DC9F2347123AEB481A62BC740077FD8FF4A22B04E67A33BEA9EFC1F3FC07E492F0C0BF320D0557CA0D8F8CEAA8C1AD99AF61DADF905CCC73540CA307B9EC41F706FCEB78AEC131D2C4AF9F9A47B393E78BCFD7F5730444E3211CF3794B01FC101B1EC1A311EC4413D52953D8B84FB0C5A07CCCEB9FCD11829AF2F6E43850EA67CC0D8F29445B7A9A8C37CD2B954D7DE940D8A2F0BAED939D13810C62297B9B91A4637524541009976363DC47CB5A037CA45DE690CEA685AB8AC223C448AF7FDC054D0CB786A8DDDACF37010D529CD67FD509489FF483F2B8CFD20EB7453E02C7F13CA0D358C0691E37282A7B294B9802AA78F4620735C9418619FF06E6182CFF0CAA6DB05F1BEB1C6B91F346E5D3E96C3B2A62C6E9C83E8081C655BD8A88A38792F2A8F69CAA34339BF37850EAC88124C5038C2AADB9C7BB3ABFD8C50EC3DA823EFD14C895D51109655C1EE69FA42B33C20C86A861411F3E32C871DDEADD81E823A97A008AC60195781EFD17429553F9D12997942D7C4551E01C6F486587E94B8F87CFF23744115BBA2494F90B0A917C40AEE486091B015773A6AC74497893663C7421C75D8936FD40419480BD335DE872238E61BDCECD5210A57B59EAB15268E28C03C897C9D33F276ED33F67B2D7D7F022E046C7045A76F1398AD1BDB10FB635F2AD9F7FC12454293EA54397D8CD76DB89238529666BA29FCDBC89EB073D371870C926E0D90DAEBE087AF1C016F40BFFDAA389724252E7693A0769D62E31867C9FCA2577B1A2F4DB3354E7C5AC08238E75287DDDC5EFE1E5F57B6FFE43A0B19685E63F2E4B3BC0F8AF3E84AA499551E89E099E41123D8B38A4CC86077AD80DD013C53CFD86EF7CCD66D8446FB55E74A303554E250168F81CB254904AB5D9E589748767CA17EEDDCB4418C5FE6E1D29CEC90F7ED7E6FD770E1F8A89CD86061E16C8C4C16542AD9FBDCFAF896B1DB3326753B6CB0ABE2392FF313A3BEFA7DE99EB4E3B75753F527C451F0EBE7434250D08D690EBF5A8071C37E0528BF718ABE32F436C821494B4A5B6E20853AEB2C3CBB965DA9F2DC6C0DD6B4B85EA6D437157C4C02323B796271617CBA5E53ECFE35A287B6D5BFAE32CF011C795450536D10F3EA9E47085AF25D66AB6E152D5FC06DC5AF22C53AA2BE68ECDBF1F6F49341C861C46B069F9C32C2F8BF94EAA991E30547C19EC54C0B4D22F285FA0C8F8BEA4667188FD10BE738F6E6527E13FAD0970B564C9263A7FBBF33FE3AE86B72ADE3421502B3AEB85804B10CD3AFE6BA64C2B9B46FB5B7E902E059ED0BEC4322A526AD5F851FE0CA6BEEF5010A738919AC70E66D0ABDBB123A2DE96E7290D5593CC6B4B190C4A91A256EFF7B3A8BFA8C5676B3040C4CC3A741114D15F58F8851AB59EF04EE386031597C353B9C94F254FC497F9890CF3F4DB2AF5C545757329E7D673DC5E3CFC54B9E20B72C0576ED576C30438205F782F745151A8EE370C6B8ADF4DDF727619449EB9212258FB3E3652DB0306189224D56DBC66FCF3869213987042CEB6C2D65C39E76426392C42D8E49430538ADDE22C7BE9A12E23412B2D377A4F38105829717ACAAC122B27A2CA1D7BE73A9C3A51F0D313E4C304BB62E989D02BF92814D5A1D8C71FE5D93500F0F60D9B8B7F8E7C2B256966CA8013705A89749248551F04184EB93231764367833F72F12260798E5347BBAA917B24FDF7C9A93E4E78E8456F9A5EC476EA33A757BB0D25E77535937DCBAA7280F8F6779B79279FDC63683F71446583EF4AA9B368CC163F677A425FE420A6C778D2A911C82BDC314CC1B52294038032CDE64580FFF9CBC00CF2B3397DBB33ABCAA7685BF3F176760FED323F3C47FDCB71BA03E4FA377C11F4BE03185138A0ACABC01217A0AB10B3DA02C08F9E8941E30D28E1B5B71EB1DFBA130EC8F6C4454313A6E54229561E0C7B5349E5E111DA47F83A852B6A9229DD041578668FA23FA0FBE170E4CF85B45F3020DD7C81EDB9B86634AE867F27D7E4EF3DC40C85CBE46BD6458C50AE73B5DD05A5A4643F55B23D81EBF832A9D8F24151C83AAB59B2D0D0500B9137298E3052CDDECD3A9D1701C11B1C24AD8386C33279EB95E59B4FCB475C3E4FAF71B6A9DA3E510F2BFCB7E0BA2F435D1E7B586CDA7DAEBF4CF87D54FFC1783B171379F7345FB746594AA62958A6F696D2BA79C744910BFBF29F942B606D16814E643AAB16E1C0D978457BBDCC3DB2A38DFD9A05F468D555146D2CA66BEF627E3FBB84EC0868CCC8A87BCC53AEFC2C9E4B75279312502FD7EE940E633791A37489C3B2478BCABDCA5F8549C03AF06303B35105DED5EA5D3E5CF124B57770BA25871052BC54AD71E8FFA38E9C71CC05D750824DEF70072DE15D01AD50CE09A59130A31BDAD5CF41214B7603CFD58FDE1DF3B91055DAD6CCD72F7932AF2E5DA692E6C67E60B35FCF9D6F5AC548F368FA52644B5DCDA3C28ECEFC577ECE53D4202B701FFFE7FC54FB9E79C786E4909A4130C5E6BB1F9EB2BCCCDD309F6B1C2F31C54E4D65248AB9F54A55DFB250CB77962A2EBAA41C0F6215A01BA3F77724BD93FAE842F9A15DBD9A53BB201D3F631B645AEF2CABBD634A2D7C97B8B279E5E070EA30CE44EDF0A932D8758184EDDB79378ECE44845D14A09BE694157D4B398A656613992CBC7DF2EF083563BD7657CFEB45121B6D6C0EB91F023F7C5F072AE990C51F399DC5F439927FAE9C73985C534C67D7C14BF4A272C3854B3F0F4C356", + "signature": "E09777840287AA8BDFA47D6D9CAB122BDC3BF78E4927DBCF13C93EAA7E424FFDBEC355963D8B15D770B3DA043C504E90FC61381A93527734D42DE2307F582CE18185FC7057DD1F374AFADD9D1D7C965A4FE9D726F9BBC1FD88294FB54D9DEECD357F5912CC06DA6D9F9530D592A623472D4464E38DFB5FD17CCF5000ED1DB12AB64C63F301F0FA93AF702EE1DA8582C8FBCCAA850AF85EC5A9D3B23C07EDB1ACF143407ADB2B15D851331D88634CF8252237CBC1E80B40B11B1C0C7D69D154659F2B4D0A7AF57E33C7DA177D5463D0E87ECB1B0EC665A62675AA1B6E61858C89608931552324409100CFA01CFF77AC23FB5CF352F89708B5CA934DB21F3FFB0E0D81DFCE7D2ADD71672CDFE52F6F6B247A6417C36EE0560F88351AE2960B6D028F864D4EEB2A9750E9FE112713D4B205F4AD466BFC49FD0CA3B5D11CBB04B932E9EFF39207AFF8033685A8315046B71EAEAB459AF13BD52D052834148E8B638918BFA6096E06CB4499BCB0006F56BE9B95E059357A229D942139273305DBCFC93E517F0C0B103947075449DC8B63C3DF31A8B010909FA29D65AFFEB00557121AC057D91D4C39B8F844DE281851C2440F96DC08F71F7C2078615DC11A94C71FFE47561E6CF90AE6D86D8EAEFBCD050C98A7BE2D234A0B0173595062249D7920EA70C4990D50B8598AFD3E675A2F84813688CBD020D3E8642EB467AF3E9E0DF034758AC41300BA1047BAEC4A20466100339EA47FBBF48ED9448A41E3105C2186DF55636F7A998D1A36B0D5B2B4FAB1C03E39C5837D478DC6A01BF077D47D95D29E86776E32FD644A56201FBC05B7B8B03C29AD2257BDEC578C76594C347977430ED840FFF65D0C70D0B5761ECC17CD9FF7657C2238130F10BE23CBF0EF304B4BE65D9D6C4883EB08D10DF684B6845C85F65ED4C88B3F272EBA8DAB13E2439BEEF0D4C46A672CBB7F4BC0B2C195C724FEB1E539E019950D9EA02C1DCED72EB649EBF9A383FB110E6927BA2F06AFC476EAFB4D908C2BA460015F509790833A7BCC49E2193D4ABA34B4F47CD2BCB1870AC64BB429E9DE0C377281252B1695F29B41DB6850427F22255343DF4B4DF2FE31E057E50659DAB6A4453740EFCB4C230A7F0A2BDAA004539C2362D613713E1504ECDD54D9224CB051E72BA2BC7FF7DC3850FB84F8F5ED264C84EE5B3BD2C82A78AFC44043152CE04A6BFDC45DE11AFB721D5D998F65149864795DCB809B37A02B3988FE8A487110387D02FAAB7B10C3392E7ADC298B033376CE09664D5FDA6130EE7229A62F81D814DDB0D17D5EFCC395BB74252CF3ED495BC8D2C637B77E523537DAC2978219031DE87B56DF4702DDF1BBAD8AF900C0C3FF48428786E7828BA46132F46C916C0824FE5E27B500100FD39EC5A98D9A314AF03B2B1AE2E7812A4E764BCB6C1E71028A19F6109E317FAFA7ABC295B4EA7487CEC29BE6FFDB0F256925A5312145DE541959D08FAAE7B954A3475C590D9F52C99AEB2936FDDE27AD3F5B39CBEA9514D0B4ED57688107958CE931B16D065FAED6C3D75B5A1208DA6A9B88028DBE065AB573BFCCC40A5E1E3857BFF906A2D76BFA5D53D3A38303C38FD2DBAF7D98C5314A57AD856DBDE30E584BE125F7EE3D32D21E5B77C060C1E7F0AB8F6E0DBB7495615498B5A1A8E473BADB3D3E355E4840753A8F156801E43B1624959771624CA979CCE8071D25DB6EF7EAFD71982989C2485D24D552F9C76EA9CC0D54C6B4E865E5AC0DD82B188FBCA3BD40DC158324A4E54A85E5C0DAEC532330A213EA3424152FE2637F49F1A5B7C6BBA3602C12F55F2D2B4D9B35AC67C6A5B082BCD251035B0DD6D3532E774D33C2181CDF20B1E7765E1F61FF0EB0B31EB039F8B8188569DF8F6EBBB2505B5C130E04D1A19D717633122733158649887AD0D581BCE0B7F566F3E9752BD5577EE073258AEB83650D30A4DA77FE619BF15B804215D56B80A2C3F422A23006FD5C1791D15255796399A45E11A5D772148F2579AA20EE320D7AE820DEA412374FCA0C974123FA5004820C82E3078F73FC2F2B84B809587DDFD07DC6F3A154DD15950E4FF93303D16F0E9A4DF36736A307B11837FF4C1C27A27ED984ADD024B1EF151EB1F2B4B60474A9988EB852ABDC4256DBB98AC0FE89A9420546CC4CB0D4593957F189A329A9DFC550CB7E8E51518E28788F42DC65CAC0F186CEAD2EB75954F739F2BAF9E48FA85619CFF72627BD95997DCCA465B2D7D8B0EAA2E9EE3795569A1C11BEB3581F14EC4B051420CF32E83955F684F2DAFF9CEBF0B17511570590D5F5A82586DAAB6FD6EF669D779118E1B04FF4BC900366F5D98B1AAC81A3AAC011D5CDEBD063943EF332E6E2943869C2BF9E8F809ED6224FBA314577F112CFD4B85FD51C913A65220288B110C900D37E8B4CD26B07340E32F21B0350380E56157830CCC1660A56F4D2D2775F7F1833CABF3624E78D946B2885411A12497BF527C34BBBC352636A9BE93FE2F0C4CC7A23E7C8EC9A89EE2B0006B0D9C6A316BC917883392E0003677630EA4EE5BF0DB5531C0EBD400D0BE2B1DDB893DEF8CF6D710FDF43322B5C1A2BC17143E4F563B707E4E3162A35C3D7F324B164A671A7EB5AD188DE229431C81F4F554F45D365018A7386AB6A8498C70D6CF4C21D6EC50508C7E07DABB5FA56A490C7DE263271D60C884945B69FDB75A8F6747B0998311631001651984B4DC3442E61903DA550366D0195A68F4AB71DC0468F25829A23053E3EE60E1368F686674659D53FAF600A699998655493ADA6762C6446513E9D3800575D21509F4C5F3ADBEFAF295B1A94BC537CD4981D00D8F369987A43433B169ABAEBBEA11F0265F1D46E5E2736FD40358AC05517AD2E4328955C41C45409F86CB7701ABEEE41980397E3B8AD69C50A7D8FE01D4D66C3F852164CD0843717AF39D6D5D43E52A494CEAAE2F1B465CF08C3EDDECE60198093EF61B8D2FE1EDF073CB0EA1413167E6474421CFAA590D9EC22F5E1C41DBB90B4776B94D0AE4EB8853E80D1CE105D85658D26CA4E7076E4190988D219555848585867D98689841CBE0E8F5F22D6FEBD5BA553ACDF1C2AC44B57FA0247285F4E46F48663C2958EDC4C0B70A38A73FAD041D106EB760ED5C123692326FA778501D470212964FB5E27AC8BE756286B520F5E124907283255DB7CBB702BA59674DEB1B9FA7542644D954FB9F2FF19F34214BE0F6DAE808B5CD21B3C6DA903FB69F066344DE78815728B9E896011B3F5B780A080C68844AF02312B9485E86D02C0DC2BDFB747E866B1F335E12580EE284B0FEBEFC314E4F288BCA56EF6BEA7C2297D3A9982A65C2AB4FBE352F3DB5E0215870485F9FA8E38713DC54BA46578BAB18193DC75A4420EA2856EDF27F7DB98EF464E71F3582580D11BEBDA6113960E88476F95FD03E3D3010F7127F6E4991DF883FB925646C3B1AC759D1B0F92F3C33AC941DF5F112AB983F13029B270BA4D4E15356B0511830787E8361445E308A0B39503BAE45EC0DBF24D55B52D0952B4549AD0B2A22E8547AD35D1C86F6F9FE68BCD31138B9FC1FAB2ABEB6D2043A324DBE57E88CB8BB6B90C3970327705592A418FC82A46FC67AC2F9020C299FCA13C52169C80C0C05EEF87A9DE9AA9CCAC529E8111AB9E5EEFF80CBB8B68466C390AF5F23F88661932FA5CE5C934F33BF691C47D543B82051E3FD5E50EC24734E2EA6417F971089A39A65AA3202DFB44B3DFADC273ECF593723E22D8C3B7E007CE18EF280626C3DD1A6CD35A566775B31F4BE5165BF51CA83131C9A10F9CD7140DC4EA510AE177FDDAEDF03E47615694D54812F8B998CD0767D1FAF0494EF5E637FF8058A4D67819CD25F0C5472797AAFD15084B5701690A53C611BDA46A3CED728239CDF7665876FFB795F49D7C02EDDC6BDF4BA8A72F84C72F6630AF8E098109C49140889C15507A3F067B8985F56E10AC4FA8ED5994E003BCE46A659D985642CC55B174C6E16445C07F0D6BEA10E307AF6439312E3A187B6BE1ABABBCD8D87332C304831515D2396BD124C7859ECEC291F42DF76F8088E6CE937A3B6292D685E4247682E147F5DA6D4A0C733D526C17EF68A3B4B96E6B60B5DC53B55F7FC207C42E8FF0F180ACCBF0DB931B60057C4D7A5BA86D7B1B135939E61799145C84F94B9EA85FA646D4A752E899B4784D395EAE6188645D9A9DBD1086DEF0F63A525215E45A43CDA8EBDE5E7499EF30614C5CA447927C13B222EF2E9F8B7D6FC2AD69B908024AF7C40D015B5695428206E3E9C20199C7F1F43F6EEE3174623C2FBCFE901A3EC6C052691921ACA27D85B785CE33E0BFE9DC317AE88CF49F22639B191A98027AD0684E573118831AF5E3F662E6740A4321A93DEEF73E0D966BBED80C0A972C912CF02AE0702E5C054C2D9F8A24C6EEFF028869AC971F21A8A66A376327ED6E55F6F8939E01E602770A38759C54AA915CA3C2E0CDD6F02B1143A3F9BAB0D8CD8BDBD3ED0B345F4288702213A9A65446E47B1A13935E301056CEE00D8D9AA3D7CE09CD38F0E92ABDA290EA6EAFE2736F75185E89B114E795163908A1651B8BE134D900D55B4B1E27E9844142E15D3F17E483BBA702EAC7A3EF3260A28DE43E59DF3F2C4FB37DFE76534C9E8796E479B804DD3ACDDD3AE5547D395659A7143B8852674E2281BFF269A528283B9A5A71BB954678868FBE6E7403BD5845C076B8123DA83DFB1E89C116EAF99E28B4CB114B913A822741BB8E9896E7264F8642F76342909957DBB0A280287F77E4BDE2659A2023516F707303A59C717BD9B1FE00958889CC05D566A5AE965F4AD505FEBAF66A65A95C1CDB630C429CDE44479F79F0BDA285ECED4D54FB1921E6652F7D171F29E0FEDCFBAF7B6604B21CD0178241B275D73CD51B3B3B10238018C7D35E2352595A4352C49E588355B245649D0765F08402E1568F5D263C5EADD75F11DF04B26BFEFA5D356CA358316F534A72CEBAD3CC778242DB6ED0DED18EB20173BB121994C114CF4A421A8BAE2525E2076B79BAD08FDF63757799882F9D9342A036C739BED839F33DB9677BBEA65C359E76E1883F08B5D1774132223EDA2884046C316C59FD6F462DB07CC181C035BD2ACC864AF1695CC2685451FF8AFA58CC89E9024D667263EBE0A6DAEBA7A603FA0970D3E8E0B9E357405F283FDF4FFD58531A358A88289BA5156A9981E1C1DD33A43897C5A6471B6DB637483E666EE4599DDC09DB7065F39373B4AE4EB907FF15DC31EAFF5ACD2695C133C0CA0905B97C2AA0E48F12D6481F5EAE92480C82680A46EBB749AE43B0DC18959281B6FD5E655EF7FDFD82B45B940428325190FAF9C723EF66B5B8B4D0FB54443EA80AD65D06542C3FAB301E25BAB2D295437070F497FBC1CCAD375DFAFAF7E560BB3179782B31FB141E8AB9287E90467956F2A864501F1317FF2E39C29F5F52E59460C12974A2F6D063E16233AF7F38981EF1AE22936B129E97CD6D80273E21B8482F575AC8D645288E9D122F23C2AF609DDF1CBC029EDDAB76486A481E4DB74DC6E37C8F3C59781AF1D1489A074C55DE3AEB83FF5072E152D0B5192BFEB1708F26F3F0E4EBE075BA64A487A5AB3E5B47F0E5346F0DB7D9AC9A5DEC5EB0A7C73147F80FAEF8D22A5D6C3805690B727A18A88AD18AAE0C026315BDB9B587F6D6865B1862E072F30688270B5D028B9CEFEA8487639CEA0473870E0A8BFCE3E8767E2FBFEF67E2A27E2464C499F77178667D3FA75FF0D0EBF76BB6A3FA5F2F442FB17CA916929EB8594DBD68DAB7970B8A553BDFFB62ABEF3A8CC83E411F865FFD530454578A55234DC06C2E28C5939A2BBDFF095FBB09AB7D150255FC642B8377F05E39CAE0B50B45566EAF03D8C5BC7657EDD1F01C50CE0D3BEE2FAF4F0B8FD291D4484C27BB0518716EBF86D03B98C3355A38AED8C504319ADBD7EC67AEABBCD6C0510A03C2B3C6B68E9FD4ECB9EC0575FAE2516845E3D6F7A4966CBB86865515E2C975F037EF6F7D5561258DEA0695368FC6205E29C1493153A660BB1F7B47C1ACC2CD2636C9FBAF485DEB1516867B09B70726CB7D928483636C07C9BF93A83DF386757C554136C1CAC692CAD45E59919A00901CCE414E13759F8E799D84B95143D372051F511F9456422E7ADFA6CE21029646DDA93D178003C9775E9BC66B78F4FA0735B93407840D2C9D0763878D1F2E3C4EC295E49A18615CD64E774317FAA24354ADF83437A98C51BB89B4C1A05BAFC4D8AA5FCF6D240848FE8BF77B7A3C55A5EA4E97E6EAE7447D169814501B670B712274D1B51557D3E3D58ADE7E402F4ECE11930D735CB665E764D8E7C2312342EFC671AC9947067595D29F03465675729D631556952EC6458C1FA780A97CE18EFA6C701B0E3EE9384E17EC2BCCD53B0A094359C2370CCF7E5D16BA3563544C06CFD557D968CDC23217075759D1001D57676B7DE4092B33577B8C25263E535462BFD900232997A3A8BBBDC6DA142A7682CFD3E7EB1415222A798AB0D7FA04545665BED3DB00000000000000000000000000000000040B1119232B343B" +} diff --git a/priv/kat/mldsa87_boot_check.json b/priv/kat/mldsa87_boot_check.json new file mode 100644 index 0000000..4ce2d64 --- /dev/null +++ b/priv/kat/mldsa87_boot_check.json @@ -0,0 +1,105 @@ +{ + "slice": "143", + "note": "The ML-DSA-87 verifier check's cases, in the order they run, each with the one answer it must give (the PM's Decision of 2026-10-20 19:35Z, item 2). `from` is a file under priv/kat/ that carries its source URL and the source file's SHA-256; `entry` names the case inside a Wycheproof file. `flip` derives a case from the pinned valid vector by flipping bit 0 of the first byte of the message or of the signature. `expect` is true, false or raises; `measured` is what each runtime answered on 2026-10-20 (elixir over :crypto.verify/5, mode off).", + "cases": [ + { + "id": "acvp_siggen_tc63", + "from": "acvp/mldsa87_siggen.json", + "expect": true, + "describes": "ACVP sigGen, tgId 5, tcId 63: ML-DSA-87, external interface, pure, deterministic, empty context", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "true", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + } + }, + { + "id": "wycheproof_1", + "from": "wycheproof/mldsa87_verify.json", + "expect": true, + "describes": "Wycheproof tcId 1, ValidSignature", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "true", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + }, + "entry": "valid" + }, + { + "id": "acvp_sigver_tc64", + "from": "acvp/mldsa87_sigver.json", + "expect": false, + "describes": "ACVP sigVer, tgId 5, tcId 64: testPassed false, modified signature (commitment)", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "false", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + } + }, + { + "id": "wycheproof_8", + "from": "wycheproof/mldsa87_verify.json", + "expect": false, + "describes": "Wycheproof tcId 8, ModifiedSignature", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "false", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + }, + "entry": "invalid" + }, + { + "id": "wycheproof_18", + "from": "wycheproof/mldsa87_verify.json", + "expect": false, + "describes": "Wycheproof tcId 18, InvalidHintsEncoding", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "false", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + }, + "entry": "invalid_hints" + }, + { + "id": "wycheproof_77", + "from": "wycheproof/mldsa87_verify.json", + "expect": false, + "describes": "Wycheproof tcId 77, InfinityNormViolation", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "false", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + }, + "entry": "infinity_norm" + }, + { + "id": "wycheproof_6", + "from": "wycheproof/mldsa87_verify.json", + "expect": false, + "describes": "Wycheproof tcId 6, IncorrectSignatureLength (4,626 bytes)", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "false", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + }, + "entry": "wrong_length" + }, + { + "id": "wycheproof_1_message_flip", + "from": "wycheproof/mldsa87_verify.json", + "expect": false, + "describes": "Wycheproof tcId 1 with bit 0 of the message's first byte flipped", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "false", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + }, + "entry": "valid", + "flip": "message" + }, + { + "id": "wycheproof_1_signature_flip", + "from": "wycheproof/mldsa87_verify.json", + "expect": false, + "describes": "Wycheproof tcId 1 with bit 0 of the signature's first byte flipped", + "measured": { + "OTP 28.5.0.8, OpenSSL 3.5.9": "false", + "OTP 28.5.0.6, OpenSSL 3.0.13": "raises {:badarg, \"Bad digest type\"}: ML-DSA-87 unavailable" + }, + "entry": "valid", + "flip": "signature" + } + ] +} diff --git a/priv/kat/wycheproof/mldsa87_verify.json b/priv/kat/wycheproof/mldsa87_verify.json index 6db74ad..01610eb 100644 --- a/priv/kat/wycheproof/mldsa87_verify.json +++ b/priv/kat/wycheproof/mldsa87_verify.json @@ -32,5 +32,25 @@ "pk": "17a508179b35057099111733da28fd1a2265de7d8ab22d5279f13bca84cc42a5b8c9644c121e7e1b81723c5295be288fb6c36bfa188b6e08d913a152350947fa2c8ccc3fd01b319f65a2058a1dff54133946cfeb408d0b6dfde6bbebd7e0591cfe83b8b5452ceef6c855f7d33e06a0d269345089ed0d3ad67d84d8a4a34d16836004cff125469e8c3387abd788b620e30c1fc23909117a0e34c42a6631d9791347b1b2a3c9ab3082416211afb7bc3f6ce630a7019af19f736cdfacb1e7db66b65ef56844d2a2b0753d09283a7a0b66f77596384e95f7ceddd1c4ba20edc11f1eaab695bb963f6eda1c383754aa372a0d7729bfa6e0f142131c2367ba3f89ce3de6c357f9a7225b7cb85f6b3e8a3a122e8501fd1446b8152a415c19dda1d2e4590cd994f6664b4d1abd7381468c3a085abe2741a0cfbb81880664b271677245c4a471bf8bb8e0192eb32e4fb5e8560f3c50d6b19a353e486d0fcc2a35ac046286e707e095f61786d92212686a65d39b6863e0f8cec1e1997f2f845e4878ca9df650c746765296790863e51d012d32dffcbd746aa2276d04c0a57cd1b3d6ed06c0d66a0897aae5c49c97b6f19ae829baaafbfed28a52c05963c6eea9eff69528294207f8cda75280f7c486e6848791c8e37015479f2e13c28a9fe654dbde11689875203aaec51be3da7cab1cf31e4ec476c0c830cbdd04ac02167c0a6fbfdd6548b1fa525d235c7e3fca8d63e6427503b0a45c0bfddb428b837c32e8755441077bfe1c0142bac357b012a46545bf4148d465472dcf89c9d73b62357087e229f53a450d3cce41c8ee21a9d54b61e34a794f5b1406a70724ab0c3712c49df231ef30a956075e907c51b63dd1f9453dbe60e25b0f3cc0354dfd7c9119313919e77cb2c92f544d3e5302b8827603e936b567e99bfe9904932585a9f01a5a1b5bce07565f1d84c6b1c5c86259e1fefcff18cd06861122be6836be21e40be4eaf6bcabee8f634f95520aa914bb51c54dbd67d1b9dc5e38831e786c283979a963a3206b98e339edec4128b0502d4d47813869713e431a529a03c7f54b50123680f2b7f256f5d2b40642203259b9e85c62253d5670ce372193f28b5aa48ddd643c54756a2cff808c109f74772961d8db6bb8a17547c8f29c7f5ff3ea06740b867d84917e07f3978ad0281a20689eef58467e768b6178a9b36a567289fd39762bb3e4254031b2798a4550857f6af369d484392cddd7b48eaa2942e2cbfe754d5ee2da2b7fa71222e4a525ff5224d551a778ebd828e4e0499adc74ff0d59a5abc78ad6a8abafeedb3c99045a14423507f85597b1a7f540982f7d72ea13449110b442d54b78029b4c7fe3b49396dc6c3b7d58792538fa907963de10a4b724548142541cdf1512e0f7ff1b10a93de63541b8cc3268b4de20ed26739ee8973b6507ebe48965602c35fa3f7d4278146b598d7d7044e16e97e9351f7c51ac25573b7232ae2432638e9166190e7f7a7dcb5096ecb5d10017cdea2a82b4f56c7385041c6919a7e36e11beac77ec3f25df44e7b596c1542c1e376de3667c0e903fe25b57c338e9d93c5570c484f0ddab4f57d38f292b23599d9efc7a9fd9e078aaddca0acb1a196d6c45d3c8be6f39e8cdbe3299e370b262e0bf6fb5f005cae2b12879289d00bd8039de6a571c310d87557f5c9a4f64a0bde7177a8464722a04bf87fa2cb0e312d4fa6e536c61d65dc2c1baf144b0d1d1d75f4c860626ff773933efa9941d105c53a1d92c4f7c7bba4aa969590acef1e50901870f59715ac14d9846d83871a77367be57c63f88bc2c02eabafe678f44925a3e605979282fcd3f284736a1d346c033cb782dd615e886683fc37cd87a91422857774c63c6659096eba393c56225ed8c3485b4f89ecb07d53526281a6426ae7d67cda52fec5ac32320caae9b96000bcbe9e8782be88cb1ca6dcaffb74ef04c77e03a994bea2c89e4fcfa44cd0c9f4e30705a8b7b20df8c76b05a4479400e07db03d243e9fe4c90d34e9245f1e574be9a388f5355482077e4e98b919de024e666fdd7d51ed2a0d58a823e7497eb07303cf1d6d5f10a536be980220de5856727e5c13981839cfa19740988e7771a2b984f53ae3a5916ed881a4a90fe524f0bb3778355882864f8961fade32e656fcf9f524e748c8196a1f1bbc57bf8da7b36de9b0080f0c7bb8487a2b7bb7a81a8ff43a2539b367c9a48c70041520f05ca3dae316dbbe3118218216f52b7bcdba7557c4c9d861803a5e2ee01d3682e1261d7cae0a99fb8de909eb2bc1e112aa43cc2fa9c76a222bd85faaaba5d9ec2198ac45a295181a324a0592632b89e2752582cd5e01e1a610e7563faee10b76d853109e257e7c0c248a9fb7933f514b07b4f4e3a4a3d2cd22e8cc45ebda3bef5948aa050f01eff85ae98d19f69c51e67ff89f2df0c5268acfdd325e84591317e05cab4f9e6358f249c4ddf4019fbc8f511549a733898a50efa9e0793083de0b15b5bf78d9f63d8df830d42df2fefa27b89e0ede2a702eb9467118fc0ed44edc63ad1b1935877c34843fea06fdf388bbf83e501723a13cc6cc2efbb9691fe28fc1d45270591e5bdf7aa1c82673544ee29d9e6c9da3328f21e9729bffd7f4e56de585909679a74037105fdac3f51ae35f69d9763d2e4cfeb1d4a8fdce99bf1aa21f866a9f523b2a9549e12258a4d19900cf5db37b67da19b23563bd1d701c6106fccb28e4689c62e1a6cf1abd763d7239c2258b765610d4478be9f1650cb8d18923592ad0024076e52f9bd0a3894fe97bc0a1646b4c37f62c27f32d0df270260f47c49a5caf110e4cf80168a7d54b1c70bed9bd5d9a143ce869a05cd44ee266aecd6bfedb39be79e7c7d5c11a99575ebc0f389cc55a4fe1469a2d61b70bfe4b74e3e27521a037d2b9f4fdb377231e2ceb214ba90f6953865c683215203ce963875c6524c01b789e0389a9f0c386eb236f0dfba6c95df4f28ccc7ae7cd473f9dcd20817cccdd211bcbc78b064e936e4ba2813df531128428ddf410e6ca07044aeb4cfcc0a16c995ec51c8af16a541ce18dbeb69a26635632dcc24ee52a5eedce38c502cd0e356ec31341c893f92e6063c3a160a53d34b85e92357a8ebaaad8f206771be43ee48cc409825a7094bda529ee18776d9e67f1fa1c1419514309d70ba2443be2f63b6943478d6c0f56dd058731e53de4c30bfc7d915e9284a56248e81944392881666680d4991f04269ec9a83b24b458ed59a6c274de452ab3013c103a4920543e6a7d22dadfd764f6ea39d49b910ee0dc216e547aa5fb4382a72a568ebe83ec00416fb5830dc21c24ae72416602870cb52c3a8a1c4c12a4b287b9b800d31c287ca161f404a9e598a5358d28b3aae43e534846bcd0d7a9c7652ae01e6698c79e315aca8198f36de45af7084b1cb21ca2ba0ee3a547a7343a10ef9e3fd17b0a4060badd1409a0562cba25b84fd578268fac53cfbca08e6cf6e5419f57262eb5813c1d1324e0df1d483ade08d8f6c62498e262485ac7c2872b11b42e5c1b797fc12e838b38a711d364d45cd1ed35f7faffdf4b0fb0eaa312fc3d5af77909b0649cbbacea10c9831273922b5b05172face9ce6cf324edf6e2f5f5fa0a9f0463eee938b30adf3e55664f94d274cd87dea901a7e08e805", "msg": "0100000000000000000000000000000000000000000000000000000000000000", "sig": "8243e3f28052056de7cc4b042fe1d959767644a1b000c5fc3cab98cd8d86670c2025a62784c13c5eb0f71b9546dae058ec5027d4e6ef29dd36e8ac6ba1dbea78cd5126919dec30e8bf50e9034aa5582d522bb97d30a7dbfb1af52d9cb655b7561a17a9df3d09b277849b0f4106cec822d715bc4dacea258f60f4b9adea3b394627b318d2f305dd612fa0826791b03c0a55f456ac9b8ddcbc67c88b6e07375e3aa5114a95afdc98e1167e7cb1c74bce4c835d1b47cd1be95356cf6abc3c9b36dcd2ce4b0267a1e0afc0cc9b7e3d0df7111efe0f936c79bb2ec2b3e65b0641373ba1096bd1006f19e0fda9c7d8082f5e09ae1673cbae86caf98cfe853a2edd27c48fffba468dd6808b0996cdc5eb50f7847f80e51f353ae9afc4a0008248901c25b5328dcae28617d3dcc9556f3e1bd86000e1630210bc8a6ca56d4f0bd61ec09b0ed6d4961cb56ff253039bc8f3b4f9e406ce7e408e8861e599ad05e238d214fface8605e3a3e2173d4b5e1456edced533cacf7135e0d212c4934982072a924074dd6ff9eb3985eaa4e108799c5609b936e2a0dcb32369ccf34766462dd2e310e561fbb09800719bdea3eff789b64b3602c2303286499cbf45cc769d8e955c2a0bc87e6dd60a94284fea121540966d1a8cb893695279771a12eb8b05e33cfe1ce5566337bf90dd241d9c31fb07c6205940b77f158a7250c5698d6c5c251e787bc81338f2171bbdeb3dfb29b2e9202c29365f33e18fe927ef0919482260c01b2fc94c90222ba4dcb14723dfbb3d6fce66da6eb6013bc455848e5c2e3f52f1bddc5596d4a5f18c8f9085b3f25b4d95f8497b818808e5628f94f03b4d838c013a3c2bae7498ed2f23449d77ce94069d2d1c50d12df6d8fb8c8f72b03bf04a3df67ce2990b56595fec781ecbedced215fc0d0b5e34c10819c242a122a4deeb607fb9d3dc68ead4ee07726082d132beb524ce9937b66ef218c221d77cee77ce255b5f18e7cd7c3e2ef9afe80c7bb6675128c90cf6b483d96bc6156f2b7a4d4c98097d2b3086519cacdc3bf35f8127cdd071136b889f1111c0db2953d4d47f8a9de23df6372d65e25b4f8887b4553ad11de8ffe01264ee810a5caac6cb214baf3ec4e5062bdeb0f892014e3551e1e8f5fa50defd5bbc7ec14839b1660d2b5afa5d67a945989f934205902b3c8d62a2190621c6fd3f47be0a69f68b61a5ed1b2c4ff03c7c22fb67a621a64889b1bdf1c27ca1c7322e6233d433cf9bf0ef2c3877bc9780fea6b1a7cc15af6d370e57ef36ddd0a612f1f7e25efdc7762c70c9cd925e545672ec8acdc77872745c7fee82c572d34c9341ba176a2f1e4e629bd1342d00d8bdbc12eaf17e3c2399057ff2a6ebc7834cc80f3ead92b86e0c9cd0a909eb074d97e7783074374454eeeee970e8aa7fe4c27eccc340adb62fc838fd23554554e62c0e4ee0a50a1f2cba806b6172dfca4e3943d4af28fc4e6beb054487d47f26d0d56b8c0f70393eba137c5f3c278438d264d35ff2225f4b8400ee97e550d7770637745cb7ca1cca4825cdcb9c9eedea9bccb9e7c9c2f43e5b95892422514890c465a4784ac10dfd3c3c5ecaeb4f74d7c7c1f41b2ef8a6e27879ce049054a3947e2f71a36c11f439af849bedddd2d4e23fa71857aaf34d8a082d034b3e6a438bd195e07c9d9a0d66a10a7c6a93bd9346314d8ba8ad5b2df800db47d38266b5052d82f7585ff57c9a4ab2f5c09f95b2e050b57e3135fc1b3832d86bde221baf6996c9a95e50984c7a2776a948f4ec98a21ee77507932e8da0fa1baaeeee4ccf34ee03f62bf3c267b3e02c327ec57c17e4cb2ef1be78e22c4090ec356c0c9d2b729b44e5d74d1583aa73de3d2187c910c5f30cb41586c014e1e44fa91c6155c646ead2f60e42017156326d8a4258addc65dc2fcf63d80c9d504d77222ce363b313bf396cffd8cfb802bf6c2f99e5e440fdbb374900b569a31f04e2ccbcaa29985e60c980b75dc8167e6526825005b07ee9dfe7df419ca5660315a5ddd47d70aa0a47e9e914f0160fccf4980a77a36fb78f02530dc01a88fc2e2be50d20f6066410671ad48bb65f4eee60b296abe47aa86967e454a4892c46d1b125fbf121c2972575de626b5b3320b419a58c844f6d0e4b4aa7308cffd4240aa4180f97b1241ff641d09c90bb40d4d4071f85b913a1a35421aac0be77db6c53e964537ca6ffd8795f21345b62d37a50a6544420f7142d54e6440864a0726172937974a12d775f69037266f501a9120fa31f71dbb64d562e11749260d4d3b3ddec8049199e0c34593dc34b7f5f51cdb963205ff57250d7bed8856deebceb18e8bec50dc74e9b3bfa359f43fdca881c9412fe5c791a7b0e0313300d6bd0b9b7177efffebb7aab23af990ca0fdea4957c2412ea51a7b72efc6be1aba08ed7bba5097be312b4cd3e684e47ef0aa96a5bc18da275bb9ef77f9ba05f4eb5fe1d8b88bfb7a8edfee8705dbe213005052d6c137ac399fb29c5c146bfb371a1f994f0b0563e5aa443167321318bb002434a79b2705aa24cfc36f7f748128bf7e38041a64cdc5459f2446ba34ce73959fe504587b5f5c2f91d2b5b9ded36724cf7f375cebc67dc5f3c5eb37a9d6095250c82d414544726e22392e9a7859898aacfdf18d2361f55139d32dd4786634ea2f59b0ea8db2f03ec335a3d8add4491c3377e3d5113b59e79360d816ec53834c8994ef04f634368d3d135aecaa1d54da5b63b606dac31c5969007e501360e60559e6f01c25a608724b13b978835b4cfcb69ca2deeb2d8b6235ebce9b0123ffad14c4cba462e47c54f7a5a93c5c8e1abe257e21ead4a6216a06aa454a15f8780c1632895dc497062048f04085e360918eaf7d800d4dc7b9907adb5a1efd1a982375792ea5c27d73bfb9f837dec8077cdbdcbadf95b02aacb8b88a725df65743db5a59348bbce2bd46a80bc9485aba23500f03890e8d114153bfd037a87d1ae27a7576d86bf435373d021c85a2a31cb1d978127f89fdcb7ecad2fef36de77ce19307e4ef1ad6fbf2c23224613868c184b318b77430c5e783ed0745c9b76e635fc808cbb62652baef12c848730893b7c6c3c713711f6d7819c8bbd126be7c743e66dfeef86b13ee6d74735edd1884badfa8dcad7107d86f841f69aeea05b28d75670696ee81fc34fa3a822b31a945141c56ae5154a56a04353e6a3c120d47ca11144d460005c75789e5251a225155d3655f6566dbbe26e248532ee4b5345c97b9eadb6f4bf629eca64d269eec8487efb7f7557052490d8fa144f5c0deadef6549cf5c167a02f208f2889d8cf9f380cea5960394ab163cad4b7a56dfdbc67be3f0e915ab030bc5018ac5f5a636b219aa00c077a22437d975689cee84b2b2555a3d5ff145ddf2a0e27b724265cd60c4db153e9975cd8ac5bb5658e5f61cddd821c31c349f770536be773ddf11b42d44fc22d5f954617b49a74e998459f230498e428927e875afe309779aca4bdab38eeb03fb8345b58ffac10a7b1e862e1fc9eb44def447a1e89ad707d36cd9d74f5f38a36251bdef3adc807ac384976b7c82133ff8d0ad5041116c428a45c9447934945def2f008aeac3477cafe3508392bcb5fd3db35a589f38c1b944e079aefbfadccc22061227936566c1b662c62d3816324582058dab11512eb1d6bbafac7cf7bbc4ff88801431ba5b14146fe27456e67fa285b81ebd34cf82fea2977dc70979b82f03d7e17815cf783b54e6e5096609653181c94dc8183c8b5561a8f8a3790f22c261ef3dcb7eb8c7a3eb0f432f016c92e0ee5084d121cfdd34b214bf7715de4dd6bba38d05a90806787cda65aa739d41d9511d7e0bdd9b6395f8b205be597590980a17f631d877a801c469f2e81fe6a44b915d89b04a3b35f087517be09cc2460af73d34b9c898ebe8dc48176316b3b747e1a8d6e0fd7d19753ca03b5c3ef1d0d9122e7c5fd373bbabec86da39fc3a223d3de048b2519fbd892634821dc195c011c078eddacde76fcc1a728906214e12be5901da2739751c2a5aed665b10a55b265e80c75e6d11e1d8ba9ae2e27fced1234b11ec8296eaafc0cbce4d65f1750a6fd82cd3fce774d66249d0d4e21272330cfbe89ab722ee78ea6ccc3113f6829e7d437b307b6eb0cec0082511ef8c69492ed46752523d5e4bbef2799eb76940f4035c101833f7059fa4c9c7765aa0d378c96982e47285aad8a2c5dc33303e2063166c10a8158f5e9e0b86fe152035d767be0dec7b4fc97313bd50c27bbd2323aee3d8b508a2fe395024cd8331996fed16f97ebf70ecbe997de5b8bb888ae8e522360097eed1c96fb2ad50af4a2a8961be88b46aecbcf31c573d5f172074f20e98d68be6129a1360a7c8dbc0dd74a21d936deb873d33e66f8d8f6ba73d8a05a473272b249025a8ad5740f7f37aa5661efe6558138d8487644ffc05f952777083ee67d180b80b1128a3a1b93dd3aee189f9eea635a49da7adfa127b7e2e5567e3e0d58efb6ae588d336bf38121e6f5494e66bc1a9e1f975d596468563f2af990bf08ec933016e9c50f086904f30ec130bf4a735d7b414d1726aa03222ccddef29b4ee69062e217928a00eb0dab678ecacd601ef33d6482e39bfcb04325289f7316d38938460e023ed12f38c38e08fdf6c75341909902408a5fe8786c25716683661f0060222db5d9a1188e9624d6eff0bbd3164adc28d6e09b339c0d825a6653503bf7251972f208cfdc0bdd1557f2f9892f3db0a9efc8409bf6c83b2e31bad172cb2dd95ae311d873bb80e642342814bb71890b5ac9e116f0f367bc85a790839e8dc1dd52b167f6abb3809ef2e8a34f6dde611aa4f75409f6c9e0bef4353d1c2c50df590130365e20204ffe392ee3438d644b8eea3f4a92c3184e232745957ffb88326b7846d4a64eda2cdd5b95412235e6420de10158930f675900f9b558867afbe6c182e37cbeb3625d88adf0dec9c8a3a096dca43b3c7e9eaa54384f4f987354407f2855f4ee5746ffcac55293d9187a668ec72c12468f588b7088b069b20aa03cd1ff3bd9af26e922922e327ba6a424eb0aa9180cb60da4c45a6b76aea03258818d9010ffb6ed4fd0e4453188513a15382e4e057708f4ef24d57d00968009c1022f3a40acca9dfa5523d7fd3e63603f198a5c72855e1b6169a333925fa5c4325efab41ca4800149dfdc8ce7635836978f797bdebf875294128f3ad72ed91ca8b52476f4c7e0c67082113d2dc327a4f6deee33bda8a7db96672b990614edcc7138cea1a006867ff22528f8eb7602720c58564cc8a9cff02b404355a03d60f03fa07cc74a6ba70dc4aa8f2122e5b7863af835ce5fa0e80ce1e62534615a240aadaf1e09f8d201559e19879f6f7715cc459229784bd67a3db8e6d65cf4b149b678a89661b3d0a2f22709347e28ba2bc4d4c408c7df1861266ae1fe47a32c22eea95164a42fdd9070e4403f5a8cf39bcff30f97b7dbbe54cfe09f1dde66f84be59754555e64af1d52508e59b97110440ae59da8cb70f2e7c551d3e41b281b80bead47668dcf8e7f1b2abc09af9af89f51c8690ac965aa2ef6ef565a532c7a0aba5520c41c1d365326a5d5e6f1b6796ccc828290e18a502c2fd403d915f96e01cf1936703ef01a781d7f48efde73d99e80f551768f1cab025f176cda4c9222a074426af4f94547b5108b89534344424f1985558111b570ca48955ed1035af01a507e93abb177447f778461a5908e5e1040b9ebae6d1d2f303cfb463d9784cffc3a95c6631b318ccd952ae9915c6de13374446cad492f84ecc36a4a95d520a76ada6a5437ee4719e73cf997b5a3a590a68356419888ddee7ba6db7288c36d5aef6030123bbf029f8ecb8302155d4160ff4c4c82cbc3f97b463791bbdd561431ca1fec380b31b5fcbdc920e634bd2b42eb8826d96b605a69c60c92a51359ab687b90b78ae6d94e51b64d721844b8b73a71fd1f201c41f2871e3a6e5d0b0e0050437938dc298f241974ae1c00e90e72e04c6ca294416c2ea243174076463b027addf5c423a93bbffa08893f9d401dd970ab0a907ecadde16a41bc95d492dfb8ad2d7ba7f96a98c30afe47011501608df033bf1d49b1b5135542ae8b0f6e556e0e49a9883e4632c6da8f6f4734e2754736280e0979faa883c5815033896644361363045923b8774b8aae0241f2ece112daf259ce072a1fb2c236913a52a9c2a9ad0b2e3a74a3c612a654f313b230673161bd54f9c0c907652a4a1e13677661fead894b893f8245525e6e39d43e5cef2f5686b07c94dfddf62d35f9c256df6c71ed2e4aed383cc6841b3ed6de8019ba6464c6db9c7c7156fe67b899745a65f0fe624b25a2763b7453233d522fd3a2073309ad4228452b84367d229831c86156df0e04aac208b9433c46d8fd03f252453505295ea12278724d473e261c1b05edcdbcad15e3ce847c6f1f04dd9241dbd7cf816f52332e250efff6beb7b3976452282708d4c6917f59422af0dcb9a89a81775b410900000000000000000000000000090e1518222d343e" + }, + "infinity_norm": { + "tcId": 77, + "comment": "violate index 179 in the positive direction", + "flags": [ + "InfinityNormViolation" + ], + "pk": "17a508179b35057099111733da28fd1a2265de7d8ab22d5279f13bca84cc42a5b8c9644c121e7e1b81723c5295be288fb6c36bfa188b6e08d913a152350947fa2c8ccc3fd01b319f65a2058a1dff54133946cfeb408d0b6dfde6bbebd7e0591cfe83b8b5452ceef6c855f7d33e06a0d269345089ed0d3ad67d84d8a4a34d16836004cff125469e8c3387abd788b620e30c1fc23909117a0e34c42a6631d9791347b1b2a3c9ab3082416211afb7bc3f6ce630a7019af19f736cdfacb1e7db66b65ef56844d2a2b0753d09283a7a0b66f77596384e95f7ceddd1c4ba20edc11f1eaab695bb963f6eda1c383754aa372a0d7729bfa6e0f142131c2367ba3f89ce3de6c357f9a7225b7cb85f6b3e8a3a122e8501fd1446b8152a415c19dda1d2e4590cd994f6664b4d1abd7381468c3a085abe2741a0cfbb81880664b271677245c4a471bf8bb8e0192eb32e4fb5e8560f3c50d6b19a353e486d0fcc2a35ac046286e707e095f61786d92212686a65d39b6863e0f8cec1e1997f2f845e4878ca9df650c746765296790863e51d012d32dffcbd746aa2276d04c0a57cd1b3d6ed06c0d66a0897aae5c49c97b6f19ae829baaafbfed28a52c05963c6eea9eff69528294207f8cda75280f7c486e6848791c8e37015479f2e13c28a9fe654dbde11689875203aaec51be3da7cab1cf31e4ec476c0c830cbdd04ac02167c0a6fbfdd6548b1fa525d235c7e3fca8d63e6427503b0a45c0bfddb428b837c32e8755441077bfe1c0142bac357b012a46545bf4148d465472dcf89c9d73b62357087e229f53a450d3cce41c8ee21a9d54b61e34a794f5b1406a70724ab0c3712c49df231ef30a956075e907c51b63dd1f9453dbe60e25b0f3cc0354dfd7c9119313919e77cb2c92f544d3e5302b8827603e936b567e99bfe9904932585a9f01a5a1b5bce07565f1d84c6b1c5c86259e1fefcff18cd06861122be6836be21e40be4eaf6bcabee8f634f95520aa914bb51c54dbd67d1b9dc5e38831e786c283979a963a3206b98e339edec4128b0502d4d47813869713e431a529a03c7f54b50123680f2b7f256f5d2b40642203259b9e85c62253d5670ce372193f28b5aa48ddd643c54756a2cff808c109f74772961d8db6bb8a17547c8f29c7f5ff3ea06740b867d84917e07f3978ad0281a20689eef58467e768b6178a9b36a567289fd39762bb3e4254031b2798a4550857f6af369d484392cddd7b48eaa2942e2cbfe754d5ee2da2b7fa71222e4a525ff5224d551a778ebd828e4e0499adc74ff0d59a5abc78ad6a8abafeedb3c99045a14423507f85597b1a7f540982f7d72ea13449110b442d54b78029b4c7fe3b49396dc6c3b7d58792538fa907963de10a4b724548142541cdf1512e0f7ff1b10a93de63541b8cc3268b4de20ed26739ee8973b6507ebe48965602c35fa3f7d4278146b598d7d7044e16e97e9351f7c51ac25573b7232ae2432638e9166190e7f7a7dcb5096ecb5d10017cdea2a82b4f56c7385041c6919a7e36e11beac77ec3f25df44e7b596c1542c1e376de3667c0e903fe25b57c338e9d93c5570c484f0ddab4f57d38f292b23599d9efc7a9fd9e078aaddca0acb1a196d6c45d3c8be6f39e8cdbe3299e370b262e0bf6fb5f005cae2b12879289d00bd8039de6a571c310d87557f5c9a4f64a0bde7177a8464722a04bf87fa2cb0e312d4fa6e536c61d65dc2c1baf144b0d1d1d75f4c860626ff773933efa9941d105c53a1d92c4f7c7bba4aa969590acef1e50901870f59715ac14d9846d83871a77367be57c63f88bc2c02eabafe678f44925a3e605979282fcd3f284736a1d346c033cb782dd615e886683fc37cd87a91422857774c63c6659096eba393c56225ed8c3485b4f89ecb07d53526281a6426ae7d67cda52fec5ac32320caae9b96000bcbe9e8782be88cb1ca6dcaffb74ef04c77e03a994bea2c89e4fcfa44cd0c9f4e30705a8b7b20df8c76b05a4479400e07db03d243e9fe4c90d34e9245f1e574be9a388f5355482077e4e98b919de024e666fdd7d51ed2a0d58a823e7497eb07303cf1d6d5f10a536be980220de5856727e5c13981839cfa19740988e7771a2b984f53ae3a5916ed881a4a90fe524f0bb3778355882864f8961fade32e656fcf9f524e748c8196a1f1bbc57bf8da7b36de9b0080f0c7bb8487a2b7bb7a81a8ff43a2539b367c9a48c70041520f05ca3dae316dbbe3118218216f52b7bcdba7557c4c9d861803a5e2ee01d3682e1261d7cae0a99fb8de909eb2bc1e112aa43cc2fa9c76a222bd85faaaba5d9ec2198ac45a295181a324a0592632b89e2752582cd5e01e1a610e7563faee10b76d853109e257e7c0c248a9fb7933f514b07b4f4e3a4a3d2cd22e8cc45ebda3bef5948aa050f01eff85ae98d19f69c51e67ff89f2df0c5268acfdd325e84591317e05cab4f9e6358f249c4ddf4019fbc8f511549a733898a50efa9e0793083de0b15b5bf78d9f63d8df830d42df2fefa27b89e0ede2a702eb9467118fc0ed44edc63ad1b1935877c34843fea06fdf388bbf83e501723a13cc6cc2efbb9691fe28fc1d45270591e5bdf7aa1c82673544ee29d9e6c9da3328f21e9729bffd7f4e56de585909679a74037105fdac3f51ae35f69d9763d2e4cfeb1d4a8fdce99bf1aa21f866a9f523b2a9549e12258a4d19900cf5db37b67da19b23563bd1d701c6106fccb28e4689c62e1a6cf1abd763d7239c2258b765610d4478be9f1650cb8d18923592ad0024076e52f9bd0a3894fe97bc0a1646b4c37f62c27f32d0df270260f47c49a5caf110e4cf80168a7d54b1c70bed9bd5d9a143ce869a05cd44ee266aecd6bfedb39be79e7c7d5c11a99575ebc0f389cc55a4fe1469a2d61b70bfe4b74e3e27521a037d2b9f4fdb377231e2ceb214ba90f6953865c683215203ce963875c6524c01b789e0389a9f0c386eb236f0dfba6c95df4f28ccc7ae7cd473f9dcd20817cccdd211bcbc78b064e936e4ba2813df531128428ddf410e6ca07044aeb4cfcc0a16c995ec51c8af16a541ce18dbeb69a26635632dcc24ee52a5eedce38c502cd0e356ec31341c893f92e6063c3a160a53d34b85e92357a8ebaaad8f206771be43ee48cc409825a7094bda529ee18776d9e67f1fa1c1419514309d70ba2443be2f63b6943478d6c0f56dd058731e53de4c30bfc7d915e9284a56248e81944392881666680d4991f04269ec9a83b24b458ed59a6c274de452ab3013c103a4920543e6a7d22dadfd764f6ea39d49b910ee0dc216e547aa5fb4382a72a568ebe83ec00416fb5830dc21c24ae72416602870cb52c3a8a1c4c12a4b287b9b800d31c287ca161f404a9e598a5358d28b3aae43e534846bcd0d7a9c7652ae01e6698c79e315aca8198f36de45af7084b1cb21ca2ba0ee3a547a7343a10ef9e3fd17b0a4060badd1409a0562cba25b84fd578268fac53cfbca08e6cf6e5419f57262eb5813c1d1324e0df1d483ade08d8f6c62498e262485ac7c2872b11b42e5c1b797fc12e838b38a711d364d45cd1ed35f7faffdf4b0fb0eaa312fc3d5af77909b0649cbbacea10c9831273922b5b05172face9ce6cf324edf6e2f5f5fa0a9f0463eee938b30adf3e55664f94d274cd87dea901a7e08e805", + "msg": "496620796f752063616e2072656164207468697320796f75206b6e6f7720686f7720746f20686578206465636f6465", + "sig": "0c2d7329325d492e324f377be5ceb6530fdecf7bc3a9d3c75b207625b876adb39b1cd39e29bde731db0071f3dc44da1a0ea0bc35d953b359e6b3056c2e3319490212e21490c3df821f102d7c1c90446632c7b389e42ad3020d0af465bff7f5c9c3159a5361c2f27a0ac25bc4ef1b59524f1c234d251eb4f1504bc665596ee7465d5bc85c3d3f800cd31490a5ee3ace0867d51c2ba6ff85f5e81ccba43f7227c9119102221dc4ced944ee2cb8ae321e7a7e2cc604239bf4acea57a3034eaa0687c6a9aec51aa163218935a935c1f3d4ea4925ba8e0065dcbc85c6a7f07d2e251eb91a64051099d39f28ceeb6ed914b94ae678052ecbf2cd940d55dfc7d88640bdacc1a3e506a5b5c8e897b5bf4460dc6570ded39b2ef9012ad716a148bc483aa5e3b809c9de7c37287f8a3a8d0f0bbec01990f52a67273c556184cff788f92440d592c3a4f97f1b64b500fa0452705219cc63d599e6010c0969f07d43625772f86e6c605d85248e378778f0eba4a9c5dafdaedce0b876aebb625f4d058bca37ed25bc2c62c70a9d6e572b99b3961933e759bc045ad3f81329d4bbcdc213dee381aef11a48cb83838ff28bdb2127af9dad18d802a50a3a84ec84690df69f50f90f728cf92b023728acb7abb1109ecfd72252236d810272cc7ccb4bf2f77a02d71762f387ddc047c574b6b26659eb1e10387254a2f454b3c407f12c94e4e661f49f04001ae47d20805354c3952f8f86fa943292238263ae943c270c22a674fc499d24bb22aea98e88e92c9f9a79a4530e9ece563e5fdfb48e9906e29f19407ea98941d022a9cab68faeb85cc35ac487efe956383124f824fb574562de18df12f94fe3a8cd19e28afc6d5dbf226fabe8b7fd116e9cefc9c7e5b3619e49b0264bfeb76e5a9627aa7972ac5700c7f22ba19725e04dcfbb344a76938683f439cfa229a71c4211dbe736db5358fc6785dac2f96fa34c2d381f473b160616f809f0163e906150f5303b4f22b245e6bf356a715276d3fe524ea74f7f1ae44ec21df7c3fab9a284b56b3afc6ad6c04fe2a41f08e94613fa073afcf1a753592948eca50edd7d7e8543e22ee0291668351018afa61d4ddc766d54a6bc90e3e1d90fda5c263a6e2fba1a3c339395a7dc1a04db46334322e3d302d35b3e171479953c5b54825232639f14629284baff74ec1f4a4b61811db7184e23c31dae081934cc1aa8bd6b0198022cec02b8aa315c625547e158627826c6b405c3bd5d82fe733276eb19677363f92ce2b9262271dc8f2b64258d80b667c92ce2c38b7c51891d7ab0a0dee0d2e023d572bdb0d3447b9b1d7e66e109904ad620f8f0b42c5b8eb324ae3ebac6735b5c405ee913380e445ae3422e52213fa6b1b6732972b3adeb1b85624f3ce7369b9ee05ada10974c6fd907a201069717e58e01ee2a79b9695498b9300262c8b3f7ab741343d8e26c9fdac56f2dcc56c7afb98a83b84ea5ec709072102213ae0e40554c83cb349bc1b42dc30ed3bc1d7be3c4653256a61578647c0a060fe90f93245077da171988b5a91952b53da419e37dc8e5e08e4670a22d5b06868536e64ef88a5370f38f284d8b3d774306414d94d43cae30abc6904f1674393833b8a193306818747b6d64a99021f7a207b2d3f66769a8ef933a8d7b2795ef391d8c67788e23bb7e5862c175756a031cda44d10f2d432a822a4eb2cc61baf82f560196d9dc5d703d330ed5b15626d6f94f3a8166ee253673c3d37fb7357c47328d34b548664f6a3a36de47186d7cae9ebb4a75afbfd97ab307e18ad29adc46b70372786f32deff9f7a2ca513d26bc6e59d1d2dc6e9049f75d515ded53cc5d9a20f0fee3b0c48c6c39c01d02b08dc5e174af9f0246028ec8fc4fdfa432c5c81a6c680273dd048bdfe6aa6ed9a5b0991920b1c2a9cb7d10a348c440a33283de6c42c1ef4d6416cebe13f4065b3aeb62ef4a0902cd064448a2ab26aa08d7ded06b4aad0ad66427a74b1a442301f1350ad25f9c362b270b6c74c2ad0db18f6451ccffcb0c5579d7ac4c89186f52340c094bcad558da981d4b8808546be0a3886d5ce69da240542e397e287adbac619807e86f0bd42d3d591ac5a9c60536e3c0e4a7cde567331b884aeba5b6ba47eb192b3735407f1a0e04937e21057c970465d96f1a2b11e60e271fcd03c1fc732ebb0f75eb53c147f044b5d05130b3e7984888997c10efedd0197cb523bf68445fc829ec59d686fda523a83617b58849f172bc2be7ab6d66fea8149b0381a6d0ba028ca5fdaa4d942727dffe0f5227b9f9195e30940e79347e0a1b76fc92c3ae9e10d59433086378356bffbd979cdd5836880b18a0e1b6aa12914319e62b24848c48c4bbfc116cfe50bbc753255fe316482076ac3fd13444afd097a91551c5f317dce6a68acd095438c36e98570c81e19502a6a694a320a3d0320ca95d6407344559652435e89d79b522c6210229c917c5abd418f6ddc15a28f80cecd0ed15774110253918f120ac2e391a636faf0c25d48b1f2fbd73f7d07d9bdc2a57bcccd79277e78828f02debc56b527eda0d0db6f44df8e5e3214f419922b9962d60c4409d4cf33a7930f969aa8733d48ec0e9ab966571a4247c3b38c18fa74227a2a64b435939df09e9467b916db807fd2559b66b5f4141ef478a033d9296ee6ab0507ae7deb108eb2d5b0a9335da8de5a7123d004ca44f448d98718b17946758527d6d5141dc64cceaec20d87cc77a4f45c7c74e718c1eb387cdb50334eb66adbbc931a0039183646c79d61516c8315893f3d6e9fcb335ba923475aaa2d1643d6d33c89b5b1e7b6a0f746f71c7ac4139b055a8fa862c4c4486f469997f5b9936a4a09b81cf0bcd28f9c47c85924de300211a324e2253aa1bd4e21525e6de93d9461704d2f95f94ba8bb5adf375e96fbd1bd6afa55347b995d78c281b7489a44d7d49af68c8005135fae8bcd884b3d8e0a09c655b591aa79918c89a90264f0f7f5d8645acfcb083cc5ae36002d0a3c7d68e6196bf846af761022d26893e9a86ccb0b1bfd8d9171ae7bd21a629a124675e9bed926054258d09a24fbfdac2ed3c4926819496fedf3b1df39115e8d5a83152d2d94ab832ba3e970df75879f14afefc7d2b9124847b98ab28e569ceef7590437a5290526c6658b4b6fe0275f16abe224c0cd07a7ef7e698fdb67be47a4bfe743e4fbd5723f3f3521288c49661ebfadd45ee282815891eadf6dc3339d9f5d105e7dd7a902da8e72fb5c34bc4500cc66732193095bd5b884e583cb401a68d47bdcfbb17ecfd593208bbf1038fded0c8a209200a98f8d159030de133b11a3262f90cd5569460c823a9e326d2ccb1b2a96ec5d04375ab8d52a60fd9eeaec31e1bb893c0a57d7415d5cb4c380219f8b0c7cd54c7235a39cafb75df462fd661aa5fb0d3898ce8be0e4ec5292c40edac9b43c211c5bdb9565f35ee80e26917aa99c4b92220b209135995c5b881041c516351c4b8805901e94f0879fcfe15aaf0d6b3d53af297bf10dab8b83ffedc450ef7010fa70f230d5a59c409e291864a0c50093e935cf2e482f2071cb04afce2d7d7a37307a2e0079623e04062fe271a7beb34e1debcd7e4d98acb5a4f39ce5ff8d5ba071f1fd2adb0cbdbd602f656c308caa57031431fd262c26c907b5e78277723628993e7da3a7954a06c501acb1525738c588c5c8a4cbc74709f88b8ac064870f1e6205f0ff882b065879de4f145e22cfcc7f8fbd6c8431c987fde7440e12e57e712bb1b1b5c77c653824590f59bf492e53633cc3783532369eb3316e8568d4ed2c852457e64c8df988bc8a6da916cc42f9a02a3562bc30d8e6313e6b81902692b1b7dde6d30e7b943f63835fc1663b4269a14ce2c83fdbaf8a13310ea2b813a382535869fa6d9fd32875984259e40c64483c16aea1bea8d56912d67d6fdfe30976af471239c7b8a4277f479ad4d416e7a6535489397a0ce72985b7c6713b62cdf7d1cf9d2b86b3abbe719ee0944d31d6cf9662fb72e510b0d26d0b281704cca5998e414d7ab1b64fbc44024d3c441c77d172e2419fde49fadb96267c59d673b8ea2a8e1e94e26e11b1ec8572560403dff833652a7025c5db744aa2e7a0749be4d29447b464f6b475856c19beb3a2cffd0157cbed21add58d2cdb4f02ee8090952b4878e44ebb31157a31a4ffc3b14b37487973e07f15a0b317ff7019abf1951be999dc48c80a2fcf6874cbb89a6d05509b3e8c45f3324f9a7629176faca2d1ea397f153c0bef2ed791186db9e5a5d4b7ab61b5d4f721c94d49285ff1e4dc733565e57505aff7c1da8d849a022a3a7a523256f96910e2c0b0c56a761db94ec6b464696e06ec49a6856164ea723356c59c6ae03f7b31b398789cf81d8c349206fe439de85907df7fe87040b7cd848a6dd223ff7de68220ba76c4d4b556e86acb72112ca8b7c8e6a922bbfd746aecad4d7bd601043a0039feec9e36c9837f239fe3f606ca97034e2663c711eb51f317a96146cd6d90ca76ced496bdfcb42ec2e9cf557ea33a4e5ccc96b3918b2d87b150460a4a88c6379f4394f8080e2fd28aadc53cd6864fbc6e391bcb89972b6863bb2bc3b3352bef2e5b1a39f91518ef9fb3bc19657668126156a3432353e4eaf72f6067f3f1af9535d26fa9c74da3bbe81bbd5cc9cfeaf97d7af7b9544321a93186a39019ee36802e54f617c32ab02caebca878ad07409dae618eac7f69300aeb4b009006e8615980a4779825fa9f202e34bd365cb5512161a18bc9113a86dd196793fb02501e97ca6fea94c2e2b0c9769184eeb1bd623f158beec227aee437c0bcc89aabb7946873327381d0be36f6c96bac377877abdcb9b62da6b587f03d144cd4befe205ef004dac5e0e18c4dddfe65e32abfcd238b3d0d38f69fd1c218c1be5a8ccb403233a86453b557ef1d43c80ce2f4558a302b8da215cd3f7ce3b94738ef84f262f469801a6476db7ceaeb4cc6dc734acd569e25c9de965a2d5faf17f9a06dd234d39f4b5600239649c47c58c9739b33dfe4a84527cbff367eb4c194657157495dc5396a542a5f84504d950f987f3fe6f4a10c09188c36197e9f655124cf8fb24d40ca68a3f4f85439d8ff01f9f1e8b6c0359044755a1166ae1eaf3ffc35b554739200839614532663d8eba025fd4fe1bfca8b5662b318775938456793f306bdfaffdc4a403dbb41e9eb54e833062f9305bc7e676a7fbfd24cc152b5fb63302604cc82e071dc45e43a99f05f1e36579bbcedd19cc5ee4a932c4268be5f8deb1715aaceb71149ddf934fa022b0f31031aad9846d744fdfc620f056dde6ad25f49c125b6d402afe05ef608d29d6f91395150eca9ca49178e8e896911a8657a9e042733c26d7c258e2663ae0848f0d7391810dc15915f5800a3ed797ab4eebff07e1f68ffedc51590af274d7c85b044d2b62c133d28f804e50b5a674a830ad9956c909bcc21ea5bcaf2fc07f0fae4ec3f9c4d78c992a01b85839db467d5b0f1d59ce22d752545869d6a53e7532f8a3f631c7e1dcc3ee7569deb478304c019278096ccbb58489a08eef1058ede2cecd354b4650709722dfce63db2e21bb1cdba890e18a08d10228c8bb0489aa57598894b360622aac42692b7f5106a932a5ccdfdfe4d439ab9d68d6be72b0ffbdd2e7c355ecb47644c58015f9ea9fec874fe48ea8b01da9c7ee2a1b904e63ed960f09ce11be0a2536a4911a5b2b9a219e0fddc5461b8bf95dd6a7679f3137d87a5066665c96136371c6df67327e9f61bb147124729cf336ec35d02b8e2b068c3269a5de4f595f0005e215719c3416c73b18e368da13ce958d0f3d28256ff100b611351abb01deb19f0a0a8728c45eea9edad2a6b1aea38b6ffcc307eaeaa90124643c80070092d3225146106905a93e12cb22c2ba15741db9bb3be2f4031065ac775d0ed00962bebf92d473fef69e8c72fbe99725f43591afc85b8dee8fc3963a49e4f8f19002f3d5fb16d377b234674abbb35e4094cbb1f4472bdbc5a5ff4bfa07862ceda56501e5a3e9e45d2ad03c1ace39cbe792d1e5ee97c4e0053836c3a2d4726551d8c856312065f4b7cf82d50046d623a5db51952a7d88ac733e7b6ed0e9081a7e439a39bf86af3e758a16cfcb878ca6361d54da9bff52a994c7e6dd81961524f3c6b9e1db7f2f8cc8196030b4f9cffce20cfb88d6534a608fbd28e0578927a62e25f2ec95c63ad0e546d00370a1373f8a7ff98f6fc46101a4e3ae3ca3ce3fdc08ee4af6852887dac034a4fa49dc414be45d6910c0389ec0b06b5d9225421792a847b14728e79c14e2da1a7f0af6f08ffc707747cdebbedfa777688341a48493d03ff306d3a97cd1c1d1a981f9e4b1f560cfb520d4015a95fb889c71df46bd515fda501301dac0480446d5f8f3d8ec8438a18012535b3a0431872949603104d7584a7b5fd42616e9b9fafec1d1f275c4062657678898bb60824363753b1dffb3b66707a7d81b5c7d0dfe5f10526565fb6b8e1e8658d000000000000000000000000000000000000080f131b232f3739" + }, + "wrong_length": { + "tcId": 6, + "comment": "short signature", + "flags": [ + "IncorrectSignatureLength" + ], + "pk": "17a508179b35057099111733da28fd1a2265de7d8ab22d5279f13bca84cc42a5b8c9644c121e7e1b81723c5295be288fb6c36bfa188b6e08d913a152350947fa2c8ccc3fd01b319f65a2058a1dff54133946cfeb408d0b6dfde6bbebd7e0591cfe83b8b5452ceef6c855f7d33e06a0d269345089ed0d3ad67d84d8a4a34d16836004cff125469e8c3387abd788b620e30c1fc23909117a0e34c42a6631d9791347b1b2a3c9ab3082416211afb7bc3f6ce630a7019af19f736cdfacb1e7db66b65ef56844d2a2b0753d09283a7a0b66f77596384e95f7ceddd1c4ba20edc11f1eaab695bb963f6eda1c383754aa372a0d7729bfa6e0f142131c2367ba3f89ce3de6c357f9a7225b7cb85f6b3e8a3a122e8501fd1446b8152a415c19dda1d2e4590cd994f6664b4d1abd7381468c3a085abe2741a0cfbb81880664b271677245c4a471bf8bb8e0192eb32e4fb5e8560f3c50d6b19a353e486d0fcc2a35ac046286e707e095f61786d92212686a65d39b6863e0f8cec1e1997f2f845e4878ca9df650c746765296790863e51d012d32dffcbd746aa2276d04c0a57cd1b3d6ed06c0d66a0897aae5c49c97b6f19ae829baaafbfed28a52c05963c6eea9eff69528294207f8cda75280f7c486e6848791c8e37015479f2e13c28a9fe654dbde11689875203aaec51be3da7cab1cf31e4ec476c0c830cbdd04ac02167c0a6fbfdd6548b1fa525d235c7e3fca8d63e6427503b0a45c0bfddb428b837c32e8755441077bfe1c0142bac357b012a46545bf4148d465472dcf89c9d73b62357087e229f53a450d3cce41c8ee21a9d54b61e34a794f5b1406a70724ab0c3712c49df231ef30a956075e907c51b63dd1f9453dbe60e25b0f3cc0354dfd7c9119313919e77cb2c92f544d3e5302b8827603e936b567e99bfe9904932585a9f01a5a1b5bce07565f1d84c6b1c5c86259e1fefcff18cd06861122be6836be21e40be4eaf6bcabee8f634f95520aa914bb51c54dbd67d1b9dc5e38831e786c283979a963a3206b98e339edec4128b0502d4d47813869713e431a529a03c7f54b50123680f2b7f256f5d2b40642203259b9e85c62253d5670ce372193f28b5aa48ddd643c54756a2cff808c109f74772961d8db6bb8a17547c8f29c7f5ff3ea06740b867d84917e07f3978ad0281a20689eef58467e768b6178a9b36a567289fd39762bb3e4254031b2798a4550857f6af369d484392cddd7b48eaa2942e2cbfe754d5ee2da2b7fa71222e4a525ff5224d551a778ebd828e4e0499adc74ff0d59a5abc78ad6a8abafeedb3c99045a14423507f85597b1a7f540982f7d72ea13449110b442d54b78029b4c7fe3b49396dc6c3b7d58792538fa907963de10a4b724548142541cdf1512e0f7ff1b10a93de63541b8cc3268b4de20ed26739ee8973b6507ebe48965602c35fa3f7d4278146b598d7d7044e16e97e9351f7c51ac25573b7232ae2432638e9166190e7f7a7dcb5096ecb5d10017cdea2a82b4f56c7385041c6919a7e36e11beac77ec3f25df44e7b596c1542c1e376de3667c0e903fe25b57c338e9d93c5570c484f0ddab4f57d38f292b23599d9efc7a9fd9e078aaddca0acb1a196d6c45d3c8be6f39e8cdbe3299e370b262e0bf6fb5f005cae2b12879289d00bd8039de6a571c310d87557f5c9a4f64a0bde7177a8464722a04bf87fa2cb0e312d4fa6e536c61d65dc2c1baf144b0d1d1d75f4c860626ff773933efa9941d105c53a1d92c4f7c7bba4aa969590acef1e50901870f59715ac14d9846d83871a77367be57c63f88bc2c02eabafe678f44925a3e605979282fcd3f284736a1d346c033cb782dd615e886683fc37cd87a91422857774c63c6659096eba393c56225ed8c3485b4f89ecb07d53526281a6426ae7d67cda52fec5ac32320caae9b96000bcbe9e8782be88cb1ca6dcaffb74ef04c77e03a994bea2c89e4fcfa44cd0c9f4e30705a8b7b20df8c76b05a4479400e07db03d243e9fe4c90d34e9245f1e574be9a388f5355482077e4e98b919de024e666fdd7d51ed2a0d58a823e7497eb07303cf1d6d5f10a536be980220de5856727e5c13981839cfa19740988e7771a2b984f53ae3a5916ed881a4a90fe524f0bb3778355882864f8961fade32e656fcf9f524e748c8196a1f1bbc57bf8da7b36de9b0080f0c7bb8487a2b7bb7a81a8ff43a2539b367c9a48c70041520f05ca3dae316dbbe3118218216f52b7bcdba7557c4c9d861803a5e2ee01d3682e1261d7cae0a99fb8de909eb2bc1e112aa43cc2fa9c76a222bd85faaaba5d9ec2198ac45a295181a324a0592632b89e2752582cd5e01e1a610e7563faee10b76d853109e257e7c0c248a9fb7933f514b07b4f4e3a4a3d2cd22e8cc45ebda3bef5948aa050f01eff85ae98d19f69c51e67ff89f2df0c5268acfdd325e84591317e05cab4f9e6358f249c4ddf4019fbc8f511549a733898a50efa9e0793083de0b15b5bf78d9f63d8df830d42df2fefa27b89e0ede2a702eb9467118fc0ed44edc63ad1b1935877c34843fea06fdf388bbf83e501723a13cc6cc2efbb9691fe28fc1d45270591e5bdf7aa1c82673544ee29d9e6c9da3328f21e9729bffd7f4e56de585909679a74037105fdac3f51ae35f69d9763d2e4cfeb1d4a8fdce99bf1aa21f866a9f523b2a9549e12258a4d19900cf5db37b67da19b23563bd1d701c6106fccb28e4689c62e1a6cf1abd763d7239c2258b765610d4478be9f1650cb8d18923592ad0024076e52f9bd0a3894fe97bc0a1646b4c37f62c27f32d0df270260f47c49a5caf110e4cf80168a7d54b1c70bed9bd5d9a143ce869a05cd44ee266aecd6bfedb39be79e7c7d5c11a99575ebc0f389cc55a4fe1469a2d61b70bfe4b74e3e27521a037d2b9f4fdb377231e2ceb214ba90f6953865c683215203ce963875c6524c01b789e0389a9f0c386eb236f0dfba6c95df4f28ccc7ae7cd473f9dcd20817cccdd211bcbc78b064e936e4ba2813df531128428ddf410e6ca07044aeb4cfcc0a16c995ec51c8af16a541ce18dbeb69a26635632dcc24ee52a5eedce38c502cd0e356ec31341c893f92e6063c3a160a53d34b85e92357a8ebaaad8f206771be43ee48cc409825a7094bda529ee18776d9e67f1fa1c1419514309d70ba2443be2f63b6943478d6c0f56dd058731e53de4c30bfc7d915e9284a56248e81944392881666680d4991f04269ec9a83b24b458ed59a6c274de452ab3013c103a4920543e6a7d22dadfd764f6ea39d49b910ee0dc216e547aa5fb4382a72a568ebe83ec00416fb5830dc21c24ae72416602870cb52c3a8a1c4c12a4b287b9b800d31c287ca161f404a9e598a5358d28b3aae43e534846bcd0d7a9c7652ae01e6698c79e315aca8198f36de45af7084b1cb21ca2ba0ee3a547a7343a10ef9e3fd17b0a4060badd1409a0562cba25b84fd578268fac53cfbca08e6cf6e5419f57262eb5813c1d1324e0df1d483ade08d8f6c62498e262485ac7c2872b11b42e5c1b797fc12e838b38a711d364d45cd1ed35f7faffdf4b0fb0eaa312fc3d5af77909b0649cbbacea10c9831273922b5b05172face9ce6cf324edf6e2f5f5fa0a9f0463eee938b30adf3e55664f94d274cd87dea901a7e08e805", + "msg": "48656c6c6f20776f726c64", + "sig": "ba4275ff54c22d2d09ea1937a0667362acd44925c6d6965fad350b111d1cbcce68ddbd0e576d1a8810eb4e71623781f32f747d44c8e693749df191682f588906949d97617a4b0ec54ad966818dee88b95f0f28ca24bfc5bfe0c316140b0662c43093ae48b899cc71e5739e9d67095ed987a79b6a0e7aac960c3c4125f0e92bc9435d10bfffae34bb3af05e977ebe0bafcbeb2381c5afe3379667b4c201aebf162dbd0a4bd1baa88fb2f88fa970499a848737d3cf94cc8ce278880a169cad91f304e4e8f1091d4cf39d9a3ab9f88dcc6f3bc4df311a5be0cba290365b3e879527e2a77f0cb6eccc9d85a5e592fb00f3a2e925a26d295a6b82746d7f534c83c35bc4826ee4910216b9a2867032698996fb0e1669b539ccd2ec74d181f4844e8f4d28f9c174316c12dadb54cb1dde7338238a20731c2565bb959f8e3086273ed03abc7ac515728750633083c0b397b29d385d13f5afd2529b32f00dce66c9dc8ea93d99c8b61c5e0ab2fc70de2a8dabdcaf290d53e8fca7561bda8c516ad475e4ec6c7cd2603aa3c8a71d9fa5dc7efc33cf318bebc1f1594e6ea25c69b8f9ce34a65b8ab0ae8dd3538bd267d86c584b8f354d7e4776ed4dd59a73f9e70a1df572f033b69b3eafa5a901e02515472e37258608875ca469de07db71cd6b8dc7edeb3d866ed2d219e44fcb133a066e89d8e3013569ca6f1fee7bf4ae56a6d32a5f3e5a530819c31aadabc8a88503edbea9cdfa3171762e1e8bcebaf9bb6af7e540102d5fc810bbcf1e02ae564e04d9dc55dab0a9392d6c95a317730d9793954da2cb16544d15403d0db01e85881e2d4f1b9b98458e1af0985f98b014f08f200558f2fed7a70c352a27423bedeebb3775c0a1ede9d461d2aea303c09c8b1f73fc37a5b3a01fb24a131574f7eac90c78baa38cebe81e2335dafda20299f76d6a0ba77d2954d11381674f4069f45e133886d64222d92583d5908e3ab6eb6a72cfb41f7dc3e71c1383888f61624bdcaf12fb716de98232ee329af1dd045f30d377234db7bfd11ddab0b3108329e16ce568c8bada39a98df5a5f72a72f063fa4253313a806013f61ce5adbe54cf42180ebf6e496bd4b42aeacf069aaa8e1c231fd037b394d78a69d1742b45bc5784ab8593a198077423c4c357d734f9899cfe9b3b62b6b9c3f4d781b8484a3fd0eea7e8d6945f4ba2b046fee079b7f032bebbc402918daa2aa1c9433dc3bcfbd7f49a5d7a293833c21c1bade3e8a7ec3c83d485529de51b5993cdffe23b770e25acab0ab9fb3059f14952d9464ebfa36a6274d8da317a7b07c2afe38ba28ca942cf7bfaee4020e59911c047b2aa24d1787baae1bc3546364b782358676d75c7698769f1d4a0d5dccade7fcf80e308437f7fc24fdfbf72625abf1b0f534da62cf860da1efd986950e3e19a085999f4d008fce38459c673befcdf2287c1766e106beb4f3175e97812da141330dcb2beb3265e38c8423c19dd50a655c9e8dc969f6367f3383b644d53a26875d53cea26de429266b506e70e7e6832886ed06d81738b0b482bd23bda396eb674ddfdb64803d6c4fae2f040170b5a28923279838b7b876220d02dc7478666f7c3287b1ba4a2f8228e8c491a55ba459805c601b986caea27ee9436f63383351c74d673643b15f6007fc6dc49e337a65a8a96ecd7eef4ad730bf3dc1972fc396703ee26af1156dc4beb46c47d99bb69ebdaf81c2d738d0f70e57a2c7162f5a55242255675f22082aa43f5b3dd862b535b2a15bb815b4e9faf16a302552cd6098d40930dfad7a7c609d6aeade814242a8bf0721c1c26d0d3daa7a638880a6411c6538d80c259d31b639aadaea49563a8d7f5ddb64291d6b086c80d72bfe7d26802cbd20fd6ba5495011e42da1c82483cbe8e37838e73c48f79f65b205476bee600399feda6aaaa939eee12cb34be4e6bdbd18032c85b54a2713551de677a16ca0142ff97b77bc963e8f3c57d91df9b49474b01ca514641842893abc3181caee3f49b635d17daf41bcc4aeb1116ec4b3e78ff1480cf3a5d9c6a154384b88516834d19196976e2ba97cee91f7a0d73f7f8146e58dc0fc8f510511d39d82a7ba531a4abcce6b624035d753a37c5980343cfc7724cf83efb0c33fc4abb5b002241bf57a46b67cb5a4cbd637b2f19bc93b368d97e13c6a62c8443c8222e0a90c3ed1972cc739b824fdf729ed8eca02ad96bd78bf6d2b3d2853e24fa93199ff41635176b31aab2207013d0a9317fa49668dfeb672b8129e6175a2998642ab8e74f0823e3b5480ae80180ca395f5348744a3c7b344891f008aab65914760b5fe852615ad6425216b1c5e777db1a46517cd01a77b277cfa2c6f250c2d68c495fef28feee6e0716817d6b30716f5ca48001805042133eca17a41c2219784ce0f12858ccfd371c77b90966ea04c3996851edf31aef962946628007de5531b06fddf3449f6c552eaf6e16b3e9160e265900b8c8e414732505e02660e123d45a3d6f1b15e56fd759d38e821e27e84967e95c4b0d2a48e008897e655d1b65b76299f1e3074209abe44b37f8786b02df23aa4dcebe512e6312f1a3d6d781d749635247aca897626f5ee688f517df6cfe947ddead820f07fed4bcbe7f1bee3f19117b5666dc123d528f2bf03db346d6afb53804b0681aea98a9479fc6de5ae974d2c0d2055f07ba8a1d2d8b6a7e08a6805bd8634cc190c4988b502f475c36d78c7b3dda0057c818835bbf21c5a7c13bb6fd91cd3cfd76abe4c2ff908a08a3000b9021500ff94b297cec0fb3e51b1cae7026ab4347bfd5b6a641a3d347f45a2a2aacd22e521e00da0c3986c67672c5d7d7e61e7edafc15d42aa42cb37fa8621f79e9096b092efe853ab318b3e4bbd90f20165a1be1d3aa5b3de43751b65abbd952599869778393c4351ab8f534fd49e16547df01c40bfaf56de60b4fb0019bc34177cc8e2236d219fb3c0b84dedae6b88e134280eabd1823420cd1afe6d929774967fd7d885fbad33d89ed9dc5e0eb978eab5d96c50bed5887aa8277880c7b06bf2780cee82ce639a1e3344c88a25102edcc17a4cb48989c4ad6a998726ede31deb0b98107f40858bad7864983f6ecdf0c9761a42751b19d5360daa7fddbbd2e2292638b95c763ca1e747eedef0dd387e9d9ec9e5afee207d8c45703c5befc2de3878d313655ce85ad984250cdf054360f33d41dc193060d42cf9528b1fb91d6ca3395199e25a1a7739eba9a6a4ac2c417ae615940b3eb1b746dd0ecc7b2f7acdff887110115629f70877dafcd7a6625fa1b9e256bc8fe1d66005dbcf12fde0a5fcda5d4f23d58ece91d60eb91274df8d9d17d4a39e63533acf1b317db979b04ca3ab9a0bcba652d0010ca3fcd33ed8f8a62faf42f78b37912d3adb410f20bf16b31359cacc25fb783083a3f065f0a2dd6fe58b8f594e11a87bf0f4c5f5493f334c18b03ebdefebce50228937ec13a8c221b617450486291071f3c14f64f66c927dd4bd623c214ac35433b8a6875cdf00916476eac0f196858aa1484bc1cd45b726d33a965619829b8deaa9d9fa0c3c210f23967ce26a4bdd939cff8aa662f70fb0af97ee44bcb9e2755000a195741d8919e4dcb1a5caae21009f686fa1489c72f16f9fee76b5410ece7f406947f4a19f394a5121da79f3216777b0fee5423328156ecf0b4548dbd5b3f7b526d6b9cfd57576f67dd521c314c2d37474ed0cf732c3b073a101c735a4c4e6b33c9aaa12c91d147ad1075a20287d36c388657614a9c648d8e49cce8cfa282b2a2e8e9da6e4444e4b6aed1bd0ada5009a335cd0500bdae9a01b97f7e8cfe8372398e750de92ee4a524393a19826d19de5e762fb53a88b9b4657e9c6d7d01a4124e3e39532f614aec5cb88d982b78b2ff568017c92f6a1ce5298b5f323b5ee61695038ef0c3a7a339cfac31cf7875a4563046a40e7b35cef1d37d811b342fcbb5373122415befc23cb656a619f7c262c443403b23ba20e341a079918dec6f4f801b92781179ad7ac1951f39ddb0b1f1fb95b28c0f4593a04486f0e0e86bb3b014674879aad10f41e0bad34d40bc817b6fd43c1dde8547882d82e5111e208107e9c9736d16ca77ac7453d7b6c7976a7dd6a4c6c0252185bc9948660a07b66151b09b980d8572ae829ab2f6d900cc63066c4ecb3a0317e8a9acaa8e22a216721f3d69c67843df2fbedd88bc424f761cdf420de4ff2c55b6925b1826c1c4134090d2aae82a7f64a8d428efe1b21097a6fdbc9b8a31c4d46c7b32d478bf5b9181bef8af4d3486958d7c198c46a5ad771090bb64e7fc2bd7677ae7618b861c83c13177075063517eff40cfd52ae56650e9e7035f783bfe8920c754e98470b327909ac2a407e07864710544b8adbc075502c75b5f0fdbca0cde5d94b013141c96b55ae1b60ab63f9e792641690af41b05a78233935fd7f82852ebb2602a10459a709256c41d108c2aeb71925089cc79b121eabd5edc54f3f7c8929685b88ab29700c7fb2f247cd7962c8ce6dc9f79f9358d6a7989d24ccc17d0dbaa0b7fbe73479164181ae7d6b6a02678cce46f74ea2bd387ecb73041817b429a0220e1c3635fe492f5f3a8e2b65c086fe24c375563d7220856dd8f970716d548492964a156554dc88810c3c4f81dcc3ae80243e19679a3be9c9b1b8c707b416e9166c54a568bb9d84728c1a283d9231a12b13688ce2362901342652d66bf44cec223f561d2735d977c6adcf57e9066220d0770fc77fdf6ff367a2f36ae20062887beb88d1b453f267aef9bc763ab716bacb9214c38b95f4f2f3f6c236aa71aef83c1ae4b26133678884476c1d7c6d3e7b99f13e028ac6cebddfae4793f9ae975f3d66b725b500e7c7d2f664eaa0c358deb91cb2d6173394b306749d2bfb2684b985769bdb682e922b75555d38dad1899057a64ef6ea361e4712244d02d0dfec8c3d40770122770c2538d6a14a8462ef18eb705c16e5ba30aa5366447c94869060e4df155f7d01ffda04c1ced0ad5fbe5fdd85856e1e49320319687d31a6e4c7145479f45f43a9b8e9ffe4cbacfad4a21e5445e119df2996cce8b11d0f224efb4f18b544d456e2fb1d96fcd99fc319dbd86720621ac25b490f3611f7e5655bb3940a503c07dbf41f4b87593595a66008808744668371a54ce1b9dfdaa16f90415e57470bc23898d13d8351ba34369e96347da13d012b4eab32ada90668654c5ed2b433716b1b4170f640cf4b40659efcf4150237bbc25f72b248be85cd482b55ce2f5f73e8f02efd2465805b37d12487465ec1085bc6602b71862771af13e13baece4916b1ddef9ce016c92db9fb9e82aee5e1c4e22c45090ad1c19801ce1c541ff3902baea7a12dbcac6ec2d128ac7acb203463921ca6ce1d182d60d553ddecc4a3175eec2e924e9191e0d69aa49ca0b653495b8c62b802e443e669220f2a4047a56ed5cb7431a3387a435070795e6e63d242a74555e97371989c6d0040748e89ac316618d5d6eb7bff8d91e953afbe00464df4e4f6380c273b7ac5934cacb6c3be4da6649c8a5ea12bdf9afa1ec1e5053db7668c10ae2df75c4b3b14525369bf33741525a7630158aca3d3c7da5c1d71d3f63c0c2948a43236968d623c6c163eb757f0c78d6ae682ff4e4b673be07193e8d6c106c92851b393f0523491d5152e06de675fa22ae7bded329836a8ca0b955a59cb575395952c6ea0cb1644f2cada196b96b44ee12115ff9668e32886103d8f8109fac4a2287738ee1d2d4c1c19dab94eaa2757ea32016df60d7286099eb010ba570b5791ce4c54d860b15d56c53a5ddb543b0b602b3a87ca5213aa9647e51b1cb1736697506081240f4f7163a646f2be30e62c617d7572d820a113f96320834a2f43842160511923bd1ef4f723a2ab9b242fffe97cf0199c9f2ebca266a63beea0af279f2d18651b2ea9789d03025857c8aefc5f8bc6ee92ab7c6ffed2606f9c7ef25cb6c96140256a08cc58218869c52f3e5074fe5ace87a86057cc29fc845ff275f1b2dbc5991cbebe4c556b29ce3da3415958f0c9c68a5c664a1da60365ce56b9a4df24ec5d69c6f5a2e685885f6973c0ccd4caf7e000346bdea502b1418c8819e05bb7bee31bbd235819b49c892f7dadade576cb8ca68bda916a598c32ec03cceba2d01a7960f38680facccdaaf05e254dd8c3631a7222d1561c998c54b16f17a425f3247232dca035d545202e1b180340219ce31c66987bc86f983240e7fd2c926e8af77e6ea9f4139ff1edc0c323b67ceadead8f64a8004164d942f6aa65a28544d7205d41aafe62c5bfc6fd9fc322e2a8b620532d00d1add7a0d25a9dbaff90e19de14ac2ca97e83a2d77e63888ba0f7eaf9b18a7f2e5f148d16887580e55e894ae79024385f439acd071494e6469b77d07e763553652a470f3b4bd8bb7968052f3a969dacced51572bd125870849ce3e55359b2e17eabc3153b5f62868ea0ca1f40738488dbe0020c103f53678b9eb1c10000000000000000000000000000000000000000000000000000070a1115182027" } -} \ No newline at end of file +} diff --git a/test/control_mapping_test.exs b/test/control_mapping_test.exs index 2f0387a..4bcd3e7 100644 --- a/test/control_mapping_test.exs +++ b/test/control_mapping_test.exs @@ -321,7 +321,13 @@ defmodule Trinity.ControlMappingTest do String.starts_with?(Atom.to_string(fun), "check_"), do: "Trinity.Toolchain.#{fun}/#{arity}" - checks = checks ++ ["Trinity.Crypto.Preflight.mldsa87/1"] + checks = + checks ++ + [ + "Trinity.Crypto.Preflight.verifier/1", + "Trinity.Crypto.Preflight.signer/1", + "Trinity.Crypto.BootCheck.decide/3" + ] assert length(checks) >= 3 assert CM.uncited(checks, CM.cited_checks(rows()).funs, "runtime refusal") == [] diff --git a/test/trinity/crypto/boot_check_node_test.exs b/test/trinity/crypto/boot_check_node_test.exs new file mode 100644 index 0000000..2eddf38 --- /dev/null +++ b/test/trinity/crypto/boot_check_node_test.exs @@ -0,0 +1,132 @@ +# SPDX-FileCopyrightText: Sudo Apt Holdings LLC +# SPDX-License-Identifier: Apache-2.0 +defmodule Trinity.Crypto.BootCheckNodeTest do + @moduledoc """ + Slice 143, AC4 and AC5 at the only place their refusals are real: a boot, in a separate OS + process, by `Trinity.ToolchainNodeTest`'s recipe (`XDG_DATA_HOME` and `Trinity.BootIsolation` per + case, `MIX_ENV=test`, `mix run --no-start`, assertions on the one line the child prints). + + The child inherits this VM's runtime and mode, so each expectation comes from this VM, read here + and not from the code under test: whether ML-DSA-87 signs (`MLDSA87Oracle`), and the mode + `crypto:info_fips/0` reports. Each run prints which branch it took. + """ + use ExUnit.Case, async: false + + @moduletag timeout: 180_000 + + @isolate """ + Trinity.BootIsolation.isolate!(System.fetch_env!("TRINITY_BOOT_TAG")) + """ + + @report """ + case Application.ensure_all_started(:trinity) do + {:ok, _} -> + IO.puts("BOOT_OK") + + receipt = + Enum.find_value(1..50, fn _ -> + Trinity.Receipts.boot_receipt() || (Process.sleep(100) && nil) + end) + + IO.puts("VERIFIER " <> ((receipt && receipt.subject["mldsa87_verifier"]) || "none")) + System.halt(0) + + {:error, reason} -> + IO.puts("BOOT_REFUSED " <> inspect(reason, limit: :infinity, printable_limit: :infinity)) + System.halt(3) + end + """ + + @nss """ + r = Application.get_env(:trinity, :receipts, []) + Application.put_env(:trinity, :receipts, Keyword.put(r, :profile, :nss)) + """ + + defp boot(name, script, env \\ []) do + tag = Trinity.BootIsolation.tag(name) + on_exit(fn -> Trinity.BootIsolation.drop!(tag) end) + root = Path.join(System.tmp_dir!(), "bootcheck-#{name}-#{System.unique_integer([:positive])}") + File.mkdir_p!(root) + on_exit(fn -> File.rm_rf(root) end) + + System.cmd("mix", ["run", "--no-start", "-e", @isolate <> script], + env: + [ + {"MIX_ENV", "test"}, + {"TRINITY_PROFILE", nil}, + {"TRINITY_AUTHORITY", nil}, + {"XDG_DATA_HOME", root}, + {"TRINITY_BOOT_TAG", tag} + ] ++ env, + stderr_to_stdout: true + ) + end + + defp refusal(out) do + out + |> String.split("\n") + |> Enum.find("", &String.starts_with?(&1, "BOOT_REFUSED")) + |> String.replace(~S(\"), ~S(")) + end + + describe "AC4: the verifier check at boot" do + # The seam plants a wrong answer on the first case, which the check reaches on every runtime. A + # tree whose boot does not run the check, or does not refuse on a wrong answer, boots: this + # test's red. + test "a node whose verifier answers a case wrongly refuses, naming the case, in every profile" do + for {profile, extra} <- [{"commercial", ""}, {"nss", @nss}] do + {out, status} = + boot( + "wrong-#{profile}", + extra <> + ~s|Application.put_env(:trinity, :mldsa87_preflight_fault, "acvp_siggen_tc63")\n| <> + @report + ) + + line = refusal(out) + + assert status == 3, + "#{profile}: a node with a wrong verifier booted:\n" <> + String.slice(out, -3000, 3000) + + # On nss the provider's identity is asked first, and on every CI runtime it refuses first. + if profile == "commercial", + do: assert(line =~ ~s|{:mldsa87_verifier_wrong, "acvp_siggen_tc63", :planted}|, line), + else: assert(line =~ "nss_", line) + end + end + + test "on commercial the runtime's own answer decides, and the boot receipt says it" do + {out, status} = boot("commercial-unplanted", @report) + assert status == 0, String.slice(out, -3000, 3000) + want = if Trinity.TestSupport.MLDSA87Oracle.signs?(), do: "ok", else: "unavailable" + assert [_, got] = Regex.run(~r/^VERIFIER (\S+)$/m, out), out + assert got == want + + IO.puts( + "boot_check_node_test: a commercial node boots, its verifier #{got} (fips #{:crypto.info_fips()})" + ) + end + end + + describe "AC5: on nss" do + # No CI runtime carries CMVP #5523's provider, so an nss node refuses on every leg; which reason + # depends on the mode, which this VM reports. The pass is `boot_check_test.exs`'s. + test "an nss node refuses, naming the mode or the provider's build string" do + {out, status} = boot("nss", @nss <> @report) + line = refusal(out) + assert status == 3, "an nss node booted:\n" <> String.slice(out, -3000, 3000) + + case :crypto.info_fips() do + :enabled -> + assert line =~ "{:nss_fips_provider_buildinfo, ", line + assert line =~ ~s|"3.6.0-r4"}|, line + + mode -> + assert line =~ "{:nss_requires_fips_mode, #{inspect(mode)}}", line + end + + IO.puts("boot_check_node_test: an nss node refuses (fips #{:crypto.info_fips()}): " <> line) + end + end +end diff --git a/test/trinity/crypto/boot_check_test.exs b/test/trinity/crypto/boot_check_test.exs new file mode 100644 index 0000000..0698a75 --- /dev/null +++ b/test/trinity/crypto/boot_check_test.exs @@ -0,0 +1,105 @@ +# SPDX-FileCopyrightText: Sudo Apt Holdings LLC +# SPDX-License-Identifier: Apache-2.0 +defmodule Trinity.Crypto.BootCheckTest do + @moduledoc """ + Slice 143, AC4 and AC5, the decision half: what a boot does with the verifier check's answer and, + on `nss`, with the FIPS provider's identity. `decide/3` is pure, so every branch is shown here on + every leg, the `nss` pass included, which no CI runtime can show at a real boot (it needs CMVP + #5523's provider). The boots themselves are `boot_check_node_test.exs`. + """ + # Not async: one case writes the boot's verifier answer, which is global. + use ExUnit.Case, async: false + + alias Trinity.Crypto.BootCheck + + @fips_ok %{ + fips: :enabled, + info: %{fips_provider_available: true, fips_provider_buildinfo: ~c"3.6.0-r4"} + } + @off %{fips: :not_enabled, info: %{fips_provider_available: false}} + @unavailable {:error, {:mldsa87_verifier_failed, :unavailable, :no_mldsa}} + @wrong {:error, {:mldsa87_verifier_failed, "wycheproof_8", true}} + + test "the build string is the security policy's, pinned" do + assert BootCheck.nss_buildinfo() == "3.6.0-r4" + end + + describe "a case answered wrongly" do + test "refuses in every profile, naming the case" do + for profile <- [:commercial, :nss], runtime <- [@off, @fips_ok] do + expected = + if profile == :nss and runtime == @off, + do: {:error, {:nss_requires_fips_mode, :not_enabled}}, + else: {:error, {:mldsa87_verifier_wrong, "wycheproof_8", true}} + + assert BootCheck.decide(profile, @wrong, runtime) == expected + end + end + end + + describe "on commercial" do + test "a runtime that cannot run ML-DSA-87 boots, and one that passes boots" do + assert BootCheck.decide(:commercial, @unavailable, @off) == :ok + assert BootCheck.decide(:commercial, :ok, @off) == :ok + end + end + + describe "on nss" do + test "the validated module's build string, in FIPS mode, with the check passing, boots" do + assert BootCheck.decide(:nss, :ok, @fips_ok) == :ok + + assert BootCheck.decide( + :nss, + :ok, + put_in(@fips_ok, [:info, :fips_provider_buildinfo], "3.6.0-r4") + ) == :ok + end + + test "FIPS mode not enabled refuses, naming the mode" do + for mode <- [:not_enabled, :not_supported] do + assert BootCheck.decide(:nss, :ok, %{@fips_ok | fips: mode}) == + {:error, {:nss_requires_fips_mode, mode}} + end + end + + test "no FIPS provider refuses" do + runtime = put_in(@fips_ok, [:info, :fips_provider_available], false) + + assert BootCheck.decide(:nss, :ok, runtime) == + {:error, {:nss_requires_fips_provider, false}} + end + + test "another provider's build string refuses, naming both" do + # Red Hat's provider on the `fips` leg reports this (docs/fips-leg.md). + runtime = put_in(@fips_ok, [:info, :fips_provider_buildinfo], ~c"3.0.7-cda111b5812c30d4") + + assert BootCheck.decide(:nss, :ok, runtime) == + {:error, {:nss_fips_provider_buildinfo, "3.0.7-cda111b5812c30d4", "3.6.0-r4"}} + + assert BootCheck.decide(:nss, :ok, %{@fips_ok | info: %{fips_provider_available: true}}) == + {:error, {:nss_fips_provider_buildinfo, "", "3.6.0-r4"}} + end + + test "the validated module in place but no ML-DSA-87 refuses" do + assert BootCheck.decide(:nss, @unavailable, @fips_ok) == + {:error, {:nss_requires_mldsa87, :no_mldsa}} + end + end + + test "the boot receipt's wording for each answer" do + held = :persistent_term.get({BootCheck, :verifier}, nil) + + try do + for {answer, words} <- [ + {:ok, "ok"}, + {@unavailable, "unavailable"}, + {@wrong, "wrong:wycheproof_8"} + ] do + :persistent_term.put({BootCheck, :verifier}, answer) + assert BootCheck.verifier_answer() == words + end + after + :persistent_term.put({BootCheck, :verifier}, held) + end + end +end diff --git a/test/trinity/crypto/preflight_test.exs b/test/trinity/crypto/preflight_test.exs index ac1a898..904af49 100644 --- a/test/trinity/crypto/preflight_test.exs +++ b/test/trinity/crypto/preflight_test.exs @@ -2,12 +2,11 @@ # SPDX-License-Identifier: Apache-2.0 defmodule Trinity.Crypto.PreflightTest do @moduledoc """ - Slice 137, AC5: the ML-DSA-87 known-answer check. + Slice 143, AC1 to AC3: the ML-DSA-87 checks by role (slice 137's preflight, rebuilt). - The step logic is proven against a stand-in for `:crypto`, so it holds on every leg, including - the ones where the real runtime has no ML-DSA. The real `:crypto` is exercised where the - oracle says the runtime signs (tag `:mldsa87_runtime`), and against the runtime's own answer - everywhere. + The check logic is proven against stand-ins for `:crypto`, so it holds on every leg, including + the ones where the real runtime has no ML-DSA. The real `:crypto` is exercised where the oracle + says the runtime signs (tag `:mldsa87_runtime`), and against the runtime's own answer everywhere. """ # Not async: the fault seam is application environment. use ExUnit.Case, async: false @@ -17,51 +16,44 @@ defmodule Trinity.Crypto.PreflightTest do defmodule GoodCrypto do @moduledoc false - # Answers the published cases as a correct ML-DSA-87 would, from the vectors themselves, and - # signs fresh keys with a keyed hash so that a changed message fails. + # Answers each published case as a correct ML-DSA-87 would, from the table itself, and signs + # with a keyed hash so a changed message fails. It defines no generate_key/2: the signer check + # must never generate a key, and a call would raise here. + @cases Trinity.Crypto.Preflight.cases() @v Trinity.Crypto.Preflight.vectors() - def generate_key(:mldsa87, []), do: {"pub", {:seed, "priv"}} - def generate_key(:mldsa87, [], sk) do if sk == @v.keygen.sk, do: {@v.keygen.pk, sk}, else: {"other", sk} end + def sign(:mldsa87, :none, msg, {:expandedkey, sk}), do: :crypto.hash(:sha512, [sk, msg]) def sign(:mldsa87, :none, msg, {:seed, seed}), do: :crypto.hash(:sha512, [seed, msg]) def verify(:mldsa87, :none, msg, sig, pk) do - cond do - pk == @v.keygen.pk -> - sig == :crypto.hash(:sha512, [@v.keygen.seed, msg]) - - known = Enum.find(Map.values(@v.sigver), &(&1.sig == sig and &1.msg == msg)) -> - known == @v.sigver["valid"] - - pk == "pub" -> - sig == :crypto.hash(:sha512, ["priv", msg]) - - true -> - false + case Enum.find(@cases, &(&1.msg == msg and &1.sig == sig and &1.pk == pk)) do + %{expect: expect} -> expect + nil -> signed_here?(msg, sig, pk) end end - end - defmodule RaisingKeygen do - @moduledoc false - def generate_key(:mldsa87, [], _sk), do: raise(ErlangError, original: :notsup) + defp signed_here?(msg, sig, pk) do + cond do + pk == @v.signing.pk -> sig == :crypto.hash(:sha512, [@v.signing.sk, msg]) + pk == @v.keygen.pk -> sig == :crypto.hash(:sha512, [@v.keygen.seed, msg]) + true -> false + end + end end - defmodule WrongPublicKey do + defmodule NoMLDSA do @moduledoc false - defdelegate generate_key(a, b), to: GoodCrypto - def generate_key(:mldsa87, [], sk), do: {"not the published key", sk} - defdelegate sign(a, b, c, d), to: GoodCrypto - defdelegate verify(a, b, c, d, e), to: GoodCrypto + # What :crypto raises where the linked OpenSSL has no ML-DSA (OpenSSL 3.0.13, measured). + def verify(:mldsa87, :none, _msg, _sig, _pk), + do: :erlang.error({:badarg, {~c"pkey.c", 278}, ~c"Bad digest type"}) end defmodule AcceptsAnything do @moduledoc false - defdelegate generate_key(a, b), to: GoodCrypto defdelegate generate_key(a, b, c), to: GoodCrypto defdelegate sign(a, b, c, d), to: GoodCrypto def verify(:mldsa87, :none, _msg, _sig, _pub), do: true @@ -69,93 +61,170 @@ defmodule Trinity.Crypto.PreflightTest do defmodule RejectsEverything do @moduledoc false - defdelegate generate_key(a, b), to: GoodCrypto - defdelegate generate_key(a, b, c), to: GoodCrypto - defdelegate sign(a, b, c, d), to: GoodCrypto def verify(:mldsa87, :none, _msg, _sig, _pub), do: false end + defmodule WrongPublicKey do + @moduledoc false + def generate_key(:mldsa87, [], sk), do: {"not the published key", sk} + defdelegate sign(a, b, c, d), to: GoodCrypto + defdelegate verify(a, b, c, d, e), to: GoodCrypto + end + + defmodule SignsWrongly do + @moduledoc false + defdelegate generate_key(a, b, c), to: GoodCrypto + def sign(:mldsa87, :none, _msg, _key), do: "not a signature" + defdelegate verify(a, b, c, d, e), to: GoodCrypto + end + setup do on_exit(fn -> Application.delete_env(:trinity, :mldsa87_preflight_fault) end) end - describe "AC5: the step logic, against a stand-in" do - test "a provider that does all four answers :ok" do - assert Preflight.mldsa87(GoodCrypto) == :ok - assert Preflight.ok?(GoodCrypto) + describe "AC1: the table" do + test "every case names its source, an expected answer and what each runtime measured" do + %{"cases" => cases} = File.read!("priv/kat/mldsa87_boot_check.json") |> JSON.decode!() + ids = Enum.map(cases, & &1["id"]) + assert length(ids) == 9 and ids == Enum.uniq(ids) + + for c <- cases do + assert c["expect"] in [true, false, "raises"], c["id"] + assert map_size(c["measured"]) >= 2, c["id"] + src = File.read!(Path.join("priv/kat", c["from"])) |> JSON.decode!() + assert src["source"] =~ ~r{^https://raw\.githubusercontent\.com/}, c["id"] + assert src["source_sha256"] =~ ~r/^[0-9a-f]{64}$/, c["id"] + end + + assert Enum.map(Preflight.cases(), & &1.id) == ids end - test "the steps run in this order, the published answers first" do - assert Preflight.steps() == [:keygen_kat, :sigver_kat, :keygen, :sign, :verify, :negative] + test "the cases are the ones the Decision names, positives first, from the pinned commits" do + gen = File.read!("priv/kat/acvp/mldsa87_siggen.json") |> JSON.decode!() + ver = File.read!("priv/kat/acvp/mldsa87_sigver.json") |> JSON.decode!() + wy = File.read!("priv/kat/wycheproof/mldsa87_verify.json") |> JSON.decode!() + + for acvp <- [gen, ver] do + assert {acvp["parameterSet"], acvp["tgId"], acvp["signatureInterface"], acvp["preHash"]} == + {"ML-DSA-87", 5, "external", "pure"} + + assert acvp["context"] == "" and acvp["externalMu"] == false + assert acvp["source"] =~ "975de31eb83d87039ec88934fdc47d8c312b892d" + end + + assert {gen["tcId"], gen["deterministic"]} == {63, true} + assert {ver["tcId"], ver["testPassed"]} == {64, false} + assert wy["source"] =~ "7a325932231bd7a724c791c8b0d65ea4d6d2f435" + + assert Enum.map(~w(valid invalid invalid_hints infinity_norm wrong_length), &wy[&1]["tcId"]) == + [1, 8, 18, 77, 6] + + assert Enum.map(Preflight.cases(), & &1.expect) == + [true, true, false, false, false, false, false, false, false] + + assert byte_size(Enum.at(Preflight.cases(), 6).sig) == 4626 + end + end + + describe "AC2: the verifier check, against stand-ins" do + test "a verifier that answers every case rightly is :ok" do + assert Preflight.verifier(GoodCrypto) == :ok end - for step <- [:keygen_kat, :sigver_kat, :keygen, :sign, :verify, :negative] do - test "a planted failure at #{step} is named, and no later step is reached" do - Application.put_env(:trinity, :mldsa87_preflight_fault, unquote(step)) + test "a planted fault on each case is named, wrong" do + for %{id: id} <- Preflight.cases() do + Application.put_env(:trinity, :mldsa87_preflight_fault, id) - assert Preflight.mldsa87(GoodCrypto) == - {:error, {:mldsa87_preflight_failed, unquote(step), :planted}} + assert Preflight.verifier(GoodCrypto) == + {:error, {:mldsa87_verifier_failed, id, :planted}} end end - test "a runtime with no ML-DSA raises in the first step, which is a named failure, not a crash" do - assert {:error, {:mldsa87_preflight_failed, :keygen_kat, reason}} = - Preflight.mldsa87(RaisingKeygen) + test "a verifier that says true to everything fails at the first negative" do + assert Preflight.verifier(AcceptsAnything) == + {:error, {:mldsa87_verifier_failed, "acvp_sigver_tc64", true}} + end - assert is_binary(reason) + test "a verifier that says false to everything fails at the first positive" do + assert Preflight.verifier(RejectsEverything) == + {:error, {:mldsa87_verifier_failed, "acvp_siggen_tc63", false}} end - test "a provider that derives the wrong public key fails at :keygen_kat" do - assert {:error, {:mldsa87_preflight_failed, :keygen_kat, {:public_key_from_secret_key, _}}} = - Preflight.mldsa87(WrongPublicKey) + test "a runtime with no ML-DSA-87 is unavailable, never a crash and never a pass" do + assert {:error, {:mldsa87_verifier_failed, :unavailable, {:erlang, {:badarg, _, _}}}} = + Preflight.verifier(NoMLDSA) end + end - test "a verifier that accepts Wycheproof's bit-flipped signature fails at :sigver_kat, naming the case" do - %{"invalid" => %{tc: tc}} = Preflight.vectors().sigver + describe "AC3: the signer check, against stand-ins" do + test "a provider that does every step answers :ok, without generating a key" do + assert Preflight.signer(GoodCrypto) == :ok + assert Preflight.ok?(GoodCrypto) + assert Preflight.signer_steps() == [:verifier, :keygen_kat, :sign_kat, :sign_negative] + end + + test "a planted failure at each step is named" do + for step <- Preflight.signer_steps() do + Application.put_env(:trinity, :mldsa87_preflight_fault, step) - assert Preflight.mldsa87(AcceptsAnything) == - {:error, {:mldsa87_preflight_failed, :sigver_kat, {:wycheproof, tc, false, true}}} + assert Preflight.signer(GoodCrypto) == + {:error, {:mldsa87_preflight_failed, step, :planted}} + end end - test "a verifier that refuses everything fails at :keygen_kat, before any fresh key" do - assert Preflight.mldsa87(RejectsEverything) == + test "a verifier failure stops the signer check at :verifier, with the verifier's reason" do + assert Preflight.signer(AcceptsAnything) == {:error, - {:mldsa87_preflight_failed, :keygen_kat, - {:seed_signature_under_public_key, false}}} + {:mldsa87_preflight_failed, :verifier, + {:mldsa87_verifier_failed, "acvp_sigver_tc64", true}}} end - test "the published cases are the ones the fixtures name, with their provenance" do - acvp = File.read!("priv/kat/acvp/mldsa87_keygen.json") |> JSON.decode!() - wy = File.read!("priv/kat/wycheproof/mldsa87_verify.json") |> JSON.decode!() - assert {acvp["tgId"], acvp["tcId"], acvp["parameterSet"]} == {3, 51, "ML-DSA-87"} - assert acvp["source"] =~ "975de31eb83d87039ec88934fdc47d8c312b892d" + test "a provider that derives the wrong public key fails at :keygen_kat" do + assert {:error, {:mldsa87_preflight_failed, :keygen_kat, {:public_key_from_secret_key, _}}} = + Preflight.signer(WrongPublicKey) + end - assert {wy["valid"]["tcId"], wy["invalid"]["tcId"], wy["invalid_hints"]["tcId"]} == - {1, 8, 18} + test "a signature that does not verify under the published key fails" do + assert {:error, {:mldsa87_preflight_failed, :keygen_kat, _}} = + Preflight.signer(SignsWrongly) + end - assert wy["source"] =~ "7a325932231bd7a724c791c8b0d65ea4d6d2f435" - assert byte_size(Preflight.vectors().keygen.pk) == 2592 - assert byte_size(Preflight.vectors().sigver["valid"].sig) == 4627 + test "the census: nothing in the check can generate a key" do + src = File.read!("lib/trinity/crypto/preflight.ex") + refute src =~ ~r/generate_key\(\s*:mldsa87\s*,\s*\[\]\s*\)/ + refute function_exported?(GoodCrypto, :generate_key, 2) end end describe "the real :crypto" do - test "the preflight agrees with the independent oracle on this runtime" do - assert Preflight.ok?() == MLDSA87Oracle.signs?(), - "preflight #{inspect(Preflight.mldsa87())}, fips #{:crypto.info_fips()}" + test "the verifier check agrees with the independent oracle on this runtime" do + answer = Preflight.verifier() + + IO.puts( + "preflight_test: verifier #{inspect(answer, limit: 3)} (fips #{:crypto.info_fips()})" + ) + + if MLDSA87Oracle.signs?(), + do: assert(answer == :ok), + else: assert({:error, {:mldsa87_verifier_failed, :unavailable, _}} = answer) end @tag :mldsa87_runtime - test "where the runtime signs, a planted failure at each step is named" do - assert Preflight.mldsa87() == :ok + test "where the runtime signs, both checks pass and a planted fault at each case and step is named" do + assert Preflight.signer() == :ok + + for %{id: id} <- Preflight.cases() do + Application.put_env(:trinity, :mldsa87_preflight_fault, id) + assert {:error, {:mldsa87_verifier_failed, ^id, :planted}} = Preflight.verifier() + end - for step <- Preflight.steps() do + for step <- Preflight.signer_steps() do Application.put_env(:trinity, :mldsa87_preflight_fault, step) - assert {:error, {:mldsa87_preflight_failed, ^step, :planted}} = Preflight.mldsa87() + assert {:error, {:mldsa87_preflight_failed, ^step, :planted}} = Preflight.signer() end end - test "the ML-DSA-87 signer is available exactly when the preflight passes (F-137-1)" do + test "the ML-DSA-87 signer is available exactly when the signer check passes (F-137-1)" do assert Trinity.Receipts.Signer.MLDSA87.available?() == Preflight.ok?() end end diff --git a/test/trinity/effects/boot_receipt_test.exs b/test/trinity/effects/boot_receipt_test.exs index 6b4c605..8ab7ea5 100644 --- a/test/trinity/effects/boot_receipt_test.exs +++ b/test/trinity/effects/boot_receipt_test.exs @@ -41,15 +41,13 @@ defmodule Trinity.Effects.BootReceiptTest do r = Receipts.boot_receipt() %{erts: erts, elixir: elixir} = Trinity.Toolchain.current() - want = - case Trinity.Crypto.Preflight.mldsa87() do - :ok -> "ok" - {:error, {:mldsa87_preflight_failed, step, _}} -> "failed:#{step}" - end + # Slice 143: the verifier check's answer, expected from the independent oracle, not from the + # code under test: a runtime that signs ML-DSA-87 verifies the published cases rightly. + want = if Trinity.TestSupport.MLDSA87Oracle.signs?(), do: "ok", else: "unavailable" body = Trinity.TestSupport.ReceiptBody.of(r) - for {key, value} <- [{"erts", erts}, {"elixir", elixir}, {"mldsa87_preflight", want}] do + for {key, value} <- [{"erts", erts}, {"elixir", elixir}, {"mldsa87_verifier", want}] do assert r.subject[key] == value, key assert body["subject"][key] == value, "#{key} is not in the signed body" end diff --git a/test/trinity/receipts/signer_test.exs b/test/trinity/receipts/signer_test.exs index e09244e..9d94cec 100644 --- a/test/trinity/receipts/signer_test.exs +++ b/test/trinity/receipts/signer_test.exs @@ -94,7 +94,7 @@ defmodule Trinity.Receipts.SignerTest do case Signer.MLDSA87.available?() do false -> assert {:error, {:mldsa87_preflight_failed, step, _}} = KeyCustody.select() - assert step in Trinity.Crypto.Preflight.steps() + assert step in Trinity.Crypto.Preflight.signer_steps() true -> assert {:ok, :mldsa87} = KeyCustody.select() diff --git a/test/trinity/toolchain_node_test.exs b/test/trinity/toolchain_node_test.exs index 161ec56..3fc49c6 100644 --- a/test/trinity/toolchain_node_test.exs +++ b/test/trinity/toolchain_node_test.exs @@ -378,25 +378,25 @@ defmodule Trinity.ToolchainNodeTest do ) end - # Only where the runtime signs ML-DSA-87: elsewhere the check fails at :keygen before a fault + # Only where the runtime signs ML-DSA-87: elsewhere the check fails at :verifier before a fault # planted later can be observed. `test/test_helper.exs` derives the tag from the oracle. @tag :mldsa87_runtime test "a failing preflight refuses the boot, naming the step" do # The seam plants a failure at one step. A tree without the preflight ignores the key and # boots, which is this test's red. - {out, status} = regulated_mldsa87("mldsa87-preflight-fails", :verify) + {out, status} = regulated_mldsa87("mldsa87-preflight-fails", :sign_kat) line = refusal_line(out) assert status == 3, """ a regulated node configured to sign receipts with ML-DSA-87 booted although the \ - known-answer check failed at :verify. + known-answer check failed at :sign_kat. #{String.slice(out, -3000, 3000)} """ assert line =~ "mldsa87_preflight_failed", line - assert line =~ ":verify", line + assert line =~ ":sign_kat", line end # With no fault planted the answer is the runtime's own, and the expectation comes from an @@ -432,7 +432,7 @@ defmodule Trinity.ToolchainNodeTest do script = @mldsa87_receipts <> - "Application.put_env(:trinity, :mldsa87_preflight_fault, :sign)\n" <> @report + "Application.put_env(:trinity, :mldsa87_preflight_fault, :sign_kat)\n" <> @report {out, status} = boot([{"XDG_DATA_HOME", root}, {"TRINITY_BOOT_TAG", tag}], script) assert status == 0, String.slice(out, -3000, 3000) diff --git a/test/trinity_web/live/receipts_live_test.exs b/test/trinity_web/live/receipts_live_test.exs index 652e243..053cd9b 100644 --- a/test/trinity_web/live/receipts_live_test.exs +++ b/test/trinity_web/live/receipts_live_test.exs @@ -73,7 +73,7 @@ defmodule TrinityWeb.ReceiptsLiveTest do assert html =~ boot.receipt_hash # Slice 137 AC8: the runtime and the ML-DSA-87 check are on the page. assert html =~ boot.subject["erts"] - assert html =~ boot.subject["mldsa87_preflight"] + assert html =~ boot.subject["mldsa87_verifier"] html = view |> element("#verify") |> render_click() assert html =~ "verified: " assert html =~ "exit 0"