From 8c0fe4bf4c31176d37bee078f1d743cd42269b04 Mon Sep 17 00:00:00 2001 From: Dan Hepworth Date: Mon, 5 Oct 2026 12:59:40 -0400 Subject: [PATCH 1/8] bip324: derive hybrid keys from an ordered transport transcript Add the hybrid ML-KEM-1024 key recipe of #184 to BIP324Cipher. Nothing calls it from the transport yet; E3 does. - Initialize(..., retain_for_hybrid) keeps the raw BIP324 ECDH secret, starts a running SHA256 transcript with ellswift_I || ellswift_R, and snapshots the network magic and cipher direction. The default (false) keeps today's secret lifetime and the existing vectors. - AddVersionContents hashes CompactSize(len) || contents, responder first, through a small stream adapter without copying the contents. - SwitchToHybrid derives HKDF-SHA256 with salt "qbit_v2_hybrid_mlkem1024" || magic over ss_ECDH || ss_MLKEM || TH, re-emplaces all four packet ciphers with counters at 0 and replaces the session id; garbage terminators stay. Misuse (not initialized, not retained, wrong contents count, a second switch) returns false and changes nothing. - The retained secret, IKM, HKDF state and derived keys are wiped on switch, in DiscardHybridSecret and in the destructor. Copying is deleted. The same recipe goes into test_framework/crypto/bip324_cipher.py, and contrib/devtools/generate-pq-transport-vectors.py uses it, the Python ML-KEM-1024 and the BIP324 ECDH helpers (imported by path, never the C++ code) to write src/test/data/pq_transport_vectors.json: inputs, every intermediate, both confirmations and first packets, and negative cases. Its --check mode runs in lint through test/lint/lint-pq-transport-vectors.py. bip324_tests gains hybrid_vectors (both roles and self-decrypt mode, with every intermediate recomputed from qbit's own primitives), hybrid_transcript_binding, hybrid_api_order and hybrid_rekey_boundaries. The bip324_cipher_roundtrip fuzz target gains an optional switch, transcript and secret mutations and discards. The existing BIP324 vectors are unchanged. Part of #184 (#199, E1). Co-Authored-By: Claude Opus 5.5 --- .../devtools/generate-pq-transport-vectors.py | 349 ++++++++++++++ src/bip324.cpp | 93 +++- src/bip324.h | 41 +- src/test/CMakeLists.txt | 1 + src/test/bip324_tests.cpp | 456 ++++++++++++++++++ src/test/data/pq_transport_vectors.json | 239 +++++++++ src/test/fuzz/bip324.cpp | 86 +++- .../test_framework/crypto/bip324_cipher.py | 148 +++++- test/lint/lint-pq-transport-vectors.py | 19 + 9 files changed, 1422 insertions(+), 10 deletions(-) create mode 100755 contrib/devtools/generate-pq-transport-vectors.py create mode 100644 src/test/data/pq_transport_vectors.json create mode 100755 test/lint/lint-pq-transport-vectors.py diff --git a/contrib/devtools/generate-pq-transport-vectors.py b/contrib/devtools/generate-pq-transport-vectors.py new file mode 100755 index 0000000000..f21e27d025 --- /dev/null +++ b/contrib/devtools/generate-pq-transport-vectors.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026-present The qbit core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. +"""Generate the hybrid post-quantum v2 transport vectors (src/test/data/pq_transport_vectors.json). + +The values come from the Python reference in test/functional/test_framework/crypto, imported by +path: bip324_cipher.py for the key recipe and packet encryption, mlkem.py for ML-KEM-1024, and +ellswift.py, secp256k1.py and hkdf.py for the BIP324 ECDH part. This script never calls the C++ +code under test. Every input is derived from a fixed label, so the output is reproducible byte for +byte. The recipe is specified in doc/design/pq-transport.md. + +Run without arguments to rewrite the file, or with --check (as the lint does) to fail when the +committed file differs from what the reference produces. +""" + +import argparse +import hashlib +import importlib +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +FUNCTIONAL_DIR = ROOT / "test" / "functional" +OUTPUT = ROOT / "src" / "test" / "data" / "pq_transport_vectors.json" + +PQ_MLKEM1024 = 0xF0 +# Network magic in wire order (src/kernel/chainparams.cpp); bip324_tests checks it against the chain. +MAGIC = {"main": bytes.fromhex("444f24a8"), "regtest": bytes.fromhex("a66b1fda")} +# First application messages: a v2 short-id ping (18) and the matching pong (19). +PING = bytes([18]) + bytes.fromhex("0123456789abcdef") +PONG = bytes([19]) + bytes.fromhex("0123456789abcdef") + + +def load_reference(): + """Import the test framework's crypto modules from the source tree.""" + sys.path.insert(0, str(FUNCTIONAL_DIR)) + names = ("bip324_cipher", "ellswift", "hkdf", "mlkem", "secp256k1") + return {name: importlib.import_module(f"test_framework.crypto.{name}") for name in names} + + +def seed(label, size=32): + """Deterministic input bytes for a label.""" + out = b"" + counter = 0 + while len(out) < size: + out += hashlib.sha256(f"qbit pq transport vectors/{label}/{counter}".encode()).digest() + counter += 1 + return out[:size] + + +def tagged_hash(tag, data): + tag_hash = hashlib.sha256(tag.encode()).digest() + return hashlib.sha256(tag_hash + tag_hash + data).digest() + + +def make_key(ref, label): + """Deterministic private key and ElligatorSwift encoding of its public key.""" + secp, ellswift = ref["secp256k1"], ref["ellswift"] + priv = int.from_bytes(seed(f"{label}/privkey"), "big") % (secp.GE.ORDER - 1) + 1 + x = (priv * secp.G).x + counter = 0 + while True: + u_seed = seed(f"{label}/ellswift/{counter}", 33) + u = secp.FE(int.from_bytes(u_seed[:32], "big")) + t = ellswift.xswiftec_inv(x, u, u_seed[32] & 7) if u != 0 else None + if t is not None: + assert ellswift.xswiftec(u, t) == x + return priv.to_bytes(32, "big"), u.to_bytes() + t.to_bytes() + counter += 1 + + +def bip324_ecdh(ref, priv, ellswift_theirs, ellswift_ours, initiating): + """The BIP324 ECDH secret (what CKey::ComputeBIP324ECDHSecret returns).""" + x = ref["ellswift"].ellswift_ecdh_xonly(ellswift_theirs, priv) + pubkeys = ellswift_ours + ellswift_theirs if initiating else ellswift_theirs + ellswift_ours + return tagged_hash("bip324_ellswift_xonly_ecdh", pubkeys + x) + + +def record(ref, header, payload): + """One version-contents record: CompactSize(1 + len(payload)) || header || payload.""" + return ref["bip324_cipher"].ser_compact_size(1 + len(payload)) + bytes([header]) + payload + + +class Exchange: + """One side's view of a hybrid handshake, from the version contents onwards.""" + + def __init__(self, ref, ss_ecdh, ellswift_i, ellswift_r, contents_first, contents_second, ss_mlkem, magic): + cipher = ref["bip324_cipher"] + # The transcript always lists the responder's contents first; a misbehaving side may not. + self.transcript_input = cipher.hybrid_transcript_input(ellswift_i, ellswift_r, contents_first, contents_second) + self.transcript_hash = hashlib.sha256(self.transcript_input).digest() + self.keys = cipher.hybrid_derive_keys(ss_ecdh, ss_mlkem, self.transcript_hash, magic) + + def ciphers(self, ref, initiator): + return ref["bip324_cipher"].hybrid_ciphers(self.keys, initiator) + + +def first_packets(ref, exchange, initiator, contents): + """The key confirmation and the first application packet one side sends.""" + cipher = ref["bip324_cipher"] + send_l, send_p, _, _ = exchange.ciphers(ref, initiator) + confirmation = cipher.encrypt_packet(send_l, send_p, b"", ignore=True) + assert len(confirmation) == 20 + return confirmation, cipher.encrypt_packet(send_l, send_p, contents) + + +def receive(ref, exchange, initiator, packets): + """Decrypt packets in order as one side; return each result of decrypt_packet.""" + _, _, recv_l, recv_p = exchange.ciphers(ref, initiator) + return [ref["bip324_cipher"].decrypt_packet(recv_l, recv_p, packet) for packet in packets] + + +def make_vector(ref, index, chain, comment, shape): + mlkem, hkdf = ref["mlkem"], ref["hkdf"] + magic = MAGIC[chain] + priv_i, ellswift_i = make_key(ref, f"{index}/initiator") + priv_r, ellswift_r = make_key(ref, f"{index}/responder") + ss_ecdh = bip324_ecdh(ref, priv_i, ellswift_r, ellswift_i, initiating=True) + assert ss_ecdh == bip324_ecdh(ref, priv_r, ellswift_i, ellswift_r, initiating=False) + + # Today's BIP324 values, which the switch must leave alone (garbage terminators) or replace + # (session id). + salt_ecdh = b"bitcoin_v2_shared_secret" + magic + garbage_terminators = hkdf.hkdf_sha256(length=32, ikm=ss_ecdh, salt=salt_ecdh, info=b"garbage_terminators") + ecdh_session_id = hkdf.hkdf_sha256(length=32, ikm=ss_ecdh, salt=salt_ecdh, info=b"session_id") + + keygen_seed = seed(f"{index}/mlkem_keygen", mlkem.SEED_SIZE) + encaps_m = seed(f"{index}/mlkem_encaps", mlkem.MSG_SIZE) + ek, dk = mlkem.mlkem1024_keygen(keygen_seed) + assert mlkem.mlkem1024_check_ek(ek) + ss_mlkem, ct = mlkem.mlkem1024_encaps(ek, encaps_m) + assert mlkem.mlkem1024_decaps(dk, ct) == ss_mlkem + + offer = record(ref, PQ_MLKEM1024, ek) + accept = record(ref, PQ_MLKEM1024, ct) + assert offer[:4] == accept[:4] == bytes.fromhex("fd2106f0") and len(offer) == len(accept) == 1572 + if shape == "single": + contents_r, contents_i = offer, accept + elif shape == "extra_records": + # The first 0xF0 record wins, wherever it sits: the initiator encapsulates to the offer's ek + # and ignores the second 0xF0 record's (different, valid) ek. Unknown headers are ignored + # too, but the transcript covers every record of both contents. + other_ek, _ = mlkem.mlkem1024_keygen(seed(f"{index}/mlkem_keygen_duplicate", mlkem.SEED_SIZE)) + contents_r = offer + record(ref, PQ_MLKEM1024, other_ek) + record(ref, 0xF1, bytes.fromhex("0001")) + contents_i = record(ref, 0x01, b"qbit") + accept + else: + # A 65,535-byte unknown record: its record length (65,536) and the responder's contents + # length in the transcript (67,113) both take the 0xfe CompactSize form. + assert shape == "large_contents" + contents_r = offer + record(ref, 0xF1, bytes(65535)) + contents_i = accept + assert contents_r[1572] == 0xFE and ref["bip324_cipher"].ser_compact_size(len(contents_r))[0] == 0xFE + + exchange = Exchange(ref, ss_ecdh, ellswift_i, ellswift_r, contents_r, contents_i, ss_mlkem, magic) + if shape == "single": + assert len(exchange.transcript_input) == 3278 + init_confirmation, init_first = first_packets(ref, exchange, True, PING) + resp_confirmation, resp_first = first_packets(ref, exchange, False, PONG) + assert receive(ref, exchange, False, [init_confirmation, init_first]) == [(0, (True, b"")), (len(PING), (False, PING))] + assert receive(ref, exchange, True, [resp_confirmation, resp_first]) == [(0, (True, b"")), (len(PONG), (False, PONG))] + + vector = { + "comment": comment, + "chain": chain, + "magic": magic.hex(), + "initiator_privkey": priv_i.hex(), + "initiator_ellswift": ellswift_i.hex(), + "responder_privkey": priv_r.hex(), + "responder_ellswift": ellswift_r.hex(), + "ss_ecdh": ss_ecdh.hex(), + "ecdh_session_id": ecdh_session_id.hex(), + "initiator_garbage_terminator": garbage_terminators[:16].hex(), + "responder_garbage_terminator": garbage_terminators[16:].hex(), + "mlkem_keygen_seed": keygen_seed.hex(), + "mlkem_encaps_m": encaps_m.hex(), + "ek": ek.hex(), + "ct": ct.hex(), + "ss_mlkem": ss_mlkem.hex(), + "contents_responder": contents_r.hex(), + "contents_initiator": contents_i.hex(), + "transcript_input": exchange.transcript_input.hex(), + "transcript_hash": exchange.transcript_hash.hex(), + "salt": exchange.keys["salt"].hex(), + "ikm": exchange.keys["ikm"].hex(), + "prk": exchange.keys["prk"].hex(), + } + for label in ref["bip324_cipher"].HYBRID_KEY_LABELS: + vector[label] = exchange.keys[label].hex() + vector.update({ + "initiator_confirmation": init_confirmation.hex(), + "responder_confirmation": resp_confirmation.hex(), + "initiator_first_contents": PING.hex(), + "initiator_first_packet": init_first.hex(), + "responder_first_contents": PONG.hex(), + "responder_first_packet": resp_first.hex(), + }) + state = {"ss_ecdh": ss_ecdh, "ellswift_i": ellswift_i, "ellswift_r": ellswift_r, "dk": dk, "ct": ct, + "ss_mlkem": ss_mlkem, "contents_r": contents_r, "contents_i": contents_i, "magic": magic, + "exchange": exchange, "confirmations": {True: init_confirmation, False: resp_confirmation}} + return vector, state + + +def make_negative(ref, index, state, comment, initiator, contents_first, contents_second, ss_mlkem): + """One side derives from a different transcript or secret than its honest peer. + + Records what that side derives and sends, and what each side decodes from the other's key + confirmation: a nonzero length fails at once (reason length), a zero length on the tag.""" + bad = Exchange(ref, state["ss_ecdh"], state["ellswift_i"], state["ellswift_r"], contents_first, contents_second, + ss_mlkem, state["magic"]) + honest = state["exchange"] + assert bad.keys["session_id"] != honest.keys["session_id"] + confirmation, _ = first_packets(ref, bad, initiator, b"") + [(length, result)] = receive(ref, bad, initiator, [state["confirmations"][not initiator]]) + [(peer_length, peer_result)] = receive(ref, honest, not initiator, [confirmation]) + assert result is None and peer_result is None + negative = { + "comment": comment, + "vector": index, + "role": "initiator" if initiator else "responder", + "transcript_contents": [contents_first.hex(), contents_second.hex()], + "ss_mlkem": ss_mlkem.hex(), + "session_id": bad.keys["session_id"].hex(), + "confirmation": confirmation.hex(), + "decrypted_length": length, + "failure": "length" if length else "tag", + "peer_decrypted_length": peer_length, + "peer_failure": "length" if peer_length else "tag", + } + return negative + + +def make_confirmation_failure(ref, index, state, comment, receiver_is_initiator, packet): + """A first hybrid packet that fails the receiver's key confirmation check under the honest keys.""" + [(length, result)] = receive(ref, state["exchange"], receiver_is_initiator, [packet]) + if length: + failure = "length" + elif result is None: + failure = "tag" + else: + assert result == (False, b"") + failure = "not_decoy" + return { + "comment": comment, + "vector": index, + "receiver": "initiator" if receiver_is_initiator else "responder", + "packet": packet.hex(), + "decrypted_length": length, + "failure": failure, + } + + +def generate(): + ref = load_reference() + cipher, mlkem = ref["bip324_cipher"], ref["mlkem"] + vectors = [] + states = [] + for index, (chain, comment, shape) in enumerate(( + ("main", "Mainnet, one offer record and one accept record (the ordinary exchange).", "single"), + ("regtest", "Regtest, one offer record and one accept record.", "single"), + ("main", "Mainnet, extra records. The responder's contents are its offer, a second 0xf0 record with a " + "different valid ek, and an unknown 0xf1 record; the initiator encapsulates to the ek of the " + "first 0xf0 record and ignores the rest. The initiator's contents are an unknown 0x01 record " + "followed by its accept, which is still the first 0xf0 record there. The transcript covers every " + "record of both contents.", "extra_records"), + ("main", "Mainnet; the responder's contents are its offer followed by an unknown 0xf1 record with a " + "65,535-byte zero payload, so that record's length (65,536) and the responder's contents length " + "in the transcript (67,113) both use the 0xfe CompactSize form.", "large_contents"), + )): + vector, state = make_vector(ref, index, chain, comment, shape) + vectors.append(vector) + states.append(state) + + state = states[0] + bad_ct = bytes([state["ct"][0] ^ 0x01]) + state["ct"][1:] + bad_ss = mlkem.mlkem1024_decaps(state["dk"], bad_ct) + assert bad_ss != state["ss_mlkem"] + flipped_ss = bytes([state["ss_mlkem"][0] ^ 0xFF]) + state["ss_mlkem"][1:] + negatives = [ + make_negative(ref, 0, state, "Bit 0 of the ciphertext flipped in transit: the responder hashes the " + "damaged accept and decapsulation returns the implicit-rejection secret.", + False, state["contents_r"], record(ref, PQ_MLKEM1024, bad_ct), bad_ss), + make_negative(ref, 0, state, "The initiator's ML-KEM shared secret has byte 0 inverted after " + "encapsulation (a local fault); EK and CT are untouched.", + True, state["contents_r"], state["contents_i"], flipped_ss), + make_negative(ref, 0, state, "The responder hashes the two contents in the wrong order.", + False, state["contents_i"], state["contents_r"], state["ss_mlkem"]), + ] + + # Damaged first packets under the honest keys of vector 0, one for each confirmation failure reason. + init_confirmation = state["confirmations"][True] + resp_send_l, resp_send_p, _, _ = state["exchange"].ciphers(ref, False) + confirmation_failures = [ + make_confirmation_failure(ref, 0, state, "The initiator's confirmation with bit 0 of its first byte flipped: " + "the length decrypts to 1.", + False, bytes([init_confirmation[0] ^ 0x01]) + init_confirmation[1:]), + make_confirmation_failure(ref, 0, state, "The initiator's confirmation with bit 0 of its last tag byte flipped: " + "the length decrypts to 0 and the tag fails.", + False, init_confirmation[:-1] + bytes([init_confirmation[-1] ^ 0x01])), + make_confirmation_failure(ref, 0, state, "The responder's first hybrid packet is an authenticated empty packet " + "without the ignore bit.", + True, cipher.encrypt_packet(resp_send_l, resp_send_p, b"", ignore=False)), + ] + assert [failure["failure"] for failure in confirmation_failures] == ["length", "tag", "not_decoy"] + + document = { + "comment": [ + "Hybrid post-quantum BIP324 v2 transport: the ML-KEM-1024 key recipe under version-contents " + "header 0xf0, specified in doc/design/pq-transport.md.", + "Generated by contrib/devtools/generate-pq-transport-vectors.py from the Python reference in " + "test/functional/test_framework/crypto. Do not edit by hand.", + "Byte strings are lowercase hex. Packets are BIP324 packets: encrypted length (3) || encrypted " + "header and contents || tag (16). Each side's key confirmation is its hybrid packet 0, and its " + "first application packet is hybrid packet 1.", + "confirmation_failures are first hybrid packets that the named receiver must reject under the honest " + "keys of the vector; negative_vectors are sides that derived from different inputs than their peer.", + ], + "header": f"{PQ_MLKEM1024:02x}", + "salt_label": cipher.HYBRID_SALT_LABEL.decode(), + "labels": list(cipher.HYBRID_KEY_LABELS), + "vectors": vectors, + "confirmation_failures": confirmation_failures, + "negative_vectors": negatives, + } + return json.dumps(document, indent=2) + "\n" + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--check", action="store_true", help=f"fail if {OUTPUT.relative_to(ROOT)} is stale") + args = parser.parse_args() + + text = generate() + if args.check: + current = OUTPUT.read_text(encoding="utf8") if OUTPUT.exists() else None + if current != text: + print(f"{OUTPUT.relative_to(ROOT)} does not match the Python reference. If the recipe change is " + "intended, it needs a new version-contents header (doc/design/pq-transport.md); then run " + f"{Path(__file__).relative_to(ROOT)} to regenerate it.", file=sys.stderr) + sys.exit(1) + print(f"{OUTPUT.relative_to(ROOT)} matches the Python reference.") + return + OUTPUT.write_text(text, encoding="utf8") + print(f"Wrote {OUTPUT.relative_to(ROOT)}") + + +if __name__ == "__main__": + main() diff --git a/src/bip324.cpp b/src/bip324.cpp index 9c5a1572bb..3ba2c8aeaa 100644 --- a/src/bip324.cpp +++ b/src/bip324.cpp @@ -8,9 +8,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -22,6 +24,20 @@ #include #include +namespace { + +/** Serialization stream that feeds every written byte into a running SHA256, without copying. */ +class TranscriptWriter +{ + CSHA256& m_hasher; + +public: + explicit TranscriptWriter(CSHA256& hasher) noexcept : m_hasher{hasher} {} + void write(std::span src) noexcept { m_hasher.Write(UCharCast(src.data()), src.size()); } +}; + +} // namespace + BIP324Cipher::BIP324Cipher(const CKey& key, std::span ent32) noexcept : m_key(key) { @@ -31,7 +47,12 @@ BIP324Cipher::BIP324Cipher(const CKey& key, std::span ent32) no BIP324Cipher::BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept : m_key(key), m_our_pubkey(pubkey) {} -void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt) noexcept +BIP324Cipher::~BIP324Cipher() +{ + DiscardHybridSecret(); +} + +void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt, bool retain_for_hybrid) noexcept { // Determine salt (fixed string + network magic bytes) const auto& message_header = Params().MessageStart(); @@ -63,6 +84,19 @@ void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator // Derive session id from shared secret. hkdf.Expand32("session_id", UCharCast(m_session_id.data())); + // Keep what SwitchToHybrid() needs: the ECDH secret itself, the transcript started with the + // actual-role public keys (initiator first), the network magic and the cipher direction. + if (retain_for_hybrid) { + m_retained_ecdh = ecdh_secret; + m_hybrid_transcript.emplace(); + const EllSwiftPubKey& ellswift_initiator{initiator ? m_our_pubkey : their_pubkey}; + const EllSwiftPubKey& ellswift_responder{initiator ? their_pubkey : m_our_pubkey}; + m_hybrid_transcript->Write(UCharCast(ellswift_initiator.data()), ellswift_initiator.size()); + m_hybrid_transcript->Write(UCharCast(ellswift_responder.data()), ellswift_responder.size()); + std::copy(std::begin(message_header), std::end(message_header), m_hybrid_magic.begin()); + m_hybrid_send_is_initiator = side; + } + // Wipe all variables that contain information which could be used to re-derive encryption keys. memory_cleanse(ecdh_secret.data(), ecdh_secret.size()); memory_cleanse(hkdf_32_okm.data(), sizeof(hkdf_32_okm)); @@ -70,6 +104,63 @@ void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator m_key = CKey(); } +bool BIP324Cipher::AddVersionContents(std::span contents) noexcept +{ + if (!m_hybrid_transcript || m_version_contents_count >= 2) return false; + TranscriptWriter writer{*m_hybrid_transcript}; + WriteCompactSize(writer, contents.size()); + writer.write(contents); + ++m_version_contents_count; + return true; +} + +bool BIP324Cipher::SwitchToHybrid(std::span ss_mlkem) noexcept +{ + if (!*this || !m_retained_ecdh || !m_hybrid_transcript || m_version_contents_count != 2 || m_hybrid_switched) return false; + + // Determine salt (fixed string + network magic bytes, as snapshotted by Initialize). + std::string salt = std::string{"qbit_v2_hybrid_mlkem1024"} + std::string(std::begin(m_hybrid_magic), std::end(m_hybrid_magic)); + + // IKM = ss_ECDH (32) || ss_MLKEM (32) || TH (32), where finishing the transcript gives TH. + std::array ikm; + std::copy(m_retained_ecdh->begin(), m_retained_ecdh->end(), ikm.begin()); + std::copy(ss_mlkem.begin(), ss_mlkem.end(), ikm.begin() + ECDH_SECRET_SIZE); + m_hybrid_transcript->Finalize(UCharCast(ikm.data() + ECDH_SECRET_SIZE + 32)); + + // Derive the hybrid keys, and re-emplace all four ciphers, so their counters start at 0. + bool side = m_hybrid_send_is_initiator; + CHKDF_HMAC_SHA256_L32 hkdf(UCharCast(ikm.data()), ikm.size(), salt); + std::array hkdf_32_okm; + hkdf.Expand32("initiator_L", UCharCast(hkdf_32_okm.data())); + (side ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data())); + (side ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data())); + (side ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data())); + (side ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + + // Replace the session id. The garbage terminators are not re-derived. + hkdf.Expand32("session_id", UCharCast(m_session_id.data())); + + // Wipe everything that could re-derive the keys, including the retained secret. + memory_cleanse(ikm.data(), ikm.size()); + memory_cleanse(hkdf_32_okm.data(), hkdf_32_okm.size()); + memory_cleanse(&hkdf, sizeof(hkdf)); + DiscardHybridSecret(); + m_hybrid_switched = true; + return true; +} + +void BIP324Cipher::DiscardHybridSecret() noexcept +{ + if (m_retained_ecdh) { + memory_cleanse(m_retained_ecdh->data(), m_retained_ecdh->size()); + m_retained_ecdh.reset(); + } + m_hybrid_transcript.reset(); +} + void BIP324Cipher::Encrypt(std::span contents, std::span aad, bool ignore, std::span output) noexcept { assert(output.size() == contents.size() + EXPANSION); diff --git a/src/bip324.h b/src/bip324.h index c6b82cb969..6cd3c2dc6d 100644 --- a/src/bip324.h +++ b/src/bip324.h @@ -7,10 +7,12 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -40,10 +42,23 @@ class BIP324Cipher std::array m_send_garbage_terminator; std::array m_recv_garbage_terminator; + /** Hybrid key schedule state, only kept when Initialize() was called with retain_for_hybrid. */ + std::optional m_retained_ecdh; + std::optional m_hybrid_transcript; + std::array m_hybrid_magic{}; + uint8_t m_version_contents_count{0}; + bool m_hybrid_send_is_initiator{false}; + bool m_hybrid_switched{false}; + public: /** No default constructor; keys must be provided to create a BIP324Cipher. */ BIP324Cipher() = delete; + /** Wipes any retained hybrid secret. */ + ~BIP324Cipher(); + BIP324Cipher(const BIP324Cipher&) = delete; + BIP324Cipher& operator=(const BIP324Cipher&) = delete; + /** Initialize a BIP324 cipher with specified key and encoding entropy (testing only). */ BIP324Cipher(const CKey& key, std::span ent32) noexcept; @@ -58,8 +73,32 @@ class BIP324Cipher * initiator is set to true if we are the initiator establishing the v2 P2P connection. * self_decrypt is only for testing, and swaps encryption/decryption keys, so that encryption * and decryption can be tested without knowing the other side's private key. + * retain_for_hybrid keeps the ECDH secret and starts the hybrid transcript, so that + * SwitchToHybrid() can later replace the keys. Without it, the ECDH secret is wiped here. + */ + void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt = false, + bool retain_for_hybrid = false) noexcept; + + /** Add the full version packet contents of one side to the hybrid transcript, the + * responder's first and the initiator's second. + * + * Returns false, changing nothing, unless Initialize() retained the hybrid state, no + * DiscardHybridSecret() or SwitchToHybrid() happened since, and fewer than two contents + * were added. */ - void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt = false) noexcept; + [[nodiscard]] bool AddVersionContents(std::span contents) noexcept; + + /** Replace all four packet ciphers (counters at 0) and the session id with keys derived from + * the retained ECDH secret, the ML-KEM shared secret and the transcript. The garbage + * terminators are unchanged, and the retained secret is wiped. + * + * Returns false, changing nothing, unless the cipher is initialized, the hybrid state is + * retained, both version contents were added and no earlier switch happened. + */ + [[nodiscard]] bool SwitchToHybrid(std::span ss_mlkem) noexcept; + + /** Wipe the retained ECDH secret and reset the transcript; the current ciphers stay in use. */ + void DiscardHybridSecret() noexcept; /** Determine whether this cipher is fully initialized. */ explicit operator bool() const noexcept { return m_send_l_cipher.has_value(); } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 204f378b34..673e436286 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -166,6 +166,7 @@ target_json_data_sources(test_bitcoin data/p2mr_script_boundary_vectors.json data/p2mr_v1_manifest.json data/p2mr_vectors.json + data/pq_transport_vectors.json data/script_tests.json data/sighash.json data/testnet4_launch_difficulty.json diff --git a/src/test/bip324_tests.cpp b/src/test/bip324_tests.cpp index 2e71bf22a5..ee36045246 100644 --- a/src/test/bip324_tests.cpp +++ b/src/test/bip324_tests.cpp @@ -2,11 +2,19 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include + #include #include +#include +#include +#include +#include #include #include +#include #include +#include #include #include #include @@ -15,12 +23,88 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include +#include namespace { +using Bytes = std::vector; + +Bytes ToBytes(std::span bytes) { return {bytes.begin(), bytes.end()}; } + +Bytes Concat(std::initializer_list> parts) +{ + Bytes out; + for (const auto& part : parts) out.insert(out.end(), part.begin(), part.end()); + return out; +} + +Bytes CompactSize(uint64_t size) +{ + DataStream stream; + WriteCompactSize(stream, size); + return {stream.begin(), stream.end()}; +} + +/** A version-contents record: CompactSize(1 + payload size) || header || payload. */ +Bytes Record(uint8_t header, std::span payload) +{ + return Concat({CompactSize(1 + payload.size()), std::as_bytes(std::span{&header, 1}), payload}); +} + +Bytes EncryptPacket(BIP324Cipher& sender, std::span contents, bool ignore) +{ + Bytes packet(contents.size() + BIP324Cipher::EXPANSION); + sender.Encrypt(contents, {}, ignore, packet); + return packet; +} + +/** Decrypt one complete packet: nullopt if its decrypted length doesn't fit the packet or its tag fails. */ +std::optional> DecryptPacket(BIP324Cipher& receiver, std::span packet, uint32_t& length) +{ + length = receiver.DecryptLength(packet.first(BIP324Cipher::LENGTH_LEN)); + if (packet.size() != length + BIP324Cipher::EXPANSION) return std::nullopt; + Bytes contents(length); + bool ignore{false}; + if (!receiver.Decrypt(packet.subspan(BIP324Cipher::LENGTH_LEN), {}, ignore, contents)) return std::nullopt; + return std::make_pair(ignore, std::move(contents)); +} + +bool Receives(BIP324Cipher& receiver, std::span packet, std::span contents, bool ignore) +{ + uint32_t length; + const auto result = DecryptPacket(receiver, packet, length); + return result && result->first == ignore && std::ranges::equal(result->second, contents); +} + +/** Add both version contents (responder's first) and switch to the hybrid keys. */ +bool SwitchWith(BIP324Cipher& cipher, std::span contents_first, std::span contents_second, + std::span ss_mlkem) +{ + return cipher.AddVersionContents(contents_first) && cipher.AddVersionContents(contents_second) && cipher.SwitchToHybrid(ss_mlkem); +} + +void InitializePair(BIP324Cipher& initiator, BIP324Cipher& responder, bool retain_for_hybrid) +{ + initiator.Initialize(responder.GetOurPubKey(), /*initiator=*/true, /*self_decrypt=*/false, retain_for_hybrid); + responder.Initialize(initiator.GetOurPubKey(), /*initiator=*/false, /*self_decrypt=*/false, retain_for_hybrid); +} + +/** Check one packet each way between an initiator and a responder. */ +void CheckPair(BIP324Cipher& initiator, BIP324Cipher& responder, std::span contents) +{ + BOOST_CHECK(Receives(responder, EncryptPacket(initiator, contents, false), contents, false)); + BOOST_CHECK(Receives(initiator, EncryptPacket(responder, contents, false), contents, false)); +} + struct BIP324Test : BasicTestingSetup { void TestBIP324PacketVector( uint32_t in_idx, @@ -300,4 +384,376 @@ BOOST_AUTO_TEST_CASE(packet_test_vectors) { "27cf3c977dec7a089ae3660b0ec0bf5e15d9704df4e3d9e54831552dddbb07afabc03b42c9f912a5225c855850a30fc74db1d04ee00b19085fbfe6786084f559cf8531bffbd706db7a6bec021b2c946d1dd5ecbc84299205434bbde2910fb3543f75a08153e1471e02649be9c4f2e1767b7415243fa8eeedc55cb77a36651966"); } +BOOST_AUTO_TEST_CASE(hybrid_vectors) +{ + // The vectors come from an independent Python reference (contrib/devtools/generate-pq-transport-vectors.py). + UniValue doc; + BOOST_REQUIRE(doc.read(json_tests::pq_transport_vectors)); + BOOST_CHECK_EQUAL(doc["header"].get_str(), "f0"); + BOOST_CHECK_EQUAL(doc["salt_label"].get_str(), "qbit_v2_hybrid_mlkem1024"); + const std::vector labels{"initiator_L", "initiator_P", "responder_L", "responder_P", "session_id"}; + BOOST_REQUIRE_EQUAL(doc["labels"].size(), labels.size()); + for (size_t i = 0; i < labels.size(); ++i) BOOST_CHECK_EQUAL(doc["labels"][i].get_str(), labels[i]); + + const UniValue& vectors = doc["vectors"]; + BOOST_REQUIRE(vectors.size() >= 4); + for (size_t index = 0; index < vectors.size(); ++index) { + const UniValue& vec = vectors[index]; + BOOST_TEST_MESSAGE("hybrid vector " << index << ": " << vec["comment"].get_str()); + const auto hex = [&](const std::string& key) { return ParseHex(vec[key].get_str()); }; + const std::string chain = vec["chain"].get_str(); + BOOST_REQUIRE(chain == "main" || chain == "regtest"); + SelectParams(chain == "main" ? ChainType::MAIN : ChainType::REGTEST); + BOOST_CHECK(std::ranges::equal(hex("magic"), MakeByteSpan(Params().MessageStart()))); + + CKey key_i, key_r; + const auto priv_i = ParseHex(vec["initiator_privkey"].get_str()), priv_r = ParseHex(vec["responder_privkey"].get_str()); + key_i.Set(priv_i.begin(), priv_i.end(), true); + key_r.Set(priv_r.begin(), priv_r.end(), true); + BOOST_REQUIRE(key_i.IsValid() && key_r.IsValid()); + const Bytes ellswift_i = hex("initiator_ellswift"), ellswift_r = hex("responder_ellswift"); + const EllSwiftPubKey pub_i{ellswift_i}, pub_r{ellswift_r}; + const Bytes ss_ecdh = hex("ss_ecdh"), contents_r = hex("contents_responder"), contents_i = hex("contents_initiator"); + const Bytes ss_mlkem_bytes = hex("ss_mlkem"); + BOOST_REQUIRE_EQUAL(ss_mlkem_bytes.size(), 32U); + const std::span ss_mlkem{ss_mlkem_bytes}; + + // The inputs and every intermediate agree with qbit's own primitives. + BOOST_CHECK(std::ranges::equal(key_i.ComputeBIP324ECDHSecret(pub_r, pub_i, /*initiating=*/true), ss_ecdh)); + BOOST_CHECK(std::ranges::equal(key_r.ComputeBIP324ECDHSecret(pub_i, pub_r, /*initiating=*/false), ss_ecdh)); + BOOST_CHECK(!std::ranges::search(contents_r, Record(0xF0, hex("ek"))).empty()); + BOOST_CHECK(!std::ranges::search(contents_i, Record(0xF0, hex("ct"))).empty()); + const Bytes transcript_input = Concat({ellswift_i, ellswift_r, CompactSize(contents_r.size()), contents_r, + CompactSize(contents_i.size()), contents_i}); + BOOST_CHECK(transcript_input == hex("transcript_input")); + Bytes transcript_hash(CSHA256::OUTPUT_SIZE); + CSHA256().Write(UCharCast(transcript_input.data()), transcript_input.size()).Finalize(UCharCast(transcript_hash.data())); + BOOST_CHECK(transcript_hash == hex("transcript_hash")); + const std::string salt_label{"qbit_v2_hybrid_mlkem1024"}; + const Bytes salt = Concat({std::as_bytes(std::span{salt_label}), MakeByteSpan(Params().MessageStart())}); + BOOST_CHECK(salt == hex("salt")); + const Bytes ikm = Concat({ss_ecdh, ss_mlkem, transcript_hash}); + BOOST_CHECK(ikm == hex("ikm")); + Bytes prk(CHMAC_SHA256::OUTPUT_SIZE); + CHMAC_SHA256(UCharCast(salt.data()), salt.size()).Write(UCharCast(ikm.data()), ikm.size()).Finalize(UCharCast(prk.data())); + BOOST_CHECK(prk == hex("prk")); + for (const auto& label : labels) { + Bytes okm(CHMAC_SHA256::OUTPUT_SIZE); + const uint8_t one{1}; + CHMAC_SHA256(UCharCast(prk.data()), prk.size()).Write(UCharCast(label.data()), label.size()).Write(&one, 1).Finalize(UCharCast(okm.data())); + BOOST_CHECK_MESSAGE(okm == hex(label), label); + } + + // The derived length and packet keys produce each side's key confirmation. + for (const std::string role : {"initiator", "responder"}) { + FSChaCha20 l_cipher{hex(role + "_L"), BIP324Cipher::REKEY_INTERVAL}; + FSChaCha20Poly1305 p_cipher{hex(role + "_P"), BIP324Cipher::REKEY_INTERVAL}; + Bytes confirmation(BIP324Cipher::EXPANSION); + const std::array zero_length{}; + const std::array header{BIP324Cipher::IGNORE_BIT}; + l_cipher.Crypt(zero_length, std::span{confirmation}.first(BIP324Cipher::LENGTH_LEN)); + p_cipher.Encrypt(header, {}, {}, std::span{confirmation}.subspan(BIP324Cipher::LENGTH_LEN)); + BOOST_CHECK(confirmation == hex(role + "_confirmation")); + } + + // BIP324Cipher in both roles, and in self-decrypt mode, which decrypts its own role's packets. + for (const bool self_decrypt : {false, true}) { + for (const bool initiator : {true, false}) { + const std::string ours{initiator ? "initiator" : "responder"}, theirs{initiator ? "responder" : "initiator"}; + BIP324Cipher cipher(initiator ? key_i : key_r, initiator ? pub_i : pub_r); + cipher.Initialize(initiator ? pub_r : pub_i, initiator, self_decrypt, /*retain_for_hybrid=*/true); + BOOST_CHECK(std::ranges::equal(cipher.GetSessionID(), hex("ecdh_session_id"))); + const Bytes send_terminator = ToBytes(cipher.GetSendGarbageTerminator()); + const Bytes recv_terminator = ToBytes(cipher.GetReceiveGarbageTerminator()); + BOOST_CHECK(send_terminator == hex(ours + "_garbage_terminator")); + BOOST_CHECK(recv_terminator == hex(theirs + "_garbage_terminator")); + + BOOST_REQUIRE(SwitchWith(cipher, contents_r, contents_i, ss_mlkem)); + BOOST_CHECK(std::ranges::equal(cipher.GetSessionID(), hex("session_id"))); + BOOST_CHECK(std::ranges::equal(cipher.GetSendGarbageTerminator(), send_terminator)); + BOOST_CHECK(std::ranges::equal(cipher.GetReceiveGarbageTerminator(), recv_terminator)); + + const std::string sender{self_decrypt ? ours : theirs}; + BOOST_CHECK(Receives(cipher, hex(sender + "_confirmation"), {}, /*ignore=*/true)); + BOOST_CHECK(Receives(cipher, hex(sender + "_first_packet"), hex(sender + "_first_contents"), /*ignore=*/false)); + if (!self_decrypt) { + BOOST_CHECK(EncryptPacket(cipher, {}, /*ignore=*/true) == hex(ours + "_confirmation")); + BOOST_CHECK(EncryptPacket(cipher, hex(ours + "_first_contents"), /*ignore=*/false) == hex(ours + "_first_packet")); + } + } + } + } + + const auto hex = [](const UniValue& obj, const std::string& key) { return ParseHex(obj[key].get_str()); }; + // A cipher in one role of a vector, initialized with the hybrid state retained. + const auto make_cipher = [&](const UniValue& vec, bool as_initiator) { + SelectParams(vec["chain"].get_str() == "main" ? ChainType::MAIN : ChainType::REGTEST); + CKey key; + const auto priv = ParseHex(vec[as_initiator ? "initiator_privkey" : "responder_privkey"].get_str()); + key.Set(priv.begin(), priv.end(), true); + const EllSwiftPubKey pub_i{hex(vec, "initiator_ellswift")}, pub_r{hex(vec, "responder_ellswift")}; + auto cipher = std::make_unique(key, as_initiator ? pub_i : pub_r); + cipher->Initialize(as_initiator ? pub_r : pub_i, as_initiator, /*self_decrypt=*/false, /*retain_for_hybrid=*/true); + return cipher; + }; + // Why a receiver's key confirmation check rejects a first hybrid packet ("none" if it passes). + const auto confirmation_failure = [](BIP324Cipher& receiver, std::span packet, uint32_t& length) -> std::string { + const auto result = DecryptPacket(receiver, packet, length); + if (length != 0) return "length"; + if (!result) return "tag"; + return result->first ? "none" : "not_decoy"; + }; + + // First hybrid packets that fail the receiver's check under the honest keys, one per reason. + const UniValue& failures = doc["confirmation_failures"]; + BOOST_REQUIRE_EQUAL(failures.size(), 3U); + std::set reasons; + for (size_t index = 0; index < failures.size(); ++index) { + const UniValue& fail = failures[index]; + BOOST_TEST_MESSAGE("confirmation failure " << index << ": " << fail["comment"].get_str()); + const UniValue& vec = vectors[fail["vector"].getInt()]; + const bool initiator{fail["receiver"].get_str() == "initiator"}; + BOOST_REQUIRE(initiator || fail["receiver"].get_str() == "responder"); + const Bytes ss_mlkem = hex(vec, "ss_mlkem"), packet = hex(fail, "packet"); + BOOST_REQUIRE_EQUAL(ss_mlkem.size(), 32U); + BOOST_REQUIRE_EQUAL(packet.size(), BIP324Cipher::EXPANSION); + auto receiver = make_cipher(vec, initiator); + BOOST_REQUIRE(SwitchWith(*receiver, hex(vec, "contents_responder"), hex(vec, "contents_initiator"), + std::span{ss_mlkem})); + uint32_t length; + BOOST_CHECK_EQUAL(confirmation_failure(*receiver, packet, length), fail["failure"].get_str()); + BOOST_CHECK_EQUAL(length, fail["decrypted_length"].getInt()); + reasons.insert(fail["failure"].get_str()); + } + BOOST_CHECK((reasons == std::set{"length", "tag", "not_decoy"})); + + // One side derives from a different transcript or ML-KEM secret than its honest peer, so each + // side's key confirmation fails at the other: at once on a nonzero length, else on the tag. + const UniValue& negatives = doc["negative_vectors"]; + BOOST_REQUIRE(negatives.size() >= 3); + for (size_t index = 0; index < negatives.size(); ++index) { + const UniValue& neg = negatives[index]; + BOOST_TEST_MESSAGE("negative hybrid vector " << index << ": " << neg["comment"].get_str()); + const UniValue& vec = vectors[neg["vector"].getInt()]; + const bool initiator{neg["role"].get_str() == "initiator"}; + BOOST_REQUIRE(initiator || neg["role"].get_str() == "responder"); + + const Bytes ss_bad = hex(neg, "ss_mlkem"), ss_good = hex(vec, "ss_mlkem"); + BOOST_REQUIRE(ss_bad.size() == 32 && ss_good.size() == 32); + auto mismatched = make_cipher(vec, initiator); + const UniValue& transcript_contents = neg["transcript_contents"]; + BOOST_REQUIRE_EQUAL(transcript_contents.size(), 2U); + BOOST_REQUIRE(SwitchWith(*mismatched, ParseHex(transcript_contents[0].get_str()), + ParseHex(transcript_contents[1].get_str()), std::span{ss_bad})); + BOOST_CHECK(std::ranges::equal(mismatched->GetSessionID(), hex(neg, "session_id"))); + BOOST_CHECK(!std::ranges::equal(mismatched->GetSessionID(), hex(vec, "session_id"))); + BOOST_CHECK(EncryptPacket(*mismatched, {}, /*ignore=*/true) == hex(neg, "confirmation")); + uint32_t length; + BOOST_CHECK_EQUAL(confirmation_failure(*mismatched, hex(vec, initiator ? "responder_confirmation" : "initiator_confirmation"), length), + neg["failure"].get_str()); + BOOST_CHECK_EQUAL(length, neg["decrypted_length"].getInt()); + + auto honest = make_cipher(vec, !initiator); + BOOST_REQUIRE(SwitchWith(*honest, hex(vec, "contents_responder"), hex(vec, "contents_initiator"), + std::span{ss_good})); + BOOST_CHECK_EQUAL(confirmation_failure(*honest, hex(neg, "confirmation"), length), neg["peer_failure"].get_str()); + BOOST_CHECK_EQUAL(length, neg["peer_decrypted_length"].getInt()); + } +} + +BOOST_AUTO_TEST_CASE(hybrid_transcript_binding) +{ + const CKey key_i = GenerateRandomKey(), key_r = GenerateRandomKey(); + const Bytes ent_i = m_rng.randbytes(32), ent_r = m_rng.randbytes(32); + const Bytes other_ent_i = m_rng.randbytes(32), other_ent_r = m_rng.randbytes(32); + const Bytes offer = Record(0xF0, m_rng.randbytes(1568)), accept = Record(0xF0, m_rng.randbytes(1568)); + BOOST_REQUIRE_EQUAL(offer.size(), 1572U); + const Bytes unknown = Record(0xF1, std::vector{std::byte{0x00}, std::byte{0x01}}); + const Bytes other_unknown = Record(0xF1, std::vector{std::byte{0x00}, std::byte{0x02}}); + std::array ss; + m_rng.fillrand(ss); + std::array other_ss{ss}; + other_ss[31] ^= std::byte{0x80}; + const auto flip = [](Bytes bytes, size_t pos) { bytes.at(pos) ^= std::byte{0x01}; return bytes; }; + + struct Case { + std::string name; + bool ecdh_changes; + ChainType chain; + Bytes ent_i, ent_r, contents_r, contents_i; + std::array ss; + }; + const std::vector cases{ + {"base", false, ChainType::MAIN, ent_i, ent_r, offer, accept, ss}, + {"initiator ellswift", true, ChainType::MAIN, other_ent_i, ent_r, offer, accept, ss}, + {"responder ellswift", true, ChainType::MAIN, ent_i, other_ent_r, offer, accept, ss}, + {"ek", false, ChainType::MAIN, ent_i, ent_r, flip(offer, 1000), accept, ss}, + {"ct", false, ChainType::MAIN, ent_i, ent_r, offer, flip(accept, 1000), ss}, + {"unknown record", false, ChainType::MAIN, ent_i, ent_r, Concat({offer, unknown}), accept, ss}, + {"unknown record payload", false, ChainType::MAIN, ent_i, ent_r, Concat({offer, other_unknown}), accept, ss}, + {"duplicate record", false, ChainType::MAIN, ent_i, ent_r, Concat({offer, offer}), accept, ss}, + {"record order", false, ChainType::MAIN, ent_i, ent_r, Concat({unknown, offer}), accept, ss}, + {"initiator records", false, ChainType::MAIN, ent_i, ent_r, offer, Concat({accept, unknown}), ss}, + {"length prefix", false, ChainType::MAIN, ent_i, ent_r, Concat({offer, std::span{accept}.first(1)}), Bytes(accept.begin() + 1, accept.end()), ss}, + {"swapped contents", false, ChainType::MAIN, ent_i, ent_r, accept, offer, ss}, + {"ml-kem secret", false, ChainType::MAIN, ent_i, ent_r, offer, accept, other_ss}, + {"magic", true, ChainType::REGTEST, ent_i, ent_r, offer, accept, ss}, + }; + + std::vector> derived; + Bytes base_ecdh_session_id; + for (const auto& c : cases) { + BOOST_TEST_MESSAGE("binding case: " << c.name); + SelectParams(c.chain); + BIP324Cipher initiator(key_i, c.ent_i), responder(key_r, c.ent_r); + InitializePair(initiator, responder, /*retain_for_hybrid=*/true); + const Bytes ecdh_session_id = ToBytes(initiator.GetSessionID()); + if (base_ecdh_session_id.empty()) base_ecdh_session_id = ecdh_session_id; + // Only the hybrid layer sees the version contents and the ML-KEM secret. + BOOST_CHECK((ecdh_session_id != base_ecdh_session_id) == c.ecdh_changes); + + // Two honest sides still agree. + BOOST_REQUIRE(SwitchWith(initiator, c.contents_r, c.contents_i, c.ss)); + BOOST_REQUIRE(SwitchWith(responder, c.contents_r, c.contents_i, c.ss)); + BOOST_CHECK(std::ranges::equal(initiator.GetSessionID(), responder.GetSessionID())); + const Bytes confirmation_i = EncryptPacket(initiator, {}, /*ignore=*/true); + const Bytes confirmation_r = EncryptPacket(responder, {}, /*ignore=*/true); + BOOST_CHECK(Receives(responder, confirmation_i, {}, /*ignore=*/true)); + BOOST_CHECK(Receives(initiator, confirmation_r, {}, /*ignore=*/true)); + CheckPair(initiator, responder, unknown); + derived.push_back({ToBytes(initiator.GetSessionID()), confirmation_i, confirmation_r}); + } + + // Every change gives a different session id and different keys in both directions. + for (size_t a = 0; a < cases.size(); ++a) { + for (size_t b = a + 1; b < cases.size(); ++b) { + for (size_t value = 0; value < 3; ++value) { + BOOST_CHECK_MESSAGE(derived[a][value] != derived[b][value], cases[a].name << " vs " << cases[b].name << " value " << value); + } + } + } +} + +BOOST_AUTO_TEST_CASE(hybrid_api_order) +{ + SelectParams(ChainType::MAIN); + const CKey key_i = GenerateRandomKey(), key_r = GenerateRandomKey(); + const Bytes ent_i = m_rng.randbytes(32), ent_r = m_rng.randbytes(32); + const Bytes contents_r = Record(0xF0, m_rng.randbytes(1568)), contents_i = Record(0xF0, m_rng.randbytes(1568)); + std::array ss; + m_rng.fillrand(ss); + const Bytes message = m_rng.randbytes(40); + + // Before Initialize, nothing is accepted. + { + BIP324Cipher cipher(key_i, ent_i); + BOOST_CHECK(!cipher.SwitchToHybrid(ss)); + BOOST_CHECK(!cipher.AddVersionContents(contents_r)); + BOOST_CHECK(!cipher.SwitchToHybrid(ss)); + cipher.DiscardHybridSecret(); + BOOST_CHECK(!cipher); + } + + // Without retain_for_hybrid (the default), today's secret lifetime: nothing to switch with. + { + BIP324Cipher initiator(key_i, ent_i), responder(key_r, ent_r); + InitializePair(initiator, responder, /*retain_for_hybrid=*/false); + const Bytes session_id = ToBytes(initiator.GetSessionID()); + BOOST_CHECK(!initiator.AddVersionContents(contents_r)); + BOOST_CHECK(!initiator.SwitchToHybrid(ss)); + BOOST_CHECK(std::ranges::equal(initiator.GetSessionID(), session_id)); + CheckPair(initiator, responder, message); + } + + // With retention: switching early or twice, and a third contents call, are rejected and change nothing. + { + BIP324Cipher initiator(key_i, ent_i), responder(key_r, ent_r); + InitializePair(initiator, responder, /*retain_for_hybrid=*/true); + const Bytes ecdh_session_id = ToBytes(initiator.GetSessionID()); + BOOST_CHECK(!initiator.SwitchToHybrid(ss)); + BOOST_CHECK(initiator.AddVersionContents(contents_r)); + BOOST_CHECK(!initiator.SwitchToHybrid(ss)); + BOOST_CHECK(std::ranges::equal(initiator.GetSessionID(), ecdh_session_id)); + // The ECDH keys keep working while the switch is pending. + CheckPair(initiator, responder, message); + BOOST_CHECK(initiator.AddVersionContents(contents_i)); + BOOST_CHECK(!initiator.AddVersionContents(contents_i)); + BOOST_CHECK(responder.AddVersionContents(contents_r)); + BOOST_CHECK(responder.AddVersionContents(contents_i)); + BOOST_CHECK(!responder.AddVersionContents({})); + + BOOST_CHECK(initiator.SwitchToHybrid(ss)); + BOOST_CHECK(responder.SwitchToHybrid(ss)); + const Bytes hybrid_session_id = ToBytes(initiator.GetSessionID()); + BOOST_CHECK(hybrid_session_id != ecdh_session_id); + BOOST_CHECK(std::ranges::equal(responder.GetSessionID(), hybrid_session_id)); + BOOST_CHECK(!initiator.SwitchToHybrid(ss)); + BOOST_CHECK(!initiator.AddVersionContents(contents_i)); + BOOST_CHECK(std::ranges::equal(initiator.GetSessionID(), hybrid_session_id)); + CheckPair(initiator, responder, message); + initiator.DiscardHybridSecret(); + CheckPair(initiator, responder, message); + } + + // DiscardHybridSecret (a legacy outcome): the ECDH keys stay in use and nothing can switch later. + { + BIP324Cipher initiator(key_i, ent_i), responder(key_r, ent_r); + InitializePair(initiator, responder, /*retain_for_hybrid=*/true); + const Bytes ecdh_session_id = ToBytes(initiator.GetSessionID()); + BOOST_CHECK(initiator.AddVersionContents(contents_r)); + BOOST_CHECK(initiator.AddVersionContents(contents_i)); + initiator.DiscardHybridSecret(); + initiator.DiscardHybridSecret(); + responder.DiscardHybridSecret(); + BOOST_CHECK(!initiator.SwitchToHybrid(ss)); + BOOST_CHECK(!initiator.AddVersionContents(contents_i)); + BOOST_CHECK(!responder.AddVersionContents(contents_r)); + BOOST_CHECK(!responder.SwitchToHybrid(ss)); + BOOST_CHECK(std::ranges::equal(initiator.GetSessionID(), ecdh_session_id)); + CheckPair(initiator, responder, message); + } +} + +BOOST_AUTO_TEST_CASE(hybrid_rekey_boundaries) +{ + SelectParams(ChainType::MAIN); + const CKey key_i = GenerateRandomKey(), key_r = GenerateRandomKey(); + const Bytes ent_i = m_rng.randbytes(32), ent_r = m_rng.randbytes(32); + const Bytes contents_r = Record(0xF0, m_rng.randbytes(1568)), contents_i = Record(0xF0, m_rng.randbytes(1568)); + std::array ss; + m_rng.fillrand(ss); + // Hybrid packets 0..449 in each direction cross the rekeys after packets 223 and 447. + constexpr unsigned HYBRID_PACKETS{2 * BIP324Cipher::REKEY_INTERVAL + 2}; + + std::optional> fresh_stream; + for (const unsigned ecdh_packets : {0U, 1U, 223U, 224U, 447U}) { + BOOST_TEST_MESSAGE("switch after " << ecdh_packets << " ECDH packets each way"); + BIP324Cipher initiator(key_i, ent_i), responder(key_r, ent_r); + InitializePair(initiator, responder, /*retain_for_hybrid=*/true); + for (unsigned i = 0; i < ecdh_packets; ++i) { + BOOST_REQUIRE(Receives(responder, EncryptPacket(initiator, {}, /*ignore=*/true), {}, /*ignore=*/true)); + BOOST_REQUIRE(Receives(initiator, EncryptPacket(responder, {}, /*ignore=*/true), {}, /*ignore=*/true)); + } + BOOST_REQUIRE(SwitchWith(initiator, contents_r, contents_i, ss)); + BOOST_REQUIRE(SwitchWith(responder, contents_r, contents_i, ss)); + + std::vector stream; + for (unsigned i = 0; i < HYBRID_PACKETS; ++i) { + // Packet 0 is the key confirmation, an empty decoy. + const bool ignore{i == 0}; + const Bytes contents(ignore ? 0 : 1 + i % 7, std::byte(i & 0xff)); + stream.push_back(EncryptPacket(initiator, contents, ignore)); + BOOST_REQUIRE_MESSAGE(Receives(responder, stream.back(), contents, ignore), "initiator packet " << i); + stream.push_back(EncryptPacket(responder, contents, ignore)); + BOOST_REQUIRE_MESSAGE(Receives(initiator, stream.back(), contents, ignore), "responder packet " << i); + } + // All four hybrid ciphers start at 0, whatever the ECDH ciphers carried before the switch. + if (!fresh_stream) { + fresh_stream = std::move(stream); + } else { + BOOST_CHECK(stream == *fresh_stream); + } + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/data/pq_transport_vectors.json b/src/test/data/pq_transport_vectors.json new file mode 100644 index 0000000000..04943c09e5 --- /dev/null +++ b/src/test/data/pq_transport_vectors.json @@ -0,0 +1,239 @@ +{ + "comment": [ + "Hybrid post-quantum BIP324 v2 transport: the ML-KEM-1024 key recipe under version-contents header 0xf0, specified in doc/design/pq-transport.md.", + "Generated by contrib/devtools/generate-pq-transport-vectors.py from the Python reference in test/functional/test_framework/crypto. Do not edit by hand.", + "Byte strings are lowercase hex. Packets are BIP324 packets: encrypted length (3) || encrypted header and contents || tag (16). Each side's key confirmation is its hybrid packet 0, and its first application packet is hybrid packet 1.", + "confirmation_failures are first hybrid packets that the named receiver must reject under the honest keys of the vector; negative_vectors are sides that derived from different inputs than their peer." + ], + "header": "f0", + "salt_label": "qbit_v2_hybrid_mlkem1024", + "labels": [ + "initiator_L", + "initiator_P", + "responder_L", + "responder_P", + "session_id" + ], + "vectors": [ + { + "comment": "Mainnet, one offer record and one accept record (the ordinary exchange).", + "chain": "main", + "magic": "444f24a8", + "initiator_privkey": "b90007e04658c9f83710e61170984a6261ee2091d3f237a1f065666e741e96eb", + "initiator_ellswift": "6b5fb320cab84c788ef6393262172b222facedee5e55f4e2fa44bae00b1f36102dd8739200e025a0443a6c6d3d52f003e4b94d296ed3a68e9dacc567f0c7bd09", + "responder_privkey": "5047c9486dd8881232243d049d47d5afacf4438dffde0579cdbe28f807231f18", + "responder_ellswift": "3d3449542df2dc02aedca2a14e5c6b22f2b797b3191488cdbdc1ff6ad3ef40a77e62023395a490bbacc9de3da7fd37f70592ec361e34c01c761ad4ea2029712c", + "ss_ecdh": "7e8dd0eda7d613290fcfc7919af8675a2405e6585378777b9d2f02e592f1fb1d", + "ecdh_session_id": "ab8e9ae6fe9d33e2e10f847855298fbe295ed45625608b425ca118d09dbfd0a4", + "initiator_garbage_terminator": "4f698c0b17c882f46d09697e67cbcd75", + "responder_garbage_terminator": "2c85474143c749d2ee0489bb61920613", + "mlkem_keygen_seed": "1b5f52efd3c68106de6e780c8e7e0d57e78445b65a778779d2cd78cec5ed6289f973c11bd432e4642d99bb2d272e4da7032f4645430ac8d5ac98b9d782835ccd", + "mlkem_encaps_m": "3ddd894ca239fdae4e54bf00f7608fb9a1348fce50f7ba09f163b00e82f1474c", + "ek": "4ef53cd8d75470aa5492cc10d1b519a02ac168e5394226756c89ace3a2174cb08fe962b490f3c2cf88a60391513083a330718b73257c554c7cf0ca1865fbbf682435605c5a7f0636b19ab1c7e2c9cb40cf62c47940b17855f7c2e4d1869409ad7b3b047052bfd7366195d84890f03ac23032a63c545ed99f07e55bc81771836917b5139954d413162b9b5745556865710a5401bd9b6e3382adef4108b80833ed2337aaf2392a9704e239cb4a35332bf8257c288200389d054ba9acf772c61ab9f74875353b1bb933bff0e12f72b8c1537c4b47a74750d5617eb4c99d72af4e401961f588ff9a617b775ef7280a8a2843f1b408e6cbcf7385493a3b9e86230ba3c5676c5cb8885c317855a103e915aeac2cb27c36392494bb2aac04f481d38ba33cf7376711cce831380c580bdd887b3183a8fb2642857b3283c51a82c788e5293ddbd01bc46609f6017f40294f4b43842a709db0a7203565ceebda5721c1201569217ad817cd6316fc8c7f3036a4d5a679095bb4376c9e75360a64051f17fb8445f4556d37180c83cbee261629620ecec139bb8c609da90f9da2a8e1297793546ef90c0bffb3b0c836215bb9108191af7803cd5f26163c577404002aaed44de693892cb04f87e060dc5b9db9d00a4ea6b2734505c04857c31ccaa68bc833733d1e2837e0b5aec46354867933824399f132805893330c73c11c988d1adb6aac9b9bbcbc41263158e2b960c30665571484900ac7621a3e7fd1252ffb271f763ddc98293a5b929790468b9b4ed848c9ba531adaa31fec60367bc48eae612777cb79d7b56a17f8a6afc1c1febb86ec286ef9260cb27079c0d5a74910b3ef26b09ec96de697735e91841b9224d33abf2e1b14b15460d9055093268c75c17aa1ba15b5463e68d32c99a10d40c603b45c1fb81b5965c17379b898cba3cd1471bb07c8586f232d5c29ade9831f06163a3d60c672535e8fb0a01fd4435853480b073ec0ac32bc8a876cc3cd43bbb7bf8b3dc77874be68bff1d0cdf6a2900d8bca43f29b87c4395c7a794c39a45e89c3d9e3bf7046b5bc204a75a30a707242525c81cf4ac93c2a8b266c59eaaa0d997348e9fc7bd0294231c559a5cc1465a22317e714cc1431c092bed0665f242098c556b1c091a18519c1fa91354d6403f71972e78c23a28a276798826b3b711e76aa17f27c24214d6c1c6e8a53736cd986d9e446068532f1a6214cc98eb1625cd064456b0481abe2a38aeaa26845b77b1926b791a4a3b56739997edeb097284a9a6217438fd92b65399d0ecb2f23ccb1c8397496413615c26304d5272d60bc34f3ba6b5c652f693bc702be90349f27fa019173508a94bdf5f1153b341d20d040eb506ea0d40b561c0178c662a7c5b6bd130535355a8a5438ae1c0e2ab872f92800ba319b34d587e52860eb183b0bdb24ec024a111b369d3871c1c7ce9ec97207a208b4ec32ea142c61d23bdbb40325ca0756b36c5de19bed851d0ce6a30c175e467957d72b65f5552007dab6cd474d095ab556353111b9664a012d7fd767b3d549138580a087103d72bb602520f9393317e72e3fbc5d7206a24ccaae03fa844619a4acaa8c246053ed122c9765bc9164aa1c7257ac5893e0619bb965c48f24b5a5747d02f38705a09c905b82918856487382f40c28b73916b8059c29b39853189997978e49b05df1495c66a777e5f84ef99575f51b33fb157d41e54a7611c082387c6da242c3042fce7711525104f46233cad9882e45796a09c59b8471287b19c4f6205ea0b3543c341f8c169c11010cd52abda1b934024b9181c047ecad01844709d7834aa056648097f2109e4fac640d32ad80cc5581ac60296a5b51d615241c8cc0bcccfe777b97e6a5475011603a8ad7eaafe741572018840b989d83f986163633bc68919d571211d317c0a3c0347529b49101c2d451844b5083404da25b4d37931743139953fcc8374369260b88193284bc96250d02c05ecc6809a44a38e24261083dc3953d4e8a093bda800b4b2614d76a4355c6a900251e3cb9de069ed2ebaa34b34aae3661362218b5350b4f68c9bc6b11d0f5753e792cf300acdb94c52eab50d406967f498a8327420d6397886380a94019b307b8188a4496b223ebe27075b87c5d8680855ac7c5b892d357247aecb55e6089a2dba345f65d259b8d40a253a450a546b46abc3d1804f4c6ecc2a992fdff4e7d", + "ct": "79578b92edfe5618d5dd9284a2004e45ebe3a37cc4c0eef53eeaf8d322303b91edfb0aa7c453d4c0d06bc29c2e904f20db8e6e0567d6a1303ad190d7c087a76da634a6e0ac11f4f78496ca11862c87acb0fc273061479a89758886d0b0d78375b1edf317ddfa889507df4e74853fe34892580a127e104f0bc4df583d8415b54367efafea0995c7e7ed7a1dedb8112a22e8278f90361dbf0a1f7af1246b609c9981f95a919312540d8bbd207eb75cf9feb4f9d2689b071f1f5b0d790573ca40db398374d3e50e3c2d46bb966567cd9c9b1589ee62206ead95a80483d4fa567ff697d40d804acdde8ec654fd5ba413133fabb99db19d949ecde78d55cb563766e714cf2138f50457809757b618b004ad48c1cb0b0f1c95e0fbe1f43813d5f035c4b3ad1b6a5c1c5fa0a1d27a0cb6d3de7b3a054e6fcbc24e286e6a77e45ab56bc5ecb070a00c22974d05832832bd40ab96010039be5da0464731993e2bccfcc468533cbfe3089a6f579d5a06cbe4bf08ddb85fe9996ed95fe9442130e49c61d1bf0881968672eae12c568189f3285671358e68ae0fbb5075ed137127788aa1f96033c89f41ade84cc61aaeae9b2c86862b1e956c74d39bd6e8f00907fd586cd099ab4224883c2e0c07ac4bb3f74a5a59975ef0e844583370cf077671fbfc2a333182dce2ac9cc8f3ba0d73cd27ae60aabc1d7689f7bd2d7ecd5d97164293cc88fe7adbadb3a24798f7b2be578c7780a681cc902b69b7fe4aa8f535e4a9ed83d002c1ea0f28e711be6231cabde2ea19655688c8d70b413420651852db3bcad5c6b66267d516088b3955a6884ca44a83c9f179c5901ff4edefea074ee0cd425a7dd8a855ea49affbd7a8604c490213dfa7bf48ac12b738fae7cd1697e757f26a59826e64b7799a47703e037c9ca7cf18828574092a9b0af255a5180eef96f96e5d2f51cc621d7a03c7bb05eb80d386415f243f5d59451d5f95c93f4616cca643cd6be65109d440461e752d2999745facd5eacaea338d6ce940b54c8dba44f059c94fa65a74456b747c065f2aba2b8580931e1e5db4cd6505d4cc50ce68e15a1f9e0069edd1a8d5bbfa9180f0a88fef1d773a5b417b67ddd8729c3a781bfcdeb527126579f5d11c5550525869a3236e1f21940542c7ce69e3a265c24d9aefeab6c0852636708a74718729f35a401fd73b46b204ec92b40d9ec0e5f924c967a8a4e219910d40af2d19e76869bfb7c95868e4572334bc35f7071d6254cb3cdf82ee94d3b685fe48a49ad95503ca19b39e316e57a32a1eaa9af4573a32bfe94dd77c744484781c2efe891e2b4b65b38e12dbab69b436d4b0ac6a9b512ee898b95906163f32839fa2985fadead35d79fa03a284debc60c51daa8a017c1d4dc4dc7e884b1374d131fde2ca6e591672b259754b4f76bbf6ed3eeeee94e4e7c496344a3b38c8b4dea12684bbc0713ec6b7684479e883a0a515d681581beab290d952fa20f6f9ae63dc00a55d1ef3f592d9a86d97a40ae74717623dc1ffdf2588f241d6600e6f34163db805273266f58e75e074d7d079d40755012da5ba3203d2bfdc9100e361148456a7ac423ba9cf3bd135f3e64c2f262fcdb728e0682e8297cc2af4e0467603990459d923cf6974cc27f9c02f535d6962cd67bbfd1287ba4148647153fd33ef9e482e2555ff6ee243ce6ab277af1494944b2913ecf29819507571427d7c1852d684a6359a72698b65ef040b8a03c3fffca6f4a9e13359db9a1e3d2e4fac6d469357d9d9ca6663f6a3f80076c6ec937aa16d23b12fb0ee483c260ad6e751a2814720e36dfd26202b14c1cd18e3f9477e924292d84d905c056bd73ceecede186f3b6987abe328fb3e1e48b2bed3885b9d9e9af6bdebc0c7f9cc6f7954b38905e3d8ee41f780ca446792326a94837c367104983ea9b906a7f7a8b4bbf44700694c27e7670e4a7b3e5df7218d8d67fd68bda52592e650de25b1a81ed304d81cadbad366833d94d7e69a8433c9d7edfb1043b99a08265de814121119d3f7eaa74c8082bd9b4a91bb2478605a2f2e077679665d24391ea37c748c5c6e35f00fd202a13c032270b0d30f0af8bb1d7c2570055980748fd7761d6a801043ecc04051f7d60a5b7361436549762a9f3ba7eb7344658b154b8b680ba8ac2eeec07e4cc3cac211255c3f901e713db12984a00450011790a0f2d6af22d5d35b83c5483dd4e8", + "ss_mlkem": "22c8bf5c4858ac2e5f94b6e945ca142ccc28b98e7b954a78899630a7bfa3135f", + "contents_responder": "fd2106f04ef53cd8d75470aa5492cc10d1b519a02ac168e5394226756c89ace3a2174cb08fe962b490f3c2cf88a60391513083a330718b73257c554c7cf0ca1865fbbf682435605c5a7f0636b19ab1c7e2c9cb40cf62c47940b17855f7c2e4d1869409ad7b3b047052bfd7366195d84890f03ac23032a63c545ed99f07e55bc81771836917b5139954d413162b9b5745556865710a5401bd9b6e3382adef4108b80833ed2337aaf2392a9704e239cb4a35332bf8257c288200389d054ba9acf772c61ab9f74875353b1bb933bff0e12f72b8c1537c4b47a74750d5617eb4c99d72af4e401961f588ff9a617b775ef7280a8a2843f1b408e6cbcf7385493a3b9e86230ba3c5676c5cb8885c317855a103e915aeac2cb27c36392494bb2aac04f481d38ba33cf7376711cce831380c580bdd887b3183a8fb2642857b3283c51a82c788e5293ddbd01bc46609f6017f40294f4b43842a709db0a7203565ceebda5721c1201569217ad817cd6316fc8c7f3036a4d5a679095bb4376c9e75360a64051f17fb8445f4556d37180c83cbee261629620ecec139bb8c609da90f9da2a8e1297793546ef90c0bffb3b0c836215bb9108191af7803cd5f26163c577404002aaed44de693892cb04f87e060dc5b9db9d00a4ea6b2734505c04857c31ccaa68bc833733d1e2837e0b5aec46354867933824399f132805893330c73c11c988d1adb6aac9b9bbcbc41263158e2b960c30665571484900ac7621a3e7fd1252ffb271f763ddc98293a5b929790468b9b4ed848c9ba531adaa31fec60367bc48eae612777cb79d7b56a17f8a6afc1c1febb86ec286ef9260cb27079c0d5a74910b3ef26b09ec96de697735e91841b9224d33abf2e1b14b15460d9055093268c75c17aa1ba15b5463e68d32c99a10d40c603b45c1fb81b5965c17379b898cba3cd1471bb07c8586f232d5c29ade9831f06163a3d60c672535e8fb0a01fd4435853480b073ec0ac32bc8a876cc3cd43bbb7bf8b3dc77874be68bff1d0cdf6a2900d8bca43f29b87c4395c7a794c39a45e89c3d9e3bf7046b5bc204a75a30a707242525c81cf4ac93c2a8b266c59eaaa0d997348e9fc7bd0294231c559a5cc1465a22317e714cc1431c092bed0665f242098c556b1c091a18519c1fa91354d6403f71972e78c23a28a276798826b3b711e76aa17f27c24214d6c1c6e8a53736cd986d9e446068532f1a6214cc98eb1625cd064456b0481abe2a38aeaa26845b77b1926b791a4a3b56739997edeb097284a9a6217438fd92b65399d0ecb2f23ccb1c8397496413615c26304d5272d60bc34f3ba6b5c652f693bc702be90349f27fa019173508a94bdf5f1153b341d20d040eb506ea0d40b561c0178c662a7c5b6bd130535355a8a5438ae1c0e2ab872f92800ba319b34d587e52860eb183b0bdb24ec024a111b369d3871c1c7ce9ec97207a208b4ec32ea142c61d23bdbb40325ca0756b36c5de19bed851d0ce6a30c175e467957d72b65f5552007dab6cd474d095ab556353111b9664a012d7fd767b3d549138580a087103d72bb602520f9393317e72e3fbc5d7206a24ccaae03fa844619a4acaa8c246053ed122c9765bc9164aa1c7257ac5893e0619bb965c48f24b5a5747d02f38705a09c905b82918856487382f40c28b73916b8059c29b39853189997978e49b05df1495c66a777e5f84ef99575f51b33fb157d41e54a7611c082387c6da242c3042fce7711525104f46233cad9882e45796a09c59b8471287b19c4f6205ea0b3543c341f8c169c11010cd52abda1b934024b9181c047ecad01844709d7834aa056648097f2109e4fac640d32ad80cc5581ac60296a5b51d615241c8cc0bcccfe777b97e6a5475011603a8ad7eaafe741572018840b989d83f986163633bc68919d571211d317c0a3c0347529b49101c2d451844b5083404da25b4d37931743139953fcc8374369260b88193284bc96250d02c05ecc6809a44a38e24261083dc3953d4e8a093bda800b4b2614d76a4355c6a900251e3cb9de069ed2ebaa34b34aae3661362218b5350b4f68c9bc6b11d0f5753e792cf300acdb94c52eab50d406967f498a8327420d6397886380a94019b307b8188a4496b223ebe27075b87c5d8680855ac7c5b892d357247aecb55e6089a2dba345f65d259b8d40a253a450a546b46abc3d1804f4c6ecc2a992fdff4e7d", + "contents_initiator": "fd2106f079578b92edfe5618d5dd9284a2004e45ebe3a37cc4c0eef53eeaf8d322303b91edfb0aa7c453d4c0d06bc29c2e904f20db8e6e0567d6a1303ad190d7c087a76da634a6e0ac11f4f78496ca11862c87acb0fc273061479a89758886d0b0d78375b1edf317ddfa889507df4e74853fe34892580a127e104f0bc4df583d8415b54367efafea0995c7e7ed7a1dedb8112a22e8278f90361dbf0a1f7af1246b609c9981f95a919312540d8bbd207eb75cf9feb4f9d2689b071f1f5b0d790573ca40db398374d3e50e3c2d46bb966567cd9c9b1589ee62206ead95a80483d4fa567ff697d40d804acdde8ec654fd5ba413133fabb99db19d949ecde78d55cb563766e714cf2138f50457809757b618b004ad48c1cb0b0f1c95e0fbe1f43813d5f035c4b3ad1b6a5c1c5fa0a1d27a0cb6d3de7b3a054e6fcbc24e286e6a77e45ab56bc5ecb070a00c22974d05832832bd40ab96010039be5da0464731993e2bccfcc468533cbfe3089a6f579d5a06cbe4bf08ddb85fe9996ed95fe9442130e49c61d1bf0881968672eae12c568189f3285671358e68ae0fbb5075ed137127788aa1f96033c89f41ade84cc61aaeae9b2c86862b1e956c74d39bd6e8f00907fd586cd099ab4224883c2e0c07ac4bb3f74a5a59975ef0e844583370cf077671fbfc2a333182dce2ac9cc8f3ba0d73cd27ae60aabc1d7689f7bd2d7ecd5d97164293cc88fe7adbadb3a24798f7b2be578c7780a681cc902b69b7fe4aa8f535e4a9ed83d002c1ea0f28e711be6231cabde2ea19655688c8d70b413420651852db3bcad5c6b66267d516088b3955a6884ca44a83c9f179c5901ff4edefea074ee0cd425a7dd8a855ea49affbd7a8604c490213dfa7bf48ac12b738fae7cd1697e757f26a59826e64b7799a47703e037c9ca7cf18828574092a9b0af255a5180eef96f96e5d2f51cc621d7a03c7bb05eb80d386415f243f5d59451d5f95c93f4616cca643cd6be65109d440461e752d2999745facd5eacaea338d6ce940b54c8dba44f059c94fa65a74456b747c065f2aba2b8580931e1e5db4cd6505d4cc50ce68e15a1f9e0069edd1a8d5bbfa9180f0a88fef1d773a5b417b67ddd8729c3a781bfcdeb527126579f5d11c5550525869a3236e1f21940542c7ce69e3a265c24d9aefeab6c0852636708a74718729f35a401fd73b46b204ec92b40d9ec0e5f924c967a8a4e219910d40af2d19e76869bfb7c95868e4572334bc35f7071d6254cb3cdf82ee94d3b685fe48a49ad95503ca19b39e316e57a32a1eaa9af4573a32bfe94dd77c744484781c2efe891e2b4b65b38e12dbab69b436d4b0ac6a9b512ee898b95906163f32839fa2985fadead35d79fa03a284debc60c51daa8a017c1d4dc4dc7e884b1374d131fde2ca6e591672b259754b4f76bbf6ed3eeeee94e4e7c496344a3b38c8b4dea12684bbc0713ec6b7684479e883a0a515d681581beab290d952fa20f6f9ae63dc00a55d1ef3f592d9a86d97a40ae74717623dc1ffdf2588f241d6600e6f34163db805273266f58e75e074d7d079d40755012da5ba3203d2bfdc9100e361148456a7ac423ba9cf3bd135f3e64c2f262fcdb728e0682e8297cc2af4e0467603990459d923cf6974cc27f9c02f535d6962cd67bbfd1287ba4148647153fd33ef9e482e2555ff6ee243ce6ab277af1494944b2913ecf29819507571427d7c1852d684a6359a72698b65ef040b8a03c3fffca6f4a9e13359db9a1e3d2e4fac6d469357d9d9ca6663f6a3f80076c6ec937aa16d23b12fb0ee483c260ad6e751a2814720e36dfd26202b14c1cd18e3f9477e924292d84d905c056bd73ceecede186f3b6987abe328fb3e1e48b2bed3885b9d9e9af6bdebc0c7f9cc6f7954b38905e3d8ee41f780ca446792326a94837c367104983ea9b906a7f7a8b4bbf44700694c27e7670e4a7b3e5df7218d8d67fd68bda52592e650de25b1a81ed304d81cadbad366833d94d7e69a8433c9d7edfb1043b99a08265de814121119d3f7eaa74c8082bd9b4a91bb2478605a2f2e077679665d24391ea37c748c5c6e35f00fd202a13c032270b0d30f0af8bb1d7c2570055980748fd7761d6a801043ecc04051f7d60a5b7361436549762a9f3ba7eb7344658b154b8b680ba8ac2eeec07e4cc3cac211255c3f901e713db12984a00450011790a0f2d6af22d5d35b83c5483dd4e8", + "transcript_input": "6b5fb320cab84c788ef6393262172b222facedee5e55f4e2fa44bae00b1f36102dd8739200e025a0443a6c6d3d52f003e4b94d296ed3a68e9dacc567f0c7bd093d3449542df2dc02aedca2a14e5c6b22f2b797b3191488cdbdc1ff6ad3ef40a77e62023395a490bbacc9de3da7fd37f70592ec361e34c01c761ad4ea2029712cfd2406fd2106f04ef53cd8d75470aa5492cc10d1b519a02ac168e5394226756c89ace3a2174cb08fe962b490f3c2cf88a60391513083a330718b73257c554c7cf0ca1865fbbf682435605c5a7f0636b19ab1c7e2c9cb40cf62c47940b17855f7c2e4d1869409ad7b3b047052bfd7366195d84890f03ac23032a63c545ed99f07e55bc81771836917b5139954d413162b9b5745556865710a5401bd9b6e3382adef4108b80833ed2337aaf2392a9704e239cb4a35332bf8257c288200389d054ba9acf772c61ab9f74875353b1bb933bff0e12f72b8c1537c4b47a74750d5617eb4c99d72af4e401961f588ff9a617b775ef7280a8a2843f1b408e6cbcf7385493a3b9e86230ba3c5676c5cb8885c317855a103e915aeac2cb27c36392494bb2aac04f481d38ba33cf7376711cce831380c580bdd887b3183a8fb2642857b3283c51a82c788e5293ddbd01bc46609f6017f40294f4b43842a709db0a7203565ceebda5721c1201569217ad817cd6316fc8c7f3036a4d5a679095bb4376c9e75360a64051f17fb8445f4556d37180c83cbee261629620ecec139bb8c609da90f9da2a8e1297793546ef90c0bffb3b0c836215bb9108191af7803cd5f26163c577404002aaed44de693892cb04f87e060dc5b9db9d00a4ea6b2734505c04857c31ccaa68bc833733d1e2837e0b5aec46354867933824399f132805893330c73c11c988d1adb6aac9b9bbcbc41263158e2b960c30665571484900ac7621a3e7fd1252ffb271f763ddc98293a5b929790468b9b4ed848c9ba531adaa31fec60367bc48eae612777cb79d7b56a17f8a6afc1c1febb86ec286ef9260cb27079c0d5a74910b3ef26b09ec96de697735e91841b9224d33abf2e1b14b15460d9055093268c75c17aa1ba15b5463e68d32c99a10d40c603b45c1fb81b5965c17379b898cba3cd1471bb07c8586f232d5c29ade9831f06163a3d60c672535e8fb0a01fd4435853480b073ec0ac32bc8a876cc3cd43bbb7bf8b3dc77874be68bff1d0cdf6a2900d8bca43f29b87c4395c7a794c39a45e89c3d9e3bf7046b5bc204a75a30a707242525c81cf4ac93c2a8b266c59eaaa0d997348e9fc7bd0294231c559a5cc1465a22317e714cc1431c092bed0665f242098c556b1c091a18519c1fa91354d6403f71972e78c23a28a276798826b3b711e76aa17f27c24214d6c1c6e8a53736cd986d9e446068532f1a6214cc98eb1625cd064456b0481abe2a38aeaa26845b77b1926b791a4a3b56739997edeb097284a9a6217438fd92b65399d0ecb2f23ccb1c8397496413615c26304d5272d60bc34f3ba6b5c652f693bc702be90349f27fa019173508a94bdf5f1153b341d20d040eb506ea0d40b561c0178c662a7c5b6bd130535355a8a5438ae1c0e2ab872f92800ba319b34d587e52860eb183b0bdb24ec024a111b369d3871c1c7ce9ec97207a208b4ec32ea142c61d23bdbb40325ca0756b36c5de19bed851d0ce6a30c175e467957d72b65f5552007dab6cd474d095ab556353111b9664a012d7fd767b3d549138580a087103d72bb602520f9393317e72e3fbc5d7206a24ccaae03fa844619a4acaa8c246053ed122c9765bc9164aa1c7257ac5893e0619bb965c48f24b5a5747d02f38705a09c905b82918856487382f40c28b73916b8059c29b39853189997978e49b05df1495c66a777e5f84ef99575f51b33fb157d41e54a7611c082387c6da242c3042fce7711525104f46233cad9882e45796a09c59b8471287b19c4f6205ea0b3543c341f8c169c11010cd52abda1b934024b9181c047ecad01844709d7834aa056648097f2109e4fac640d32ad80cc5581ac60296a5b51d615241c8cc0bcccfe777b97e6a5475011603a8ad7eaafe741572018840b989d83f986163633bc68919d571211d317c0a3c0347529b49101c2d451844b5083404da25b4d37931743139953fcc8374369260b88193284bc96250d02c05ecc6809a44a38e24261083dc3953d4e8a093bda800b4b2614d76a4355c6a900251e3cb9de069ed2ebaa34b34aae3661362218b5350b4f68c9bc6b11d0f5753e792cf300acdb94c52eab50d406967f498a8327420d6397886380a94019b307b8188a4496b223ebe27075b87c5d8680855ac7c5b892d357247aecb55e6089a2dba345f65d259b8d40a253a450a546b46abc3d1804f4c6ecc2a992fdff4e7dfd2406fd2106f079578b92edfe5618d5dd9284a2004e45ebe3a37cc4c0eef53eeaf8d322303b91edfb0aa7c453d4c0d06bc29c2e904f20db8e6e0567d6a1303ad190d7c087a76da634a6e0ac11f4f78496ca11862c87acb0fc273061479a89758886d0b0d78375b1edf317ddfa889507df4e74853fe34892580a127e104f0bc4df583d8415b54367efafea0995c7e7ed7a1dedb8112a22e8278f90361dbf0a1f7af1246b609c9981f95a919312540d8bbd207eb75cf9feb4f9d2689b071f1f5b0d790573ca40db398374d3e50e3c2d46bb966567cd9c9b1589ee62206ead95a80483d4fa567ff697d40d804acdde8ec654fd5ba413133fabb99db19d949ecde78d55cb563766e714cf2138f50457809757b618b004ad48c1cb0b0f1c95e0fbe1f43813d5f035c4b3ad1b6a5c1c5fa0a1d27a0cb6d3de7b3a054e6fcbc24e286e6a77e45ab56bc5ecb070a00c22974d05832832bd40ab96010039be5da0464731993e2bccfcc468533cbfe3089a6f579d5a06cbe4bf08ddb85fe9996ed95fe9442130e49c61d1bf0881968672eae12c568189f3285671358e68ae0fbb5075ed137127788aa1f96033c89f41ade84cc61aaeae9b2c86862b1e956c74d39bd6e8f00907fd586cd099ab4224883c2e0c07ac4bb3f74a5a59975ef0e844583370cf077671fbfc2a333182dce2ac9cc8f3ba0d73cd27ae60aabc1d7689f7bd2d7ecd5d97164293cc88fe7adbadb3a24798f7b2be578c7780a681cc902b69b7fe4aa8f535e4a9ed83d002c1ea0f28e711be6231cabde2ea19655688c8d70b413420651852db3bcad5c6b66267d516088b3955a6884ca44a83c9f179c5901ff4edefea074ee0cd425a7dd8a855ea49affbd7a8604c490213dfa7bf48ac12b738fae7cd1697e757f26a59826e64b7799a47703e037c9ca7cf18828574092a9b0af255a5180eef96f96e5d2f51cc621d7a03c7bb05eb80d386415f243f5d59451d5f95c93f4616cca643cd6be65109d440461e752d2999745facd5eacaea338d6ce940b54c8dba44f059c94fa65a74456b747c065f2aba2b8580931e1e5db4cd6505d4cc50ce68e15a1f9e0069edd1a8d5bbfa9180f0a88fef1d773a5b417b67ddd8729c3a781bfcdeb527126579f5d11c5550525869a3236e1f21940542c7ce69e3a265c24d9aefeab6c0852636708a74718729f35a401fd73b46b204ec92b40d9ec0e5f924c967a8a4e219910d40af2d19e76869bfb7c95868e4572334bc35f7071d6254cb3cdf82ee94d3b685fe48a49ad95503ca19b39e316e57a32a1eaa9af4573a32bfe94dd77c744484781c2efe891e2b4b65b38e12dbab69b436d4b0ac6a9b512ee898b95906163f32839fa2985fadead35d79fa03a284debc60c51daa8a017c1d4dc4dc7e884b1374d131fde2ca6e591672b259754b4f76bbf6ed3eeeee94e4e7c496344a3b38c8b4dea12684bbc0713ec6b7684479e883a0a515d681581beab290d952fa20f6f9ae63dc00a55d1ef3f592d9a86d97a40ae74717623dc1ffdf2588f241d6600e6f34163db805273266f58e75e074d7d079d40755012da5ba3203d2bfdc9100e361148456a7ac423ba9cf3bd135f3e64c2f262fcdb728e0682e8297cc2af4e0467603990459d923cf6974cc27f9c02f535d6962cd67bbfd1287ba4148647153fd33ef9e482e2555ff6ee243ce6ab277af1494944b2913ecf29819507571427d7c1852d684a6359a72698b65ef040b8a03c3fffca6f4a9e13359db9a1e3d2e4fac6d469357d9d9ca6663f6a3f80076c6ec937aa16d23b12fb0ee483c260ad6e751a2814720e36dfd26202b14c1cd18e3f9477e924292d84d905c056bd73ceecede186f3b6987abe328fb3e1e48b2bed3885b9d9e9af6bdebc0c7f9cc6f7954b38905e3d8ee41f780ca446792326a94837c367104983ea9b906a7f7a8b4bbf44700694c27e7670e4a7b3e5df7218d8d67fd68bda52592e650de25b1a81ed304d81cadbad366833d94d7e69a8433c9d7edfb1043b99a08265de814121119d3f7eaa74c8082bd9b4a91bb2478605a2f2e077679665d24391ea37c748c5c6e35f00fd202a13c032270b0d30f0af8bb1d7c2570055980748fd7761d6a801043ecc04051f7d60a5b7361436549762a9f3ba7eb7344658b154b8b680ba8ac2eeec07e4cc3cac211255c3f901e713db12984a00450011790a0f2d6af22d5d35b83c5483dd4e8", + "transcript_hash": "66f0655505928bdeec6ce4cf2b1de08ff16c2cb1cf04b781011e30ca6687c240", + "salt": "716269745f76325f6879627269645f6d6c6b656d31303234444f24a8", + "ikm": "7e8dd0eda7d613290fcfc7919af8675a2405e6585378777b9d2f02e592f1fb1d22c8bf5c4858ac2e5f94b6e945ca142ccc28b98e7b954a78899630a7bfa3135f66f0655505928bdeec6ce4cf2b1de08ff16c2cb1cf04b781011e30ca6687c240", + "prk": "cacbbffc106ea035465bc498d22562e83989c039390ddc1ea36e0fe7ba0ac6b4", + "initiator_L": "d723af332d033d229fe91db011f04cd005657fcc35d59b93b6771e77dc9bd572", + "initiator_P": "72462d2ed71f2b6b9c7435745166155bbd61ab04e2e21798945d1282f690e7d2", + "responder_L": "167756b477acb286099da2a866ad9e2d94ec719236dc61836f2956451148ec12", + "responder_P": "95eb7e61e0e575bd36db2a0b589553984942db0da9f43f8d8bc48012f2323d21", + "session_id": "c749d7dadf82b12615fa4ccdeef8cbce4ffb935ad22a7eac1fbc52d8d1394ea4", + "initiator_confirmation": "69abb2b1c9601dad7baf8038bc3601eaa42527de", + "responder_confirmation": "a634faec713fb192a52b9e810216b8a36d39516b", + "initiator_first_contents": "120123456789abcdef", + "initiator_first_packet": "4bbe69dca55d32377b3cee47741171e83115bae7d56d7a9a81dd94394a", + "responder_first_contents": "130123456789abcdef", + "responder_first_packet": "be28dfc2f7f52321d961b644a0ff9fb473d4f9055c6e9523c696751721" + }, + { + "comment": "Regtest, one offer record and one accept record.", + "chain": "regtest", + "magic": "a66b1fda", + "initiator_privkey": "0df7f6c17b741f30d8bd9f5746f019cc1ea364e4e9a75817e181b549d7b0b5b3", + "initiator_ellswift": "fd575a36275251cbf6a287fcafac3419d93b941834964ef2c71235474a86325e075cdcd6a60c478c638ad23cf2c3436395e56b1bb02da9d45c785ba20b6ef484", + "responder_privkey": "eb4a8dbc4505b9df7274e18bc3b620950db233416242a1f0fe3aefcff30deca2", + "responder_ellswift": "cdcdc96b9b01f8dffb480f2cb943e33fcfaa1dd3b20c6be6e25375bdc0f2358fb70615a2103fedde8396327d92966af4a5022bd5537c3816fd21bc036d7dcca3", + "ss_ecdh": "7cc5f298684b388f81e651af731d3d6231f0d0c4c01ce8aae3695f7c24d0d8d3", + "ecdh_session_id": "45094627e9ead1daaa94b2544a58e5086e5e8d0e8d880b971b686e8ec330bd86", + "initiator_garbage_terminator": "3f75a8e72d0759da9311620c7a73dfb9", + "responder_garbage_terminator": "0cebb7d6501e09172b2962ddd16203f9", + "mlkem_keygen_seed": "2147741c766fc3c925963d84d939aa8d54ecf928052d948102f5585d2d100287d9ba0dbbdb1518dec4061dfb7cc2248c03c90ec2f49e361ca59d007f61f88703", + "mlkem_encaps_m": "16c0ebdd39df0e8a59e598c9e300a6a7b22e4e51f6b38a54c77a0c07ef44bb53", + "ek": "36a9c8146c2f789c479f2acd47c7c64f7594550b059d2a4cb7f3add2970ed600888ad782df574a72608283085a433c0ceb9300f5440141848e8352874b83839659629de75e953a0b25a74e8d449c7eb215de1042ef95b140bac0d35cb21c31204c8c22e09861d0a63c6af3029e489e50b023b6dab3295a6d1a7533f571741a5602d25286cbab504b5724371712aa65a84c47782ffc69f1414c81f47479368e6dab1cb14c1fabe24a9531c2e5e97e54c2625b56204104cd11fb77ee999682d745a832c10120ae13b63a016ab99a46a57f549ea5fa6668d2491a4b8689c96b1f8414b6815a22ba70dd2a459cf64365e72677f3b7a74c33a3a62f2fa345ca3b60b41c7abe1a2e1e6b3f43d68be86cce15eba2470204ecc458cb082196d814d9c22aa58675d3337e64b36198d92c1b17470222cab7696f0d8ca4488966205360dfca5d85d1a90ba80b58688adadc02299716bf086dcf5cbc06414cb1fc58091c5a68281f42940fc6da795bb320fefb88cd60727db44a8fda0110c92529f64f726b1834470b1224a1a5c59845b945a1e1a2316039f4b64df9934b40f89df766c279e75a91450ad0e68276f91a3efac90536b836364d55080056d0622533800126b818685a9ad8b052ec72aed13c80085ac9b07a6186553740c7c8a27f1a593a19719eae0519de338408dc7441724248f2b5803682cbf36debd97b265ab60362b59ab2585cf518252565ab1143ed8a53bf40a03d97022b0100afe597226b9e1d3793c98bb9b0b5b167031cb0bc775c16699e785cd60aabeee250a55b5c0881af22265962855a13654dd7022603078b038745c1aa515188642280b2ce68437b5bc458b463cdab65fac07755c862a8d1cd09ca11fb625c61136e4f4c022d6b5f94f5276098bf709b56a5a57a736422a93898041a42075926de23b90f105f488c1d1cb95e7ba5049ee7b23d4766ef6823d45a1d623cbc9d2a9ea1b43dd0b4b30026b2e0d84533142bf9c83a98575ee8d92427d22ee4a03e66e0a11f8a23674b9c0cc248a42b2e1aa4bc422a5f3715523b3769d330628fb61e0a5215c94a4c31408e968c700fc859e2f2230e3c6db387a703fa90b7e05e2c4136848c0372c9c73a424db9a2a497c7b22e565b0317ac085a848122b219dabbb706bb13e35762d26f9b59a475309a50787e0cac18982931f7759b7ca20cdf0246350640f4a9afa2857425519a0c86508db0a87b526c281c0818dc00c7e7c346821ec449759cf602e0bbace8e1a5f493196c1c265c338ca346430ff6796fb58f50e6670db7929ec38c4433bc2d0189dcb873223a56e2f3276065244067c394dc2a433a44e3b329bc28b4e366c9158861cb191ca7343b679c4782c2162831854a170a39f07995aa2502a9a6abbbbab28bc7aed1995136048d81099497aaf3a051659065db5194193064328a56e3f90b39cccd5e511c70d191cbf6b690635db56674fb52b5849b6c655cc789326c900282b1c87a916039af087daba8171af6a2bfe15182192a3e2741b49c9a97427211bc45ec890cbb6221d0637eec0a3e47598a490c8dd8a11102f1ae2425029c072b878615703064b63b7b4e614a47a36080e88dcbdc15408c26e21464766a5740dc96d042ab81a470c85133ae1b9bede688872a742e94aef3cb03081653a40379962b5f5ec34617535ecc471eadac4b205719831158658b00ab8911d8352157e0866625944b690bc5b71101ec244bf4493fb049b5a1c91e3aa200268bdf68831fb347f2614205ab640c8cc59ef7bd3fa7bf45d315db6bc1b1c80d3e692f249788520766033c827674709e8460a3a685333726779a0a76308356367a00228b50f2a6854c6947da1b70d489b6d46fae76352132b024263874c60b147269f78711a9e22a92e05b4b05a6f3507ef723a0716611530459c96a895e1269f9d545edc4a9a665b47cda99d149100c642723601ffd449ac2574a20f71e9b59293f543ab95832955c852f1246758738ddcb76d5db5d8af237345302e4c5884ff10e14c974d7fbc986ec16863ca4c7c0309c92ae6ff9a1d7d55021fc83d1777b500535e74aad34523299068d8d6bba212c2d6ad9a29024a1f192ae96d49cb48835df87367110be3b79710aabccb0055c39cc30fbe3b8b6a68e96c4173616134318ad8bfa64f779c9cd673889c315195180783bb84b15d3877961f7b077e9dc5eda19438ba0", + "ct": "b061c303ccf518a36279d1972c4a5343e63eff500b479471fadc34ce13e6500c6e22a57d56a75a8c3de538cbe8d3c925807af223d9516893cee61cf67cbd010674da54545221c56455174c4f4c9ab094728b179051edb581a4611574e29a8c73b203b8a54c3eb46151dc394f73b71086411a2ac6fa461cc4e7e9d41b84229edd8deb4209c79899b7c668707ebc7ca3b157ea93fc0815a75f17fe167d05b898af756c7ae5268c881d2b38fe7d0b77eb6860a20effe16807194e0e1c190fabf32b97f37e2984b4ef600ee215c6a19fd179d29ddaaa0d65bf03bea367c819fee7ce333c689ee60c9b46dde659c55d5114b70faf031f66a62c2c2ea2302674b324d032efb54980f14459aece5bba882b1a303c0168183e07ee9da0bfe56537fb83ccca73f263f4e2a98fcae96964183e93231e0a00c5293d0e007c57e8cd019cc35625d2d3cac71026d86d11f78bba42e32f301748be147654ad88f261dfd4a48a2e93d7a2d78e37d1fa00557de8d4c5afb8fb2c5e77e641b0f77a931a167ee15189b02dde09842f8b5b1938c7bf0bddddf3d46103f9a0d2bcb04b18786b4ce751b56977c41d9401dc9be25a81c948d6d6da4065478beaa38ebfe8dac127be0d88919dd924f5ece5afd2baae1c2d38002d8916375ccb85fb0bc9c6de9c56d01c95305c6bbfdf5c9f58436b06c499f3968805924342e0a9c7ba1e1f564d633c1aa1a67e6b3727c48d502ab2e1ee8245197f7390a219d5e99cf70d0813a200acbb39ed68680991d7d8c88303ba70d045112dcc68b3f0bea17a390ade438c780ce98418734c59904ce7a5e048d77d1493ef345290a467034cf9c40cfbbc546bca323637d579d4a36ee43ed24387309907b6ec277f0aaf9fc97534328b9f442efeffe398768b82b3d0e579527f7dbbdd2a07b2ef898191020253f007f67aaa54ffb77fffb47246a5308b291d83ff347f41b740c023fffe9b2fcdf3dc2b7009eda3370ededde10613409594540c7458bcab9154fa3edc1aabed7e3f20580749b0f2a680b705023ccde800622561307ea1c3ce925bba2bd4c9397996c33a09daa8a4b9df3079e4d3ddc6abec1f8d7d9f2a0e668c6af584c6153d8b6abb44cf099a0ae51cc4b7f324c8e063b5cfa152d6eb2996ba79473b790427b86c071dc504df51963f42be903f4c97519634b24b0a3c36a39237fe7a30e39cf95902553397aee1708d8f1a5fbe7ade6dc8e45dd3bcd040b427167936b941870eba6206655c57f5bdaa84118d7fb5625e8e5f1ee9396f479d64c6d1bcfd5d0e839784f170ac9e1347c0ca4c893ba419680cf9cf668fd64b817813200136e99a464cda883e736959be84ebde9b8499982eeced11784ec8b2cee9a3942485b93e82ca34f49c97a4cb0cbedef734cd6866b1b9143bb75693f59941604c0bac544830a4edbcaf9f7153e5d330d97231d9021ead5600274a3809a25ebf2ca01b68a83e55bda94872f27000b08fb5ea0e9baf1dde890ac843576bd98cec38aeaf527bef947dc3712e02c8ecbd5315e7c5a0394d908d152997d69d0468b8b10a6b876233143f8fe415297d8dc3e61bff5817af0873e7f56831c084eece18e909801158fab83f35c3fae602ec4d85be2370694b4304c943fb077df9fcea1fd7943572909fb40e452d5c1eb4a24b499a51d976a991987d39975b0a23e33fd8e07c54d88f34dc004a76bdb20243967722c79a32c65ddc25b7a677a3cde595c98710f575025dba871dee9c087a89feee0aa82a05bad08998774525b5ba783286092faf676f59bd3929e44697f16248f306093d246eb558c228a4e909f8640752f804c7270946958a3bcaa2cf8640bbec909dae7eb10e0cce1a80156f200ebfbb0fce05bcc03849f2919ab9fe0f61c24a1b831af0f36c44dac399f9512c00cc9cc28d6f28eeef5a0ab53ec58e7d80a63461ae232e05772f6c06d92176e109fdea0009fab76afb22b648bc54b61012bc020fe104b17297bb25b790592346c507d9298e4c4d90d7304efc4bc42ec679a3a92a72fa4b24f36c1b070f043f35f755285a5c4daff87c853863c912b523fff1a23cd4d0b93d672b82873061f02901063ae79637ab3d1724a860de0e4932cbf2033c52fcf671cc9fc2dc9774e97162d69502166bf2f454a9ba20d0d128624d7bc90cfe40476af4970e28be4dfab73b5ad05d34509ac1e28ecc1c4ad3c0ecb3e21d", + "ss_mlkem": "88d05ce5eedd4bf68930cd8c235e2098a78f77496b623e40af0a45121c1732d9", + "contents_responder": "fd2106f036a9c8146c2f789c479f2acd47c7c64f7594550b059d2a4cb7f3add2970ed600888ad782df574a72608283085a433c0ceb9300f5440141848e8352874b83839659629de75e953a0b25a74e8d449c7eb215de1042ef95b140bac0d35cb21c31204c8c22e09861d0a63c6af3029e489e50b023b6dab3295a6d1a7533f571741a5602d25286cbab504b5724371712aa65a84c47782ffc69f1414c81f47479368e6dab1cb14c1fabe24a9531c2e5e97e54c2625b56204104cd11fb77ee999682d745a832c10120ae13b63a016ab99a46a57f549ea5fa6668d2491a4b8689c96b1f8414b6815a22ba70dd2a459cf64365e72677f3b7a74c33a3a62f2fa345ca3b60b41c7abe1a2e1e6b3f43d68be86cce15eba2470204ecc458cb082196d814d9c22aa58675d3337e64b36198d92c1b17470222cab7696f0d8ca4488966205360dfca5d85d1a90ba80b58688adadc02299716bf086dcf5cbc06414cb1fc58091c5a68281f42940fc6da795bb320fefb88cd60727db44a8fda0110c92529f64f726b1834470b1224a1a5c59845b945a1e1a2316039f4b64df9934b40f89df766c279e75a91450ad0e68276f91a3efac90536b836364d55080056d0622533800126b818685a9ad8b052ec72aed13c80085ac9b07a6186553740c7c8a27f1a593a19719eae0519de338408dc7441724248f2b5803682cbf36debd97b265ab60362b59ab2585cf518252565ab1143ed8a53bf40a03d97022b0100afe597226b9e1d3793c98bb9b0b5b167031cb0bc775c16699e785cd60aabeee250a55b5c0881af22265962855a13654dd7022603078b038745c1aa515188642280b2ce68437b5bc458b463cdab65fac07755c862a8d1cd09ca11fb625c61136e4f4c022d6b5f94f5276098bf709b56a5a57a736422a93898041a42075926de23b90f105f488c1d1cb95e7ba5049ee7b23d4766ef6823d45a1d623cbc9d2a9ea1b43dd0b4b30026b2e0d84533142bf9c83a98575ee8d92427d22ee4a03e66e0a11f8a23674b9c0cc248a42b2e1aa4bc422a5f3715523b3769d330628fb61e0a5215c94a4c31408e968c700fc859e2f2230e3c6db387a703fa90b7e05e2c4136848c0372c9c73a424db9a2a497c7b22e565b0317ac085a848122b219dabbb706bb13e35762d26f9b59a475309a50787e0cac18982931f7759b7ca20cdf0246350640f4a9afa2857425519a0c86508db0a87b526c281c0818dc00c7e7c346821ec449759cf602e0bbace8e1a5f493196c1c265c338ca346430ff6796fb58f50e6670db7929ec38c4433bc2d0189dcb873223a56e2f3276065244067c394dc2a433a44e3b329bc28b4e366c9158861cb191ca7343b679c4782c2162831854a170a39f07995aa2502a9a6abbbbab28bc7aed1995136048d81099497aaf3a051659065db5194193064328a56e3f90b39cccd5e511c70d191cbf6b690635db56674fb52b5849b6c655cc789326c900282b1c87a916039af087daba8171af6a2bfe15182192a3e2741b49c9a97427211bc45ec890cbb6221d0637eec0a3e47598a490c8dd8a11102f1ae2425029c072b878615703064b63b7b4e614a47a36080e88dcbdc15408c26e21464766a5740dc96d042ab81a470c85133ae1b9bede688872a742e94aef3cb03081653a40379962b5f5ec34617535ecc471eadac4b205719831158658b00ab8911d8352157e0866625944b690bc5b71101ec244bf4493fb049b5a1c91e3aa200268bdf68831fb347f2614205ab640c8cc59ef7bd3fa7bf45d315db6bc1b1c80d3e692f249788520766033c827674709e8460a3a685333726779a0a76308356367a00228b50f2a6854c6947da1b70d489b6d46fae76352132b024263874c60b147269f78711a9e22a92e05b4b05a6f3507ef723a0716611530459c96a895e1269f9d545edc4a9a665b47cda99d149100c642723601ffd449ac2574a20f71e9b59293f543ab95832955c852f1246758738ddcb76d5db5d8af237345302e4c5884ff10e14c974d7fbc986ec16863ca4c7c0309c92ae6ff9a1d7d55021fc83d1777b500535e74aad34523299068d8d6bba212c2d6ad9a29024a1f192ae96d49cb48835df87367110be3b79710aabccb0055c39cc30fbe3b8b6a68e96c4173616134318ad8bfa64f779c9cd673889c315195180783bb84b15d3877961f7b077e9dc5eda19438ba0", + "contents_initiator": "fd2106f0b061c303ccf518a36279d1972c4a5343e63eff500b479471fadc34ce13e6500c6e22a57d56a75a8c3de538cbe8d3c925807af223d9516893cee61cf67cbd010674da54545221c56455174c4f4c9ab094728b179051edb581a4611574e29a8c73b203b8a54c3eb46151dc394f73b71086411a2ac6fa461cc4e7e9d41b84229edd8deb4209c79899b7c668707ebc7ca3b157ea93fc0815a75f17fe167d05b898af756c7ae5268c881d2b38fe7d0b77eb6860a20effe16807194e0e1c190fabf32b97f37e2984b4ef600ee215c6a19fd179d29ddaaa0d65bf03bea367c819fee7ce333c689ee60c9b46dde659c55d5114b70faf031f66a62c2c2ea2302674b324d032efb54980f14459aece5bba882b1a303c0168183e07ee9da0bfe56537fb83ccca73f263f4e2a98fcae96964183e93231e0a00c5293d0e007c57e8cd019cc35625d2d3cac71026d86d11f78bba42e32f301748be147654ad88f261dfd4a48a2e93d7a2d78e37d1fa00557de8d4c5afb8fb2c5e77e641b0f77a931a167ee15189b02dde09842f8b5b1938c7bf0bddddf3d46103f9a0d2bcb04b18786b4ce751b56977c41d9401dc9be25a81c948d6d6da4065478beaa38ebfe8dac127be0d88919dd924f5ece5afd2baae1c2d38002d8916375ccb85fb0bc9c6de9c56d01c95305c6bbfdf5c9f58436b06c499f3968805924342e0a9c7ba1e1f564d633c1aa1a67e6b3727c48d502ab2e1ee8245197f7390a219d5e99cf70d0813a200acbb39ed68680991d7d8c88303ba70d045112dcc68b3f0bea17a390ade438c780ce98418734c59904ce7a5e048d77d1493ef345290a467034cf9c40cfbbc546bca323637d579d4a36ee43ed24387309907b6ec277f0aaf9fc97534328b9f442efeffe398768b82b3d0e579527f7dbbdd2a07b2ef898191020253f007f67aaa54ffb77fffb47246a5308b291d83ff347f41b740c023fffe9b2fcdf3dc2b7009eda3370ededde10613409594540c7458bcab9154fa3edc1aabed7e3f20580749b0f2a680b705023ccde800622561307ea1c3ce925bba2bd4c9397996c33a09daa8a4b9df3079e4d3ddc6abec1f8d7d9f2a0e668c6af584c6153d8b6abb44cf099a0ae51cc4b7f324c8e063b5cfa152d6eb2996ba79473b790427b86c071dc504df51963f42be903f4c97519634b24b0a3c36a39237fe7a30e39cf95902553397aee1708d8f1a5fbe7ade6dc8e45dd3bcd040b427167936b941870eba6206655c57f5bdaa84118d7fb5625e8e5f1ee9396f479d64c6d1bcfd5d0e839784f170ac9e1347c0ca4c893ba419680cf9cf668fd64b817813200136e99a464cda883e736959be84ebde9b8499982eeced11784ec8b2cee9a3942485b93e82ca34f49c97a4cb0cbedef734cd6866b1b9143bb75693f59941604c0bac544830a4edbcaf9f7153e5d330d97231d9021ead5600274a3809a25ebf2ca01b68a83e55bda94872f27000b08fb5ea0e9baf1dde890ac843576bd98cec38aeaf527bef947dc3712e02c8ecbd5315e7c5a0394d908d152997d69d0468b8b10a6b876233143f8fe415297d8dc3e61bff5817af0873e7f56831c084eece18e909801158fab83f35c3fae602ec4d85be2370694b4304c943fb077df9fcea1fd7943572909fb40e452d5c1eb4a24b499a51d976a991987d39975b0a23e33fd8e07c54d88f34dc004a76bdb20243967722c79a32c65ddc25b7a677a3cde595c98710f575025dba871dee9c087a89feee0aa82a05bad08998774525b5ba783286092faf676f59bd3929e44697f16248f306093d246eb558c228a4e909f8640752f804c7270946958a3bcaa2cf8640bbec909dae7eb10e0cce1a80156f200ebfbb0fce05bcc03849f2919ab9fe0f61c24a1b831af0f36c44dac399f9512c00cc9cc28d6f28eeef5a0ab53ec58e7d80a63461ae232e05772f6c06d92176e109fdea0009fab76afb22b648bc54b61012bc020fe104b17297bb25b790592346c507d9298e4c4d90d7304efc4bc42ec679a3a92a72fa4b24f36c1b070f043f35f755285a5c4daff87c853863c912b523fff1a23cd4d0b93d672b82873061f02901063ae79637ab3d1724a860de0e4932cbf2033c52fcf671cc9fc2dc9774e97162d69502166bf2f454a9ba20d0d128624d7bc90cfe40476af4970e28be4dfab73b5ad05d34509ac1e28ecc1c4ad3c0ecb3e21d", + "transcript_input": "fd575a36275251cbf6a287fcafac3419d93b941834964ef2c71235474a86325e075cdcd6a60c478c638ad23cf2c3436395e56b1bb02da9d45c785ba20b6ef484cdcdc96b9b01f8dffb480f2cb943e33fcfaa1dd3b20c6be6e25375bdc0f2358fb70615a2103fedde8396327d92966af4a5022bd5537c3816fd21bc036d7dcca3fd2406fd2106f036a9c8146c2f789c479f2acd47c7c64f7594550b059d2a4cb7f3add2970ed600888ad782df574a72608283085a433c0ceb9300f5440141848e8352874b83839659629de75e953a0b25a74e8d449c7eb215de1042ef95b140bac0d35cb21c31204c8c22e09861d0a63c6af3029e489e50b023b6dab3295a6d1a7533f571741a5602d25286cbab504b5724371712aa65a84c47782ffc69f1414c81f47479368e6dab1cb14c1fabe24a9531c2e5e97e54c2625b56204104cd11fb77ee999682d745a832c10120ae13b63a016ab99a46a57f549ea5fa6668d2491a4b8689c96b1f8414b6815a22ba70dd2a459cf64365e72677f3b7a74c33a3a62f2fa345ca3b60b41c7abe1a2e1e6b3f43d68be86cce15eba2470204ecc458cb082196d814d9c22aa58675d3337e64b36198d92c1b17470222cab7696f0d8ca4488966205360dfca5d85d1a90ba80b58688adadc02299716bf086dcf5cbc06414cb1fc58091c5a68281f42940fc6da795bb320fefb88cd60727db44a8fda0110c92529f64f726b1834470b1224a1a5c59845b945a1e1a2316039f4b64df9934b40f89df766c279e75a91450ad0e68276f91a3efac90536b836364d55080056d0622533800126b818685a9ad8b052ec72aed13c80085ac9b07a6186553740c7c8a27f1a593a19719eae0519de338408dc7441724248f2b5803682cbf36debd97b265ab60362b59ab2585cf518252565ab1143ed8a53bf40a03d97022b0100afe597226b9e1d3793c98bb9b0b5b167031cb0bc775c16699e785cd60aabeee250a55b5c0881af22265962855a13654dd7022603078b038745c1aa515188642280b2ce68437b5bc458b463cdab65fac07755c862a8d1cd09ca11fb625c61136e4f4c022d6b5f94f5276098bf709b56a5a57a736422a93898041a42075926de23b90f105f488c1d1cb95e7ba5049ee7b23d4766ef6823d45a1d623cbc9d2a9ea1b43dd0b4b30026b2e0d84533142bf9c83a98575ee8d92427d22ee4a03e66e0a11f8a23674b9c0cc248a42b2e1aa4bc422a5f3715523b3769d330628fb61e0a5215c94a4c31408e968c700fc859e2f2230e3c6db387a703fa90b7e05e2c4136848c0372c9c73a424db9a2a497c7b22e565b0317ac085a848122b219dabbb706bb13e35762d26f9b59a475309a50787e0cac18982931f7759b7ca20cdf0246350640f4a9afa2857425519a0c86508db0a87b526c281c0818dc00c7e7c346821ec449759cf602e0bbace8e1a5f493196c1c265c338ca346430ff6796fb58f50e6670db7929ec38c4433bc2d0189dcb873223a56e2f3276065244067c394dc2a433a44e3b329bc28b4e366c9158861cb191ca7343b679c4782c2162831854a170a39f07995aa2502a9a6abbbbab28bc7aed1995136048d81099497aaf3a051659065db5194193064328a56e3f90b39cccd5e511c70d191cbf6b690635db56674fb52b5849b6c655cc789326c900282b1c87a916039af087daba8171af6a2bfe15182192a3e2741b49c9a97427211bc45ec890cbb6221d0637eec0a3e47598a490c8dd8a11102f1ae2425029c072b878615703064b63b7b4e614a47a36080e88dcbdc15408c26e21464766a5740dc96d042ab81a470c85133ae1b9bede688872a742e94aef3cb03081653a40379962b5f5ec34617535ecc471eadac4b205719831158658b00ab8911d8352157e0866625944b690bc5b71101ec244bf4493fb049b5a1c91e3aa200268bdf68831fb347f2614205ab640c8cc59ef7bd3fa7bf45d315db6bc1b1c80d3e692f249788520766033c827674709e8460a3a685333726779a0a76308356367a00228b50f2a6854c6947da1b70d489b6d46fae76352132b024263874c60b147269f78711a9e22a92e05b4b05a6f3507ef723a0716611530459c96a895e1269f9d545edc4a9a665b47cda99d149100c642723601ffd449ac2574a20f71e9b59293f543ab95832955c852f1246758738ddcb76d5db5d8af237345302e4c5884ff10e14c974d7fbc986ec16863ca4c7c0309c92ae6ff9a1d7d55021fc83d1777b500535e74aad34523299068d8d6bba212c2d6ad9a29024a1f192ae96d49cb48835df87367110be3b79710aabccb0055c39cc30fbe3b8b6a68e96c4173616134318ad8bfa64f779c9cd673889c315195180783bb84b15d3877961f7b077e9dc5eda19438ba0fd2406fd2106f0b061c303ccf518a36279d1972c4a5343e63eff500b479471fadc34ce13e6500c6e22a57d56a75a8c3de538cbe8d3c925807af223d9516893cee61cf67cbd010674da54545221c56455174c4f4c9ab094728b179051edb581a4611574e29a8c73b203b8a54c3eb46151dc394f73b71086411a2ac6fa461cc4e7e9d41b84229edd8deb4209c79899b7c668707ebc7ca3b157ea93fc0815a75f17fe167d05b898af756c7ae5268c881d2b38fe7d0b77eb6860a20effe16807194e0e1c190fabf32b97f37e2984b4ef600ee215c6a19fd179d29ddaaa0d65bf03bea367c819fee7ce333c689ee60c9b46dde659c55d5114b70faf031f66a62c2c2ea2302674b324d032efb54980f14459aece5bba882b1a303c0168183e07ee9da0bfe56537fb83ccca73f263f4e2a98fcae96964183e93231e0a00c5293d0e007c57e8cd019cc35625d2d3cac71026d86d11f78bba42e32f301748be147654ad88f261dfd4a48a2e93d7a2d78e37d1fa00557de8d4c5afb8fb2c5e77e641b0f77a931a167ee15189b02dde09842f8b5b1938c7bf0bddddf3d46103f9a0d2bcb04b18786b4ce751b56977c41d9401dc9be25a81c948d6d6da4065478beaa38ebfe8dac127be0d88919dd924f5ece5afd2baae1c2d38002d8916375ccb85fb0bc9c6de9c56d01c95305c6bbfdf5c9f58436b06c499f3968805924342e0a9c7ba1e1f564d633c1aa1a67e6b3727c48d502ab2e1ee8245197f7390a219d5e99cf70d0813a200acbb39ed68680991d7d8c88303ba70d045112dcc68b3f0bea17a390ade438c780ce98418734c59904ce7a5e048d77d1493ef345290a467034cf9c40cfbbc546bca323637d579d4a36ee43ed24387309907b6ec277f0aaf9fc97534328b9f442efeffe398768b82b3d0e579527f7dbbdd2a07b2ef898191020253f007f67aaa54ffb77fffb47246a5308b291d83ff347f41b740c023fffe9b2fcdf3dc2b7009eda3370ededde10613409594540c7458bcab9154fa3edc1aabed7e3f20580749b0f2a680b705023ccde800622561307ea1c3ce925bba2bd4c9397996c33a09daa8a4b9df3079e4d3ddc6abec1f8d7d9f2a0e668c6af584c6153d8b6abb44cf099a0ae51cc4b7f324c8e063b5cfa152d6eb2996ba79473b790427b86c071dc504df51963f42be903f4c97519634b24b0a3c36a39237fe7a30e39cf95902553397aee1708d8f1a5fbe7ade6dc8e45dd3bcd040b427167936b941870eba6206655c57f5bdaa84118d7fb5625e8e5f1ee9396f479d64c6d1bcfd5d0e839784f170ac9e1347c0ca4c893ba419680cf9cf668fd64b817813200136e99a464cda883e736959be84ebde9b8499982eeced11784ec8b2cee9a3942485b93e82ca34f49c97a4cb0cbedef734cd6866b1b9143bb75693f59941604c0bac544830a4edbcaf9f7153e5d330d97231d9021ead5600274a3809a25ebf2ca01b68a83e55bda94872f27000b08fb5ea0e9baf1dde890ac843576bd98cec38aeaf527bef947dc3712e02c8ecbd5315e7c5a0394d908d152997d69d0468b8b10a6b876233143f8fe415297d8dc3e61bff5817af0873e7f56831c084eece18e909801158fab83f35c3fae602ec4d85be2370694b4304c943fb077df9fcea1fd7943572909fb40e452d5c1eb4a24b499a51d976a991987d39975b0a23e33fd8e07c54d88f34dc004a76bdb20243967722c79a32c65ddc25b7a677a3cde595c98710f575025dba871dee9c087a89feee0aa82a05bad08998774525b5ba783286092faf676f59bd3929e44697f16248f306093d246eb558c228a4e909f8640752f804c7270946958a3bcaa2cf8640bbec909dae7eb10e0cce1a80156f200ebfbb0fce05bcc03849f2919ab9fe0f61c24a1b831af0f36c44dac399f9512c00cc9cc28d6f28eeef5a0ab53ec58e7d80a63461ae232e05772f6c06d92176e109fdea0009fab76afb22b648bc54b61012bc020fe104b17297bb25b790592346c507d9298e4c4d90d7304efc4bc42ec679a3a92a72fa4b24f36c1b070f043f35f755285a5c4daff87c853863c912b523fff1a23cd4d0b93d672b82873061f02901063ae79637ab3d1724a860de0e4932cbf2033c52fcf671cc9fc2dc9774e97162d69502166bf2f454a9ba20d0d128624d7bc90cfe40476af4970e28be4dfab73b5ad05d34509ac1e28ecc1c4ad3c0ecb3e21d", + "transcript_hash": "b5daca7d9f565561eb65188fe847b42035584b7d5a10a37b03780093d4759002", + "salt": "716269745f76325f6879627269645f6d6c6b656d31303234a66b1fda", + "ikm": "7cc5f298684b388f81e651af731d3d6231f0d0c4c01ce8aae3695f7c24d0d8d388d05ce5eedd4bf68930cd8c235e2098a78f77496b623e40af0a45121c1732d9b5daca7d9f565561eb65188fe847b42035584b7d5a10a37b03780093d4759002", + "prk": "4f41efbac786e859440a53e08165ef9c65c8994578090ed105a5a21b732d8e02", + "initiator_L": "b52abaf0643e95f6604bb5b96cd74bb2082396f60e77db1129ebbf087a6acf25", + "initiator_P": "ca616100d18947e39791dbd10291ee65633ca83d2c18313553d0def027a2fd15", + "responder_L": "08276142743e4d7060120d65f4b191385dc9557862c984dc9e5d26fd45aca1d3", + "responder_P": "fa2b20ce6b6184abba4452a743b4bcd1c131a7d772306c0cc55241a09e37eb7d", + "session_id": "a4d00d3bbf7b2827f5174b6ae15392d50cf7c10301d9176b8a112dbc8ea3030c", + "initiator_confirmation": "18842cc0feb7d46095f2b5248c365780ed704865", + "responder_confirmation": "be555e4bc6f9008845aab5051714e5b762f7f343", + "initiator_first_contents": "120123456789abcdef", + "initiator_first_packet": "849e3283bc0b5f18b87f6a0d43aa9a84e3f67f67f1e514ba6f8829bc4b", + "responder_first_contents": "130123456789abcdef", + "responder_first_packet": "dafaef5b2d7f8349646d1b0c75da82a2b1850b1802184e814a9c4c8193" + }, + { + "comment": "Mainnet, extra records. The responder's contents are its offer, a second 0xf0 record with a different valid ek, and an unknown 0xf1 record; the initiator encapsulates to the ek of the first 0xf0 record and ignores the rest. The initiator's contents are an unknown 0x01 record followed by its accept, which is still the first 0xf0 record there. The transcript covers every record of both contents.", + "chain": "main", + "magic": "444f24a8", + "initiator_privkey": "7a8fce38025c8d5e293a3fa1700aeb0377cd4f24deddfb9c942b8dcf43d482e9", + "initiator_ellswift": "0c80fda2ca53954371093ebf6c8a05459a8a7b191144bb9b2b45cfcf9325f65e12d1fc1ac3eb53bb888e25deaa0d76ca2ee327264a1b6c0c33f214bde668ff6d", + "responder_privkey": "72f7f006e2be303b761305ebee497574abad3ea7b1542ea5b6d7e7ebb390dd5f", + "responder_ellswift": "e9455f0cabdb2176c839614c5b5b438eb0233819ff63197572b15b5657fc3664fae78cf87f8a1012873d9b7c30e4d11fedef245c7fe3a50cdcfc1a52a0fb5ecc", + "ss_ecdh": "5c673cb7a5b0f773e77a240a65a089b914d26e08092a2eb87d5f6dc82039d79a", + "ecdh_session_id": "b5de8be0eee3939ad30ad9ad402515b289297d9eef37b58327842f0bd94796b8", + "initiator_garbage_terminator": "e548e658f884c0717202aca407756c5b", + "responder_garbage_terminator": "9b050c8ac06961ec6cdb826dadcb4f70", + "mlkem_keygen_seed": "b04358d9549f8211c95e3e95d6f8d967d3f6319039028314fd7c43e68177f234166d646ef8bf960a873ffd68585ff6439fe8d9fd996ae9025fc9bce73beafcf5", + "mlkem_encaps_m": "94b194e2b14bdb50be8c305fc471b35865217c8d97aecf8848088398b8afa05d", + "ek": "391892c89cc4fbd7cf2b9b8eb563a681446d51ac93006d179dc79417544f12432319ebc23d77385b047582e922ecdc6dd1359166fc6353f1cd13404723f8a35f202d570a71baf435f6c14bb0d748ae8809e290107f222eb3a12ac180999ba1afaaf642dbc9294a60ac6ecc02fc8a825d541cd5e723bdc9ce84fa97847009317c0c5ab348acd921eafb8730729329714b710c091b383249465a124534b3db4d451a77ede1b60cc308cd9578a2d22050324e0aa724efc14c24555c8f6a4258a998e6ba0230c22bdc267d5ca9780d603f91712533d96ea2899b9989688ba97597245c72412f73c83a471883061c627283c19e355a7ad80d29565401955987a2af908b9f1fa265b4a12f1ca34aa86027d79876a8c3b0766a5064637684a40497e962d8e5acee309b37215a458227ae09654023c5317a91955147e6880d8187053b40872ed8c8aac0a7d690794751940a379a0617a9c61c4d2ba5c4b36a7c9ad8cc1e5acca6db3b7f5b4a394ba87a03cbe036787a37a39dfbbb86d183d050054e83a61a3b9e5a17ba11c16862e35c6b5705f16143bd856996ec159a039f0600071434928151c2b7a4ced4b6a54237b17a32cf15116e2d03aaff7b1e07b0bcfa2c844f0932d15085739a7040937a8a62783518c6e63007c0ba10b280944db447296369aa495a84e3471fbaad4f50473240102509ce45726e26f95d0256035ce21a39c1430ca1884b5519bcf6833bfa65c3aa89677b5d18164a6230a34a26c204ba6bb3d162f42135e567c490b58d1dd1b5f808bcd8e029e734bcab762a49f8b015cc63dbe9b4dfb8446b31452a1a428fa0a5c7e499a62a620ac915f3d85ebc39ac944b2977c07898987b7c2771f9136e6d2b0aa42a6c391c8456e17aa6d6b1e5036ed3fb5f00475ac4808de28ba686c027b38862db4149c84a86395c05824947fa3876a867be2f10666a5c683b8b190ed841b473a22bb35949f74a9580ceb370284127c445c759d7ccc5e66a4bb950b4a54071f496c570f5a641991c705a9b3bf2131ab6254a120c3a5259687c256c7a0ba23ac234eab05f34423907b9d16cb76371c3233c74327729540a1a8bfaa786f324c4c446c2b5307534937e98804da8598c95a434e7b99ca2144bf753d3a2564685cd3fdb1574b472276b933b579a1f88a197c7ad6f1c02b3b7c6c8250855e15585f253b9174cf9700e5a762cc129cce48b4cb39588c74854e4c7735d085295735533a9674fb889c2e5c225f7206f383fd429c3b0a050c95950579b806f3c7324c022d253b254054236eb054200888ca93e5b2aa5df5aafd6840fbe780dd926624b0a22bebb443b9a033f33189fd5532d0750adf3c1a346164355210eab12876c1304da9898c2c8f5a475f766af2a092af3e31bd8e7407dc8a9faf096fda24ccb230337caa5e4a8aa0d497a50f59a08ab12437223506b92a0e9bd84f80865e81b3ffb671074cdf0d03e1537bc50a87b9433af26b10f01349c56373112dc0ccdc24b8d15b33237993732b9ff5b8a089b88e54102b7336f4b8ba6c5c6c6bca19a691592f65b57b4f47c8e2889e8d8c72bd29856d83a8687a46d6ccef7e9be6af75aa9367c97fbaac7251cd0cc444d81115824beddfb26c3a430788781c1dca72d96c4bf77a8b02812c792c3c472272fd25048d3230b494b6058326ec66d3a90a23747b30a7328cb8b8c4e95b6183920d40653681293d3f7c094e76d8cb98b481585c610ae34e5226f9abd37938875737400d0178bbc743c5c6727778d227010ab88bcac680d11c581fd215b33d2c2ddd3631de5a1848b76c9514780698727e7b53108c4d122aefd9a55174047beac7e06297fbd383218424e95978b7833389843c321476cd555c4e10561f7540769bc1c7bb444cb7b8c500813eef5a7f4d89ac0f315fcc7bd3b55c350c74685e81e30919ab32c5f33a8cea2518850768b99d311f310a19cf954b80b4c897928155705f4200d70a5039443cd2f484ebe936329e4b5c7540b47f4663f31c6dc535aeb0a360eb02a6d0890db24b31732acf59b7303405d798aa5527c274dc774349124b310194c16cac635a803523df22b35e4f66101807c61ec5aac6bc60e955568119b08c4163d3c4218750d4ca0c0a677b864e3b4beea934a745df18bb8dab289b561414b9494b25d116502a23d2bce210cd4b9cb2ffaf261d18ca277589c727be84555102b19", + "ct": "f9002f5e1e408d9aa90199fca791fc86e7eedd51fa56fb897954f9a30610855b4f5c737fbebb93618c3e6664fd400ed471e3243a63c8d287ef7f435719e540f24c789f4790340678ee90468f3e42586e98c5a42ff9f3fd8c255af6441a16ce2581ee8fd5970acf91e7a7497ba144990c2c8a84f37b5e65a238ffae62740f9e8c8fc309d43156a27dadebfdbe04a23553b3ea9779e86eac2b1cd1b1281bd486ce3e597d0aef96d3aae22660a3951a303afb7335978718e38387d68cc6f7eef0f8fb0b9b7a37df6a46a7eb27b1bf7dec788fc4e88ab252a573fde9dec0b693fbb00e6647db52d994252473861b218479179c1ab32e82e81ad71eaec92a2dafc2efb84ff11ce686fec244581d17cc0b91a9db6b6fe6c49e5919337d324981d023227e028600285ef795a449c69895e7dd96ef925365623a862d03ebd86ffa3fff2acb6b4165a73e030259ef0b94d0abc36a7a87ba2f12565c850af548e170d4927bce6de8b2e888ac7ee47266ba230629da59d6573ccb0651c0dcd5bb9926262a70ef9633a914bd57c8aaf4219d0cbe8713748070e93e33f5ac5aa937d9553e58451cc3a8699c940295bb3867e654a51887b0e82fc35eec05958503a256a19e97520e4a6d04a194c6c4d2e6d5abdad5c91d6aed8e9f5f9ef963ff92e334ef04b86b77b6e75a135173755508978831a831df77298cbb8ec4c99c00eb2696b473777532f3ea911ba03dd99e086cbb7a64c5a1c38154300fbe11e311599a2b6e1a14df63ec7cf2462b835c64b89cf6249aab2835865d78b2c16859acd0bbbd9481315d1a21d22b3cbb4f57cc32bbbb08bf6a7a7032899927ff1903c7aef6658b703b0663f4780c4c2927fbd279d32c05514e9c00d37754ceb4c98501de4334c6a35fd19159bfc571f42d1ca02343aa690da3a8f4a98874dfb9d02fb474b5ea399c820bec6334e186448236c7f809616c2df4ba1863f34f36770fa88b1f2d0dcb267f675ad9e990c5f6cd4e95a4f1fa5085a414ec200d218a34c399ca5b900a3b36084270019f1edddd97037951d60bf3c1524e5e17db214659f70188132e33f19c697fd708aa04d2262eb09c59040c0192795c53bae668442566e237bc23f0c8eec5b569cdee9522a698a312e8e5ddc788a39eead2bb8719b298f25c6045cf0c2cce2928c729c54c8d8925c8e043c566fd6b2dc9e725fd47b743d571e4299434849d4c7789d7f56ff01b3aef1dced7f601cfc427df3ebcf704aa722164cf2d3ce6491456ce0bc5a4279bec79e48e8222808ac0d51fa0e51e25de4a69942fc215b5a87aca441de5b4395863cddb03dc9c3ac6bc833ad08eae1e2438be5b5c4cce66bd7c1fcc59d7b927494ed4569fe6f5db9569edf8b65a5fd4bae073c699ee6c50200d5b5824542a40873a18d5b881ca86ccdb1c9ba5387095e7ffb03da4b3ddb5d2a0e5395a21ac4288105a2385fe1477eceac41e7f5d520e854f9511d62a13839395f6c37390ed8ca46b92ef285222b1addfa8a3a119b0e37366bcbc782693fb6e079ba4ded9a75a29016ff6af04ac1fc04bf6173e58cc922f7c7ac6060dea4b4169a420c7e80d8c034d118de811cf600102b235e1ef3fbb33dd9d8fe9d2a83c55192ae8c3a3476cd94bd6beae4e3f281b8d3477dc68d508db659b9472c8564f1877bf1a319e88cc304c8f0c0708a15787f75082ba35c5aa9b9bb01b1fe8f55a259429c0484f233b8c445112ebfbb3c77ad8b977a97497675a28e810df5e4cb905c7e840fcfa126a2da3d246626e73df022ba75563b696335ea8da1035d22634c1c029e00d808a5b1e4e663475a907c5f18e4c3ffc711ccca38030cf4f2dab4bcfb0a69d6ab835ab7f514f40e623508a4e4b9b98cc177000d002304b84978dff9db4ac1fdea8376839e339ce5daeb2a8918a06c540a6c43082aa3cb99473eeb6067ca8e7dbe31d60e7c37569da75e690fb79a55dea00ce872cba268eca78be5a9b367855a280097c1d9b345ad69040cfbec6fcfdc137c276d23aab7e8c81ccc658677454f394b1913f22d27c1f7f0be0c4bbd2a355a719de059e17e1efe3ff2b304d27779fe3780c30e8dc4c8352586db8de9a0d83fb69b0ab90241733ecedc94c3ddcb3c65012e9971b547d35bc7f47101049ddf77c278e373b184fcf0befbba38b628f3da097fab9ac37819a9c0a019093f8e398e7df873fc9c497f971f2763d9f", + "ss_mlkem": "c1608e04a8b9a552945aefe36cef26784e1b20d96516f9c5db47b8a9847062d3", + "contents_responder": "fd2106f0391892c89cc4fbd7cf2b9b8eb563a681446d51ac93006d179dc79417544f12432319ebc23d77385b047582e922ecdc6dd1359166fc6353f1cd13404723f8a35f202d570a71baf435f6c14bb0d748ae8809e290107f222eb3a12ac180999ba1afaaf642dbc9294a60ac6ecc02fc8a825d541cd5e723bdc9ce84fa97847009317c0c5ab348acd921eafb8730729329714b710c091b383249465a124534b3db4d451a77ede1b60cc308cd9578a2d22050324e0aa724efc14c24555c8f6a4258a998e6ba0230c22bdc267d5ca9780d603f91712533d96ea2899b9989688ba97597245c72412f73c83a471883061c627283c19e355a7ad80d29565401955987a2af908b9f1fa265b4a12f1ca34aa86027d79876a8c3b0766a5064637684a40497e962d8e5acee309b37215a458227ae09654023c5317a91955147e6880d8187053b40872ed8c8aac0a7d690794751940a379a0617a9c61c4d2ba5c4b36a7c9ad8cc1e5acca6db3b7f5b4a394ba87a03cbe036787a37a39dfbbb86d183d050054e83a61a3b9e5a17ba11c16862e35c6b5705f16143bd856996ec159a039f0600071434928151c2b7a4ced4b6a54237b17a32cf15116e2d03aaff7b1e07b0bcfa2c844f0932d15085739a7040937a8a62783518c6e63007c0ba10b280944db447296369aa495a84e3471fbaad4f50473240102509ce45726e26f95d0256035ce21a39c1430ca1884b5519bcf6833bfa65c3aa89677b5d18164a6230a34a26c204ba6bb3d162f42135e567c490b58d1dd1b5f808bcd8e029e734bcab762a49f8b015cc63dbe9b4dfb8446b31452a1a428fa0a5c7e499a62a620ac915f3d85ebc39ac944b2977c07898987b7c2771f9136e6d2b0aa42a6c391c8456e17aa6d6b1e5036ed3fb5f00475ac4808de28ba686c027b38862db4149c84a86395c05824947fa3876a867be2f10666a5c683b8b190ed841b473a22bb35949f74a9580ceb370284127c445c759d7ccc5e66a4bb950b4a54071f496c570f5a641991c705a9b3bf2131ab6254a120c3a5259687c256c7a0ba23ac234eab05f34423907b9d16cb76371c3233c74327729540a1a8bfaa786f324c4c446c2b5307534937e98804da8598c95a434e7b99ca2144bf753d3a2564685cd3fdb1574b472276b933b579a1f88a197c7ad6f1c02b3b7c6c8250855e15585f253b9174cf9700e5a762cc129cce48b4cb39588c74854e4c7735d085295735533a9674fb889c2e5c225f7206f383fd429c3b0a050c95950579b806f3c7324c022d253b254054236eb054200888ca93e5b2aa5df5aafd6840fbe780dd926624b0a22bebb443b9a033f33189fd5532d0750adf3c1a346164355210eab12876c1304da9898c2c8f5a475f766af2a092af3e31bd8e7407dc8a9faf096fda24ccb230337caa5e4a8aa0d497a50f59a08ab12437223506b92a0e9bd84f80865e81b3ffb671074cdf0d03e1537bc50a87b9433af26b10f01349c56373112dc0ccdc24b8d15b33237993732b9ff5b8a089b88e54102b7336f4b8ba6c5c6c6bca19a691592f65b57b4f47c8e2889e8d8c72bd29856d83a8687a46d6ccef7e9be6af75aa9367c97fbaac7251cd0cc444d81115824beddfb26c3a430788781c1dca72d96c4bf77a8b02812c792c3c472272fd25048d3230b494b6058326ec66d3a90a23747b30a7328cb8b8c4e95b6183920d40653681293d3f7c094e76d8cb98b481585c610ae34e5226f9abd37938875737400d0178bbc743c5c6727778d227010ab88bcac680d11c581fd215b33d2c2ddd3631de5a1848b76c9514780698727e7b53108c4d122aefd9a55174047beac7e06297fbd383218424e95978b7833389843c321476cd555c4e10561f7540769bc1c7bb444cb7b8c500813eef5a7f4d89ac0f315fcc7bd3b55c350c74685e81e30919ab32c5f33a8cea2518850768b99d311f310a19cf954b80b4c897928155705f4200d70a5039443cd2f484ebe936329e4b5c7540b47f4663f31c6dc535aeb0a360eb02a6d0890db24b31732acf59b7303405d798aa5527c274dc774349124b310194c16cac635a803523df22b35e4f66101807c61ec5aac6bc60e955568119b08c4163d3c4218750d4ca0c0a677b864e3b4beea934a745df18bb8dab289b561414b9494b25d116502a23d2bce210cd4b9cb2ffaf261d18ca277589c727be84555102b19fd2106f02e0151b6e29076483f5e416de08aafc9b5bbdf05c906c94a468bc219536b1d81c3ff6201220bb3efd564d77c5a1577c46762a0ea8c9feb0501f37628f078577bb9bcef2ac773d079bfa34c365c275bd936b5cc62ac7b2b5faba3a4250c1cbbc1d83443b790af42ba620a205877da585a55bf27f83853c8cf6f23a203039f0a519f1df2754fa0363f82cb10b39a71487ba10925460b8709239af8c060c492c1c374aa5ee06b5dd484e13a7f677c4758d639e4f5b94038acb8837ca4cb33f1a52a2d69821f676f6640532cc8aad7d2891416303609c1b2a3c35f532502211249791f9802902a729c87bc9bc3ba4741c8adadaab8353b92d839b65aea9aa37c5e04ec7842d92f32e627b6b259baa7192b840769e28dcf3780c6356fe5f35e1b0b7c85dc93e6ea0109f205fbd0b35955049217987200be27fb6347db7e15a4b0decbc2fb521357344061c65d0684650ca788501c12d2a644f6db7f82a1ce15f50b5c1cb3e7573b804aa2792113b1c6093b3992dac72bc470040e0c6427092fbd761881899e23547b4d9717b235c94b5827c5da3499fc49b20bbed1c755f535489d776e15e596c7d207dde4cef26261b5001673da163013b1d31c3e72419568010ed67b6d3f737dff3161e0687286ab1cf9c9ccd08a80a0708e87816391a90a17983c503045893b70f728b1b267502c1a5ef1c42309561efae324c43658197417f33196f25c1a52e4482e5605cf6426f2056b983b634a300126097589e89026f37306e0ab8b01c633296ed9db4e75dc330b631be7531f31e891dd3cadf2661d26ab13bb8581ceec3002e2a339d432473358cdc8752d2863d8fb976aa2a215e57dbe1670cde00c915572c098681051cea8c43c969c1f358c5fa0ea437e1751c9c5cf83d3a665b736802abc73d1a73c610f18e3baecf697fec172d23b28ee941869e2a3da0a98b0c4ce15f1a9b1331d4ab4045bf56fb9519eee602b5f12175915b0731cc152c6110b7771758715176993924aa13114b7a6d921f82c668f91784a9c5761530522c06c6eb873a8795c33c860332304d8ac16520790c30c4a7080516de898ba2b19764a0eb304198ef864670103b55b63b3b364cca943c4a38e43c18e0b373f7d9c8dc6ba4ea3c6489461af6644c2cf0c9d1d4095eff5c62c1a4b0217c66eda16eb15bb556160d6311640311520b3651e92678d75b0d3e596554ba6c10037e95c8ab2b457bd0bb4b484c33606c3a1980638483a5fd0a3ff9c9d3dfb742ed08fa2a704deb092e9c79d84378b60735b07a309584b5289d49cfc242543761c9f586a4ad4a99ab2968c4262899a5487aaa9056061e33c1325f29f8c395f12608e466c0d99c58321cc3e110953afbacf81240b9c5917dbe5794654839b1777b8eca888111b84fb7578172043830774316cdbba8159997931a82ad3fa23a81997b0c461c817920d44afd6aa7841599cbedc01aee34166649fdf4c4d28045147409585a07564167c685b26aee168bb59ad29e82e00f1abe0589440341242a20af77884af53a38ecb028f728c9bea9ac7849527079e0530305edbc02131aab7666f31b5a0df1647678b0b79b36d3b5c53ad0c8d8ca90e5da5a1b6726712b9be65fb185c34baff42544d0175e5e2cd8fc249343737c346c330a660b243197cd9c8e1448192164bf8f6b8018b871d0373b2b3518ec6652c596c182804c3053fa4d75afbe052e8285ab5e6c0aa742311b7201976a390e7004cc015cdb34c91229536a6c18150cccb90a02f32048647541eca49b0e498f9f1324a2832bcf41c9fe08a09754e7552c21df20ce9a610dcd0ca48a6ce949456a01b11a718229f32b699291defcc42b3c7100e226c637b2ad2a855dde8831d89b7c3139a3454386a813b1e14aa31088a436735b029b75d127ae1e917c8ca5f4b36c8f001ae897475d3e71b7e447571969fec2668877c7402ea8f4ee11d9a47bddcf04dedea15eb735125b74e79235e95f047cc9379eeb6ae0503458807783fa1254b949449ea2f8df39b3a5c1651f06a5cb528fec135e3cbc695a6cb32306e4026bf3ac326d117c1b7aa1e8f99ab40519f0c368513668f89104df510367b54415d1c257a621efb257d4d807a21937a4474867c2615f1e17fb6f87dbde557bcfa33dc751d6c39611d9329dcd593b444cd8f431e052b23e8428c9f393129eb403ee2025d01245c97d673a7c51a8703f10001", + "contents_initiator": "050171626974fd2106f0f9002f5e1e408d9aa90199fca791fc86e7eedd51fa56fb897954f9a30610855b4f5c737fbebb93618c3e6664fd400ed471e3243a63c8d287ef7f435719e540f24c789f4790340678ee90468f3e42586e98c5a42ff9f3fd8c255af6441a16ce2581ee8fd5970acf91e7a7497ba144990c2c8a84f37b5e65a238ffae62740f9e8c8fc309d43156a27dadebfdbe04a23553b3ea9779e86eac2b1cd1b1281bd486ce3e597d0aef96d3aae22660a3951a303afb7335978718e38387d68cc6f7eef0f8fb0b9b7a37df6a46a7eb27b1bf7dec788fc4e88ab252a573fde9dec0b693fbb00e6647db52d994252473861b218479179c1ab32e82e81ad71eaec92a2dafc2efb84ff11ce686fec244581d17cc0b91a9db6b6fe6c49e5919337d324981d023227e028600285ef795a449c69895e7dd96ef925365623a862d03ebd86ffa3fff2acb6b4165a73e030259ef0b94d0abc36a7a87ba2f12565c850af548e170d4927bce6de8b2e888ac7ee47266ba230629da59d6573ccb0651c0dcd5bb9926262a70ef9633a914bd57c8aaf4219d0cbe8713748070e93e33f5ac5aa937d9553e58451cc3a8699c940295bb3867e654a51887b0e82fc35eec05958503a256a19e97520e4a6d04a194c6c4d2e6d5abdad5c91d6aed8e9f5f9ef963ff92e334ef04b86b77b6e75a135173755508978831a831df77298cbb8ec4c99c00eb2696b473777532f3ea911ba03dd99e086cbb7a64c5a1c38154300fbe11e311599a2b6e1a14df63ec7cf2462b835c64b89cf6249aab2835865d78b2c16859acd0bbbd9481315d1a21d22b3cbb4f57cc32bbbb08bf6a7a7032899927ff1903c7aef6658b703b0663f4780c4c2927fbd279d32c05514e9c00d37754ceb4c98501de4334c6a35fd19159bfc571f42d1ca02343aa690da3a8f4a98874dfb9d02fb474b5ea399c820bec6334e186448236c7f809616c2df4ba1863f34f36770fa88b1f2d0dcb267f675ad9e990c5f6cd4e95a4f1fa5085a414ec200d218a34c399ca5b900a3b36084270019f1edddd97037951d60bf3c1524e5e17db214659f70188132e33f19c697fd708aa04d2262eb09c59040c0192795c53bae668442566e237bc23f0c8eec5b569cdee9522a698a312e8e5ddc788a39eead2bb8719b298f25c6045cf0c2cce2928c729c54c8d8925c8e043c566fd6b2dc9e725fd47b743d571e4299434849d4c7789d7f56ff01b3aef1dced7f601cfc427df3ebcf704aa722164cf2d3ce6491456ce0bc5a4279bec79e48e8222808ac0d51fa0e51e25de4a69942fc215b5a87aca441de5b4395863cddb03dc9c3ac6bc833ad08eae1e2438be5b5c4cce66bd7c1fcc59d7b927494ed4569fe6f5db9569edf8b65a5fd4bae073c699ee6c50200d5b5824542a40873a18d5b881ca86ccdb1c9ba5387095e7ffb03da4b3ddb5d2a0e5395a21ac4288105a2385fe1477eceac41e7f5d520e854f9511d62a13839395f6c37390ed8ca46b92ef285222b1addfa8a3a119b0e37366bcbc782693fb6e079ba4ded9a75a29016ff6af04ac1fc04bf6173e58cc922f7c7ac6060dea4b4169a420c7e80d8c034d118de811cf600102b235e1ef3fbb33dd9d8fe9d2a83c55192ae8c3a3476cd94bd6beae4e3f281b8d3477dc68d508db659b9472c8564f1877bf1a319e88cc304c8f0c0708a15787f75082ba35c5aa9b9bb01b1fe8f55a259429c0484f233b8c445112ebfbb3c77ad8b977a97497675a28e810df5e4cb905c7e840fcfa126a2da3d246626e73df022ba75563b696335ea8da1035d22634c1c029e00d808a5b1e4e663475a907c5f18e4c3ffc711ccca38030cf4f2dab4bcfb0a69d6ab835ab7f514f40e623508a4e4b9b98cc177000d002304b84978dff9db4ac1fdea8376839e339ce5daeb2a8918a06c540a6c43082aa3cb99473eeb6067ca8e7dbe31d60e7c37569da75e690fb79a55dea00ce872cba268eca78be5a9b367855a280097c1d9b345ad69040cfbec6fcfdc137c276d23aab7e8c81ccc658677454f394b1913f22d27c1f7f0be0c4bbd2a355a719de059e17e1efe3ff2b304d27779fe3780c30e8dc4c8352586db8de9a0d83fb69b0ab90241733ecedc94c3ddcb3c65012e9971b547d35bc7f47101049ddf77c278e373b184fcf0befbba38b628f3da097fab9ac37819a9c0a019093f8e398e7df873fc9c497f971f2763d9f", + "transcript_input": "0c80fda2ca53954371093ebf6c8a05459a8a7b191144bb9b2b45cfcf9325f65e12d1fc1ac3eb53bb888e25deaa0d76ca2ee327264a1b6c0c33f214bde668ff6de9455f0cabdb2176c839614c5b5b438eb0233819ff63197572b15b5657fc3664fae78cf87f8a1012873d9b7c30e4d11fedef245c7fe3a50cdcfc1a52a0fb5eccfd4c0cfd2106f0391892c89cc4fbd7cf2b9b8eb563a681446d51ac93006d179dc79417544f12432319ebc23d77385b047582e922ecdc6dd1359166fc6353f1cd13404723f8a35f202d570a71baf435f6c14bb0d748ae8809e290107f222eb3a12ac180999ba1afaaf642dbc9294a60ac6ecc02fc8a825d541cd5e723bdc9ce84fa97847009317c0c5ab348acd921eafb8730729329714b710c091b383249465a124534b3db4d451a77ede1b60cc308cd9578a2d22050324e0aa724efc14c24555c8f6a4258a998e6ba0230c22bdc267d5ca9780d603f91712533d96ea2899b9989688ba97597245c72412f73c83a471883061c627283c19e355a7ad80d29565401955987a2af908b9f1fa265b4a12f1ca34aa86027d79876a8c3b0766a5064637684a40497e962d8e5acee309b37215a458227ae09654023c5317a91955147e6880d8187053b40872ed8c8aac0a7d690794751940a379a0617a9c61c4d2ba5c4b36a7c9ad8cc1e5acca6db3b7f5b4a394ba87a03cbe036787a37a39dfbbb86d183d050054e83a61a3b9e5a17ba11c16862e35c6b5705f16143bd856996ec159a039f0600071434928151c2b7a4ced4b6a54237b17a32cf15116e2d03aaff7b1e07b0bcfa2c844f0932d15085739a7040937a8a62783518c6e63007c0ba10b280944db447296369aa495a84e3471fbaad4f50473240102509ce45726e26f95d0256035ce21a39c1430ca1884b5519bcf6833bfa65c3aa89677b5d18164a6230a34a26c204ba6bb3d162f42135e567c490b58d1dd1b5f808bcd8e029e734bcab762a49f8b015cc63dbe9b4dfb8446b31452a1a428fa0a5c7e499a62a620ac915f3d85ebc39ac944b2977c07898987b7c2771f9136e6d2b0aa42a6c391c8456e17aa6d6b1e5036ed3fb5f00475ac4808de28ba686c027b38862db4149c84a86395c05824947fa3876a867be2f10666a5c683b8b190ed841b473a22bb35949f74a9580ceb370284127c445c759d7ccc5e66a4bb950b4a54071f496c570f5a641991c705a9b3bf2131ab6254a120c3a5259687c256c7a0ba23ac234eab05f34423907b9d16cb76371c3233c74327729540a1a8bfaa786f324c4c446c2b5307534937e98804da8598c95a434e7b99ca2144bf753d3a2564685cd3fdb1574b472276b933b579a1f88a197c7ad6f1c02b3b7c6c8250855e15585f253b9174cf9700e5a762cc129cce48b4cb39588c74854e4c7735d085295735533a9674fb889c2e5c225f7206f383fd429c3b0a050c95950579b806f3c7324c022d253b254054236eb054200888ca93e5b2aa5df5aafd6840fbe780dd926624b0a22bebb443b9a033f33189fd5532d0750adf3c1a346164355210eab12876c1304da9898c2c8f5a475f766af2a092af3e31bd8e7407dc8a9faf096fda24ccb230337caa5e4a8aa0d497a50f59a08ab12437223506b92a0e9bd84f80865e81b3ffb671074cdf0d03e1537bc50a87b9433af26b10f01349c56373112dc0ccdc24b8d15b33237993732b9ff5b8a089b88e54102b7336f4b8ba6c5c6c6bca19a691592f65b57b4f47c8e2889e8d8c72bd29856d83a8687a46d6ccef7e9be6af75aa9367c97fbaac7251cd0cc444d81115824beddfb26c3a430788781c1dca72d96c4bf77a8b02812c792c3c472272fd25048d3230b494b6058326ec66d3a90a23747b30a7328cb8b8c4e95b6183920d40653681293d3f7c094e76d8cb98b481585c610ae34e5226f9abd37938875737400d0178bbc743c5c6727778d227010ab88bcac680d11c581fd215b33d2c2ddd3631de5a1848b76c9514780698727e7b53108c4d122aefd9a55174047beac7e06297fbd383218424e95978b7833389843c321476cd555c4e10561f7540769bc1c7bb444cb7b8c500813eef5a7f4d89ac0f315fcc7bd3b55c350c74685e81e30919ab32c5f33a8cea2518850768b99d311f310a19cf954b80b4c897928155705f4200d70a5039443cd2f484ebe936329e4b5c7540b47f4663f31c6dc535aeb0a360eb02a6d0890db24b31732acf59b7303405d798aa5527c274dc774349124b310194c16cac635a803523df22b35e4f66101807c61ec5aac6bc60e955568119b08c4163d3c4218750d4ca0c0a677b864e3b4beea934a745df18bb8dab289b561414b9494b25d116502a23d2bce210cd4b9cb2ffaf261d18ca277589c727be84555102b19fd2106f02e0151b6e29076483f5e416de08aafc9b5bbdf05c906c94a468bc219536b1d81c3ff6201220bb3efd564d77c5a1577c46762a0ea8c9feb0501f37628f078577bb9bcef2ac773d079bfa34c365c275bd936b5cc62ac7b2b5faba3a4250c1cbbc1d83443b790af42ba620a205877da585a55bf27f83853c8cf6f23a203039f0a519f1df2754fa0363f82cb10b39a71487ba10925460b8709239af8c060c492c1c374aa5ee06b5dd484e13a7f677c4758d639e4f5b94038acb8837ca4cb33f1a52a2d69821f676f6640532cc8aad7d2891416303609c1b2a3c35f532502211249791f9802902a729c87bc9bc3ba4741c8adadaab8353b92d839b65aea9aa37c5e04ec7842d92f32e627b6b259baa7192b840769e28dcf3780c6356fe5f35e1b0b7c85dc93e6ea0109f205fbd0b35955049217987200be27fb6347db7e15a4b0decbc2fb521357344061c65d0684650ca788501c12d2a644f6db7f82a1ce15f50b5c1cb3e7573b804aa2792113b1c6093b3992dac72bc470040e0c6427092fbd761881899e23547b4d9717b235c94b5827c5da3499fc49b20bbed1c755f535489d776e15e596c7d207dde4cef26261b5001673da163013b1d31c3e72419568010ed67b6d3f737dff3161e0687286ab1cf9c9ccd08a80a0708e87816391a90a17983c503045893b70f728b1b267502c1a5ef1c42309561efae324c43658197417f33196f25c1a52e4482e5605cf6426f2056b983b634a300126097589e89026f37306e0ab8b01c633296ed9db4e75dc330b631be7531f31e891dd3cadf2661d26ab13bb8581ceec3002e2a339d432473358cdc8752d2863d8fb976aa2a215e57dbe1670cde00c915572c098681051cea8c43c969c1f358c5fa0ea437e1751c9c5cf83d3a665b736802abc73d1a73c610f18e3baecf697fec172d23b28ee941869e2a3da0a98b0c4ce15f1a9b1331d4ab4045bf56fb9519eee602b5f12175915b0731cc152c6110b7771758715176993924aa13114b7a6d921f82c668f91784a9c5761530522c06c6eb873a8795c33c860332304d8ac16520790c30c4a7080516de898ba2b19764a0eb304198ef864670103b55b63b3b364cca943c4a38e43c18e0b373f7d9c8dc6ba4ea3c6489461af6644c2cf0c9d1d4095eff5c62c1a4b0217c66eda16eb15bb556160d6311640311520b3651e92678d75b0d3e596554ba6c10037e95c8ab2b457bd0bb4b484c33606c3a1980638483a5fd0a3ff9c9d3dfb742ed08fa2a704deb092e9c79d84378b60735b07a309584b5289d49cfc242543761c9f586a4ad4a99ab2968c4262899a5487aaa9056061e33c1325f29f8c395f12608e466c0d99c58321cc3e110953afbacf81240b9c5917dbe5794654839b1777b8eca888111b84fb7578172043830774316cdbba8159997931a82ad3fa23a81997b0c461c817920d44afd6aa7841599cbedc01aee34166649fdf4c4d28045147409585a07564167c685b26aee168bb59ad29e82e00f1abe0589440341242a20af77884af53a38ecb028f728c9bea9ac7849527079e0530305edbc02131aab7666f31b5a0df1647678b0b79b36d3b5c53ad0c8d8ca90e5da5a1b6726712b9be65fb185c34baff42544d0175e5e2cd8fc249343737c346c330a660b243197cd9c8e1448192164bf8f6b8018b871d0373b2b3518ec6652c596c182804c3053fa4d75afbe052e8285ab5e6c0aa742311b7201976a390e7004cc015cdb34c91229536a6c18150cccb90a02f32048647541eca49b0e498f9f1324a2832bcf41c9fe08a09754e7552c21df20ce9a610dcd0ca48a6ce949456a01b11a718229f32b699291defcc42b3c7100e226c637b2ad2a855dde8831d89b7c3139a3454386a813b1e14aa31088a436735b029b75d127ae1e917c8ca5f4b36c8f001ae897475d3e71b7e447571969fec2668877c7402ea8f4ee11d9a47bddcf04dedea15eb735125b74e79235e95f047cc9379eeb6ae0503458807783fa1254b949449ea2f8df39b3a5c1651f06a5cb528fec135e3cbc695a6cb32306e4026bf3ac326d117c1b7aa1e8f99ab40519f0c368513668f89104df510367b54415d1c257a621efb257d4d807a21937a4474867c2615f1e17fb6f87dbde557bcfa33dc751d6c39611d9329dcd593b444cd8f431e052b23e8428c9f393129eb403ee2025d01245c97d673a7c51a8703f10001fd2a06050171626974fd2106f0f9002f5e1e408d9aa90199fca791fc86e7eedd51fa56fb897954f9a30610855b4f5c737fbebb93618c3e6664fd400ed471e3243a63c8d287ef7f435719e540f24c789f4790340678ee90468f3e42586e98c5a42ff9f3fd8c255af6441a16ce2581ee8fd5970acf91e7a7497ba144990c2c8a84f37b5e65a238ffae62740f9e8c8fc309d43156a27dadebfdbe04a23553b3ea9779e86eac2b1cd1b1281bd486ce3e597d0aef96d3aae22660a3951a303afb7335978718e38387d68cc6f7eef0f8fb0b9b7a37df6a46a7eb27b1bf7dec788fc4e88ab252a573fde9dec0b693fbb00e6647db52d994252473861b218479179c1ab32e82e81ad71eaec92a2dafc2efb84ff11ce686fec244581d17cc0b91a9db6b6fe6c49e5919337d324981d023227e028600285ef795a449c69895e7dd96ef925365623a862d03ebd86ffa3fff2acb6b4165a73e030259ef0b94d0abc36a7a87ba2f12565c850af548e170d4927bce6de8b2e888ac7ee47266ba230629da59d6573ccb0651c0dcd5bb9926262a70ef9633a914bd57c8aaf4219d0cbe8713748070e93e33f5ac5aa937d9553e58451cc3a8699c940295bb3867e654a51887b0e82fc35eec05958503a256a19e97520e4a6d04a194c6c4d2e6d5abdad5c91d6aed8e9f5f9ef963ff92e334ef04b86b77b6e75a135173755508978831a831df77298cbb8ec4c99c00eb2696b473777532f3ea911ba03dd99e086cbb7a64c5a1c38154300fbe11e311599a2b6e1a14df63ec7cf2462b835c64b89cf6249aab2835865d78b2c16859acd0bbbd9481315d1a21d22b3cbb4f57cc32bbbb08bf6a7a7032899927ff1903c7aef6658b703b0663f4780c4c2927fbd279d32c05514e9c00d37754ceb4c98501de4334c6a35fd19159bfc571f42d1ca02343aa690da3a8f4a98874dfb9d02fb474b5ea399c820bec6334e186448236c7f809616c2df4ba1863f34f36770fa88b1f2d0dcb267f675ad9e990c5f6cd4e95a4f1fa5085a414ec200d218a34c399ca5b900a3b36084270019f1edddd97037951d60bf3c1524e5e17db214659f70188132e33f19c697fd708aa04d2262eb09c59040c0192795c53bae668442566e237bc23f0c8eec5b569cdee9522a698a312e8e5ddc788a39eead2bb8719b298f25c6045cf0c2cce2928c729c54c8d8925c8e043c566fd6b2dc9e725fd47b743d571e4299434849d4c7789d7f56ff01b3aef1dced7f601cfc427df3ebcf704aa722164cf2d3ce6491456ce0bc5a4279bec79e48e8222808ac0d51fa0e51e25de4a69942fc215b5a87aca441de5b4395863cddb03dc9c3ac6bc833ad08eae1e2438be5b5c4cce66bd7c1fcc59d7b927494ed4569fe6f5db9569edf8b65a5fd4bae073c699ee6c50200d5b5824542a40873a18d5b881ca86ccdb1c9ba5387095e7ffb03da4b3ddb5d2a0e5395a21ac4288105a2385fe1477eceac41e7f5d520e854f9511d62a13839395f6c37390ed8ca46b92ef285222b1addfa8a3a119b0e37366bcbc782693fb6e079ba4ded9a75a29016ff6af04ac1fc04bf6173e58cc922f7c7ac6060dea4b4169a420c7e80d8c034d118de811cf600102b235e1ef3fbb33dd9d8fe9d2a83c55192ae8c3a3476cd94bd6beae4e3f281b8d3477dc68d508db659b9472c8564f1877bf1a319e88cc304c8f0c0708a15787f75082ba35c5aa9b9bb01b1fe8f55a259429c0484f233b8c445112ebfbb3c77ad8b977a97497675a28e810df5e4cb905c7e840fcfa126a2da3d246626e73df022ba75563b696335ea8da1035d22634c1c029e00d808a5b1e4e663475a907c5f18e4c3ffc711ccca38030cf4f2dab4bcfb0a69d6ab835ab7f514f40e623508a4e4b9b98cc177000d002304b84978dff9db4ac1fdea8376839e339ce5daeb2a8918a06c540a6c43082aa3cb99473eeb6067ca8e7dbe31d60e7c37569da75e690fb79a55dea00ce872cba268eca78be5a9b367855a280097c1d9b345ad69040cfbec6fcfdc137c276d23aab7e8c81ccc658677454f394b1913f22d27c1f7f0be0c4bbd2a355a719de059e17e1efe3ff2b304d27779fe3780c30e8dc4c8352586db8de9a0d83fb69b0ab90241733ecedc94c3ddcb3c65012e9971b547d35bc7f47101049ddf77c278e373b184fcf0befbba38b628f3da097fab9ac37819a9c0a019093f8e398e7df873fc9c497f971f2763d9f", + "transcript_hash": "5d1a7fb607f5d333c32e7c392994ec07f7c9dad1452331c65baaf3411fff1c96", + "salt": "716269745f76325f6879627269645f6d6c6b656d31303234444f24a8", + "ikm": "5c673cb7a5b0f773e77a240a65a089b914d26e08092a2eb87d5f6dc82039d79ac1608e04a8b9a552945aefe36cef26784e1b20d96516f9c5db47b8a9847062d35d1a7fb607f5d333c32e7c392994ec07f7c9dad1452331c65baaf3411fff1c96", + "prk": "fa8b0e64aba8e1924e3c5d05a7e1a711f9515b54c35468d0ccfd5b75177eb605", + "initiator_L": "b0ad68d1bf92b8bb7b157917f4e649e53b397c5c26292dbe2ba8bf32f09e5d47", + "initiator_P": "9b73c730d2eb2bb958b3034466419e0d9cafc875cafe2b05d99608ff790b967f", + "responder_L": "635da4c1f2ec229cda0e468a26220643a4cd863a0260537390fd59bad00b725f", + "responder_P": "edefe23ec9ccf6293555169e7454dbca231adc295f503197e7e483a158d35394", + "session_id": "7bad230ffe2b9a7e25ba58653ab6367ecc0b66854ea28e17874653ffce5a9bbc", + "initiator_confirmation": "a27114b9c378240c62f7c79420f9631e7858bb77", + "responder_confirmation": "7851573884e1f1f5bfd00def86bd4fb3b866d0a9", + "initiator_first_contents": "120123456789abcdef", + "initiator_first_packet": "c00884e7caa1e7a4a7247257bc392f10e0bf8d3aa4033acc50eec55125", + "responder_first_contents": "130123456789abcdef", + "responder_first_packet": "94af7850890ac6b2c548297f7254d529851380ff39f54e8f6fb6300e6f" + }, + { + "comment": "Mainnet; the responder's contents are its offer followed by an unknown 0xf1 record with a 65,535-byte zero payload, so that record's length (65,536) and the responder's contents length in the transcript (67,113) both use the 0xfe CompactSize form.", + "chain": "main", + "magic": "444f24a8", + "initiator_privkey": "f58415cb1b0141b0be1f891c12fd7184900ad751e4fcbb8b6436ff2a984a858e", + "initiator_ellswift": "00867888931969393e24c82320dce065753ed7cb7d4d429564beda218d92bb1054439ce4f81947b99c7c4ba8b561973fb630f5e90b17c64f1010014e0ee05274", + "responder_privkey": "9b97db6e8dcf81f31fa28d52106e27e6da08e96191137acc3db09222e0a88156", + "responder_ellswift": "22cc1b75b5e7aa62a229cb2ce96c8f9db05015f88ae40ca56161c6d05a7a482ed9ef2868a50f66e055705409098a11b58b933e7c65347239df5958c64576a4de", + "ss_ecdh": "bf3f6c4be548a2afbf33f9b33c2d0b52b3f133e87ca0dc636281ccc833ceab8f", + "ecdh_session_id": "b53d9d41bd2c8036d429d3ba1c046b6efc1ac1543904a246e4e410ea3dec7eb3", + "initiator_garbage_terminator": "8b2db9f6d682634c80d6ad8ece5f175e", + "responder_garbage_terminator": "b5ab29e9ac027058fabe3dbbc9f48b77", + "mlkem_keygen_seed": "cc4b6414cef160474eec6b225c5dc019f240b2ed6753f7f04d80cc5b587ecaf3666c614a9f7d6c8d56a5b7ca074ce15306f170164f0208888731e623824b7ec3", + "mlkem_encaps_m": "8b28bd873ef412198de56f5fdac093e39f9b01dbe2a8392811993df620a3a8cb", + "ek": "d9f1814882bf6a3513b9c69b993535a6935a7903132fb67b85da8ad3c26db6f46c84fb078b348d52e75cc6421bdef50220c10b81b132c5c92944376f5132382bf0781c18a28b132985241a6a358536e6a45df18709ecacc7121116fabb287c638e815c9e7bb9f46b0fdc972be0aca13f0974b517202aa47b23c64c8a719ecc901df9f4bc309a3615e092a82b21477a7f900b5c0857694fdb25b44b0fde954a9f75658665904183bdab66c455b951f46a57dfdac732e6cf03048cefb813b4f71795fbba857013b7d6ac61f9875b801aebf298c8b0633db092ea4b3829a93870c167d5081bb7aaa745350ded309a46e1325b87afc346009781b81626547eab3b98c6664c19b2438bb8862485ab57836f1908782bc755ea94e5375a4b80139270c4bb332a6f5c2eaec03b890008504b2af26505b104b72ae742f430132440cca2abc65963bb9709770659623d8239710a5beac68f498a0a2a091ef0f78e19cc0e42222674d8b4fba57dde3301be234f527aa31c53be2be18649b17a047caff4e57a0c93ba8a3823d441c3f1901808c19b1871afec811a9d583d6f1516640c09dfd16e25ab35efb82a419bcbfdd225d3b57760f12b8d56a2fdac0f52b05526f86ce9c73bd1dcadeaaa6056b36fc70495c66a9cecda24f6365521246bd534a77f3c898c382397200fb8530546ec240814504e0971b9b990768434932781c7035942237f4af92aaf881d04fc3d90960ed0a2c278e385155a01712c33590582a6250953e931a9f03f0493272a09716923c8bb8b41ddd762306c85e3a12d66ccbe5f271f943659bc6288f67b5599a2ac4e7cc621460cd8aa5d57013c916854589a0bcb076646896d09f677a855818ae89a3655b327b4b9a63ac0429290d5d4762ef95c5d0969276a10b19930a3f69e849abaf0d97f78893ff40a598253300e54c6b6576e36079c278b5d697401c77049a31a44eee245440b397e2398b9208a19283b8e817d679c2ee4295411f7be4c6bc2b7329420d939c30400645c7baf8cc757fa84a815c49ec7bb9ea19d9dcc0ef9d3562db79f0f810ddd14690d51276df631388a51e54116d0d247e88b140b658e7f7203523c96a05053ced944ed0890fdfa3d23d46c9d41c5b5d34bbc1b862d2b19c6138e4a46866e313244fb6fc3706ba5ac692e6610fc800b2ae57eba9499f0295b83eaae1bf03e5ba75626688f5f7b6b25c430cec60c89f445653042e6746ac3f173d18a30b2a022555001767051c948cc9392b95a5234d5c1c33629648be9238de63d1a15a2182149c89b84727961ab046ad6d7639e9032bca94e480169b5f22b438418a3c926bd944de38750b0f1a802b9a92c6c2119553a88751bb48b65f451710bd7167a31b13ad81cd36631df339b292309bef1a719080cc72abe9917465757bcb5018a84f6ad3b601c018a3b5cab962cc7b49321bb0517a5bc3a2889272b52b10d629b1316bc17dd2b323a7468f8e52c2a43b8b736bbe116327522a2975aa5bfb8b5da976c12f3beafb36210b273a967024f6b030b98139ca49b484ac2937a50374670eafc1fe4078d9d36874fa5b462aa5e6b32b4d66a948b86bcbeb706563042034227fe521149a4b45fcc86959c09f8a1a333712347102dcfcb3158280e24d0af4251c6a5925583a03cb4445b3b595932bacf43579ae6d797d56c5261567739fa8c9ce3178b3501be2121f9b249ab35bdc26008321b36d5365f6e9530156267493b8fd60c04ed386c96ec58345990b540c59179035593a454d63d43a97888d2ade3c4b4704b5b14940585b23ae8b022a8e17ae2f886bf834bf3190936790f1b856d92588476f140c9b32fa906c0cfa21daba5498197beecd17e768475ec296b675c7bbcb9b5920900d94381305552b5e58bbe203fc7b849322310eb5c477d66097e913a532c5dd79c123ba21508fccb5ab8a81c4459bb74b3d9c46143d0616ac0c196609d22a8903c5639d17aa04cd0228a7680d3101d00a044e66b4bfbc148fca3a773d92eafc9489d6051ff591f931cca910585e2299bd5f6af8b72a813f98642a28f9324c90b3403001329304ca0e15068574c617b69006e1b5d21d8cad3168669331e7d3445fd5a8cd232a4de426c87a090304c0dc16680353ac7bab44cbfa1c916884f5e917e18993fe4c778f5193cec47e5af8d3ae1b706f4b31bdf47234de201b4583d57532cec513dc3ed", + "ct": "14d9b16a1a42a50c6faf6efc3aaab564575de5e587d0c06dd158b93ed1a795a32ee291c56a4ba254d805f7546691514206735a80ea73591bc3a20040f5979a689f083ba1cb1f2ba204b25a94ce83bf48854e05203de1137582b4e482165b4b462a63ec53d5dc2291dc61da93de28691edf28fe72ffbda9361c4b73c555b1165dae61557500f32c5247ab886e0482a2fffe0d1d48abb3f732089df546ecf7b5a53e4803fc39aea2d2b5ebe31bd1c4e90db7de6d239a025f359dd7ea8533ecb131084ed0d35461b2a14622af9eb437a327f36a842e3178c1f32ad13b95e314f2758e0cb1059ab74ba366b6f3a87d79645c7aee5822c9b75ea47ce6654ac61637a608913571093c282aa7e77ce2612a073e4cf40ceba3958b592d9e32236a0110362256230368f9560596be51003596013ed48891f57989eebb7b009a0eac1dd2556af27c3305d571ed6d252157dc8a17924b2e6a80febe0ff3a29c24be8b2ca8a56505f7c55901b8a602488375b1b34d63b1ba167199dfb51c56bc025b2be55cdc9b136702d06eb71946cd4e710c51cd907c59243ababe45861a4bd7a6d097e6e8ecf5a8d60ee5bf5570bda3e1ecb873bbc119695268f2203d08e6f786a7d7af68207a9833a37bd4c65c079bf21ebcf22ea028c01547378a11a258ebdb1279014fd8d527923b5cf93c374d5c990d18bfd5eba7c5b14bfa445ad69667d1c38ca3be167657d42aa621908e9cba43a316e3c000f5c2d8802433210e3bf3c6cb7df77097e88ae0c8d23cf8a73582fa351d60b77037b9fbadf683a12fc30473493f20f8262ca2383f000a97b7adc14174167678ed0b3b8559fd528aefc320964156591cade0c9dac72b36c32f9a47147e6285a52f491a4b6c6150c9e16b63b77eae68ccf7add5502593075fa20aaa16f88d498e55d56c69ab61b83b7aa900c09a9f6d497e5711553ad9962abf0cb0f4f99830cdd6a9a9ae6f54d9e7dd8bbd533acc4a6233bbd49d6a2a2159dcd59c9613122ce4684ed49357125141a8c473aa85d31d3624c8bd0d44989f61ef9f0101490cca8be88ef630ab2f52906fca2b2981c9cfdf4a82db342d5534d33313311044f7a8034f604418d9694760a3fb8ab98b12fa0e83af9c2ce43f9dfe9b63dff9cf9c209f97a1c526660e7a39b4082966a53825b67ddc22afbb4e69bb90138bfc834c2b2c6380f5666db64d153519e33296ebbe577f6b98a13c3d54688754f9ace99168cd7bee489055fb6e50b4ac9104896d8867749d47964bb597fa3b29a4457a431879b96f633f59a23e663dc8820ad97ac85e6cc722d3bb93c780c4a7b909b1a2c9a5745cefd55bde2383d1e9ed1104923814b6b5b71bb3be9d1215c49f0120fd7e04db1d8b0921f209ccbd404f0d9bb69d754717536e402fd19e261ed162f0d98bd1f0f95e3ee720b21e9554bab58445c15d8bc99f6afcaa63ed40641feca1ea820a8de86b5e7f3da3fcadfa7d111242695f10180db0c72607a342b63c5917ca4d7aea3e8ccdb511ff487eaa01bad8c0ce0f6ac3060946b109095edc01094fa943cf5271097c7589efbad0c8ab661b8f21c0880567f160f6ddada20c109dbd2ff0fd9564638a0593bc3d03149c5de51053f73fb1717789f51ff2cadfa525536667c725a649b6bfaeb6b8859257792dbf0400cdb16bfd55c5f15fa6a33bd2fe5667b61c703706ead079dfba5b9db7069de1ecba6ee577273af84bbba8af7d494a2645e0999d46fa305c534a21b3b8493e13ec2a33f608e7748d87214d6a06a1f7a286287a3b0d9b726e2ad20a44062726ffdfa15ab839770ac3466237d234149fa8007ec336d77fdc73d357e0c696f94deae9d83e8836e5cff457ad98e11e173ec929e5daee3beea54e71f927dbd77b115a136be17a5596d1c2ff43a35aa3e941654264a59ff4b26aae2bd790f6d66b4d0570648ca6b00a93c6918a9d01af9e115bd1d2de88035538f2477b1a723da647c77ea9b36ee7795737d779bfa9caeaf2dbaff42b7e22d3d3a04677888074db0fea004eccb48ccb5df257248d10b940683c132bd540c107cbcfcc9c4a28c080cf4b213938ee30078f6b048709a3dbd85eec4e317be3b43cedbf1af4e259f39d4373b6c6608f23866c7462d80f70a305332b6aefb6e07dd688edf4248e3b7a95d36c0be7025397c8229b3e33c361cebf215470154dba18acbc7d72d4e1c651409b01e9", + "ss_mlkem": "50124a2dd20a655c9d431594979a253093dc976e424ed1076e09cced7ef06941", + "contents_responder": "fd2106f0d9f1814882bf6a3513b9c69b993535a6935a7903132fb67b85da8ad3c26db6f46c84fb078b348d52e75cc6421bdef50220c10b81b132c5c92944376f5132382bf0781c18a28b132985241a6a358536e6a45df18709ecacc7121116fabb287c638e815c9e7bb9f46b0fdc972be0aca13f0974b517202aa47b23c64c8a719ecc901df9f4bc309a3615e092a82b21477a7f900b5c0857694fdb25b44b0fde954a9f75658665904183bdab66c455b951f46a57dfdac732e6cf03048cefb813b4f71795fbba857013b7d6ac61f9875b801aebf298c8b0633db092ea4b3829a93870c167d5081bb7aaa745350ded309a46e1325b87afc346009781b81626547eab3b98c6664c19b2438bb8862485ab57836f1908782bc755ea94e5375a4b80139270c4bb332a6f5c2eaec03b890008504b2af26505b104b72ae742f430132440cca2abc65963bb9709770659623d8239710a5beac68f498a0a2a091ef0f78e19cc0e42222674d8b4fba57dde3301be234f527aa31c53be2be18649b17a047caff4e57a0c93ba8a3823d441c3f1901808c19b1871afec811a9d583d6f1516640c09dfd16e25ab35efb82a419bcbfdd225d3b57760f12b8d56a2fdac0f52b05526f86ce9c73bd1dcadeaaa6056b36fc70495c66a9cecda24f6365521246bd534a77f3c898c382397200fb8530546ec240814504e0971b9b990768434932781c7035942237f4af92aaf881d04fc3d90960ed0a2c278e385155a01712c33590582a6250953e931a9f03f0493272a09716923c8bb8b41ddd762306c85e3a12d66ccbe5f271f943659bc6288f67b5599a2ac4e7cc621460cd8aa5d57013c916854589a0bcb076646896d09f677a855818ae89a3655b327b4b9a63ac0429290d5d4762ef95c5d0969276a10b19930a3f69e849abaf0d97f78893ff40a598253300e54c6b6576e36079c278b5d697401c77049a31a44eee245440b397e2398b9208a19283b8e817d679c2ee4295411f7be4c6bc2b7329420d939c30400645c7baf8cc757fa84a815c49ec7bb9ea19d9dcc0ef9d3562db79f0f810ddd14690d51276df631388a51e54116d0d247e88b140b658e7f7203523c96a05053ced944ed0890fdfa3d23d46c9d41c5b5d34bbc1b862d2b19c6138e4a46866e313244fb6fc3706ba5ac692e6610fc800b2ae57eba9499f0295b83eaae1bf03e5ba75626688f5f7b6b25c430cec60c89f445653042e6746ac3f173d18a30b2a022555001767051c948cc9392b95a5234d5c1c33629648be9238de63d1a15a2182149c89b84727961ab046ad6d7639e9032bca94e480169b5f22b438418a3c926bd944de38750b0f1a802b9a92c6c2119553a88751bb48b65f451710bd7167a31b13ad81cd36631df339b292309bef1a719080cc72abe9917465757bcb5018a84f6ad3b601c018a3b5cab962cc7b49321bb0517a5bc3a2889272b52b10d629b1316bc17dd2b323a7468f8e52c2a43b8b736bbe116327522a2975aa5bfb8b5da976c12f3beafb36210b273a967024f6b030b98139ca49b484ac2937a50374670eafc1fe4078d9d36874fa5b462aa5e6b32b4d66a948b86bcbeb706563042034227fe521149a4b45fcc86959c09f8a1a333712347102dcfcb3158280e24d0af4251c6a5925583a03cb4445b3b595932bacf43579ae6d797d56c5261567739fa8c9ce3178b3501be2121f9b249ab35bdc26008321b36d5365f6e9530156267493b8fd60c04ed386c96ec58345990b540c59179035593a454d63d43a97888d2ade3c4b4704b5b14940585b23ae8b022a8e17ae2f886bf834bf3190936790f1b856d92588476f140c9b32fa906c0cfa21daba5498197beecd17e768475ec296b675c7bbcb9b5920900d94381305552b5e58bbe203fc7b849322310eb5c477d66097e913a532c5dd79c123ba21508fccb5ab8a81c4459bb74b3d9c46143d0616ac0c196609d22a8903c5639d17aa04cd0228a7680d3101d00a044e66b4bfbc148fca3a773d92eafc9489d6051ff591f931cca910585e2299bd5f6af8b72a813f98642a28f9324c90b3403001329304ca0e15068574c617b69006e1b5d21d8cad3168669331e7d3445fd5a8cd232a4de426c87a090304c0dc16680353ac7bab44cbfa1c916884f5e917e18993fe4c778f5193cec47e5af8d3ae1b706f4b31bdf47234de201b4583d57532cec513dc3edfe00000100f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "contents_initiator": "fd2106f014d9b16a1a42a50c6faf6efc3aaab564575de5e587d0c06dd158b93ed1a795a32ee291c56a4ba254d805f7546691514206735a80ea73591bc3a20040f5979a689f083ba1cb1f2ba204b25a94ce83bf48854e05203de1137582b4e482165b4b462a63ec53d5dc2291dc61da93de28691edf28fe72ffbda9361c4b73c555b1165dae61557500f32c5247ab886e0482a2fffe0d1d48abb3f732089df546ecf7b5a53e4803fc39aea2d2b5ebe31bd1c4e90db7de6d239a025f359dd7ea8533ecb131084ed0d35461b2a14622af9eb437a327f36a842e3178c1f32ad13b95e314f2758e0cb1059ab74ba366b6f3a87d79645c7aee5822c9b75ea47ce6654ac61637a608913571093c282aa7e77ce2612a073e4cf40ceba3958b592d9e32236a0110362256230368f9560596be51003596013ed48891f57989eebb7b009a0eac1dd2556af27c3305d571ed6d252157dc8a17924b2e6a80febe0ff3a29c24be8b2ca8a56505f7c55901b8a602488375b1b34d63b1ba167199dfb51c56bc025b2be55cdc9b136702d06eb71946cd4e710c51cd907c59243ababe45861a4bd7a6d097e6e8ecf5a8d60ee5bf5570bda3e1ecb873bbc119695268f2203d08e6f786a7d7af68207a9833a37bd4c65c079bf21ebcf22ea028c01547378a11a258ebdb1279014fd8d527923b5cf93c374d5c990d18bfd5eba7c5b14bfa445ad69667d1c38ca3be167657d42aa621908e9cba43a316e3c000f5c2d8802433210e3bf3c6cb7df77097e88ae0c8d23cf8a73582fa351d60b77037b9fbadf683a12fc30473493f20f8262ca2383f000a97b7adc14174167678ed0b3b8559fd528aefc320964156591cade0c9dac72b36c32f9a47147e6285a52f491a4b6c6150c9e16b63b77eae68ccf7add5502593075fa20aaa16f88d498e55d56c69ab61b83b7aa900c09a9f6d497e5711553ad9962abf0cb0f4f99830cdd6a9a9ae6f54d9e7dd8bbd533acc4a6233bbd49d6a2a2159dcd59c9613122ce4684ed49357125141a8c473aa85d31d3624c8bd0d44989f61ef9f0101490cca8be88ef630ab2f52906fca2b2981c9cfdf4a82db342d5534d33313311044f7a8034f604418d9694760a3fb8ab98b12fa0e83af9c2ce43f9dfe9b63dff9cf9c209f97a1c526660e7a39b4082966a53825b67ddc22afbb4e69bb90138bfc834c2b2c6380f5666db64d153519e33296ebbe577f6b98a13c3d54688754f9ace99168cd7bee489055fb6e50b4ac9104896d8867749d47964bb597fa3b29a4457a431879b96f633f59a23e663dc8820ad97ac85e6cc722d3bb93c780c4a7b909b1a2c9a5745cefd55bde2383d1e9ed1104923814b6b5b71bb3be9d1215c49f0120fd7e04db1d8b0921f209ccbd404f0d9bb69d754717536e402fd19e261ed162f0d98bd1f0f95e3ee720b21e9554bab58445c15d8bc99f6afcaa63ed40641feca1ea820a8de86b5e7f3da3fcadfa7d111242695f10180db0c72607a342b63c5917ca4d7aea3e8ccdb511ff487eaa01bad8c0ce0f6ac3060946b109095edc01094fa943cf5271097c7589efbad0c8ab661b8f21c0880567f160f6ddada20c109dbd2ff0fd9564638a0593bc3d03149c5de51053f73fb1717789f51ff2cadfa525536667c725a649b6bfaeb6b8859257792dbf0400cdb16bfd55c5f15fa6a33bd2fe5667b61c703706ead079dfba5b9db7069de1ecba6ee577273af84bbba8af7d494a2645e0999d46fa305c534a21b3b8493e13ec2a33f608e7748d87214d6a06a1f7a286287a3b0d9b726e2ad20a44062726ffdfa15ab839770ac3466237d234149fa8007ec336d77fdc73d357e0c696f94deae9d83e8836e5cff457ad98e11e173ec929e5daee3beea54e71f927dbd77b115a136be17a5596d1c2ff43a35aa3e941654264a59ff4b26aae2bd790f6d66b4d0570648ca6b00a93c6918a9d01af9e115bd1d2de88035538f2477b1a723da647c77ea9b36ee7795737d779bfa9caeaf2dbaff42b7e22d3d3a04677888074db0fea004eccb48ccb5df257248d10b940683c132bd540c107cbcfcc9c4a28c080cf4b213938ee30078f6b048709a3dbd85eec4e317be3b43cedbf1af4e259f39d4373b6c6608f23866c7462d80f70a305332b6aefb6e07dd688edf4248e3b7a95d36c0be7025397c8229b3e33c361cebf215470154dba18acbc7d72d4e1c651409b01e9", + "transcript_input": "00867888931969393e24c82320dce065753ed7cb7d4d429564beda218d92bb1054439ce4f81947b99c7c4ba8b561973fb630f5e90b17c64f1010014e0ee0527422cc1b75b5e7aa62a229cb2ce96c8f9db05015f88ae40ca56161c6d05a7a482ed9ef2868a50f66e055705409098a11b58b933e7c65347239df5958c64576a4defe29060100fd2106f0d9f1814882bf6a3513b9c69b993535a6935a7903132fb67b85da8ad3c26db6f46c84fb078b348d52e75cc6421bdef50220c10b81b132c5c92944376f5132382bf0781c18a28b132985241a6a358536e6a45df18709ecacc7121116fabb287c638e815c9e7bb9f46b0fdc972be0aca13f0974b517202aa47b23c64c8a719ecc901df9f4bc309a3615e092a82b21477a7f900b5c0857694fdb25b44b0fde954a9f75658665904183bdab66c455b951f46a57dfdac732e6cf03048cefb813b4f71795fbba857013b7d6ac61f9875b801aebf298c8b0633db092ea4b3829a93870c167d5081bb7aaa745350ded309a46e1325b87afc346009781b81626547eab3b98c6664c19b2438bb8862485ab57836f1908782bc755ea94e5375a4b80139270c4bb332a6f5c2eaec03b890008504b2af26505b104b72ae742f430132440cca2abc65963bb9709770659623d8239710a5beac68f498a0a2a091ef0f78e19cc0e42222674d8b4fba57dde3301be234f527aa31c53be2be18649b17a047caff4e57a0c93ba8a3823d441c3f1901808c19b1871afec811a9d583d6f1516640c09dfd16e25ab35efb82a419bcbfdd225d3b57760f12b8d56a2fdac0f52b05526f86ce9c73bd1dcadeaaa6056b36fc70495c66a9cecda24f6365521246bd534a77f3c898c382397200fb8530546ec240814504e0971b9b990768434932781c7035942237f4af92aaf881d04fc3d90960ed0a2c278e385155a01712c33590582a6250953e931a9f03f0493272a09716923c8bb8b41ddd762306c85e3a12d66ccbe5f271f943659bc6288f67b5599a2ac4e7cc621460cd8aa5d57013c916854589a0bcb076646896d09f677a855818ae89a3655b327b4b9a63ac0429290d5d4762ef95c5d0969276a10b19930a3f69e849abaf0d97f78893ff40a598253300e54c6b6576e36079c278b5d697401c77049a31a44eee245440b397e2398b9208a19283b8e817d679c2ee4295411f7be4c6bc2b7329420d939c30400645c7baf8cc757fa84a815c49ec7bb9ea19d9dcc0ef9d3562db79f0f810ddd14690d51276df631388a51e54116d0d247e88b140b658e7f7203523c96a05053ced944ed0890fdfa3d23d46c9d41c5b5d34bbc1b862d2b19c6138e4a46866e313244fb6fc3706ba5ac692e6610fc800b2ae57eba9499f0295b83eaae1bf03e5ba75626688f5f7b6b25c430cec60c89f445653042e6746ac3f173d18a30b2a022555001767051c948cc9392b95a5234d5c1c33629648be9238de63d1a15a2182149c89b84727961ab046ad6d7639e9032bca94e480169b5f22b438418a3c926bd944de38750b0f1a802b9a92c6c2119553a88751bb48b65f451710bd7167a31b13ad81cd36631df339b292309bef1a719080cc72abe9917465757bcb5018a84f6ad3b601c018a3b5cab962cc7b49321bb0517a5bc3a2889272b52b10d629b1316bc17dd2b323a7468f8e52c2a43b8b736bbe116327522a2975aa5bfb8b5da976c12f3beafb36210b273a967024f6b030b98139ca49b484ac2937a50374670eafc1fe4078d9d36874fa5b462aa5e6b32b4d66a948b86bcbeb706563042034227fe521149a4b45fcc86959c09f8a1a333712347102dcfcb3158280e24d0af4251c6a5925583a03cb4445b3b595932bacf43579ae6d797d56c5261567739fa8c9ce3178b3501be2121f9b249ab35bdc26008321b36d5365f6e9530156267493b8fd60c04ed386c96ec58345990b540c59179035593a454d63d43a97888d2ade3c4b4704b5b14940585b23ae8b022a8e17ae2f886bf834bf3190936790f1b856d92588476f140c9b32fa906c0cfa21daba5498197beecd17e768475ec296b675c7bbcb9b5920900d94381305552b5e58bbe203fc7b849322310eb5c477d66097e913a532c5dd79c123ba21508fccb5ab8a81c4459bb74b3d9c46143d0616ac0c196609d22a8903c5639d17aa04cd0228a7680d3101d00a044e66b4bfbc148fca3a773d92eafc9489d6051ff591f931cca910585e2299bd5f6af8b72a813f98642a28f9324c90b3403001329304ca0e15068574c617b69006e1b5d21d8cad3168669331e7d3445fd5a8cd232a4de426c87a090304c0dc16680353ac7bab44cbfa1c916884f5e917e18993fe4c778f5193cec47e5af8d3ae1b706f4b31bdf47234de201b4583d57532cec513dc3edfe00000100f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd2406fd2106f014d9b16a1a42a50c6faf6efc3aaab564575de5e587d0c06dd158b93ed1a795a32ee291c56a4ba254d805f7546691514206735a80ea73591bc3a20040f5979a689f083ba1cb1f2ba204b25a94ce83bf48854e05203de1137582b4e482165b4b462a63ec53d5dc2291dc61da93de28691edf28fe72ffbda9361c4b73c555b1165dae61557500f32c5247ab886e0482a2fffe0d1d48abb3f732089df546ecf7b5a53e4803fc39aea2d2b5ebe31bd1c4e90db7de6d239a025f359dd7ea8533ecb131084ed0d35461b2a14622af9eb437a327f36a842e3178c1f32ad13b95e314f2758e0cb1059ab74ba366b6f3a87d79645c7aee5822c9b75ea47ce6654ac61637a608913571093c282aa7e77ce2612a073e4cf40ceba3958b592d9e32236a0110362256230368f9560596be51003596013ed48891f57989eebb7b009a0eac1dd2556af27c3305d571ed6d252157dc8a17924b2e6a80febe0ff3a29c24be8b2ca8a56505f7c55901b8a602488375b1b34d63b1ba167199dfb51c56bc025b2be55cdc9b136702d06eb71946cd4e710c51cd907c59243ababe45861a4bd7a6d097e6e8ecf5a8d60ee5bf5570bda3e1ecb873bbc119695268f2203d08e6f786a7d7af68207a9833a37bd4c65c079bf21ebcf22ea028c01547378a11a258ebdb1279014fd8d527923b5cf93c374d5c990d18bfd5eba7c5b14bfa445ad69667d1c38ca3be167657d42aa621908e9cba43a316e3c000f5c2d8802433210e3bf3c6cb7df77097e88ae0c8d23cf8a73582fa351d60b77037b9fbadf683a12fc30473493f20f8262ca2383f000a97b7adc14174167678ed0b3b8559fd528aefc320964156591cade0c9dac72b36c32f9a47147e6285a52f491a4b6c6150c9e16b63b77eae68ccf7add5502593075fa20aaa16f88d498e55d56c69ab61b83b7aa900c09a9f6d497e5711553ad9962abf0cb0f4f99830cdd6a9a9ae6f54d9e7dd8bbd533acc4a6233bbd49d6a2a2159dcd59c9613122ce4684ed49357125141a8c473aa85d31d3624c8bd0d44989f61ef9f0101490cca8be88ef630ab2f52906fca2b2981c9cfdf4a82db342d5534d33313311044f7a8034f604418d9694760a3fb8ab98b12fa0e83af9c2ce43f9dfe9b63dff9cf9c209f97a1c526660e7a39b4082966a53825b67ddc22afbb4e69bb90138bfc834c2b2c6380f5666db64d153519e33296ebbe577f6b98a13c3d54688754f9ace99168cd7bee489055fb6e50b4ac9104896d8867749d47964bb597fa3b29a4457a431879b96f633f59a23e663dc8820ad97ac85e6cc722d3bb93c780c4a7b909b1a2c9a5745cefd55bde2383d1e9ed1104923814b6b5b71bb3be9d1215c49f0120fd7e04db1d8b0921f209ccbd404f0d9bb69d754717536e402fd19e261ed162f0d98bd1f0f95e3ee720b21e9554bab58445c15d8bc99f6afcaa63ed40641feca1ea820a8de86b5e7f3da3fcadfa7d111242695f10180db0c72607a342b63c5917ca4d7aea3e8ccdb511ff487eaa01bad8c0ce0f6ac3060946b109095edc01094fa943cf5271097c7589efbad0c8ab661b8f21c0880567f160f6ddada20c109dbd2ff0fd9564638a0593bc3d03149c5de51053f73fb1717789f51ff2cadfa525536667c725a649b6bfaeb6b8859257792dbf0400cdb16bfd55c5f15fa6a33bd2fe5667b61c703706ead079dfba5b9db7069de1ecba6ee577273af84bbba8af7d494a2645e0999d46fa305c534a21b3b8493e13ec2a33f608e7748d87214d6a06a1f7a286287a3b0d9b726e2ad20a44062726ffdfa15ab839770ac3466237d234149fa8007ec336d77fdc73d357e0c696f94deae9d83e8836e5cff457ad98e11e173ec929e5daee3beea54e71f927dbd77b115a136be17a5596d1c2ff43a35aa3e941654264a59ff4b26aae2bd790f6d66b4d0570648ca6b00a93c6918a9d01af9e115bd1d2de88035538f2477b1a723da647c77ea9b36ee7795737d779bfa9caeaf2dbaff42b7e22d3d3a04677888074db0fea004eccb48ccb5df257248d10b940683c132bd540c107cbcfcc9c4a28c080cf4b213938ee30078f6b048709a3dbd85eec4e317be3b43cedbf1af4e259f39d4373b6c6608f23866c7462d80f70a305332b6aefb6e07dd688edf4248e3b7a95d36c0be7025397c8229b3e33c361cebf215470154dba18acbc7d72d4e1c651409b01e9", + "transcript_hash": "a4538eb235f3f1a1128dbb1a2c213fc655ccd42fafa41c442c850244c903dbf9", + "salt": "716269745f76325f6879627269645f6d6c6b656d31303234444f24a8", + "ikm": "bf3f6c4be548a2afbf33f9b33c2d0b52b3f133e87ca0dc636281ccc833ceab8f50124a2dd20a655c9d431594979a253093dc976e424ed1076e09cced7ef06941a4538eb235f3f1a1128dbb1a2c213fc655ccd42fafa41c442c850244c903dbf9", + "prk": "a54dca5a686a40867aeb980ece78e3cc23c54cffa3a617662fcbd56b6c3788d1", + "initiator_L": "37aa1cb24cd70feceb65e1ffa7b909d39119aecb0bd6c2bb68f14b7a1b2552d1", + "initiator_P": "fc86c07860059c9b0885a1faa48daae92ba13d49eba4e0dbfad705ef165eacc4", + "responder_L": "eaf470586dea69230ed91bb8f454b25301198d3dc5aade5f3d854f61137fe942", + "responder_P": "08017b2336a5e6d8a5b379ea84d5d1fe6469952cf6c19254b3955cf0f50b12c4", + "session_id": "94376e50745dd11d5a962abe2057bb7f14dcc9e94131f49f42c3e032f004311c", + "initiator_confirmation": "b727dfa3a9b214c5e7b40559d6aebc4136fa1513", + "responder_confirmation": "dde08146e6422b6322a931bd70dff2d2a4a73124", + "initiator_first_contents": "120123456789abcdef", + "initiator_first_packet": "a13e6541e85df6f59b20c77fe3a74e78adac4b710dd99d5c59aecb326a", + "responder_first_contents": "130123456789abcdef", + "responder_first_packet": "d187660dd5bb303079116fceb19188396845a7c2e8b6530d499917880d" + } + ], + "confirmation_failures": [ + { + "comment": "The initiator's confirmation with bit 0 of its first byte flipped: the length decrypts to 1.", + "vector": 0, + "receiver": "responder", + "packet": "68abb2b1c9601dad7baf8038bc3601eaa42527de", + "decrypted_length": 1, + "failure": "length" + }, + { + "comment": "The initiator's confirmation with bit 0 of its last tag byte flipped: the length decrypts to 0 and the tag fails.", + "vector": 0, + "receiver": "responder", + "packet": "69abb2b1c9601dad7baf8038bc3601eaa42527df", + "decrypted_length": 0, + "failure": "tag" + }, + { + "comment": "The responder's first hybrid packet is an authenticated empty packet without the ignore bit.", + "vector": 0, + "receiver": "initiator", + "packet": "a634fa6c1db68e36c90668fe20a632b91a491ece", + "decrypted_length": 0, + "failure": "not_decoy" + } + ], + "negative_vectors": [ + { + "comment": "Bit 0 of the ciphertext flipped in transit: the responder hashes the damaged accept and decapsulation returns the implicit-rejection secret.", + "vector": 0, + "role": "responder", + "transcript_contents": [ + "fd2106f04ef53cd8d75470aa5492cc10d1b519a02ac168e5394226756c89ace3a2174cb08fe962b490f3c2cf88a60391513083a330718b73257c554c7cf0ca1865fbbf682435605c5a7f0636b19ab1c7e2c9cb40cf62c47940b17855f7c2e4d1869409ad7b3b047052bfd7366195d84890f03ac23032a63c545ed99f07e55bc81771836917b5139954d413162b9b5745556865710a5401bd9b6e3382adef4108b80833ed2337aaf2392a9704e239cb4a35332bf8257c288200389d054ba9acf772c61ab9f74875353b1bb933bff0e12f72b8c1537c4b47a74750d5617eb4c99d72af4e401961f588ff9a617b775ef7280a8a2843f1b408e6cbcf7385493a3b9e86230ba3c5676c5cb8885c317855a103e915aeac2cb27c36392494bb2aac04f481d38ba33cf7376711cce831380c580bdd887b3183a8fb2642857b3283c51a82c788e5293ddbd01bc46609f6017f40294f4b43842a709db0a7203565ceebda5721c1201569217ad817cd6316fc8c7f3036a4d5a679095bb4376c9e75360a64051f17fb8445f4556d37180c83cbee261629620ecec139bb8c609da90f9da2a8e1297793546ef90c0bffb3b0c836215bb9108191af7803cd5f26163c577404002aaed44de693892cb04f87e060dc5b9db9d00a4ea6b2734505c04857c31ccaa68bc833733d1e2837e0b5aec46354867933824399f132805893330c73c11c988d1adb6aac9b9bbcbc41263158e2b960c30665571484900ac7621a3e7fd1252ffb271f763ddc98293a5b929790468b9b4ed848c9ba531adaa31fec60367bc48eae612777cb79d7b56a17f8a6afc1c1febb86ec286ef9260cb27079c0d5a74910b3ef26b09ec96de697735e91841b9224d33abf2e1b14b15460d9055093268c75c17aa1ba15b5463e68d32c99a10d40c603b45c1fb81b5965c17379b898cba3cd1471bb07c8586f232d5c29ade9831f06163a3d60c672535e8fb0a01fd4435853480b073ec0ac32bc8a876cc3cd43bbb7bf8b3dc77874be68bff1d0cdf6a2900d8bca43f29b87c4395c7a794c39a45e89c3d9e3bf7046b5bc204a75a30a707242525c81cf4ac93c2a8b266c59eaaa0d997348e9fc7bd0294231c559a5cc1465a22317e714cc1431c092bed0665f242098c556b1c091a18519c1fa91354d6403f71972e78c23a28a276798826b3b711e76aa17f27c24214d6c1c6e8a53736cd986d9e446068532f1a6214cc98eb1625cd064456b0481abe2a38aeaa26845b77b1926b791a4a3b56739997edeb097284a9a6217438fd92b65399d0ecb2f23ccb1c8397496413615c26304d5272d60bc34f3ba6b5c652f693bc702be90349f27fa019173508a94bdf5f1153b341d20d040eb506ea0d40b561c0178c662a7c5b6bd130535355a8a5438ae1c0e2ab872f92800ba319b34d587e52860eb183b0bdb24ec024a111b369d3871c1c7ce9ec97207a208b4ec32ea142c61d23bdbb40325ca0756b36c5de19bed851d0ce6a30c175e467957d72b65f5552007dab6cd474d095ab556353111b9664a012d7fd767b3d549138580a087103d72bb602520f9393317e72e3fbc5d7206a24ccaae03fa844619a4acaa8c246053ed122c9765bc9164aa1c7257ac5893e0619bb965c48f24b5a5747d02f38705a09c905b82918856487382f40c28b73916b8059c29b39853189997978e49b05df1495c66a777e5f84ef99575f51b33fb157d41e54a7611c082387c6da242c3042fce7711525104f46233cad9882e45796a09c59b8471287b19c4f6205ea0b3543c341f8c169c11010cd52abda1b934024b9181c047ecad01844709d7834aa056648097f2109e4fac640d32ad80cc5581ac60296a5b51d615241c8cc0bcccfe777b97e6a5475011603a8ad7eaafe741572018840b989d83f986163633bc68919d571211d317c0a3c0347529b49101c2d451844b5083404da25b4d37931743139953fcc8374369260b88193284bc96250d02c05ecc6809a44a38e24261083dc3953d4e8a093bda800b4b2614d76a4355c6a900251e3cb9de069ed2ebaa34b34aae3661362218b5350b4f68c9bc6b11d0f5753e792cf300acdb94c52eab50d406967f498a8327420d6397886380a94019b307b8188a4496b223ebe27075b87c5d8680855ac7c5b892d357247aecb55e6089a2dba345f65d259b8d40a253a450a546b46abc3d1804f4c6ecc2a992fdff4e7d", + "fd2106f078578b92edfe5618d5dd9284a2004e45ebe3a37cc4c0eef53eeaf8d322303b91edfb0aa7c453d4c0d06bc29c2e904f20db8e6e0567d6a1303ad190d7c087a76da634a6e0ac11f4f78496ca11862c87acb0fc273061479a89758886d0b0d78375b1edf317ddfa889507df4e74853fe34892580a127e104f0bc4df583d8415b54367efafea0995c7e7ed7a1dedb8112a22e8278f90361dbf0a1f7af1246b609c9981f95a919312540d8bbd207eb75cf9feb4f9d2689b071f1f5b0d790573ca40db398374d3e50e3c2d46bb966567cd9c9b1589ee62206ead95a80483d4fa567ff697d40d804acdde8ec654fd5ba413133fabb99db19d949ecde78d55cb563766e714cf2138f50457809757b618b004ad48c1cb0b0f1c95e0fbe1f43813d5f035c4b3ad1b6a5c1c5fa0a1d27a0cb6d3de7b3a054e6fcbc24e286e6a77e45ab56bc5ecb070a00c22974d05832832bd40ab96010039be5da0464731993e2bccfcc468533cbfe3089a6f579d5a06cbe4bf08ddb85fe9996ed95fe9442130e49c61d1bf0881968672eae12c568189f3285671358e68ae0fbb5075ed137127788aa1f96033c89f41ade84cc61aaeae9b2c86862b1e956c74d39bd6e8f00907fd586cd099ab4224883c2e0c07ac4bb3f74a5a59975ef0e844583370cf077671fbfc2a333182dce2ac9cc8f3ba0d73cd27ae60aabc1d7689f7bd2d7ecd5d97164293cc88fe7adbadb3a24798f7b2be578c7780a681cc902b69b7fe4aa8f535e4a9ed83d002c1ea0f28e711be6231cabde2ea19655688c8d70b413420651852db3bcad5c6b66267d516088b3955a6884ca44a83c9f179c5901ff4edefea074ee0cd425a7dd8a855ea49affbd7a8604c490213dfa7bf48ac12b738fae7cd1697e757f26a59826e64b7799a47703e037c9ca7cf18828574092a9b0af255a5180eef96f96e5d2f51cc621d7a03c7bb05eb80d386415f243f5d59451d5f95c93f4616cca643cd6be65109d440461e752d2999745facd5eacaea338d6ce940b54c8dba44f059c94fa65a74456b747c065f2aba2b8580931e1e5db4cd6505d4cc50ce68e15a1f9e0069edd1a8d5bbfa9180f0a88fef1d773a5b417b67ddd8729c3a781bfcdeb527126579f5d11c5550525869a3236e1f21940542c7ce69e3a265c24d9aefeab6c0852636708a74718729f35a401fd73b46b204ec92b40d9ec0e5f924c967a8a4e219910d40af2d19e76869bfb7c95868e4572334bc35f7071d6254cb3cdf82ee94d3b685fe48a49ad95503ca19b39e316e57a32a1eaa9af4573a32bfe94dd77c744484781c2efe891e2b4b65b38e12dbab69b436d4b0ac6a9b512ee898b95906163f32839fa2985fadead35d79fa03a284debc60c51daa8a017c1d4dc4dc7e884b1374d131fde2ca6e591672b259754b4f76bbf6ed3eeeee94e4e7c496344a3b38c8b4dea12684bbc0713ec6b7684479e883a0a515d681581beab290d952fa20f6f9ae63dc00a55d1ef3f592d9a86d97a40ae74717623dc1ffdf2588f241d6600e6f34163db805273266f58e75e074d7d079d40755012da5ba3203d2bfdc9100e361148456a7ac423ba9cf3bd135f3e64c2f262fcdb728e0682e8297cc2af4e0467603990459d923cf6974cc27f9c02f535d6962cd67bbfd1287ba4148647153fd33ef9e482e2555ff6ee243ce6ab277af1494944b2913ecf29819507571427d7c1852d684a6359a72698b65ef040b8a03c3fffca6f4a9e13359db9a1e3d2e4fac6d469357d9d9ca6663f6a3f80076c6ec937aa16d23b12fb0ee483c260ad6e751a2814720e36dfd26202b14c1cd18e3f9477e924292d84d905c056bd73ceecede186f3b6987abe328fb3e1e48b2bed3885b9d9e9af6bdebc0c7f9cc6f7954b38905e3d8ee41f780ca446792326a94837c367104983ea9b906a7f7a8b4bbf44700694c27e7670e4a7b3e5df7218d8d67fd68bda52592e650de25b1a81ed304d81cadbad366833d94d7e69a8433c9d7edfb1043b99a08265de814121119d3f7eaa74c8082bd9b4a91bb2478605a2f2e077679665d24391ea37c748c5c6e35f00fd202a13c032270b0d30f0af8bb1d7c2570055980748fd7761d6a801043ecc04051f7d60a5b7361436549762a9f3ba7eb7344658b154b8b680ba8ac2eeec07e4cc3cac211255c3f901e713db12984a00450011790a0f2d6af22d5d35b83c5483dd4e8" + ], + "ss_mlkem": "2add583d143a71963d6fe31fe39ed47923325d513c27a8c40f4ed5d74ec7f30c", + "session_id": "44c4e64069b50d8cd3e333a38dcd6fa2c9543b4d52a7db8fc2114ad7e7136304", + "confirmation": "db84ca2e545823c5d3e0fb8c6b45995ab779a106", + "decrypted_length": 8641960, + "failure": "length", + "peer_decrypted_length": 3190909, + "peer_failure": "length" + }, + { + "comment": "The initiator's ML-KEM shared secret has byte 0 inverted after encapsulation (a local fault); EK and CT are untouched.", + "vector": 0, + "role": "initiator", + "transcript_contents": [ + "fd2106f04ef53cd8d75470aa5492cc10d1b519a02ac168e5394226756c89ace3a2174cb08fe962b490f3c2cf88a60391513083a330718b73257c554c7cf0ca1865fbbf682435605c5a7f0636b19ab1c7e2c9cb40cf62c47940b17855f7c2e4d1869409ad7b3b047052bfd7366195d84890f03ac23032a63c545ed99f07e55bc81771836917b5139954d413162b9b5745556865710a5401bd9b6e3382adef4108b80833ed2337aaf2392a9704e239cb4a35332bf8257c288200389d054ba9acf772c61ab9f74875353b1bb933bff0e12f72b8c1537c4b47a74750d5617eb4c99d72af4e401961f588ff9a617b775ef7280a8a2843f1b408e6cbcf7385493a3b9e86230ba3c5676c5cb8885c317855a103e915aeac2cb27c36392494bb2aac04f481d38ba33cf7376711cce831380c580bdd887b3183a8fb2642857b3283c51a82c788e5293ddbd01bc46609f6017f40294f4b43842a709db0a7203565ceebda5721c1201569217ad817cd6316fc8c7f3036a4d5a679095bb4376c9e75360a64051f17fb8445f4556d37180c83cbee261629620ecec139bb8c609da90f9da2a8e1297793546ef90c0bffb3b0c836215bb9108191af7803cd5f26163c577404002aaed44de693892cb04f87e060dc5b9db9d00a4ea6b2734505c04857c31ccaa68bc833733d1e2837e0b5aec46354867933824399f132805893330c73c11c988d1adb6aac9b9bbcbc41263158e2b960c30665571484900ac7621a3e7fd1252ffb271f763ddc98293a5b929790468b9b4ed848c9ba531adaa31fec60367bc48eae612777cb79d7b56a17f8a6afc1c1febb86ec286ef9260cb27079c0d5a74910b3ef26b09ec96de697735e91841b9224d33abf2e1b14b15460d9055093268c75c17aa1ba15b5463e68d32c99a10d40c603b45c1fb81b5965c17379b898cba3cd1471bb07c8586f232d5c29ade9831f06163a3d60c672535e8fb0a01fd4435853480b073ec0ac32bc8a876cc3cd43bbb7bf8b3dc77874be68bff1d0cdf6a2900d8bca43f29b87c4395c7a794c39a45e89c3d9e3bf7046b5bc204a75a30a707242525c81cf4ac93c2a8b266c59eaaa0d997348e9fc7bd0294231c559a5cc1465a22317e714cc1431c092bed0665f242098c556b1c091a18519c1fa91354d6403f71972e78c23a28a276798826b3b711e76aa17f27c24214d6c1c6e8a53736cd986d9e446068532f1a6214cc98eb1625cd064456b0481abe2a38aeaa26845b77b1926b791a4a3b56739997edeb097284a9a6217438fd92b65399d0ecb2f23ccb1c8397496413615c26304d5272d60bc34f3ba6b5c652f693bc702be90349f27fa019173508a94bdf5f1153b341d20d040eb506ea0d40b561c0178c662a7c5b6bd130535355a8a5438ae1c0e2ab872f92800ba319b34d587e52860eb183b0bdb24ec024a111b369d3871c1c7ce9ec97207a208b4ec32ea142c61d23bdbb40325ca0756b36c5de19bed851d0ce6a30c175e467957d72b65f5552007dab6cd474d095ab556353111b9664a012d7fd767b3d549138580a087103d72bb602520f9393317e72e3fbc5d7206a24ccaae03fa844619a4acaa8c246053ed122c9765bc9164aa1c7257ac5893e0619bb965c48f24b5a5747d02f38705a09c905b82918856487382f40c28b73916b8059c29b39853189997978e49b05df1495c66a777e5f84ef99575f51b33fb157d41e54a7611c082387c6da242c3042fce7711525104f46233cad9882e45796a09c59b8471287b19c4f6205ea0b3543c341f8c169c11010cd52abda1b934024b9181c047ecad01844709d7834aa056648097f2109e4fac640d32ad80cc5581ac60296a5b51d615241c8cc0bcccfe777b97e6a5475011603a8ad7eaafe741572018840b989d83f986163633bc68919d571211d317c0a3c0347529b49101c2d451844b5083404da25b4d37931743139953fcc8374369260b88193284bc96250d02c05ecc6809a44a38e24261083dc3953d4e8a093bda800b4b2614d76a4355c6a900251e3cb9de069ed2ebaa34b34aae3661362218b5350b4f68c9bc6b11d0f5753e792cf300acdb94c52eab50d406967f498a8327420d6397886380a94019b307b8188a4496b223ebe27075b87c5d8680855ac7c5b892d357247aecb55e6089a2dba345f65d259b8d40a253a450a546b46abc3d1804f4c6ecc2a992fdff4e7d", + "fd2106f079578b92edfe5618d5dd9284a2004e45ebe3a37cc4c0eef53eeaf8d322303b91edfb0aa7c453d4c0d06bc29c2e904f20db8e6e0567d6a1303ad190d7c087a76da634a6e0ac11f4f78496ca11862c87acb0fc273061479a89758886d0b0d78375b1edf317ddfa889507df4e74853fe34892580a127e104f0bc4df583d8415b54367efafea0995c7e7ed7a1dedb8112a22e8278f90361dbf0a1f7af1246b609c9981f95a919312540d8bbd207eb75cf9feb4f9d2689b071f1f5b0d790573ca40db398374d3e50e3c2d46bb966567cd9c9b1589ee62206ead95a80483d4fa567ff697d40d804acdde8ec654fd5ba413133fabb99db19d949ecde78d55cb563766e714cf2138f50457809757b618b004ad48c1cb0b0f1c95e0fbe1f43813d5f035c4b3ad1b6a5c1c5fa0a1d27a0cb6d3de7b3a054e6fcbc24e286e6a77e45ab56bc5ecb070a00c22974d05832832bd40ab96010039be5da0464731993e2bccfcc468533cbfe3089a6f579d5a06cbe4bf08ddb85fe9996ed95fe9442130e49c61d1bf0881968672eae12c568189f3285671358e68ae0fbb5075ed137127788aa1f96033c89f41ade84cc61aaeae9b2c86862b1e956c74d39bd6e8f00907fd586cd099ab4224883c2e0c07ac4bb3f74a5a59975ef0e844583370cf077671fbfc2a333182dce2ac9cc8f3ba0d73cd27ae60aabc1d7689f7bd2d7ecd5d97164293cc88fe7adbadb3a24798f7b2be578c7780a681cc902b69b7fe4aa8f535e4a9ed83d002c1ea0f28e711be6231cabde2ea19655688c8d70b413420651852db3bcad5c6b66267d516088b3955a6884ca44a83c9f179c5901ff4edefea074ee0cd425a7dd8a855ea49affbd7a8604c490213dfa7bf48ac12b738fae7cd1697e757f26a59826e64b7799a47703e037c9ca7cf18828574092a9b0af255a5180eef96f96e5d2f51cc621d7a03c7bb05eb80d386415f243f5d59451d5f95c93f4616cca643cd6be65109d440461e752d2999745facd5eacaea338d6ce940b54c8dba44f059c94fa65a74456b747c065f2aba2b8580931e1e5db4cd6505d4cc50ce68e15a1f9e0069edd1a8d5bbfa9180f0a88fef1d773a5b417b67ddd8729c3a781bfcdeb527126579f5d11c5550525869a3236e1f21940542c7ce69e3a265c24d9aefeab6c0852636708a74718729f35a401fd73b46b204ec92b40d9ec0e5f924c967a8a4e219910d40af2d19e76869bfb7c95868e4572334bc35f7071d6254cb3cdf82ee94d3b685fe48a49ad95503ca19b39e316e57a32a1eaa9af4573a32bfe94dd77c744484781c2efe891e2b4b65b38e12dbab69b436d4b0ac6a9b512ee898b95906163f32839fa2985fadead35d79fa03a284debc60c51daa8a017c1d4dc4dc7e884b1374d131fde2ca6e591672b259754b4f76bbf6ed3eeeee94e4e7c496344a3b38c8b4dea12684bbc0713ec6b7684479e883a0a515d681581beab290d952fa20f6f9ae63dc00a55d1ef3f592d9a86d97a40ae74717623dc1ffdf2588f241d6600e6f34163db805273266f58e75e074d7d079d40755012da5ba3203d2bfdc9100e361148456a7ac423ba9cf3bd135f3e64c2f262fcdb728e0682e8297cc2af4e0467603990459d923cf6974cc27f9c02f535d6962cd67bbfd1287ba4148647153fd33ef9e482e2555ff6ee243ce6ab277af1494944b2913ecf29819507571427d7c1852d684a6359a72698b65ef040b8a03c3fffca6f4a9e13359db9a1e3d2e4fac6d469357d9d9ca6663f6a3f80076c6ec937aa16d23b12fb0ee483c260ad6e751a2814720e36dfd26202b14c1cd18e3f9477e924292d84d905c056bd73ceecede186f3b6987abe328fb3e1e48b2bed3885b9d9e9af6bdebc0c7f9cc6f7954b38905e3d8ee41f780ca446792326a94837c367104983ea9b906a7f7a8b4bbf44700694c27e7670e4a7b3e5df7218d8d67fd68bda52592e650de25b1a81ed304d81cadbad366833d94d7e69a8433c9d7edfb1043b99a08265de814121119d3f7eaa74c8082bd9b4a91bb2478605a2f2e077679665d24391ea37c748c5c6e35f00fd202a13c032270b0d30f0af8bb1d7c2570055980748fd7761d6a801043ecc04051f7d60a5b7361436549762a9f3ba7eb7344658b154b8b680ba8ac2eeec07e4cc3cac211255c3f901e713db12984a00450011790a0f2d6af22d5d35b83c5483dd4e8" + ], + "ss_mlkem": "ddc8bf5c4858ac2e5f94b6e945ca142ccc28b98e7b954a78899630a7bfa3135f", + "session_id": "caa12cb88128baf00d2ca768fea2a7b0d0740af35c5fa5524dc7783b525f3082", + "confirmation": "8b4724ce6bcb46d34283fb52710bc1b12c7b25c4", + "decrypted_length": 1490822, + "failure": "length", + "peer_decrypted_length": 9891042, + "peer_failure": "length" + }, + { + "comment": "The responder hashes the two contents in the wrong order.", + "vector": 0, + "role": "responder", + "transcript_contents": [ + "fd2106f079578b92edfe5618d5dd9284a2004e45ebe3a37cc4c0eef53eeaf8d322303b91edfb0aa7c453d4c0d06bc29c2e904f20db8e6e0567d6a1303ad190d7c087a76da634a6e0ac11f4f78496ca11862c87acb0fc273061479a89758886d0b0d78375b1edf317ddfa889507df4e74853fe34892580a127e104f0bc4df583d8415b54367efafea0995c7e7ed7a1dedb8112a22e8278f90361dbf0a1f7af1246b609c9981f95a919312540d8bbd207eb75cf9feb4f9d2689b071f1f5b0d790573ca40db398374d3e50e3c2d46bb966567cd9c9b1589ee62206ead95a80483d4fa567ff697d40d804acdde8ec654fd5ba413133fabb99db19d949ecde78d55cb563766e714cf2138f50457809757b618b004ad48c1cb0b0f1c95e0fbe1f43813d5f035c4b3ad1b6a5c1c5fa0a1d27a0cb6d3de7b3a054e6fcbc24e286e6a77e45ab56bc5ecb070a00c22974d05832832bd40ab96010039be5da0464731993e2bccfcc468533cbfe3089a6f579d5a06cbe4bf08ddb85fe9996ed95fe9442130e49c61d1bf0881968672eae12c568189f3285671358e68ae0fbb5075ed137127788aa1f96033c89f41ade84cc61aaeae9b2c86862b1e956c74d39bd6e8f00907fd586cd099ab4224883c2e0c07ac4bb3f74a5a59975ef0e844583370cf077671fbfc2a333182dce2ac9cc8f3ba0d73cd27ae60aabc1d7689f7bd2d7ecd5d97164293cc88fe7adbadb3a24798f7b2be578c7780a681cc902b69b7fe4aa8f535e4a9ed83d002c1ea0f28e711be6231cabde2ea19655688c8d70b413420651852db3bcad5c6b66267d516088b3955a6884ca44a83c9f179c5901ff4edefea074ee0cd425a7dd8a855ea49affbd7a8604c490213dfa7bf48ac12b738fae7cd1697e757f26a59826e64b7799a47703e037c9ca7cf18828574092a9b0af255a5180eef96f96e5d2f51cc621d7a03c7bb05eb80d386415f243f5d59451d5f95c93f4616cca643cd6be65109d440461e752d2999745facd5eacaea338d6ce940b54c8dba44f059c94fa65a74456b747c065f2aba2b8580931e1e5db4cd6505d4cc50ce68e15a1f9e0069edd1a8d5bbfa9180f0a88fef1d773a5b417b67ddd8729c3a781bfcdeb527126579f5d11c5550525869a3236e1f21940542c7ce69e3a265c24d9aefeab6c0852636708a74718729f35a401fd73b46b204ec92b40d9ec0e5f924c967a8a4e219910d40af2d19e76869bfb7c95868e4572334bc35f7071d6254cb3cdf82ee94d3b685fe48a49ad95503ca19b39e316e57a32a1eaa9af4573a32bfe94dd77c744484781c2efe891e2b4b65b38e12dbab69b436d4b0ac6a9b512ee898b95906163f32839fa2985fadead35d79fa03a284debc60c51daa8a017c1d4dc4dc7e884b1374d131fde2ca6e591672b259754b4f76bbf6ed3eeeee94e4e7c496344a3b38c8b4dea12684bbc0713ec6b7684479e883a0a515d681581beab290d952fa20f6f9ae63dc00a55d1ef3f592d9a86d97a40ae74717623dc1ffdf2588f241d6600e6f34163db805273266f58e75e074d7d079d40755012da5ba3203d2bfdc9100e361148456a7ac423ba9cf3bd135f3e64c2f262fcdb728e0682e8297cc2af4e0467603990459d923cf6974cc27f9c02f535d6962cd67bbfd1287ba4148647153fd33ef9e482e2555ff6ee243ce6ab277af1494944b2913ecf29819507571427d7c1852d684a6359a72698b65ef040b8a03c3fffca6f4a9e13359db9a1e3d2e4fac6d469357d9d9ca6663f6a3f80076c6ec937aa16d23b12fb0ee483c260ad6e751a2814720e36dfd26202b14c1cd18e3f9477e924292d84d905c056bd73ceecede186f3b6987abe328fb3e1e48b2bed3885b9d9e9af6bdebc0c7f9cc6f7954b38905e3d8ee41f780ca446792326a94837c367104983ea9b906a7f7a8b4bbf44700694c27e7670e4a7b3e5df7218d8d67fd68bda52592e650de25b1a81ed304d81cadbad366833d94d7e69a8433c9d7edfb1043b99a08265de814121119d3f7eaa74c8082bd9b4a91bb2478605a2f2e077679665d24391ea37c748c5c6e35f00fd202a13c032270b0d30f0af8bb1d7c2570055980748fd7761d6a801043ecc04051f7d60a5b7361436549762a9f3ba7eb7344658b154b8b680ba8ac2eeec07e4cc3cac211255c3f901e713db12984a00450011790a0f2d6af22d5d35b83c5483dd4e8", + "fd2106f04ef53cd8d75470aa5492cc10d1b519a02ac168e5394226756c89ace3a2174cb08fe962b490f3c2cf88a60391513083a330718b73257c554c7cf0ca1865fbbf682435605c5a7f0636b19ab1c7e2c9cb40cf62c47940b17855f7c2e4d1869409ad7b3b047052bfd7366195d84890f03ac23032a63c545ed99f07e55bc81771836917b5139954d413162b9b5745556865710a5401bd9b6e3382adef4108b80833ed2337aaf2392a9704e239cb4a35332bf8257c288200389d054ba9acf772c61ab9f74875353b1bb933bff0e12f72b8c1537c4b47a74750d5617eb4c99d72af4e401961f588ff9a617b775ef7280a8a2843f1b408e6cbcf7385493a3b9e86230ba3c5676c5cb8885c317855a103e915aeac2cb27c36392494bb2aac04f481d38ba33cf7376711cce831380c580bdd887b3183a8fb2642857b3283c51a82c788e5293ddbd01bc46609f6017f40294f4b43842a709db0a7203565ceebda5721c1201569217ad817cd6316fc8c7f3036a4d5a679095bb4376c9e75360a64051f17fb8445f4556d37180c83cbee261629620ecec139bb8c609da90f9da2a8e1297793546ef90c0bffb3b0c836215bb9108191af7803cd5f26163c577404002aaed44de693892cb04f87e060dc5b9db9d00a4ea6b2734505c04857c31ccaa68bc833733d1e2837e0b5aec46354867933824399f132805893330c73c11c988d1adb6aac9b9bbcbc41263158e2b960c30665571484900ac7621a3e7fd1252ffb271f763ddc98293a5b929790468b9b4ed848c9ba531adaa31fec60367bc48eae612777cb79d7b56a17f8a6afc1c1febb86ec286ef9260cb27079c0d5a74910b3ef26b09ec96de697735e91841b9224d33abf2e1b14b15460d9055093268c75c17aa1ba15b5463e68d32c99a10d40c603b45c1fb81b5965c17379b898cba3cd1471bb07c8586f232d5c29ade9831f06163a3d60c672535e8fb0a01fd4435853480b073ec0ac32bc8a876cc3cd43bbb7bf8b3dc77874be68bff1d0cdf6a2900d8bca43f29b87c4395c7a794c39a45e89c3d9e3bf7046b5bc204a75a30a707242525c81cf4ac93c2a8b266c59eaaa0d997348e9fc7bd0294231c559a5cc1465a22317e714cc1431c092bed0665f242098c556b1c091a18519c1fa91354d6403f71972e78c23a28a276798826b3b711e76aa17f27c24214d6c1c6e8a53736cd986d9e446068532f1a6214cc98eb1625cd064456b0481abe2a38aeaa26845b77b1926b791a4a3b56739997edeb097284a9a6217438fd92b65399d0ecb2f23ccb1c8397496413615c26304d5272d60bc34f3ba6b5c652f693bc702be90349f27fa019173508a94bdf5f1153b341d20d040eb506ea0d40b561c0178c662a7c5b6bd130535355a8a5438ae1c0e2ab872f92800ba319b34d587e52860eb183b0bdb24ec024a111b369d3871c1c7ce9ec97207a208b4ec32ea142c61d23bdbb40325ca0756b36c5de19bed851d0ce6a30c175e467957d72b65f5552007dab6cd474d095ab556353111b9664a012d7fd767b3d549138580a087103d72bb602520f9393317e72e3fbc5d7206a24ccaae03fa844619a4acaa8c246053ed122c9765bc9164aa1c7257ac5893e0619bb965c48f24b5a5747d02f38705a09c905b82918856487382f40c28b73916b8059c29b39853189997978e49b05df1495c66a777e5f84ef99575f51b33fb157d41e54a7611c082387c6da242c3042fce7711525104f46233cad9882e45796a09c59b8471287b19c4f6205ea0b3543c341f8c169c11010cd52abda1b934024b9181c047ecad01844709d7834aa056648097f2109e4fac640d32ad80cc5581ac60296a5b51d615241c8cc0bcccfe777b97e6a5475011603a8ad7eaafe741572018840b989d83f986163633bc68919d571211d317c0a3c0347529b49101c2d451844b5083404da25b4d37931743139953fcc8374369260b88193284bc96250d02c05ecc6809a44a38e24261083dc3953d4e8a093bda800b4b2614d76a4355c6a900251e3cb9de069ed2ebaa34b34aae3661362218b5350b4f68c9bc6b11d0f5753e792cf300acdb94c52eab50d406967f498a8327420d6397886380a94019b307b8188a4496b223ebe27075b87c5d8680855ac7c5b892d357247aecb55e6089a2dba345f65d259b8d40a253a450a546b46abc3d1804f4c6ecc2a992fdff4e7d" + ], + "ss_mlkem": "22c8bf5c4858ac2e5f94b6e945ca142ccc28b98e7b954a78899630a7bfa3135f", + "session_id": "4eb19b11efa8f32faa26babc856315c4d15ce8aed78d7b211045ca2a92bb4db2", + "confirmation": "02a3575120e8cd720e0673fe02bae7bc2b03bd39", + "decrypted_length": 7974615, + "failure": "length", + "peer_decrypted_length": 11376548, + "peer_failure": "length" + } + ] +} diff --git a/src/test/fuzz/bip324.cpp b/src/test/fuzz/bip324.cpp index b787bd1668..63f3970340 100644 --- a/src/test/fuzz/bip324.cpp +++ b/src/test/fuzz/bip324.cpp @@ -11,7 +11,10 @@ #include #include +#include #include +#include +#include #include namespace { @@ -43,15 +46,20 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) auto resp_ent = provider.ConsumeBytes(32); resp_ent.resize(32); - // Initialize ciphers by exchanging public keys. + // Initialize ciphers by exchanging public keys, optionally retaining the hybrid key state. + const bool hybrid = provider.ConsumeBool(); BIP324Cipher initiator(init_key, init_ent); assert(!initiator); BIP324Cipher responder(resp_key, resp_ent); assert(!responder); - initiator.Initialize(responder.GetOurPubKey(), true); + std::array ss_init{}; + assert(!initiator.SwitchToHybrid(ss_init)); + initiator.Initialize(responder.GetOurPubKey(), true, /*self_decrypt=*/false, hybrid); assert(initiator); - responder.Initialize(initiator.GetOurPubKey(), false); + responder.Initialize(initiator.GetOurPubKey(), false, /*self_decrypt=*/false, hybrid); assert(responder); + // Switching needs both version contents first. + assert(!initiator.SwitchToHybrid(ss_init)); // Initialize RNG deterministically, to generate contents and AAD. We assume that there are no // (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid @@ -63,8 +71,72 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) assert(std::ranges::equal(initiator.GetSessionID(), responder.GetSessionID())); assert(std::ranges::equal(initiator.GetSendGarbageTerminator(), responder.GetReceiveGarbageTerminator())); assert(std::ranges::equal(initiator.GetReceiveGarbageTerminator(), responder.GetSendGarbageTerminator())); + const std::vector garbage_terminators{initiator.GetSendGarbageTerminator().begin(), initiator.GetSendGarbageTerminator().end()}; + + // With the hybrid state retained, both sides switch keys (or discard the state, as on a legacy + // outcome) before a chosen packet. The version contents and the ML-KEM secret come from the + // RNG; the responder may hash a mutated transcript or use a mutated secret, after which every + // packet must fail to decrypt. + std::optional switch_before; + bool discard{false}; + std::vector contents_r, contents_i, resp_first, resp_second; + std::array ss_resp{}; + if (hybrid) { + switch_before = provider.ConsumeIntegralInRange(0, 1000); + contents_r = rng.randbytes(provider.ConsumeIntegralInRange(0, 4096)); + contents_i = rng.randbytes(provider.ConsumeIntegralInRange(0, 4096)); + rng.fillrand(ss_init); + resp_first = contents_r; + resp_second = contents_i; + ss_resp = ss_init; + const auto mutate = [&](std::vector& bytes) { + if (bytes.empty()) { + bytes.push_back(std::byte{0}); + } else { + bytes[provider.ConsumeIntegralInRange(0, bytes.size() - 1)] ^= std::byte(1U << provider.ConsumeIntegralInRange(0, 7)); + } + }; + switch (provider.ConsumeIntegralInRange(0, 5)) { + case 0: break; + case 1: mutate(resp_first); break; + case 2: mutate(resp_second); break; + case 3: std::swap(resp_first, resp_second); break; + case 4: ss_resp[provider.ConsumeIntegralInRange(0, ss_resp.size() - 1)] ^= std::byte(1U << provider.ConsumeIntegralInRange(0, 7)); break; + case 5: discard = true; break; + } + } else { + assert(!initiator.AddVersionContents({})); + } + const bool views_differ{resp_first != contents_r || resp_second != contents_i || ss_resp != ss_init}; + bool keys_differ{false}; + unsigned packet{0}; LIMITED_WHILE(provider.remaining_bytes(), 1000) { + if (switch_before == packet++) { + if (discard) { + initiator.DiscardHybridSecret(); + responder.DiscardHybridSecret(); + assert(!initiator.AddVersionContents(contents_r)); + assert(!responder.SwitchToHybrid(ss_resp)); + } else { + const std::vector ecdh_session_id{initiator.GetSessionID().begin(), initiator.GetSessionID().end()}; + assert(initiator.AddVersionContents(contents_r)); + assert(initiator.AddVersionContents(contents_i)); + assert(!initiator.AddVersionContents(contents_i)); + assert(initiator.SwitchToHybrid(ss_init)); + assert(responder.AddVersionContents(resp_first)); + assert(responder.AddVersionContents(resp_second)); + assert(responder.SwitchToHybrid(ss_resp)); + assert(!initiator.SwitchToHybrid(ss_init)); + assert(!responder.AddVersionContents({})); + keys_differ = views_differ; + assert(std::ranges::equal(initiator.GetSessionID(), responder.GetSessionID()) == !keys_differ); + assert(!std::ranges::equal(initiator.GetSessionID(), ecdh_session_id)); + assert(std::ranges::equal(initiator.GetSendGarbageTerminator(), garbage_terminators)); + assert(std::ranges::equal(responder.GetReceiveGarbageTerminator(), garbage_terminators)); + } + } + // Mode: // - Bit 0: whether the ignore bit is set in message // - Bit 1: whether the responder (0) or initiator (1) sends @@ -107,7 +179,8 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) // Decrypt length uint32_t dec_length = receiver.DecryptLength(std::span{ciphertext}.first(initiator.LENGTH_LEN)); - if (!damage) { + const bool expect_failure{damage || keys_differ}; + if (!expect_failure) { assert(dec_length == length); } else { // For performance reasons, don't try to decode if length got increased too much. @@ -120,8 +193,9 @@ FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) std::vector decrypt(dec_length); bool dec_ignore{false}; bool ok = receiver.Decrypt(std::span{ciphertext}.subspan(initiator.LENGTH_LEN), aad, dec_ignore, decrypt); - // Decryption *must* fail if the packet was damaged, and succeed if it wasn't. - assert(!ok == damage); + // Decryption *must* fail if the packet was damaged or the sides derived different hybrid + // keys, and succeed otherwise. + assert(!ok == expect_failure); if (!ok) break; assert(ignore == dec_ignore); assert(decrypt == contents); diff --git a/test/functional/test_framework/crypto/bip324_cipher.py b/test/functional/test_framework/crypto/bip324_cipher.py index c9f0fa0151..8b6c3c112f 100644 --- a/test/functional/test_framework/crypto/bip324_cipher.py +++ b/test/functional/test_framework/crypto/bip324_cipher.py @@ -3,7 +3,8 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test-only implementation of ChaCha20 Poly1305 AEAD Construction in RFC 8439 and FSChaCha20Poly1305 for BIP 324 +"""Test-only implementation of ChaCha20 Poly1305 AEAD Construction in RFC 8439 and FSChaCha20Poly1305 for BIP 324, +and of qbit's hybrid post-quantum v2 key schedule (doc/design/pq-transport.md). It is designed for ease of understanding, not performance. @@ -11,9 +12,11 @@ anything but tests. """ +import hashlib import unittest -from .chacha20 import chacha20_block, REKEY_INTERVAL +from .chacha20 import chacha20_block, FSChaCha20, REKEY_INTERVAL +from .hkdf import hkdf_sha256, hmac_sha256 from .poly1305 import Poly1305 @@ -88,6 +91,81 @@ def encrypt(self, aad, plaintext): return self._crypt(aad, plaintext, False) +LENGTH_LEN = 3 +IGNORE_BIT = 0x80 + + +def encrypt_packet(send_l, send_p, contents, aad=b"", ignore=False): + """Encrypt one BIP324 packet with the sender's FSChaCha20 (length) and FSChaCha20Poly1305.""" + header = bytes([IGNORE_BIT if ignore else 0]) + return send_l.crypt(len(contents).to_bytes(LENGTH_LEN, 'little')) + send_p.encrypt(aad, header + contents) + + +def decrypt_packet(recv_l, recv_p, packet, aad=b""): + """Decrypt one complete BIP324 packet. + + Returns (length, None) if the decrypted length does not match the packet size or the tag + fails, and (length, (ignore, contents)) otherwise.""" + length = int.from_bytes(recv_l.crypt(packet[:LENGTH_LEN]), 'little') + if len(packet) != LENGTH_LEN + 1 + length + 16: + return length, None + plaintext = recv_p.decrypt(aad, packet[LENGTH_LEN:]) + if plaintext is None: + return length, None + return length, (bool(plaintext[0] & IGNORE_BIT), plaintext[1:]) + + +# qbit hybrid v2 key schedule: the ML-KEM-1024 recipe under version-contents header 0xF0. Any change +# to this recipe needs a new header with its own salt label (doc/design/pq-transport.md). +HYBRID_SALT_LABEL = b"qbit_v2_hybrid_mlkem1024" +HYBRID_KEY_LABELS = ("initiator_L", "initiator_P", "responder_L", "responder_P", "session_id") + + +def ser_compact_size(n): + """Serialize n as a Bitcoin CompactSize.""" + if n < 253: + return n.to_bytes(1, 'little') + if n <= 0xffff: + return b"\xfd" + n.to_bytes(2, 'little') + if n <= 0xffffffff: + return b"\xfe" + n.to_bytes(4, 'little') + return b"\xff" + n.to_bytes(8, 'little') + + +def hybrid_transcript_input(ellswift_initiator, ellswift_responder, contents_responder, contents_initiator): + """Return the bytes the transcript hash TH covers. + + Both EllSwift keys in role order, then the full version packet contents each side sent, the + responder's first, each preceded by its CompactSize length.""" + assert len(ellswift_initiator) == 64 and len(ellswift_responder) == 64 + return (ellswift_initiator + ellswift_responder + + ser_compact_size(len(contents_responder)) + contents_responder + + ser_compact_size(len(contents_initiator)) + contents_initiator) + + +def hybrid_derive_keys(ss_ecdh, ss_mlkem, transcript_hash, magic): + """Derive the hybrid v2 keys with HKDF-SHA256 (RFC 5869). + + ss_ecdh is the 32-byte BIP324 ECDH secret, ss_mlkem the 32-byte ML-KEM-1024 shared secret, + transcript_hash = SHA256(hybrid_transcript_input(...)) and magic the 4 network magic bytes + in wire order. Returns a dict with the salt, IKM and PRK, and the five derived 32-byte + values keyed by label.""" + assert len(ss_ecdh) == 32 and len(ss_mlkem) == 32 and len(transcript_hash) == 32 and len(magic) == 4 + salt = HYBRID_SALT_LABEL + magic + ikm = ss_ecdh + ss_mlkem + transcript_hash + keys = {"salt": salt, "ikm": ikm, "prk": hmac_sha256(salt, ikm)} + for label in HYBRID_KEY_LABELS: + keys[label] = hkdf_sha256(length=32, ikm=ikm, salt=salt, info=label.encode('ascii')) + return keys + + +def hybrid_ciphers(keys, initiator): + """Return fresh (send_L, send_P, recv_L, recv_P) ciphers for one side from hybrid_derive_keys().""" + ours, theirs = ("initiator", "responder") if initiator else ("responder", "initiator") + return (FSChaCha20(keys[ours + "_L"]), FSChaCha20Poly1305(keys[ours + "_P"]), + FSChaCha20(keys[theirs + "_L"]), FSChaCha20Poly1305(keys[theirs + "_P"])) + + # Test vectors from RFC8439 consisting of plaintext, aad, 32 byte key, 12 byte nonce and ciphertext AEAD_TESTS = [ # RFC 8439 Example from section 2.8.2 @@ -201,3 +279,69 @@ def test_fschacha20poly1305aead(self): dec_aead.decrypt(b"", None) plaintext = dec_aead.decrypt(aad, ciphertext) self.assertEqual(plain, plaintext) + + +class TestFrameworkHybridKeys(unittest.TestCase): + ELLSWIFT_I = bytes(range(64)) + ELLSWIFT_R = bytes(range(64, 128)) + OFFER = b"\xfd\x21\x06\xf0" + bytes([0x11]) * 1568 + ACCEPT = b"\xfd\x21\x06\xf0" + bytes([0x22]) * 1568 + SS_ECDH = bytes([0x33]) * 32 + SS_MLKEM = bytes([0x44]) * 32 + MAINNET = bytes.fromhex("444f24a8") + + def derive(self, ellswift_i=ELLSWIFT_I, ellswift_r=ELLSWIFT_R, contents_r=OFFER, contents_i=ACCEPT, + ss_ecdh=SS_ECDH, ss_mlkem=SS_MLKEM, magic=MAINNET): + th_input = hybrid_transcript_input(ellswift_i, ellswift_r, contents_r, contents_i) + return hybrid_derive_keys(ss_ecdh, ss_mlkem, hashlib.sha256(th_input).digest(), magic) + + def test_layout(self): + """Transcript and HKDF input sizes match the specification.""" + self.assertEqual(len(hybrid_transcript_input(self.ELLSWIFT_I, self.ELLSWIFT_R, self.OFFER, self.ACCEPT)), 3278) + self.assertEqual(hybrid_transcript_input(self.ELLSWIFT_I, self.ELLSWIFT_R, b"", b""), + self.ELLSWIFT_I + self.ELLSWIFT_R + b"\x00\x00") + keys = self.derive() + self.assertEqual(keys["salt"], b"qbit_v2_hybrid_mlkem1024\x44\x4f\x24\xa8") + self.assertEqual(len(keys["ikm"]), 96) + for label in HYBRID_KEY_LABELS: + self.assertEqual(keys[label], hmac_sha256(keys["prk"], label.encode('ascii') + b"\x01")) + for n, encoding in ((252, "fc"), (253, "fdfd00"), (0xffff, "fdffff"), (0x10000, "fe00000100"), + (0x100000000, "ff0000000001000000")): + self.assertEqual(ser_compact_size(n).hex(), encoding) + + def test_binding(self): + """Every input changes all five derived values.""" + base = self.derive() + variants = [ + self.derive(ellswift_i=b"\x01" + self.ELLSWIFT_I[1:]), + self.derive(ellswift_r=self.ELLSWIFT_R[:-1] + b"\x00"), + self.derive(contents_r=self.OFFER[:-1] + b"\x00"), + self.derive(contents_i=self.ACCEPT[:-1] + b"\x00"), + self.derive(contents_r=self.ACCEPT, contents_i=self.OFFER), + self.derive(contents_r=self.OFFER + self.ACCEPT[:1], contents_i=self.ACCEPT[1:]), + self.derive(contents_i=self.ACCEPT + b"\x01\xf1"), + self.derive(ss_ecdh=self.SS_MLKEM, ss_mlkem=self.SS_ECDH), + self.derive(magic=bytes.fromhex("a66b1fda")), + ] + seen = {base["session_id"]} + for keys in variants: + for label in HYBRID_KEY_LABELS: + self.assertNotEqual(keys[label], base[label]) + seen.add(keys["session_id"]) + self.assertEqual(len(seen), len(variants) + 1) + + def test_packets(self): + """Both sides' hybrid ciphers agree, across a rekey.""" + keys = self.derive() + i_send_l, i_send_p, i_recv_l, i_recv_p = hybrid_ciphers(keys, initiator=True) + r_send_l, r_send_p, r_recv_l, r_recv_p = hybrid_ciphers(keys, initiator=False) + confirmation = encrypt_packet(i_send_l, i_send_p, b"", ignore=True) + self.assertEqual(len(confirmation), 20) + self.assertEqual(decrypt_packet(r_recv_l, r_recv_p, confirmation), (0, (True, b""))) + for i in range(REKEY_INTERVAL + 1): + contents = i.to_bytes(2, 'little') + self.assertEqual(decrypt_packet(i_recv_l, i_recv_p, encrypt_packet(r_send_l, r_send_p, contents)), + (2, (False, contents))) + damaged = bytearray(encrypt_packet(r_send_l, r_send_p, b"x")) + damaged[-1] ^= 1 + self.assertEqual(decrypt_packet(i_recv_l, i_recv_p, bytes(damaged)), (1, None)) diff --git a/test/lint/lint-pq-transport-vectors.py b/test/lint/lint-pq-transport-vectors.py new file mode 100755 index 0000000000..c1d9a8ed05 --- /dev/null +++ b/test/lint/lint-pq-transport-vectors.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026-present The qbit core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. +"""Check that src/test/data/pq_transport_vectors.json matches the Python reference recipe.""" + +import subprocess +import sys +from pathlib import Path + + +def main(): + root = Path(subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True, encoding="utf8").strip()) + generator = root / "contrib" / "devtools" / "generate-pq-transport-vectors.py" + sys.exit(subprocess.run([sys.executable, str(generator), "--check"], cwd=root).returncode) + + +if __name__ == "__main__": + main() From 930d07f9d6cc6b2fd3e06f718e5f9d07645a9075 Mon Sep 17 00:00:00 2001 From: Dan Hepworth Date: Mon, 5 Oct 2026 13:01:16 -0400 Subject: [PATCH 2/8] doc: specify hybrid v2 transport (doc/design/pq-transport.md) Add the specification of the hybrid ML-KEM-1024 v2 transport from #184's design, written for interoperating implementations and outside review: the record grammar and header registry (0x00 reserved, 0xF0 for the ML-KEM-1024 offer and accept, qbit's values from 0xF0 upward), byte layouts, parse rules (unparseable contents are ignored, unlike the Transport Feature Negotiation draft), the key recipe byte by byte, the responder and initiator message rules and why progress still holds, the switch boundary, key confirmation MUSTs, failure handling, security notes, an interop walk-through of the vectors file, the convergence and retirement policy, and a contributor section. The spec quotes vector 0 in a marked block, and generate-pq-transport-vectors.py now fails (in --check mode too, so in lint) when a quoted value differs from the vectors. Part of #184 (#199, H2). Co-Authored-By: Claude Opus 5.5 --- .../devtools/generate-pq-transport-vectors.py | 40 +- doc/design/pq-transport.md | 507 ++++++++++++++++++ 2 files changed, 543 insertions(+), 4 deletions(-) create mode 100644 doc/design/pq-transport.md diff --git a/contrib/devtools/generate-pq-transport-vectors.py b/contrib/devtools/generate-pq-transport-vectors.py index f21e27d025..1796fbe1ca 100755 --- a/contrib/devtools/generate-pq-transport-vectors.py +++ b/contrib/devtools/generate-pq-transport-vectors.py @@ -11,19 +11,24 @@ byte. The recipe is specified in doc/design/pq-transport.md. Run without arguments to rewrite the file, or with --check (as the lint does) to fail when the -committed file differs from what the reference produces. +committed file differs from what the reference produces. Both modes also fail when a value the +specification quotes in a "pq-transport-vectors" block differs from the vectors. """ import argparse import hashlib import importlib import json +import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] FUNCTIONAL_DIR = ROOT / "test" / "functional" OUTPUT = ROOT / "src" / "test" / "data" / "pq_transport_vectors.json" +SPEC = ROOT / "doc" / "design" / "pq-transport.md" +# A block of "name = hex" lines quoting one vector, introduced by an HTML comment. +SPEC_BLOCK = re.compile(r"\n```text\n(.*?)\n```", re.DOTALL) PQ_MLKEM1024 = 0xF0 # Network magic in wire order (src/kernel/chainparams.cpp); bip324_tests checks it against the chain. @@ -323,7 +328,26 @@ def generate(): "confirmation_failures": confirmation_failures, "negative_vectors": negatives, } - return json.dumps(document, indent=2) + "\n" + return document + + +def check_spec(document): + """Return every value the specification quotes that differs from the vectors.""" + spec = SPEC.relative_to(ROOT) + blocks = SPEC_BLOCK.findall(SPEC.read_text(encoding="utf8")) + if not blocks: + return [f"{spec}: no quoted vector block found"] + errors = [] + for index, body in blocks: + if int(index) >= len(document["vectors"]): + errors.append(f"{spec}: quotes vector {index}, which does not exist") + continue + vector = document["vectors"][int(index)] + for line in body.splitlines(): + name, _, value = line.partition(" = ") + if vector.get(name) != value: + errors.append(f"{spec}: vector {index} quotes {name} = {value}, which does not match the vectors") + return errors def main(): @@ -331,7 +355,11 @@ def main(): parser.add_argument("--check", action="store_true", help=f"fail if {OUTPUT.relative_to(ROOT)} is stale") args = parser.parse_args() - text = generate() + document = generate() + text = json.dumps(document, indent=2) + "\n" + spec_errors = check_spec(document) + for error in spec_errors: + print(error, file=sys.stderr) if args.check: current = OUTPUT.read_text(encoding="utf8") if OUTPUT.exists() else None if current != text: @@ -339,10 +367,14 @@ def main(): "intended, it needs a new version-contents header (doc/design/pq-transport.md); then run " f"{Path(__file__).relative_to(ROOT)} to regenerate it.", file=sys.stderr) sys.exit(1) - print(f"{OUTPUT.relative_to(ROOT)} matches the Python reference.") + if spec_errors: + sys.exit(1) + print(f"{OUTPUT.relative_to(ROOT)} and the values {SPEC.relative_to(ROOT)} quotes match the Python reference.") return OUTPUT.write_text(text, encoding="utf8") print(f"Wrote {OUTPUT.relative_to(ROOT)}") + if spec_errors: + sys.exit(1) if __name__ == "__main__": diff --git a/doc/design/pq-transport.md b/doc/design/pq-transport.md new file mode 100644 index 0000000000..c78d76ae60 --- /dev/null +++ b/doc/design/pq-transport.md @@ -0,0 +1,507 @@ +# Hybrid post-quantum v2 transport + +This document specifies how two qbit nodes add an ML-KEM-1024 key exchange to +the [BIP324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki) v2 +P2P transport, so that their session keys stay confidential unless both +secp256k1 ECDH and ML-KEM-1024 are broken. It is written so that another +implementation can interoperate, and it is the starting point for outside +review. The tracking issue is +[Qbit-Org/qbit#184](https://github.com/Qbit-Org/qbit/issues/184). + +The key words MUST, MUST NOT, SHOULD and MAY are to be interpreted as described +in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and +[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174) when, and only when, they +appear in all capitals. + +## Overview + +- **Threat.** A passive observer can record v2 traffic today and, with a + cryptographically relevant quantum computer, later recover the ECDH secret + from the two 64-byte ElligatorSwift keys at the start of each connection. + Hybrid keys protect such recordings. The symmetric layer (ChaCha20-Poly1305 + with FSChaCha20 rekeying every 224 packets) is unchanged. +- **Hybrid, and ECDH stays.** Session keys come from HKDF-SHA256 over the BIP324 + ECDH secret, an ML-KEM-1024 shared secret + ([FIPS 203](https://csrc.nist.gov/pubs/fips/203/final), NIST category 5) and + a hash of the negotiation transcript. +- **No extra round trip.** The responder puts its ML-KEM encapsulation key in + its BIP324 version packet. The initiator waits for that packet, which arrives + in the same flight as today, and puts the ciphertext in its own version + packet. Both sides switch keys right after the version packets, and each + side's first packet under the new keys is a 20-byte key confirmation. +- **Compatible.** Version packet contents are ignored by BIP324 receivers, so + legacy v2 peers keep today's ECDH-only keys with no disconnect, and v1 peers + are unaffected. No consensus rule changes. +- **Cost.** 3,184 extra bytes per hybrid connection (two 1,572-byte records and + two 20-byte confirmations), and one ML-KEM key generation, encapsulation and + decapsulation. + +```text +Initiator Responder +ellswift_I || garbage_I ----------> + <---------- ellswift_R || garbage_R + <---------- terminator_R || version_R{F0 || ek} then holds +terminator_I ----------> +holds its version until version_R arrives +version_I{F0 || ct}, ECDH keys ----------> +switch to hybrid keys decapsulate; switch to hybrid keys +confirmation, hybrid keys ----------> + <---------- confirmation, hybrid keys +application packets <----------> application packets +``` + +Neither side waits for the other's confirmation before sending. A receiver +delivers application messages only after the peer's confirmation verifies. + +## Terms + +- **Contents**: the plaintext contents of a BIP324 version packet, which + BIP324 reserves for future versions. +- **Record**: one entry in the contents, as defined below. +- **Own record**: a record with header `0xF0`. +- **Offer**: the responder's own record, carrying an ML-KEM-1024 encapsulation + key `ek` (1,568 bytes). +- **Accept**: the initiator's own record, carrying an ML-KEM-1024 ciphertext + `ct` (1,568 bytes). +- **ECDH keys**: the four BIP324 packet ciphers derived from the ECDH secret + alone. **Hybrid keys**: the four packet ciphers derived by the recipe below. +- **Switch on**: a node configured to offer (as responder) and accept (as + initiator). With the switch off, a node behaves exactly as BIP324 does today. + +## Wire format + +### Records + +Version packet contents are zero or more records: + +```text +contents = record* +record = CompactSize(len) || header (1 byte) || payload (len - 1 bytes) +``` + +`len` counts the header plus the payload and MUST be at least 1. `CompactSize` +is Bitcoin's variable-length integer, and a receiver MUST reject non-canonical +encodings as a parse failure. The shape follows the bitcoin-dev +[Transport Feature Negotiation](https://gnusha.org/pi/bitcoindev/29174459-1523-4cf8-a20f-5ce1abc302c3n@googlegroups.com/) +draft. + +### Header registry + +| Header | Meaning | +|---|---| +| `0x00` | Reserved. | +| `0xF0` | `PQ_MLKEM1024`: the ML-KEM-1024 offer (sent by the responder) and accept (sent by the initiator). The sender's role says which one a record is. | + +qbit assigns its own header values from `0xF0` upward, far from the low values +a Transport Feature Negotiation table would assign first. Every other value is +unassigned, and a receiver ignores records with headers it doesn't know. + +### Offer and accept + +- The responder sends an offer only when its switch is on. The offer's payload + is the 1,568-byte encapsulation key `ek`. +- The initiator sends an accept only after it received a valid offer and its + switch is on. The accept's payload is the 1,568-byte ciphertext `ct`. + Otherwise the initiator sends empty contents. +- qbit sends the offer and the accept as the entire contents, but a receiver + MUST handle other records alongside them as the parse rules below say. + +| Object | Layout | Bytes | +|---|---|---:| +| Offer contents | `fd 21 06 f0 ‖ ek` | 1,572 | +| Accept contents | `fd 21 06 f0 ‖ ct` | 1,572 | +| Record length | CompactSize 1,569 (header plus payload) | `fd 21 06` | +| Encrypted version packet | encrypted length 3 ‖ encrypted payload (header `00` ‖ contents) 1,573 ‖ tag 16 | 1,592 | +| Encrypted confirmation | encrypted length 3 (plaintext `00 00 00`) ‖ encrypted header 1 (plaintext `80`) ‖ tag 16 | 20 | + +**AAD.** Both version packets travel under the ECDH keys, with BIP324's +associated data rule: a sender's first packet in its direction authenticates +its garbage, so if a decoy already consumed the garbage, the version packet +uses empty AAD. Confirmations always use empty AAD. + +### Parse rules + +A receiver parses the contents in one pass: + +1. **The whole contents must parse as records.** Empty contents, or a parse + failure anywhere (truncation, a zero length, a non-canonical or oversized + length, trailing bytes), mean "no features": the connection continues with + ECDH keys and MUST NOT be dropped for it. This deliberately differs from the + Transport Feature Negotiation draft, which disconnects on unparseable + contents: BIP324 receivers ignore version contents today, and qbit v1.0.0 + relies on that. +2. **The first own record wins.** Later `0xF0` records and records with unknown + headers are ignored, but the transcript still covers them. +3. **Only after the whole grammar validates** is the first own record checked. + An offer's payload MUST be exactly 1,568 bytes and MUST pass the FIPS 203 + section 7.2 encapsulation key check (every encoded coefficient below q). An + accept's payload MUST be exactly 1,568 bytes. A malformed first own record + disconnects the connection. +4. **A corrupted `ct` of the right length can't be detected**, because ML-KEM + decapsulation rejects implicitly. The two sides derive different keys, and + the key confirmation fails at once. + +A responder that sent no offer MUST ignore an own record in the initiator's +contents and never decapsulate. A node whose switch is off MUST NOT parse or +validate the peer's contents. qbit's receive limit for any packet, 4,000,013 +bytes of contents, bounds the parser's work. + +## Message rules + +### Responder + +- It MUST send its version packet after its garbage terminator without waiting + to receive anything more from the initiator, such as the initiator's garbage + terminator or version packet. It MAY send decoy packets before the version + packet, as BIP324 allows. BIP324 implementations already behave this way. +- After sending an offer, it MUST send nothing more, decoys included, until it + has processed the initiator's version packet. +- On the initiator's version packet: with no own record, or contents that fail + to parse, it keeps the ECDH keys and discards its ML-KEM state. With a + malformed accept, it disconnects. With a valid-length accept, it decapsulates + and switches. + +### Initiator + +- With its switch on, it MAY hold its version packet until the responder's + version packet arrives, and then sends the ciphertext in it. With its switch + off, it sends its version packet at once, as today. +- On the responder's version packet: with no own record, or contents that fail + to parse, it sends empty contents and keeps the ECDH keys. With a malformed + offer, it disconnects. With a valid offer, it encapsulates, sends the accept + under the ECDH keys and switches. +- It MAY send decoy packets before its version packet, under the ECDH keys. + +### Why progress still holds + +BIP324's version negotiation phase lists the initiator's steps as receiving a +packet and then sending its own version packet, and notes that version packets +*can* be sent right after the garbage terminator. Deployed initiators send at +once; a hybrid qbit initiator forgoes that optional immediate send and uses the +listed order. All of the progress conditions BIP324 sets for handshake +upgrades still hold: in particular, after sending its garbage terminator each +party moves to version negotiation without waiting for more bytes. The +initiator's wait always ends, because every responder, legacy or hybrid, MUST +send its version packet without waiting to receive anything more from the +initiator. Responders never hold before their own version packet. + +BIP324 names post-quantum cryptography upgrades to the handshake among the +features its version negotiation phase is meant to carry. + +## Key schedule + +### Retained secret + +Each side keeps the raw 32-byte BIP324 ECDH secret `ss_ECDH` (the tagged hash +`bip324_ellswift_xonly_ecdh` that BIP324's HKDF consumes) past its normal +lifetime, until it either switches or decides to stay on the ECDH keys. An +implementation SHOULD wipe it on every path. + +### Transcript + +```text +TH = SHA256( ellswift_I (64) || ellswift_R (64) + || CompactSize(len(contents_R)) || contents_R + || CompactSize(len(contents_I)) || contents_I ) +``` + +- `ellswift_I` and `ellswift_R` are the 64-byte ElligatorSwift keys the + initiator and the responder sent, in that order, whatever the local role. +- `contents_R` and `contents_I` are the full version packet contents each side + sent, responder first, including unknown and duplicate records. +- Packet framing, garbage, terminators, tags, decoys and confirmations are not + hashed. +- For the ordinary one-record exchange, the TH input is 3,278 bytes. + +### Derivation + +HKDF-SHA256 ([RFC 5869](https://www.rfc-editor.org/rfc/rfc5869)), with one +32-byte output block per label: + +```text +salt = ASCII("qbit_v2_hybrid_mlkem1024") || network_magic 24 + 4 = 28 bytes, no NUL +IKM = ss_ECDH (32) || ss_MLKEM (32) || TH (32) 96 bytes +PRK = HMAC-SHA256(key = salt, msg = IKM) +initiator_L, initiator_P, responder_L, responder_P, session_id + = HMAC-SHA256(PRK, ASCII(label) || 0x01) 32 bytes each +``` + +The network magic is the chain's 4-byte message start in wire order, the same +bytes BIP324's salt uses: mainnet `44 4f 24 a8`, regtest `a6 6b 1f da`. +`ss_MLKEM` is the 32-byte ML-KEM-1024 shared secret: the output of +encapsulation at the initiator and of decapsulation at the responder. + +### Switch + +- Switching replaces all four packet ciphers at once: the initiator sends with + `initiator_L` and `initiator_P` and receives with `responder_L` and + `responder_P`, and the responder the reverse. Each new cipher starts with its + packet counter at 0, and BIP324's rekeying every 224 packets then derives + from the hybrid keys. +- The session id becomes the hybrid `session_id`. +- The garbage terminators are not re-derived. +- The initiator switches right after queuing its version packet, and the + responder right after processing the initiator's version packet. + +### Recipe changes + +The recipe, its labels, salt and transcript layout are a wire contract between +releases. Any change takes a new header with its own salt label. A label-only +change under the same header would let two adjacent releases both accept the +offer, derive different keys and disconnect at the first hybrid packet. + +## The switch boundary + +| Direction | Packets | Keys | +|---|---|---| +| Initiator to responder | ElligatorSwift key, garbage, garbage terminator | none (plaintext) | +| Initiator to responder | decoys before the version packet; the version packet (accept or empty) | ECDH | +| Initiator to responder | after a switch: confirmation (hybrid packet 0), then decoys and application packets | hybrid | +| Responder to initiator | ElligatorSwift key, garbage, garbage terminator | none (plaintext) | +| Responder to initiator | decoys before the version packet; the version packet (offer or empty) | ECDH | +| Responder to initiator | after a switch: confirmation (hybrid packet 0), then decoys and application packets | hybrid | + +Without a switch, every packet after the garbage terminators uses the ECDH +keys, exactly as in BIP324. + +## Key confirmation + +**Sending.** Right after switching, a node MUST send a key confirmation as its +first packet under the hybrid keys: an empty decoy packet with header `0x80` +and empty AAD, 20 bytes on the wire. It consumes hybrid packet 0 in that +direction. A node MUST NOT wait for the peer's confirmation before sending +application packets. + +**Receiving.** The first packet a node receives under the hybrid keys MUST meet +all of the following, and otherwise the node MUST disconnect at once: + +- its decrypted length is 0, checked as soon as the 3 length bytes arrive, + before waiting for or allocating a body; +- its tag verifies; +- its ignore bit is set. Other header bits are ignored, as BIP324 specifies. + +A node MUST NOT deliver application messages, or report the connection as +hybrid, until the peer's confirmation verifies. Without this check, a key +mismatch decrypts to a random length, and about 24% of the time the receiver +would wait until its handshake timeout for bytes that never arrive. + +## Failure handling + +- Internal ML-KEM errors not caused by the peer never crash the node. A + responder that can't generate a key sends no offer. An initiator that can't + check the key or encapsulate continues as if its switch were off for that + connection. A responder whose decapsulation fails internally closes the + connection, because the initiator has already switched. +- Key generation draws two independent 32-byte strong random values for the + 64-byte seed, and encapsulation draws one 32-byte value. +- The decapsulation key, the retained ECDH secret, ML-KEM temporaries and + derived keys are wiped on switch, on staying with ECDH keys, at disconnect + and timeout, and in destructors. + +How qbit counts, logs and reports these outcomes, its fallback after repeated +failures and its load shedding are local policy, described in +[#184](https://github.com/Qbit-Org/qbit/issues/184); they don't change the wire +behavior above. + +qbit reports each v2 connection's negotiation status as one of these values: + +- `pending` from the start of the handshake until one of the outcomes below; +- `hybrid` once the peer's confirmation verifies; +- `legacy_peer` when the peer's version packet carries no usable offer or + accept, so the ECDH keys stay; +- `off` when the switch is off, or after a local key generation, key check or + encapsulation fault that left the connection on plain v2; +- `fallback` for an outbound connection that runs plain v2 because of earlier + failures; inbound connections never fall back. + +There is no failed status. A connection that closes on any failure above or +in key confirmation, local faults included, reports `pending` until it is +removed. + +## Security notes + +- **Version contents have ECDH-only protection.** The EK, the CT and any future + record travel under the ECDH keys. A future attacker who breaks ECDH learns + them, which is fine for ML-KEM's public values but matters for any future + record that carries secrets. +- **The guarantee holds for confirmed hybrid connections.** A switch that is + off on either side, a local fault, load shedding and fallback all permit + ECDH-only sessions. `getpeerinfo` reports `transport_pq` only once the peer's + confirmation verified. +- **This is not authentication.** BIP324 stays unauthenticated, and an active + man-in-the-middle can still relay two separate sessions. The hybrid session + id can be compared out of band the same way BIP324's can. +- **Downgrade.** Stripping an offer or an accept means forging packets under + the ECDH keys, so it takes an active attacker who breaks ECDH in real time + or sits in the middle, and an unauthenticated session can't stop the latter + anyway. A passive recorder can't downgrade a connection. +- **Transcript binding.** Both keys, both contents and the network magic feed + the derivation, so two sides that saw different negotiation bytes derive + different keys and fail the confirmation instead of continuing. +- **Distinguishability.** Hybrid sessions are distinguishable from legacy v2 + by packet sizes. Users who need to hide that should use Tor or I2P. +- **Forward secrecy.** Each connection uses a fresh ML-KEM key pair, and the + decapsulation key is wiped after the switch. + +## Interop recipe + +The vectors file +[`src/test/data/pq_transport_vectors.json`](../../src/test/data/pq_transport_vectors.json) +holds every input and intermediate. All byte strings are lowercase hex. To +check an implementation against vector 0: + +1. Select the chain in `chain` and check that its message start is `magic`. +2. Take `initiator_privkey` with `initiator_ellswift` and `responder_privkey` + with `responder_ellswift`. Each side's BIP324 ECDH gives `ss_ecdh`; BIP324's + own HKDF gives `ecdh_session_id` and the two garbage terminators, which the + switch leaves alone. +3. Run ML-KEM-1024 KeyGen on `mlkem_keygen_seed` (`d || z`) to get `ek`, and + Encaps on `ek` with `mlkem_encaps_m` to get `ct` and `ss_mlkem`. + Decapsulating `ct` with the decapsulation key gives `ss_mlkem` too. +4. Build `contents_responder` (`fd 21 06 f0 || ek`) and `contents_initiator` + (`fd 21 06 f0 || ct`). In vector 2 the responder's offer is followed by a + second `0xF0` record with a different valid `ek` and an unknown `0xF1` + record, and the initiator's accept is preceded by an unknown `0x01` record: + the KEM uses the first `0xF0` record of each contents, and the transcript + covers every record. Vector 3 appends a 65,535-byte unknown record to the + offer, so that record's length and the responder's contents length in the + transcript both take the `0xfe` CompactSize form. +5. Concatenate the transcript input and compare it with `transcript_input`, and + its SHA256 with `transcript_hash`. +6. Compare `salt`, `ikm`, `prk` and the five derived values. +7. Encrypt the initiator's confirmation (empty contents, header `0x80`, empty + AAD) with fresh ciphers keyed by `initiator_L` and `initiator_P`; it must + equal `initiator_confirmation`. Hybrid packet 1 with + `initiator_first_contents` (a short-id `ping`) must equal + `initiator_first_packet`. Do the same for the responder. +8. For each entry in `negative_vectors`, the side named by `role` derives from + `transcript_contents` and `ss_mlkem` instead of the honest values of vector + `vector`. It must + get `session_id` and send `confirmation`, and decrypting the honest peer's + confirmation must fail with `decrypted_length` (reason `length` when nonzero, + else `tag`); the honest peer fails on its confirmation with + `peer_decrypted_length`. +9. Each entry in `confirmation_failures` is a first hybrid packet that the side + named by `receiver` must reject under the honest keys of vector `vector`: + the length decrypts to `decrypted_length`, and the check fails with + `failure`, one of `length`, `tag` (a zero length with a damaged tag) and + `not_decoy`. + +### Vector 0 + + +```text +magic = 444f24a8 +initiator_ellswift = 6b5fb320cab84c788ef6393262172b222facedee5e55f4e2fa44bae00b1f36102dd8739200e025a0443a6c6d3d52f003e4b94d296ed3a68e9dacc567f0c7bd09 +responder_ellswift = 3d3449542df2dc02aedca2a14e5c6b22f2b797b3191488cdbdc1ff6ad3ef40a77e62023395a490bbacc9de3da7fd37f70592ec361e34c01c761ad4ea2029712c +ss_ecdh = 7e8dd0eda7d613290fcfc7919af8675a2405e6585378777b9d2f02e592f1fb1d +ecdh_session_id = ab8e9ae6fe9d33e2e10f847855298fbe295ed45625608b425ca118d09dbfd0a4 +ss_mlkem = 22c8bf5c4858ac2e5f94b6e945ca142ccc28b98e7b954a78899630a7bfa3135f +transcript_hash = 66f0655505928bdeec6ce4cf2b1de08ff16c2cb1cf04b781011e30ca6687c240 +salt = 716269745f76325f6879627269645f6d6c6b656d31303234444f24a8 +prk = cacbbffc106ea035465bc498d22562e83989c039390ddc1ea36e0fe7ba0ac6b4 +initiator_L = d723af332d033d229fe91db011f04cd005657fcc35d59b93b6771e77dc9bd572 +initiator_P = 72462d2ed71f2b6b9c7435745166155bbd61ab04e2e21798945d1282f690e7d2 +responder_L = 167756b477acb286099da2a866ad9e2d94ec719236dc61836f2956451148ec12 +responder_P = 95eb7e61e0e575bd36db2a0b589553984942db0da9f43f8d8bc48012f2323d21 +session_id = c749d7dadf82b12615fa4ccdeef8cbce4ffb935ad22a7eac1fbc52d8d1394ea4 +initiator_confirmation = 69abb2b1c9601dad7baf8038bc3601eaa42527de +responder_confirmation = a634faec713fb192a52b9e810216b8a36d39516b +initiator_first_packet = 4bbe69dca55d32377b3cee47741171e83115bae7d56d7a9a81dd94394a +responder_first_packet = be28dfc2f7f52321d961b644a0ff9fb473d4f9055c6e9523c696751721 +``` + +The generator's `--check` mode fails if any value above differs from the file. + +## Convergence policy and recipe retirement + +- qbit tracks bitcoin-dev, bitcoin/bips and upstream transport changes such as + [bitcoin/bitcoin#36376](https://github.com/bitcoin/bitcoin/issues/36376) + (rare BIP324 decoys). The rule against decoys between an offer and the + initiator's version packet binds only offering qbit responders, so upstream + decoys from legacy peers stay compatible. +- If a Bitcoin standard for post-quantum transport ships, qbit implements it as + a new header. At most two post-quantum recipes are supported at once, and + while two exist, `getpeerinfo` reports which one each hybrid peer uses. +- The `0xF0` recipe stays for at least two major releases after that standard + ships. The release before its removal announces it in its release notes and + warns at startup while `0xF0` connections are still in use. + +## Discussion + +This specification, its Python reference and the vectors are to be posted to +the bitcoin-dev threads +[Transport Feature Negotiation](https://gnusha.org/pi/bitcoindev/29174459-1523-4cf8-a20f-5ce1abc302c3n@googlegroups.com/) +and +[A Post-Quantum Path for BIP 324](https://groups.google.com/g/bitcoindev/c/n_5WuKVYqwI/), +asking for a reserved private-use header range and discussing ML-KEM-768 +against ML-KEM-1024. Links to those posts will be added here. + +## For contributors + +### Build and test + +```sh +cmake -B build +cmake --build build -j 8 --target test_bitcoin # builds build/bin/test_qbit +ctest --test-dir build -R bip324_tests +build/bin/test_qbit --run_test=bip324_tests +``` + +`bip324_tests` replays the vectors file (`hybrid_vectors`) and checks +transcript binding, API misuse and rekey boundaries. The Python reference has +its own unit tests: + +```sh +build/test/functional/test_runner.py feature_framework_unit_tests.py +``` + +The `bip324_cipher_roundtrip` fuzz target covers the switch, mismatched +transcripts, damaged packets and rekeys: + +```sh +cmake -B build_fuzz -DBUILD_FOR_FUZZING=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DSANITIZERS=fuzzer,address,undefined +cmake --build build_fuzz --target fuzz +FUZZ=bip324_cipher_roundtrip build_fuzz/bin/fuzz +``` + +### ML-KEM backends + +qbit uses native ML-KEM code on x86_64 with AVX2, detected at run time, and on +macOS arm64, and portable C everywhere else. That includes AArch64 Linux and +every other AArch64 ELF build: upstream's AArch64 assembly has no BTI landing +pads or GNU property note, so linking it would drop BTI/PAC/GCS marking from the +whole binary, and hardening wins. The CMake option +`WITH_MLKEM_NATIVE=AUTO|ON|OFF` controls the build: `OFF` builds portable C +only, and `ON` makes missing native support a configure error, as on AArch64 +ELF. At runtime, the hidden `-mlkemportable` option, and +`-test=mlkem_portable` on regtest, force portable C; they arrive with the +operator options ([#209](https://github.com/Qbit-Org/qbit/issues/209)). + +### Regenerating the vectors + +```sh +contrib/devtools/generate-pq-transport-vectors.py # rewrite the vectors file +contrib/devtools/generate-pq-transport-vectors.py --check # what the lint runs +``` + +The generator imports the Python reference from +`test/functional/test_framework/crypto` (`bip324_cipher.py` for the recipe, +`mlkem.py` for ML-KEM-1024) and never calls the C++ code. Every input comes +from a fixed label, so the output is byte-for-byte reproducible. Changing the +recipe changes the vectors, and both `--check` and `bip324_tests` fail; a +deliberate recipe change needs a new header. + +## References + +- [BIP324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki), + the v2 P2P transport, and its version negotiation phase. +- [FIPS 203](https://csrc.nist.gov/pubs/fips/203/final), ML-KEM. +- [RFC 5869](https://www.rfc-editor.org/rfc/rfc5869), HKDF. +- [mlkem-native v2.0.0](https://github.com/pq-code-package/mlkem-native/tree/v2.0.0), + the C library qbit uses. +- bitcoin-dev, + [Transport Feature Negotiation](https://gnusha.org/pi/bitcoindev/29174459-1523-4cf8-a20f-5ce1abc302c3n@googlegroups.com/) + (2026-09-17), and + [A Post-Quantum Path for BIP 324](https://groups.google.com/g/bitcoindev/c/n_5WuKVYqwI/). From 0f9dcb890ed964eaffe2c8943080e58b3889199b Mon Sep 17 00:00:00 2001 From: Dan Hepworth Date: Mon, 5 Oct 2026 14:00:47 -0400 Subject: [PATCH 3/8] net: implement PQ feature parsing and per-connection KEM state Add src/bip324_pq.{h,cpp} with PQHandshake, the version-contents parser and one connection's ML-KEM-1024 state for the hybrid v2 transport of #184. Nothing calls it from V2Transport yet; E3 does, so behavior is unchanged. - ParseContents is a single noexcept pass over a SpanReader. The whole contents must parse as records CompactSize(len) || header || payload (len >= 1, canonical, within the contents) before anything else is looked at; any framing error means no features. Each length is checked against what is left before SpanReader::ignore(). The first 0xF0 record wins and is returned as a span into the contents. - CheckRecordLength applies the role's length rule to that record (EK_LENGTH or CT_LENGTH); AcceptOffer runs the modulus check (EK_MODULUS) and encapsulates; DecapsulateAccept decapsulates and wipes the single-use key. SerializeRecord writes fd 21 06 f0 || payload. - Key generation draws two independent 32-byte values and encapsulation one, from GetStrongRandBytes in production or the test constructor's PQRandomSource; the ML-KEM operations come through PQKemOps. Seeds and coins are wiped. - Internal ML-KEM errors return mlkem::Error::INTERNAL and record which operation failed; nothing asserts. The first recorded failure wins, and any failure, ClearSecrets() and the destructor wipe the decapsulation key. net_tests gains pq_records_grammar, pq_records_first_wins, pq_record_semantics, pq_records_max_contents and pq_handshake_negotiation (vector 0's ek, ct and shared secret from deterministic entropy, plus injected keygen, check, encaps and decaps failures). The new pq_records fuzz target checks the parser against an independent reading of the grammar; it joins the required qbit seed corpora with 15 generated seeds, including the nightly mutation list in ci/test/03_test_script.sh. Part of #184 (#199, E2). Co-Authored-By: Claude Opus 5.5 --- ci/test/03_test_script.sh | 2 +- doc/development/fuzzing.md | 7 +- src/CMakeLists.txt | 1 + src/bip324_pq.cpp | 200 ++++++++ src/bip324_pq.h | 200 ++++++++ src/test/fuzz/CMakeLists.txt | 1 + src/test/fuzz/pq_records.cpp | 96 ++++ src/test/net_tests.cpp | 458 ++++++++++++++++++ test/fuzz/qbit_corpora/MANIFEST.json | 117 ++++- test/fuzz/qbit_corpora/README.md | 10 +- test/fuzz/qbit_corpora/generate_seeds.py | 64 +++ test/fuzz/qbit_corpora/overlay.py | 1 + test/fuzz/qbit_corpora/pq_records/accept | Bin 0 -> 1572 bytes .../qbit_corpora/pq_records/empty-own-record | 1 + test/fuzz/qbit_corpora/pq_records/long-offer | Bin 0 -> 1573 bytes .../pq_records/malformed-first-then-offer | Bin 0 -> 1576 bytes .../pq_records/minimal-unknown-records | 1 + .../pq_records/non-canonical-length | Bin 0 -> 5 bytes test/fuzz/qbit_corpora/pq_records/offer | Bin 0 -> 1572 bytes .../pq_records/offer-duplicate-unknown | Bin 0 -> 3148 bytes .../pq_records/offer-trailing-byte | Bin 0 -> 1573 bytes .../fuzz/qbit_corpora/pq_records/one-past-end | 1 + .../qbit_corpora/pq_records/oversized-length | Bin 0 -> 7 bytes .../pq_records/reserved-and-unknown-headers | Bin 0 -> 7 bytes test/fuzz/qbit_corpora/pq_records/short-offer | Bin 0 -> 1571 bytes .../pq_records/truncated-wide-length | Bin 0 -> 6 bytes .../pq_records/unknown-then-accept | Bin 0 -> 1578 bytes test/fuzz/qbit_corpora/pq_records/zero-length | Bin 0 -> 7 bytes test/fuzz/test_qbit_fuzz_runner.py | 2 +- test/fuzz/test_runner.py | 1 + 30 files changed, 1156 insertions(+), 7 deletions(-) create mode 100644 src/bip324_pq.cpp create mode 100644 src/bip324_pq.h create mode 100644 src/test/fuzz/pq_records.cpp create mode 100644 test/fuzz/qbit_corpora/pq_records/accept create mode 100644 test/fuzz/qbit_corpora/pq_records/empty-own-record create mode 100644 test/fuzz/qbit_corpora/pq_records/long-offer create mode 100644 test/fuzz/qbit_corpora/pq_records/malformed-first-then-offer create mode 100644 test/fuzz/qbit_corpora/pq_records/minimal-unknown-records create mode 100644 test/fuzz/qbit_corpora/pq_records/non-canonical-length create mode 100644 test/fuzz/qbit_corpora/pq_records/offer create mode 100644 test/fuzz/qbit_corpora/pq_records/offer-duplicate-unknown create mode 100644 test/fuzz/qbit_corpora/pq_records/offer-trailing-byte create mode 100644 test/fuzz/qbit_corpora/pq_records/one-past-end create mode 100644 test/fuzz/qbit_corpora/pq_records/oversized-length create mode 100644 test/fuzz/qbit_corpora/pq_records/reserved-and-unknown-headers create mode 100644 test/fuzz/qbit_corpora/pq_records/short-offer create mode 100644 test/fuzz/qbit_corpora/pq_records/truncated-wide-length create mode 100644 test/fuzz/qbit_corpora/pq_records/unknown-then-accept create mode 100644 test/fuzz/qbit_corpora/pq_records/zero-length diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh index 6399230d18..a36230465f 100755 --- a/ci/test/03_test_script.sh +++ b/ci/test/03_test_script.sh @@ -371,6 +371,6 @@ if [ "$RUN_FUZZ_TESTS" = "true" ]; then --require_qbit_corpus \ --mutate_min_time="${QBIT_FUZZ_MUTATE_MIN_TIME}" \ "${DIR_FUZZ_IN}" \ - asert_chain_transition asert_edge_cases asert_math auxpow mlkem mlkem_backend_diff p2mr_script pqc + asert_chain_transition asert_edge_cases asert_math auxpow mlkem mlkem_backend_diff p2mr_script pq_records pqc fi fi diff --git a/doc/development/fuzzing.md b/doc/development/fuzzing.md index 4d43732d84..cee35c7585 100644 --- a/doc/development/fuzzing.md +++ b/doc/development/fuzzing.md @@ -41,7 +41,8 @@ $ ctest --test-dir build_fuzz -R '^required_fuzz_targets$' --output-on-failure ## qbit seed corpora `qa-assets` has no inputs for `asert_chain_transition`, `asert_edge_cases`, -`asert_math`, `auxpow`, `mlkem`, `mlkem_backend_diff`, `p2mr_script` or `pqc`. +`asert_math`, `auxpow`, `mlkem`, `mlkem_backend_diff`, `p2mr_script`, `pq_records` +or `pqc`. Generated seeds for these targets, their manifest and the input format are described in [`test/fuzz/qbit_corpora/README.md`](../../test/fuzz/qbit_corpora/README.md). @@ -52,7 +53,7 @@ $ test/fuzz/qbit_corpora/overlay.py qa-assets/fuzz_corpora $ build_fuzz/test/fuzz/test_runner.py --require_qbit_corpus qa-assets/fuzz_corpora ``` -`--require_qbit_corpus` fails if any of the eight targets is not compiled or not +`--require_qbit_corpus` fails if any of the nine targets is not compiled or not selected (including through `--exclude`), has no regular input files, or is not reported as replayed by the fuzz executable. The runner prints the number of input files present and replayed for each target. @@ -68,7 +69,7 @@ replay with a mutation phase of that many seconds per target. The phase starts from the corpus, writes new inputs to a temporary directory and leaves the corpus unchanged. It fails on non-libFuzzer builds. The nightly native fuzz job sets `QBIT_FUZZ_MUTATE_MIN_TIME`, which makes `ci/test/03_test_script.sh` run -this phase for the eight targets after the normal replay. +this phase for the nine targets after the normal replay. The budget is libFuzzer's `-max_total_time`, which counts time spent loading the seed corpus (libFuzzer's `INITED` line) as well as mutation. A run can end diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0e05808cdc..7d7ce68d34 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -191,6 +191,7 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL auxpow_validation.cpp banman.cpp bip324.cpp + bip324_pq.cpp blockencodings.cpp blockfilter.cpp consensus/tx_verify.cpp diff --git a/src/bip324_pq.cpp b/src/bip324_pq.cpp new file mode 100644 index 0000000000..285da14ebe --- /dev/null +++ b/src/bip324_pq.cpp @@ -0,0 +1,200 @@ +// Copyright (c) 2026-present The qbit core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit. + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +void StrongFill32(void*, std::span out) noexcept +{ + GetStrongRandBytes(out); +} + +const PQKemOps DEFAULT_KEM_OPS{ + .keygen = &mlkem::KeyGen, + .check_public_key = &mlkem::CheckPublicKey, + .encaps = &mlkem::Encaps, + .decaps = &mlkem::Decaps, +}; + +} // namespace + +PQRandomSource DefaultPQRandomSource() noexcept +{ + return {.fill32 = &StrongFill32, .context = nullptr}; +} + +const PQKemOps& DefaultPQKemOps() noexcept +{ + return DEFAULT_KEM_OPS; +} + +PQHandshake::ParsedContents PQHandshake::ParseContents(std::span contents) noexcept +{ + try { + SpanReader reader{contents}; + std::optional> first; + while (!reader.empty()) { + // ReadCompactSize rejects non-canonical encodings and truncation by throwing. + const uint64_t len{ReadCompactSize(reader)}; + // Check the length against what is left before reading or skipping anything, as + // SpanReader::ignore() does not check bounds. + if (len == 0 || len > reader.size()) return {.kind = ParseKind::INVALID_GRAMMAR}; + uint8_t header; + reader >> header; + const size_t payload_len{size_t(len - 1)}; + if (header == PQ_MLKEM1024 && !first) { + first = contents.subspan(contents.size() - reader.size(), payload_len); + } + reader.ignore(payload_len); + } + if (first) return {.kind = ParseKind::OWN_RECORD, .payload = *first}; + return {.kind = ParseKind::NO_RECORD}; + } catch (const std::ios_base::failure&) { + return {.kind = ParseKind::INVALID_GRAMMAR}; + } +} + +PQHandshake::Record PQHandshake::SerializeRecord(std::span payload) noexcept +{ + // CompactSize(1 + 1568) = CompactSize(0x0621) = fd 21 06. + static_assert(1 + mlkem::PUBLIC_KEY_BYTES == 0x0621); + Record record; + record[0] = std::byte{0xfd}; + record[1] = std::byte{0x21}; + record[2] = std::byte{0x06}; + record[3] = std::byte{PQ_MLKEM1024}; + std::ranges::copy(std::as_bytes(payload), record.begin() + 4); + return record; +} + +PQHandshake::PQHandshake(bool initiating, PQMode mode) noexcept + : PQHandshake{initiating, mode, DefaultPQRandomSource(), DefaultPQKemOps()} {} + +PQHandshake::PQHandshake(bool initiating, PQMode mode, PQRandomSource random, const PQKemOps& ops) noexcept + : m_initiating{initiating}, m_mode{mode}, m_random{random}, m_ops{ops} {} + +PQHandshake::~PQHandshake() +{ + ClearSecrets(); +} + +std::optional PQHandshake::CheckRecordLength(std::span payload) noexcept +{ + // Only a negotiating initiator before it accepts, or a responder holding its offer's key, + // has a record to check: anything else must ignore the peer's records. + if (!Assume(m_mode == PQMode::NEGOTIATE && (m_initiating ? m_snapshot.offer == PQOfferState::NONE : m_dk != nullptr))) { + SetFailure(PQFailure::CIPHER_STATE_INTERNAL); + return std::nullopt; + } + if (payload.size() != mlkem::PUBLIC_KEY_BYTES) { + SetFailure(m_initiating ? PQFailure::EK_LENGTH : PQFailure::CT_LENGTH); + return std::nullopt; + } + return Payload{UCharCast(payload.data()), mlkem::PUBLIC_KEY_BYTES}; +} + +mlkem::Error PQHandshake::MakeOffer(Record& offer) noexcept +{ + // Only a negotiating responder offers, and only once. + if (!Assume(!m_initiating && m_mode == PQMode::NEGOTIATE && m_snapshot.offer == PQOfferState::NONE && !m_dk)) { + SetFailure(PQFailure::KEYGEN_INTERNAL); + return mlkem::Error::INTERNAL; + } + // ProcRand allows at most 32 bytes per call, so the 64-byte seed d || z takes two draws. + mlkem::Secret seed; + m_random.fill32(m_random.context, seed.Bytes().first<32>()); + m_random.fill32(m_random.context, seed.Bytes().last<32>()); + auto dk{std::make_unique()}; + mlkem::PublicKey ek; + if (m_ops.keygen(seed.Bytes(), ek, *dk) != mlkem::Error::NONE) { + SetFailure(PQFailure::KEYGEN_INTERNAL); + return mlkem::Error::INTERNAL; + } + offer = SerializeRecord(ek); + m_dk = std::move(dk); + return mlkem::Error::NONE; +} + +void PQHandshake::SetOfferSent() noexcept +{ + if (Assume(!m_initiating && m_dk != nullptr && m_snapshot.offer == PQOfferState::NONE)) { + m_snapshot.offer = PQOfferState::SENT; + } +} + +mlkem::Error PQHandshake::AcceptOffer(Payload ek, Record& accept, mlkem::SharedSecret& ss) noexcept +{ + // Only a negotiating initiator accepts, and only once. + if (!Assume(m_initiating && m_mode == PQMode::NEGOTIATE && m_snapshot.offer == PQOfferState::NONE)) { + SetFailure(PQFailure::CHECK_EK_INTERNAL); + ss.Clear(); + return mlkem::Error::INTERNAL; + } + m_snapshot.offer = PQOfferState::RECEIVED; + switch (m_ops.check_public_key(ek)) { + case mlkem::Error::NONE: + break; + case mlkem::Error::INVALID_PUBLIC_KEY: + SetFailure(PQFailure::EK_MODULUS); + ss.Clear(); + return mlkem::Error::INVALID_PUBLIC_KEY; + default: + SetFailure(PQFailure::CHECK_EK_INTERNAL); + ss.Clear(); + return mlkem::Error::INTERNAL; + } + mlkem::Secret coins; + m_random.fill32(m_random.context, coins.Bytes()); + mlkem::Ciphertext ct; + // Encaps repeats the modulus check; failing it now is a local fault, not the peer's. + if (m_ops.encaps(ek, coins.Bytes(), ct, ss) != mlkem::Error::NONE) { + SetFailure(PQFailure::ENCAPS_INTERNAL); + ss.Clear(); + return mlkem::Error::INTERNAL; + } + accept = SerializeRecord(ct); + return mlkem::Error::NONE; +} + +mlkem::Error PQHandshake::DecapsulateAccept(Payload ct, mlkem::SharedSecret& ss) noexcept +{ + if (!Assume(m_dk != nullptr)) { + SetFailure(PQFailure::DECAPS_INTERNAL); + ss.Clear(); + return mlkem::Error::INTERNAL; + } + const mlkem::Error result{m_ops.decaps(*m_dk, ct, ss)}; + // The key is single use. + ClearSecrets(); + if (result != mlkem::Error::NONE) { + SetFailure(PQFailure::DECAPS_INTERNAL); + ss.Clear(); + return mlkem::Error::INTERNAL; + } + return mlkem::Error::NONE; +} + +void PQHandshake::SetFailure(PQFailure failure) noexcept +{ + if (m_snapshot.failure == PQFailure::NONE) m_snapshot.failure = failure; + ClearSecrets(); +} + +void PQHandshake::ClearSecrets() noexcept +{ + if (m_dk) { + m_dk->Clear(); + m_dk.reset(); + } +} diff --git a/src/bip324_pq.h b/src/bip324_pq.h new file mode 100644 index 0000000000..d8f0f8cf8a --- /dev/null +++ b/src/bip324_pq.h @@ -0,0 +1,200 @@ +// Copyright (c) 2026-present The qbit core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit. + +#ifndef QBIT_BIP324_PQ_H +#define QBIT_BIP324_PQ_H + +#include + +#include +#include +#include +#include +#include +#include + +/** + * Hybrid post-quantum negotiation for the BIP324 v2 transport (doc/design/pq-transport.md). + * + * Version-packet contents are zero or more records CompactSize(len) || header || payload, where + * len >= 1 counts the header and the payload. A responder offers its ML-KEM-1024 encapsulation key + * in a PQ_MLKEM1024 record, and an initiator accepts with the ciphertext in a record with the same + * header; the sender's role says which one a record is. + */ + +/** Record header of the ML-KEM-1024 offer (responder) and accept (initiator). 0x00 is reserved. */ +inline constexpr uint8_t PQ_MLKEM1024 = 0xF0; +/** An offer or accept record: CompactSize(1569) (3 bytes), the header and the 1,568-byte payload. */ +inline constexpr size_t PQ_RECORD_BYTES = 1572; +/** A key confirmation on the wire: encrypted length (3), encrypted header (1) and tag (16). */ +inline constexpr size_t PQ_CONFIRMATION_BYTES = 20; + +static_assert(mlkem::PUBLIC_KEY_BYTES == mlkem::CIPHERTEXT_BYTES); +static_assert(PQ_RECORD_BYTES == 3 + 1 + mlkem::PUBLIC_KEY_BYTES); + +/** What one side of a v2 connection does about the hybrid key exchange. */ +enum class PQMode : uint8_t { + //! Today's v2: empty version contents, the peer's contents ignored. + OFF = 0, + //! Responders offer an encapsulation key, initiators accept a valid offer. + NEGOTIATE = 1, + //! Plain v2 toward an endpoint in the fallback set (outbound only); behaves like OFF. + FALLBACK = 2, +}; + +/** What getpeerinfo reports as transport_pq_status. */ +enum class PQStatus : uint8_t { V1 = 0, OFF = 1, PENDING = 2, HYBRID = 3, LEGACY_PEER = 4, FALLBACK = 5 }; + +/** Offer progress. SENT (responder): the offer was queued for sending (SetOfferSent()), not + * necessarily received. RECEIVED (initiator): an offer of the right length was handed to + * AcceptOffer(). */ +enum class PQOfferState : uint8_t { NONE = 0, SENT = 1, RECEIVED = 2 }; + +/** The first failure of a connection's negotiation; later failures don't replace it. */ +enum class PQFailure : uint8_t { + NONE = 0, + // malformed_record: the first own record of the peer's contents. + EK_LENGTH = 1, + EK_MODULUS = 2, + CT_LENGTH = 3, + // first_packet_failed: the peer's key confirmation. + CONFIRM_LENGTH = 4, + CONFIRM_TAG = 5, + CONFIRM_NOT_DECOY = 6, + // internal_error: local faults, never caused by peer input. + KEYGEN_INTERNAL = 7, + CHECK_EK_INTERNAL = 8, + ENCAPS_INTERNAL = 9, + DECAPS_INTERNAL = 10, + CIPHER_STATE_INTERNAL = 11, +}; + +/** Entropy for key generation and encapsulation, 32 bytes per call. */ +struct PQRandomSource { + using Fill32 = void (*)(void*, std::span) noexcept; + Fill32 fill32; + void* context{nullptr}; +}; + +/** The ML-KEM operations a handshake uses. Must have static lifetime. */ +struct PQKemOps { + decltype(&mlkem::KeyGen) keygen; + decltype(&mlkem::CheckPublicKey) check_public_key; + decltype(&mlkem::Encaps) encaps; + decltype(&mlkem::Decaps) decaps; +}; + +/** Production entropy: GetStrongRandBytes. */ +PQRandomSource DefaultPQRandomSource() noexcept; +/** Production operations: the mlkem:: wrapper. */ +const PQKemOps& DefaultPQKemOps() noexcept; + +/** + * The version-contents parser and one connection's KEM state. + * + * There is no mutex: the owning transport's receive lock protects the object. The operations + * record their own failures (EK_MODULUS and the internal ones); callers record the others with + * SetFailure(). Internal ML-KEM errors never assert: they return mlkem::Error::INTERNAL, and the + * caller decides what happens to the connection. + */ +class PQHandshake +{ +public: + enum class ParseKind : uint8_t { NO_RECORD = 0, INVALID_GRAMMAR = 1, OWN_RECORD = 2 }; + struct ParsedContents { + ParseKind kind{ParseKind::NO_RECORD}; + //! OWN_RECORD only: the payload of the first PQ_MLKEM1024 record, pointing into the contents. + std::span payload{}; + }; + using Record = std::array; + using Payload = std::span; + struct Snapshot { + PQOfferState offer{PQOfferState::NONE}; + //! The peer's version packet authenticated. + bool version_received{false}; + //! The hybrid keys were installed. + bool switched{false}; + //! The peer's key confirmation verified. + bool confirmed{false}; + PQFailure failure{PQFailure::NONE}; + }; + + /** + * Parse version-packet contents in one pass, without copying or allocating. + * + * The whole contents must parse as records. Empty contents, or a framing error anywhere + * (truncation, a zero, non-canonical or oversized length, trailing bytes), give NO_RECORD or + * INVALID_GRAMMAR: no features, never a reason to disconnect. Otherwise the first + * PQ_MLKEM1024 record wins; later ones and unknown headers are ignored. Nothing about the + * payload is checked here. + */ + static ParsedContents ParseContents(std::span contents) noexcept; + + /** An offer or accept record carrying payload. */ + static Record SerializeRecord(std::span payload) noexcept; + + /** Production entropy and operations. */ + PQHandshake(bool initiating, PQMode mode) noexcept; + /** Test entropy and operations; ops must have static lifetime. */ + PQHandshake(bool initiating, PQMode mode, PQRandomSource random, const PQKemOps& ops) noexcept; + PQHandshake(bool initiating, PQMode mode, PQRandomSource random, const PQKemOps&& ops) = delete; + ~PQHandshake(); + PQHandshake(const PQHandshake&) = delete; + PQHandshake& operator=(const PQHandshake&) = delete; + + /** + * Check the length of the first own record of the peer's contents: the offer (for an + * initiator, before AcceptOffer()) or the accept (for a responder holding the decapsulation + * key of its offer) carries exactly 1,568 bytes. A wrong length records EK_LENGTH or + * CT_LENGTH, wipes the secrets and returns nullopt. A call in any other state is a local + * bug: it records CIPHER_STATE_INTERNAL and returns nullopt. + */ + std::optional CheckRecordLength(std::span payload) noexcept; + + /** + * Responder: generate a key pair from two independent 32-byte draws. Success writes the + * offer and keeps the decapsulation key; the caller queues the offer and then calls + * SetOfferSent(). Failure (KEYGEN_INTERNAL) writes no offer. + */ + [[nodiscard]] mlkem::Error MakeOffer(Record& offer) noexcept; + + /** Responder: the offer from MakeOffer() was queued for sending. */ + void SetOfferSent() noexcept; + + /** + * Initiator: check the offered key and encapsulate to it with one 32-byte draw. Returns + * INVALID_PUBLIC_KEY (EK_MODULUS) for a key that fails the modulus check, which is the + * peer's fault, and INTERNAL (CHECK_EK_INTERNAL, ENCAPS_INTERNAL) for local faults. Only + * success writes the accept record and the shared secret. + */ + [[nodiscard]] mlkem::Error AcceptOffer(Payload ek, Record& accept, mlkem::SharedSecret& ss) noexcept; + + /** + * Responder: decapsulate the accepted ciphertext. A corrupted ciphertext of the right + * length is not an error (implicit rejection). The decapsulation key is wiped either way; + * failure (DECAPS_INTERNAL) clears ss. + */ + [[nodiscard]] mlkem::Error DecapsulateAccept(Payload ct, mlkem::SharedSecret& ss) noexcept; + + void SetVersionReceived() noexcept { m_snapshot.version_received = true; } + void SetSwitched() noexcept { m_snapshot.switched = true; } + void SetConfirmed() noexcept { m_snapshot.confirmed = true; } + /** Record a failure unless one is recorded already, and wipe the secrets. */ + void SetFailure(PQFailure failure) noexcept; + Snapshot GetSnapshot() const noexcept { return m_snapshot; } + /** Whether a decapsulation key is held (between MakeOffer() and its wipe). */ + bool HasDecapsulationKey() const noexcept { return m_dk != nullptr; } + /** Wipe and release the decapsulation key. */ + void ClearSecrets() noexcept; + +private: + const bool m_initiating; + const PQMode m_mode; + PQRandomSource m_random; + const PQKemOps& m_ops; + std::unique_ptr m_dk; + Snapshot m_snapshot; +}; + +#endif // QBIT_BIP324_PQ_H diff --git a/src/test/fuzz/CMakeLists.txt b/src/test/fuzz/CMakeLists.txt index bbaa22cdfe..0f1313c238 100644 --- a/src/test/fuzz/CMakeLists.txt +++ b/src/test/fuzz/CMakeLists.txt @@ -92,6 +92,7 @@ add_executable(fuzz policy_estimator_io.cpp poolresource.cpp pow.cpp + pq_records.cpp pqc.cpp prevector.cpp primitives_transaction.cpp diff --git a/src/test/fuzz/pq_records.cpp b/src/test/fuzz/pq_records.cpp new file mode 100644 index 0000000000..6d4ae13ffa --- /dev/null +++ b/src/test/fuzz/pq_records.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2026-present The qbit core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +/** What the version-contents grammar says about an input, computed without the serialization code. */ +struct Expected { + bool valid{false}; + //! Offset and length of the first PQ_MLKEM1024 record's payload. + std::optional> first; +}; + +/** + * An independent reading of the record grammar: CompactSize(len) || header || payload, len >= 1, + * with the minimal CompactSize encoding and no bytes left over. Lengths above MAX_SIZE need more + * input than any fuzz input has, so "longer than what is left" covers them. + */ +Expected Oracle(std::span in) +{ + Expected expected; + size_t pos{0}; + while (pos < in.size()) { + const uint8_t tag{in[pos++]}; + uint64_t len{tag}; + if (tag >= 0xfd) { + const size_t width{tag == 0xfd ? 2U : tag == 0xfe ? 4U : 8U}; + const uint64_t minimum{tag == 0xfd ? 0xfdU : tag == 0xfe ? 0x10000U : 0x100000000U}; + if (in.size() - pos < width) return {}; + len = 0; + for (size_t i{0}; i < width; ++i) len |= uint64_t{in[pos + i]} << (8 * i); + pos += width; + if (len < minimum) return {}; + } + if (len == 0 || len > in.size() - pos) return {}; + if (in[pos] == PQ_MLKEM1024 && !expected.first) expected.first = {pos + 1, size_t(len - 1)}; + pos += size_t(len); + } + expected.valid = true; + return expected; +} + +void CheckParse(std::span in) +{ + const auto contents{std::as_bytes(in)}; + const auto parsed{PQHandshake::ParseContents(contents)}; + const Expected expected{Oracle(in)}; + if (!expected.valid) { + assert(parsed.kind == PQHandshake::ParseKind::INVALID_GRAMMAR); + assert(parsed.payload.empty()); + } else if (!expected.first) { + assert(parsed.kind == PQHandshake::ParseKind::NO_RECORD); + assert(parsed.payload.empty()); + } else { + assert(parsed.kind == PQHandshake::ParseKind::OWN_RECORD); + // The payload points into the contents, at the first own record. + assert(parsed.payload.data() == contents.data() + expected.first->first); + assert(parsed.payload.size() == expected.first->second); + } +} + +} // namespace + +FUZZ_TARGET(pq_records) +{ + CheckParse(buffer); + + // A record built from the first payload bytes parses back to them, and as the first record of + // longer contents it wins if, and only if, the rest is valid grammar. + if (buffer.size() < mlkem::PUBLIC_KEY_BYTES) return; + const auto record{PQHandshake::SerializeRecord(buffer.first())}; + const auto alone{PQHandshake::ParseContents(record)}; + assert(alone.kind == PQHandshake::ParseKind::OWN_RECORD); + assert(alone.payload.data() == record.data() + 4); + assert(std::ranges::equal(alone.payload, std::as_bytes(buffer.first()))); + + std::vector combined(record.size() + buffer.size()); + std::ranges::copy(MakeUCharSpan(record), combined.begin()); + std::ranges::copy(buffer, combined.begin() + record.size()); + CheckParse(combined); + const auto parsed{PQHandshake::ParseContents(std::as_bytes(std::span{combined}))}; + assert((parsed.kind == PQHandshake::ParseKind::OWN_RECORD) == Oracle(buffer).valid); +} diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 48197b2235..9ba54b5748 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -2,10 +2,16 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include + +#include #include #include #include #include +#include +#include +#include #include #include #include @@ -24,12 +30,16 @@ #include #include +#include #include +#include +#include #include #include #include #include +#include using namespace std::literals; using namespace util::hex_literals; @@ -1583,4 +1593,452 @@ BOOST_AUTO_TEST_CASE(v2transport_test) } } +namespace { + +/** Deterministic PQ entropy: the queued 32-byte values in order, then SHA256(seed || counter). */ +struct TestPQEntropy { + std::deque> queued; + uint256 seed; + uint64_t counter{0}; + + explicit TestPQEntropy(const uint256& seed_in) : seed{seed_in} {} + + static void Fill(void* context, std::span out) noexcept + { + auto& self{*static_cast(context)}; + if (!self.queued.empty()) { + std::ranges::copy(self.queued.front(), out.begin()); + self.queued.pop_front(); + return; + } + uint8_t counter_le[8]; + WriteLE64(counter_le, self.counter++); + CSHA256().Write(self.seed.begin(), self.seed.size()).Write(counter_le, sizeof(counter_le)).Finalize(out.data()); + } + + PQRandomSource Source() { return {.fill32 = &Fill, .context = this}; } + + void Queue(std::span bytes) + { + BOOST_REQUIRE(bytes.size() % 32 == 0); + for (size_t i{0}; i < bytes.size(); i += 32) { + std::array value; + std::ranges::copy(bytes.subspan(i, 32), value.begin()); + queued.push_back(value); + } + } +}; + +/** How often each ML-KEM operation ran through COUNTING_KEM_OPS. */ +struct KemCallCounts { + int keygen{0}, check{0}, encaps{0}, decaps{0}; + int total() const { return keygen + check + encaps + decaps; } +}; +KemCallCounts g_kem_calls; + +const PQKemOps COUNTING_KEM_OPS{ + .keygen = [](std::span seed, mlkem::PublicKey& ek, mlkem::DecapsulationKey& dk) noexcept { + ++g_kem_calls.keygen; + return mlkem::KeyGen(seed, ek, dk); + }, + .check_public_key = [](std::span ek) noexcept { + ++g_kem_calls.check; + return mlkem::CheckPublicKey(ek); + }, + .encaps = [](std::span ek, std::span coins, + mlkem::Ciphertext& ct, mlkem::SharedSecret& ss) noexcept { + ++g_kem_calls.encaps; + return mlkem::Encaps(ek, coins, ct, ss); + }, + .decaps = [](const mlkem::DecapsulationKey& dk, std::span ct, mlkem::SharedSecret& ss) noexcept { + ++g_kem_calls.decaps; + return mlkem::Decaps(dk, ct, ss); + }, +}; + +std::vector HexBytes(std::string_view hex) +{ + auto bytes{TryParseHex(hex)}; + BOOST_REQUIRE(bytes); + return std::move(*bytes); +} + +std::vector Concat(std::initializer_list> parts) +{ + std::vector out; + for (const auto& part : parts) out.insert(out.end(), part.begin(), part.end()); + return out; +} + +/** A record CompactSize(1 + payload size) || header || payload. */ +std::vector MakeRecord(uint8_t header, std::span payload) +{ + DataStream stream; + WriteCompactSize(stream, 1 + payload.size()); + stream << header; + stream.write(payload); + return {stream.begin(), stream.end()}; +} + +/** A valid encapsulation key, from a fixed seed. */ +mlkem::PublicKey TestEncapsulationKey() +{ + std::array seed{}; + seed.fill(0x5a); + mlkem::PublicKey ek; + mlkem::DecapsulationKey dk; + BOOST_REQUIRE(mlkem::KeyGen(seed, ek, dk) == mlkem::Error::NONE); + return ek; +} + +/** ek with its first coefficient set to q = 3329, which fails the modulus check. */ +mlkem::PublicKey BadModulusKey(mlkem::PublicKey ek) +{ + ek[0] = 0x01; + ek[1] = (ek[1] & 0xF0) | 0x0D; + return ek; +} + +void CheckParse(std::span contents, PQHandshake::ParseKind kind, std::optional> payload = std::nullopt) +{ + const auto parsed{PQHandshake::ParseContents(contents)}; + BOOST_CHECK(parsed.kind == kind); + if (payload) { + BOOST_CHECK(parsed.payload.data() == contents.data() + payload->first); + BOOST_CHECK_EQUAL(parsed.payload.size(), payload->second); + } else { + BOOST_CHECK(parsed.payload.empty()); + } +} + +} // namespace + +BOOST_AUTO_TEST_CASE(pq_records_grammar) +{ + using enum PQHandshake::ParseKind; + // Empty contents, as every BIP324 implementation sends today: no features. + CheckParse({}, NO_RECORD); + // Unknown and reserved headers, with and without payload, are ignored. + CheckParse(HexBytes("0100"), NO_RECORD); + CheckParse(HexBytes("0301aabb"), NO_RECORD); + CheckParse(HexBytes("0100" "02f1aa" "01ff"), NO_RECORD); + // An own record with an empty payload parses; its length is checked later, by the role. + CheckParse(HexBytes("01f0"), OWN_RECORD, std::pair{2, 0}); + CheckParse(HexBytes("0201aa" "03f0bbcc"), OWN_RECORD, std::pair{5, 2}); + // len = 0, alone or after valid records. + CheckParse(HexBytes("00"), INVALID_GRAMMAR); + CheckParse(HexBytes("0201aa" "00"), INVALID_GRAMMAR); + CheckParse(HexBytes("00" "01f0"), INVALID_GRAMMAR); + // Truncated records and truncated CompactSize encodings. + CheckParse(HexBytes("05f0aabb"), INVALID_GRAMMAR); + CheckParse(HexBytes("02f0"), INVALID_GRAMMAR); + CheckParse(HexBytes("fd"), INVALID_GRAMMAR); + CheckParse(HexBytes("fd21"), INVALID_GRAMMAR); + CheckParse(HexBytes("fe010000"), INVALID_GRAMMAR); + CheckParse(HexBytes("ff01000000000000"), INVALID_GRAMMAR); + // Non-canonical CompactSize encodings of lengths that would otherwise fit. + CheckParse(HexBytes("fd0200" "f0aa"), INVALID_GRAMMAR); + CheckParse(HexBytes("fe02000000" "f0aa"), INVALID_GRAMMAR); + CheckParse(HexBytes("ff0200000000000000" "f0aa"), INVALID_GRAMMAR); + // Oversized lengths: beyond the contents (one byte, and far), exactly MAX_SIZE (which + // ReadCompactSize accepts), beyond MAX_SIZE (which it rejects), and beyond size_t on 32-bit + // platforms. None is used to skip bytes before it is checked. + static_assert(MAX_SIZE == 0x02000000); + CheckParse(HexBytes("03f0aa"), INVALID_GRAMMAR); + CheckParse(HexBytes("fdfd00" "f0aa"), INVALID_GRAMMAR); + CheckParse(HexBytes("fe00000002" "f0aa"), INVALID_GRAMMAR); + CheckParse(HexBytes("fe01000002" "f0aa"), INVALID_GRAMMAR); + CheckParse(HexBytes("ffffffffffffffffff" "f0aa"), INVALID_GRAMMAR); + CheckParse(HexBytes("ff0000000001000000" "f0aa"), INVALID_GRAMMAR); + // Trailing corruption after valid records, including after a valid own record. + CheckParse(HexBytes("0201aa" "07"), INVALID_GRAMMAR); + CheckParse(HexBytes("02f0aa" "fd"), INVALID_GRAMMAR); + CheckParse(HexBytes("02f0aa" "0301aa"), INVALID_GRAMMAR); + // Canonical 3-byte CompactSize lengths parse. + std::vector long_unknown{HexBytes("fdfd00" "01")}; + long_unknown.resize(long_unknown.size() + 0xfc); + CheckParse(long_unknown, NO_RECORD); +} + +BOOST_AUTO_TEST_CASE(pq_records_first_wins) +{ + using enum PQHandshake::ParseKind; + const mlkem::PublicKey ek{TestEncapsulationKey()}; + const auto offer{PQHandshake::SerializeRecord(ek)}; + BOOST_CHECK_EQUAL(HexStr(std::span{offer}.first(4)), "fd2106f0"); + BOOST_CHECK(std::ranges::equal(std::span{offer}.subspan(4), std::as_bytes(std::span{ek}))); + const auto malformed{MakeRecord(PQ_MLKEM1024, HexBytes("aabb"))}; + const auto unknown{MakeRecord(0xf1, HexBytes("cc"))}; + + // A valid first own record wins over a malformed duplicate, wherever unknown records are. + CheckParse(offer, OWN_RECORD, std::pair{4, mlkem::PUBLIC_KEY_BYTES}); + CheckParse(Concat({offer, malformed}), OWN_RECORD, std::pair{4, mlkem::PUBLIC_KEY_BYTES}); + CheckParse(Concat({unknown, offer, unknown, malformed}), OWN_RECORD, std::pair{unknown.size() + 4, mlkem::PUBLIC_KEY_BYTES}); + // A malformed first own record wins over a valid second one; the role's length check then fails. + CheckParse(Concat({malformed, offer}), OWN_RECORD, std::pair{2, 2}); + CheckParse(Concat({unknown, malformed, offer}), OWN_RECORD, std::pair{unknown.size() + 2, 2}); + // Any later framing corruption means no features, even after a valid own record. + CheckParse(Concat({offer, HexBytes("00")}), INVALID_GRAMMAR); + CheckParse(Concat({offer, unknown, HexBytes("05f0")}), INVALID_GRAMMAR); + CheckParse(Concat({offer, HexBytes("fd0300f0aabb")}), INVALID_GRAMMAR); + auto truncated{Concat({unknown, offer})}; + truncated.pop_back(); + CheckParse(truncated, INVALID_GRAMMAR); +} + +BOOST_AUTO_TEST_CASE(pq_record_semantics) +{ + const mlkem::PublicKey ek{TestEncapsulationKey()}; + for (const size_t size : {size_t{0}, mlkem::PUBLIC_KEY_BYTES - 1, mlkem::PUBLIC_KEY_BYTES + 1}) { + std::vector payload(size, std::byte{0x42}); + // The responder's offer, at the initiator. + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE}; + const auto contents{MakeRecord(PQ_MLKEM1024, payload)}; + const auto parsed{PQHandshake::ParseContents(contents)}; + BOOST_REQUIRE(parsed.kind == PQHandshake::ParseKind::OWN_RECORD); + BOOST_CHECK(!initiator.CheckRecordLength(parsed.payload)); + BOOST_CHECK(initiator.GetSnapshot().failure == PQFailure::EK_LENGTH); + BOOST_CHECK(initiator.GetSnapshot().offer == PQOfferState::NONE); + // The initiator's accept, at the responder, which wipes its key. + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE}; + PQHandshake::Record offer; + BOOST_REQUIRE(responder.MakeOffer(offer) == mlkem::Error::NONE); + BOOST_CHECK(!responder.CheckRecordLength(parsed.payload)); + BOOST_CHECK(responder.GetSnapshot().failure == PQFailure::CT_LENGTH); + BOOST_CHECK(!responder.HasDecapsulationKey()); + } + + // A key of the right length that fails the modulus check. + g_kem_calls = {}; + TestPQEntropy entropy{uint256::ONE}; + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE, entropy.Source(), COUNTING_KEM_OPS}; + const auto contents{PQHandshake::SerializeRecord(BadModulusKey(ek))}; + const auto payload{initiator.CheckRecordLength(PQHandshake::ParseContents(contents).payload)}; + BOOST_REQUIRE(payload); + PQHandshake::Record accept; + accept.fill(std::byte{0x33}); + mlkem::SharedSecret ss; + BOOST_CHECK(initiator.AcceptOffer(*payload, accept, ss) == mlkem::Error::INVALID_PUBLIC_KEY); + BOOST_CHECK(initiator.GetSnapshot().failure == PQFailure::EK_MODULUS); + BOOST_CHECK(initiator.GetSnapshot().offer == PQOfferState::RECEIVED); + BOOST_CHECK(std::ranges::all_of(accept, [](std::byte b) { return b == std::byte{0x33}; })); + BOOST_CHECK_EQUAL(g_kem_calls.check, 1); + BOOST_CHECK_EQUAL(g_kem_calls.total(), 1); + // No entropy was drawn for a rejected key. + BOOST_CHECK_EQUAL(entropy.counter, 0U); +} + +BOOST_AUTO_TEST_CASE(pq_records_max_contents) +{ + using enum PQHandshake::ParseKind; + // The largest contents a v2 packet can carry (1 + 12 + 4,000,000 bytes), as minimal unknown + // records. One pass, no copies, no exception. + constexpr size_t MAX_CONTENTS{4'000'013}; + std::vector contents; + contents.reserve(MAX_CONTENTS); + while (contents.size() + 2 < MAX_CONTENTS) { + contents.push_back(std::byte{0x01}); + contents.push_back(std::byte{0x01}); + } + // Ending in a malformed record: no features. + std::vector malformed{contents}; + malformed.push_back(std::byte{0x05}); + BOOST_REQUIRE_EQUAL(malformed.size(), MAX_CONTENTS); + CheckParse(malformed, INVALID_GRAMMAR); + // Ending in a valid unknown record: still no features. + std::vector valid{contents.begin(), contents.end() - 2}; + for (const auto b : HexBytes("0201aa")) valid.push_back(b); + BOOST_REQUIRE_EQUAL(valid.size(), MAX_CONTENTS); + CheckParse(valid, NO_RECORD); + // Ending in an own record: found at the very end. + const auto offer{PQHandshake::SerializeRecord(TestEncapsulationKey())}; + std::vector own{HexBytes("0201aa")}; + while (own.size() + 2 + offer.size() <= MAX_CONTENTS) { + own.push_back(std::byte{0x01}); + own.push_back(std::byte{0x01}); + } + own.insert(own.end(), offer.begin(), offer.end()); + BOOST_REQUIRE_EQUAL(own.size(), MAX_CONTENTS); + CheckParse(own, OWN_RECORD, std::pair{MAX_CONTENTS - mlkem::PUBLIC_KEY_BYTES, mlkem::PUBLIC_KEY_BYTES}); +} + +BOOST_AUTO_TEST_CASE(pq_handshake_negotiation) +{ + UniValue doc; + BOOST_REQUIRE(doc.read(json_tests::pq_transport_vectors)); + const UniValue& vector{doc["vectors"][0]}; + const auto keygen_seed{ParseHex(vector["mlkem_keygen_seed"].get_str())}; + const auto encaps_m{ParseHex(vector["mlkem_encaps_m"].get_str())}; + + // With the vector's entropy, MakeOffer draws d then z and AcceptOffer draws m, so the offer, + // the accept and both secrets are the vector's. + { + TestPQEntropy responder_entropy{uint256::ZERO}, initiator_entropy{uint256::ONE}; + responder_entropy.Queue(keygen_seed); + initiator_entropy.Queue(encaps_m); + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE, responder_entropy.Source(), DefaultPQKemOps()}; + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE, initiator_entropy.Source(), DefaultPQKemOps()}; + + PQHandshake::Record offer; + BOOST_REQUIRE(responder.MakeOffer(offer) == mlkem::Error::NONE); + BOOST_CHECK_EQUAL(HexStr(offer), "fd2106f0" + vector["ek"].get_str()); + BOOST_CHECK(responder.HasDecapsulationKey()); + // SENT means queued, which only the caller knows. + BOOST_CHECK(responder.GetSnapshot().offer == PQOfferState::NONE); + responder.SetOfferSent(); + BOOST_CHECK(responder.GetSnapshot().offer == PQOfferState::SENT); + BOOST_CHECK(responder_entropy.queued.empty()); + + const auto ek{initiator.CheckRecordLength(PQHandshake::ParseContents(offer).payload)}; + BOOST_REQUIRE(ek); + PQHandshake::Record accept; + mlkem::SharedSecret ss_initiator; + BOOST_REQUIRE(initiator.AcceptOffer(*ek, accept, ss_initiator) == mlkem::Error::NONE); + BOOST_CHECK_EQUAL(HexStr(accept), "fd2106f0" + vector["ct"].get_str()); + BOOST_CHECK_EQUAL(HexStr(ss_initiator.Bytes()), vector["ss_mlkem"].get_str()); + BOOST_CHECK(initiator.GetSnapshot().offer == PQOfferState::RECEIVED); + BOOST_CHECK(initiator.GetSnapshot().failure == PQFailure::NONE); + + const auto ct{responder.CheckRecordLength(PQHandshake::ParseContents(accept).payload)}; + BOOST_REQUIRE(ct); + mlkem::SharedSecret ss_responder; + BOOST_REQUIRE(responder.DecapsulateAccept(*ct, ss_responder) == mlkem::Error::NONE); + BOOST_CHECK_EQUAL(HexStr(ss_responder.Bytes()), vector["ss_mlkem"].get_str()); + // The decapsulation key is single use. + BOOST_CHECK(!responder.HasDecapsulationKey()); + BOOST_CHECK(responder.GetSnapshot().failure == PQFailure::NONE); + } + + // Random production entropy: both sides agree, and every handshake has fresh keys. + std::optional> previous; + for (int i = 0; i < 2; ++i) { + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE}; + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE}; + PQHandshake::Record offer, accept; + mlkem::SharedSecret ss_initiator, ss_responder; + BOOST_REQUIRE(responder.MakeOffer(offer) == mlkem::Error::NONE); + BOOST_REQUIRE(initiator.AcceptOffer(*initiator.CheckRecordLength(PQHandshake::ParseContents(offer).payload), accept, ss_initiator) == mlkem::Error::NONE); + BOOST_REQUIRE(responder.DecapsulateAccept(*responder.CheckRecordLength(PQHandshake::ParseContents(accept).payload), ss_responder) == mlkem::Error::NONE); + BOOST_CHECK(std::ranges::equal(ss_initiator.Bytes(), ss_responder.Bytes())); + std::vector current(offer.size()); + std::ranges::copy(MakeUCharSpan(offer), current.begin()); + if (previous) BOOST_CHECK(*previous != current); + previous = current; + } + + // A corrupted ciphertext of the right length is not an error: it yields a different secret. + { + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE}; + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE}; + PQHandshake::Record offer, accept; + mlkem::SharedSecret ss_initiator, ss_responder; + BOOST_REQUIRE(responder.MakeOffer(offer) == mlkem::Error::NONE); + BOOST_REQUIRE(initiator.AcceptOffer(*initiator.CheckRecordLength(PQHandshake::ParseContents(offer).payload), accept, ss_initiator) == mlkem::Error::NONE); + accept[100] ^= std::byte{0x01}; + BOOST_REQUIRE(responder.DecapsulateAccept(*responder.CheckRecordLength(PQHandshake::ParseContents(accept).payload), ss_responder) == mlkem::Error::NONE); + BOOST_CHECK(!std::ranges::equal(ss_initiator.Bytes(), ss_responder.Bytes())); + } + + using mlkem::InjectResultForTesting; + using mlkem::Operation; + namespace upstream = mlkem::upstream; + const auto all_bytes_are = [](std::span bytes, uint8_t value) { + return std::ranges::all_of(bytes, [&](uint8_t b) { return b == value; }); + }; + + // Key generation fails: no offer, no key, a recorded internal error. + { + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE}; + PQHandshake::Record offer; + offer.fill(std::byte{0x33}); + { + InjectResultForTesting inject{Operation::KEYGEN, upstream::ERR_FAIL}; + BOOST_CHECK(responder.MakeOffer(offer) == mlkem::Error::INTERNAL); + } + BOOST_CHECK(std::ranges::all_of(offer, [](std::byte b) { return b == std::byte{0x33}; })); + BOOST_CHECK(!responder.HasDecapsulationKey()); + BOOST_CHECK(responder.GetSnapshot().offer == PQOfferState::NONE); + BOOST_CHECK(responder.GetSnapshot().failure == PQFailure::KEYGEN_INTERNAL); + } + + // The key check or encapsulation fails internally: no accept, the secret cleared. + for (const auto& [op, failure] : {std::pair{Operation::CHECK_PUBLIC_KEY, PQFailure::CHECK_EK_INTERNAL}, + std::pair{Operation::ENCAPS, PQFailure::ENCAPS_INTERNAL}}) { + for (const int result : {upstream::ERR_FAIL, upstream::ERR_OUT_OF_MEMORY}) { + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE}; + const mlkem::PublicKey ek{TestEncapsulationKey()}; + PQHandshake::Record accept; + accept.fill(std::byte{0x33}); + mlkem::SharedSecret ss; + std::ranges::fill(ss.Bytes(), 0x44); + { + InjectResultForTesting inject{op, result}; + BOOST_CHECK(initiator.AcceptOffer(ek, accept, ss) == mlkem::Error::INTERNAL); + } + BOOST_CHECK(std::ranges::all_of(accept, [](std::byte b) { return b == std::byte{0x33}; })); + BOOST_CHECK(all_bytes_are(ss.Bytes(), 0)); + BOOST_CHECK(initiator.GetSnapshot().failure == failure); + } + } + // An encapsulation that rejects the key after the check passed is a local fault too. + { + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE}; + PQHandshake::Record accept; + mlkem::SharedSecret ss; + InjectResultForTesting inject{Operation::ENCAPS, upstream::ERR_INVALID_PK}; + BOOST_CHECK(initiator.AcceptOffer(TestEncapsulationKey(), accept, ss) == mlkem::Error::INTERNAL); + BOOST_CHECK(initiator.GetSnapshot().failure == PQFailure::ENCAPS_INTERNAL); + } + + // Decapsulation fails internally (including a failed key hash check): the secret cleared, the + // key wiped. + for (const int result : {upstream::ERR_FAIL, upstream::ERR_INVALID_SK}) { + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE}; + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE}; + PQHandshake::Record offer, accept; + mlkem::SharedSecret ss_initiator, ss_responder; + BOOST_REQUIRE(responder.MakeOffer(offer) == mlkem::Error::NONE); + BOOST_REQUIRE(initiator.AcceptOffer(*initiator.CheckRecordLength(PQHandshake::ParseContents(offer).payload), accept, ss_initiator) == mlkem::Error::NONE); + std::ranges::fill(ss_responder.Bytes(), 0x44); + { + InjectResultForTesting inject{Operation::DECAPS, result}; + BOOST_CHECK(responder.DecapsulateAccept(*responder.CheckRecordLength(PQHandshake::ParseContents(accept).payload), ss_responder) == mlkem::Error::INTERNAL); + } + BOOST_CHECK(all_bytes_are(ss_responder.Bytes(), 0)); + BOOST_CHECK(!responder.HasDecapsulationKey()); + BOOST_CHECK(responder.GetSnapshot().failure == PQFailure::DECAPS_INTERNAL); + } + + // ClearSecrets() and any recorded failure wipe the key; the first failure is kept. + { + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE}; + PQHandshake::Record offer; + BOOST_REQUIRE(responder.MakeOffer(offer) == mlkem::Error::NONE); + responder.ClearSecrets(); + BOOST_CHECK(!responder.HasDecapsulationKey()); + BOOST_CHECK(responder.GetSnapshot().failure == PQFailure::NONE); + } + { + PQHandshake responder{/*initiating=*/false, PQMode::NEGOTIATE}; + PQHandshake::Record offer; + BOOST_REQUIRE(responder.MakeOffer(offer) == mlkem::Error::NONE); + responder.SetFailure(PQFailure::CONFIRM_TAG); + BOOST_CHECK(!responder.HasDecapsulationKey()); + responder.SetFailure(PQFailure::CONFIRM_LENGTH); + BOOST_CHECK(responder.GetSnapshot().failure == PQFailure::CONFIRM_TAG); + } + + // The snapshot records progress. + { + PQHandshake initiator{/*initiating=*/true, PQMode::NEGOTIATE}; + BOOST_CHECK(!initiator.GetSnapshot().version_received); + initiator.SetVersionReceived(); + initiator.SetSwitched(); + initiator.SetConfirmed(); + const auto snapshot{initiator.GetSnapshot()}; + BOOST_CHECK(snapshot.version_received && snapshot.switched && snapshot.confirmed); + BOOST_CHECK(snapshot.failure == PQFailure::NONE); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/fuzz/qbit_corpora/MANIFEST.json b/test/fuzz/qbit_corpora/MANIFEST.json index 5265d09ab8..772ebb2a53 100644 --- a/test/fuzz/qbit_corpora/MANIFEST.json +++ b/test/fuzz/qbit_corpora/MANIFEST.json @@ -12,7 +12,8 @@ "fdp": "Untagged FuzzedDataProvider layout for targets without a tagged format.", "legacy": "Untagged pre-existing FuzzedDataProvider layout of pqc/p2mr_script, unchanged.", "qbfx-v1-fixture": "b'QBFX' + target tag ('P' pqc, 'M' p2mr_script) + 0x01 + selector whose low nibble is not 0xF, then the FuzzedDataProvider layout of the cached-fixture body.", - "qbfx-v1-generation": "b'QBFX' + target tag + 0x01 + selector whose low nibble is 0xF, then the legacy FuzzedDataProvider layout." + "qbfx-v1-generation": "b'QBFX' + target tag + 0x01 + selector whose low nibble is 0xF, then the legacy FuzzedDataProvider layout.", + "raw": "The whole input is the data the target parses, without a FuzzedDataProvider layout." }, "targets": { "asert_chain_transition": [ @@ -414,6 +415,120 @@ "semantics": "Tagged generation selector 0x0F followed by the legacy-checksigpqc-default-valid layout." } ], + "pq_records": [ + { + "file": "offer", + "format": "raw", + "size": 1572, + "sha256": "4db1dcc35718f86fbfacd7a3169154ce9b90ac4899d8e1ba77e607e807829335", + "semantics": "The ordinary offer of vector 0 of src/test/data/pq_transport_vectors.json: fd 21 06 f0 || ek." + }, + { + "file": "accept", + "format": "raw", + "size": 1572, + "sha256": "f55f95816bc7d6c6e5f6879842f244866bb908c313cb7065fd610f16d38638cd", + "semantics": "The ordinary accept of vector 0: fd 21 06 f0 || ct." + }, + { + "file": "offer-duplicate-unknown", + "format": "raw", + "size": 3148, + "sha256": "ec7fa0f262d470033d09f505582b1025f84a482d55b66fc3331a4e235c43cd10", + "semantics": "Vector 2's responder contents: its offer, a second valid offer and an unknown 0xf1 record. The first wins." + }, + { + "file": "unknown-then-accept", + "format": "raw", + "size": 1578, + "sha256": "144903f355c909a634fc4807c3a0ec127296f12892493ffb2fae80e771078f9e", + "semantics": "Vector 2's initiator contents: an unknown 0x01 record, then the accept." + }, + { + "file": "reserved-and-unknown-headers", + "format": "raw", + "size": 7, + "sha256": "6cf59e77bd2bad687e0f9dd4576f15cfa41ee211c670b123613ca60183984c2e", + "semantics": "Valid grammar without an own record: no features." + }, + { + "file": "empty-own-record", + "format": "raw", + "size": 2, + "sha256": "6cc0ab95752bf25ec58c91b1d603c5eb41b8fbd7ee1694d5a85005056efb1ab3", + "semantics": "An own record with an empty payload: parses, and fails the role's length check later." + }, + { + "file": "short-offer", + "format": "raw", + "size": 1571, + "sha256": "ce05ab17bb9a676487edda33fdbfd01306de9e7aa41ef39ef2a0094c2f82be2a", + "semantics": "A 1,567-byte own payload (ek_length or ct_length)." + }, + { + "file": "long-offer", + "format": "raw", + "size": 1573, + "sha256": "ebc5c90456da104eae9bd81785e97a6d0031aede95adaae5b5f880e6dd4fb0f5", + "semantics": "A 1,569-byte own payload (ek_length or ct_length)." + }, + { + "file": "malformed-first-then-offer", + "format": "raw", + "size": 1576, + "sha256": "7933842445af30e309967eb36ccec32f3ebd76f062aadbba6cb2f9e7b369b765", + "semantics": "A malformed first own record before a valid one: the first wins." + }, + { + "file": "offer-trailing-byte", + "format": "raw", + "size": 1573, + "sha256": "01e1fbb6a72ae6bd158ef5902e60d37de318f2d831271ec7eed4179f473d92fe", + "semantics": "A valid offer followed by a truncated CompactSize: invalid grammar, no features." + }, + { + "file": "zero-length", + "format": "raw", + "size": 7, + "sha256": "e034a290523f42c301c35ac085df2ed5293c495c58689bbff1b939f071f22507", + "semantics": "A record with len = 0 after a valid one: invalid grammar." + }, + { + "file": "one-past-end", + "format": "raw", + "size": 3, + "sha256": "19b27e599c63eef9fedd1c64bbb785530914c07f4f03ab723eae7474994d3c51", + "semantics": "A length one byte past the end of the contents: invalid grammar." + }, + { + "file": "truncated-wide-length", + "format": "raw", + "size": 6, + "sha256": "0d8bf907c7f5c26d45de142db505a5389e1b79c301614ba42178e88275eb2d13", + "semantics": "A valid record, then a 0xfe CompactSize cut off after two of its four bytes: invalid grammar." + }, + { + "file": "non-canonical-length", + "format": "raw", + "size": 5, + "sha256": "0682a6be8935622a35773009dfb0e26c0857c4e72424029c526122b1dec714cd", + "semantics": "Length 2 in a 3-byte CompactSize: invalid grammar." + }, + { + "file": "oversized-length", + "format": "raw", + "size": 7, + "sha256": "a70a4eb5f4febe8115e5b1feefcf4591c2589f5e5da8b1889ab858f7aa51eca9", + "semantics": "Length MAX_SIZE + 1 (0x02000001), which ReadCompactSize rejects: invalid grammar." + }, + { + "file": "minimal-unknown-records", + "format": "raw", + "size": 2001, + "sha256": "38a04844262d1d0d0a74e3ad0989c2a3cb206b859f8f084ff774ed7d5fca85cc", + "semantics": "1,000 one-byte unknown records ending in a truncated one: invalid grammar after a long valid prefix." + } + ], "pqc": [ { "file": "legacy-generated-valid-sign-verify", diff --git a/test/fuzz/qbit_corpora/README.md b/test/fuzz/qbit_corpora/README.md index 01a509aea3..1c3eb778d4 100644 --- a/test/fuzz/qbit_corpora/README.md +++ b/test/fuzz/qbit_corpora/README.md @@ -12,6 +12,7 @@ upstream `qa-assets` corpus: | `mlkem` | [`mlkem/`](mlkem) | | `mlkem_backend_diff` | [`mlkem_backend_diff/`](mlkem_backend_diff) | | `p2mr_script` | [`p2mr_script/`](p2mr_script) | +| `pq_records` | [`pq_records/`](pq_records) | | `pqc` | [`pqc/`](pqc) | ## Provenance @@ -62,6 +63,13 @@ The fixture bodies check that an unmodified fixture verifies. `pqc` also checks that any modified signature, public key or message is rejected. Legacy and generation seeds keep key generation and signing covered in every replay. +`pq_records` takes the whole input, untagged (`raw`), as BIP324 version-packet +contents: records `CompactSize(len) || header || payload`. The seeds are the +offer and accept records of `src/test/data/pq_transport_vectors.json`, records +of the wrong length, duplicate and unknown records, and framing errors that make +the whole contents invalid. The harness checks the parser against an +independent reading of the grammar. + ## Use in CI [`overlay.py`](overlay.py) copies each seed to @@ -75,6 +83,6 @@ from a reused corpus by hand if they are no longer wanted. A target path that is not a directory, or a symlink, directory or other non-regular file at a managed name, is an error; all target paths and managed names are checked before any file is written. -`test/fuzz/test_runner.py --require_qbit_corpus` then fails unless all eight +`test/fuzz/test_runner.py --require_qbit_corpus` then fails unless all nine targets are selected, each has at least one regular input file, and the fuzz binary reports replaying them. diff --git a/test/fuzz/qbit_corpora/generate_seeds.py b/test/fuzz/qbit_corpora/generate_seeds.py index db6942e068..b86e2383dc 100755 --- a/test/fuzz/qbit_corpora/generate_seeds.py +++ b/test/fuzz/qbit_corpora/generate_seeds.py @@ -650,6 +650,68 @@ def mlkem_cases() -> list[dict]: ] +# --- pq_records --------------------------------------------------------------- + +PQ_VECTORS = CORPORA_DIR.parents[2] / "src" / "test" / "data" / "pq_transport_vectors.json" +PQ_MLKEM1024 = 0xF0 + + +def compact_size(n: int) -> bytes: + if n < 0xFD: + return bytes([n]) + if n <= 0xFFFF: + return b"\xfd" + n.to_bytes(2, "little") + if n <= 0xFFFFFFFF: + return b"\xfe" + n.to_bytes(4, "little") + return b"\xff" + n.to_bytes(8, "little") + + +def pq_record(header: int, payload: bytes) -> bytes: + """A version-contents record: CompactSize(1 + len(payload)) || header || payload.""" + return compact_size(1 + len(payload)) + bytes([header]) + payload + + +def pq_records_cases() -> list[dict]: + vectors = json.loads(PQ_VECTORS.read_text(encoding="utf-8"))["vectors"] + ordinary, extra = vectors[0], vectors[2] + offer = pq_record(PQ_MLKEM1024, bytes.fromhex(ordinary["ek"])) + accept = pq_record(PQ_MLKEM1024, bytes.fromhex(ordinary["ct"])) + unknown = pq_record(0xF1, b"qbit") + minimal_unknown = pq_record(0x01, b"") * 1000 + return [ + case("offer", "raw", offer, + "The ordinary offer of vector 0 of src/test/data/pq_transport_vectors.json: fd 21 06 f0 || ek."), + case("accept", "raw", accept, "The ordinary accept of vector 0: fd 21 06 f0 || ct."), + case("offer-duplicate-unknown", "raw", bytes.fromhex(extra["contents_responder"]), + "Vector 2's responder contents: its offer, a second valid offer and an unknown 0xf1 record. The first wins."), + case("unknown-then-accept", "raw", bytes.fromhex(extra["contents_initiator"]), + "Vector 2's initiator contents: an unknown 0x01 record, then the accept."), + case("reserved-and-unknown-headers", "raw", pq_record(0x00, b"") + pq_record(0xF1, b"\xaa") + pq_record(0xFF, b""), + "Valid grammar without an own record: no features."), + case("empty-own-record", "raw", pq_record(PQ_MLKEM1024, b""), + "An own record with an empty payload: parses, and fails the role's length check later."), + case("short-offer", "raw", pq_record(PQ_MLKEM1024, bytes.fromhex(ordinary["ek"])[:-1]), + "A 1,567-byte own payload (ek_length or ct_length)."), + case("long-offer", "raw", pq_record(PQ_MLKEM1024, bytes.fromhex(ordinary["ek"]) + b"\x00"), + "A 1,569-byte own payload (ek_length or ct_length)."), + case("malformed-first-then-offer", "raw", pq_record(PQ_MLKEM1024, b"\xaa\xbb") + offer, + "A malformed first own record before a valid one: the first wins."), + case("offer-trailing-byte", "raw", offer + b"\xfd", + "A valid offer followed by a truncated CompactSize: invalid grammar, no features."), + case("zero-length", "raw", unknown + b"\x00", "A record with len = 0 after a valid one: invalid grammar."), + case("one-past-end", "raw", b"\x03\xf0\xaa", + "A length one byte past the end of the contents: invalid grammar."), + case("truncated-wide-length", "raw", b"\x02\x01\xaa\xfe\x00\x00", + "A valid record, then a 0xfe CompactSize cut off after two of its four bytes: invalid grammar."), + case("non-canonical-length", "raw", b"\xfd\x02\x00\xf0\xaa", + "Length 2 in a 3-byte CompactSize: invalid grammar."), + case("oversized-length", "raw", b"\xfe\x01\x00\x00\x02\xf0\xaa", + "Length MAX_SIZE + 1 (0x02000001), which ReadCompactSize rejects: invalid grammar."), + case("minimal-unknown-records", "raw", minimal_unknown + b"\x05", + "1,000 one-byte unknown records ending in a truncated one: invalid grammar after a long valid prefix."), + ] + + def case(name: str, fmt: str, data: bytes, semantics: str) -> dict: return {"name": name, "format": fmt, "data": data, "semantics": semantics} @@ -662,6 +724,7 @@ def case(name: str, fmt: str, data: bytes, semantics: str) -> dict: "mlkem": mlkem_cases, "mlkem_backend_diff": mlkem_cases, "p2mr_script": p2mr_cases, + "pq_records": pq_records_cases, "pqc": pqc_cases, } @@ -681,6 +744,7 @@ def build_all() -> tuple[dict, dict[str, list[dict]]]: "is not 0xF, then the FuzzedDataProvider layout of the cached-fixture body.", "qbfx-v1-generation": "b'QBFX' + target tag + 0x01 + selector whose low nibble is 0xF, then the legacy " "FuzzedDataProvider layout.", + "raw": "The whole input is the data the target parses, without a FuzzedDataProvider layout.", }, "targets": { target: [ diff --git a/test/fuzz/qbit_corpora/overlay.py b/test/fuzz/qbit_corpora/overlay.py index 0533e6507f..62c87b1a0a 100755 --- a/test/fuzz/qbit_corpora/overlay.py +++ b/test/fuzz/qbit_corpora/overlay.py @@ -32,6 +32,7 @@ "mlkem", "mlkem_backend_diff", "p2mr_script", + "pq_records", "pqc", ) MANIFEST_NAME = "MANIFEST.json" diff --git a/test/fuzz/qbit_corpora/pq_records/accept b/test/fuzz/qbit_corpora/pq_records/accept new file mode 100644 index 0000000000000000000000000000000000000000..25edad2ef35baa579501cb70e04ad41b4716becd GIT binary patch literal 1572 zcmV+<2HW}lAqMbySBsME{#F>(-I9c&08T~giE+lFgua$`wFMTQ`ErF zYr>o^kWV1nj&22K)}b&u(U8}`ho^0(G^XIJ5%l+jmdX)^EQhSH{3kGBN1BOsh=$Ox z*MoJj?eiDi`iPYW-%fOeKjTP}SPBw;5Kjxl-&j3_6}3ZW@2~0!mB;7pdL8Y!5h^0+ zCy$Ue9lr`6dhsM{V4RtO`C5^a5>yR~y&!(KT>1XA`O;{c2Ol3>4S5A~%0SyWgLKp7 z4m>SJyOw2V&77MRiSA+`ZmpH51cTK2R)6-F)D3`2&EAg2RQ+3|6B9qHxt+0{l%CD! zjaAE5H)iJ)&mlPV1XqBUSGE|i1g%KH%L@-2mEil~^f(jM@HNDftO_fr;}dR&h0sXs!>t zP<8DSaVL0+q4{7l$e%&2=uE~MuCALbhK4I1m27m=o7U*?2?zaHY|xpjLL`VhE(`~( zOSAV%T3MG~@aRNXGjPubc5(asDl;*H+~Ta9$n&}lbIm8NV5+JKRA5x!zE%Dv+18D&<8 z$kz)&G$3UdQrkPq)yB4BXVn%6i#b)Mh)kqPgURuE#gHHL?eFRbPT1JwtP;04`sd9Sm*-dVYFUDAWVd;mM{qs^e4MAx7=nd#2`ZZk@>Qi6 z4)2!vZe1@?%wipS1IN1s>wwdSL0=?4U0FpPUzN!}Mi$JbL(OaEQ3=#QMjmx7DVcO% ztkvqu>NAaO=|Htijk-keS;FUAD#ed z?a`>!yZVuU@TiaP9d|lgL3?N2*m9gYcpLoQwI>o~dG*m8RZvn`X`>@Y6*Djuqu^jA7Ezv)fa zd~`&FcpNVNi5@FUWwVYF+pB4`Hq@}JYMW6m=$N@#1{OaugP)?9U#;rXU3vNgqJ-YO zV8tD(iUE8bO~l-Oh)WZ6(J}qv%5GT}anFLwcBhP7c%qRK4FH>DF)`X@tnsRB2W$y$FiUY&{{HFA& z;WJs=njSqaPpoZ5lULc<%4TEsqxb-J#_W@Np=~3vFR<=NJSGa(=TV}8M%hnP%x`&A zvxx=c*zQ61fXYN?k}_(PgM2n|1eiXlxdx~AsIis zr6rQ)P~Ii6s2>ZeSAg1wtc zk-H>#U|KIO2X=X8T_iakqkMFXTy8b+57GjmJOd(du+t9;_`4l^C2$2vKe9ixo?YXbY&UF7Cj7OvB2;5hYwdkREY8u_=V0 W1W*ANkf8F`uOijcTZ6?&J=Ext3Ke1i literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/empty-own-record b/test/fuzz/qbit_corpora/pq_records/empty-own-record new file mode 100644 index 0000000000..2e32a41313 --- /dev/null +++ b/test/fuzz/qbit_corpora/pq_records/empty-own-record @@ -0,0 +1 @@ +ð \ No newline at end of file diff --git a/test/fuzz/qbit_corpora/pq_records/long-offer b/test/fuzz/qbit_corpora/pq_records/long-offer new file mode 100644 index 0000000000000000000000000000000000000000..9821de5c1c592ff423631fac4b8fc4759b68827e GIT binary patch literal 1573 zcmV+=2HN@kA_nkI^*q?uRB)0-2y^TN-F zrUQ{tFoUBoaf@>$d{s<*@X8ov`@d);HDFv?e+D+Onz6^?$;&{`V#Iksv3OPY!sOA0 zlnJeSI|Oi2zt=WlmDos-@H)aUGNwFKUfG`q&bEDwhP}Im=2lGb{Kdd?)KZ#!5|fB zA$r&s&0`k)jDIjTq}8T*30t%`Y@T&C3SBhu~n` zTb;Sk3Qnf7b43NfNLRxg%BG9RGjly2C^z7>uEb+hhIuoBLz(e1fLN0=40FL8n2j3S zYOI@^yu3jsF<9ccV8aGwR}_Sh3ddp^K7Y|AFZ(ASc0JsfDLPw{mykw_n@-qB$+}Y- z+M^%rU^aWij;>)RcguO#wQ3jmrmw-l{=0_kC~o;C46<-}z}2To5VP+lu%5|n=9hC` zk%SwPB-1*-E*liFRAAW!P?IK%b-{X}x)rrXK4{Y{nV}6p#sjomAGjM?Wx;cKxR}eM z%@lFF2gq1&BQ0Dht?7dw1{OL!V8(J&UXQS#AJjuwQ%DO3KESLpyo!fx!_7mxx4(-$ z$9QzUXut8$&Gw>@4U5V{@|%amIb3>qOgW@piNo3Bzi>viydX+-qY7|xLQ-6T&q~QW zDvKs;S?a0{nR7_#{Cm(TLNUczrOXs%q9YgQ6wDMcz>>buW?v*An8jAHz>%Sa8NvFI zHBDp#_Zf2Mj3c6oCuf*~YddiscB&Whd?X=FY#eTiQ*&(DhS}ss28A;5rXftpj0g-c1ij=+e@fABX9U#y^>rif>)C*P| z0eHq@r^U9t69qLjT8dOSt{e_3xN`X@0J<@oG}VXYC}8UtI}6(+>;g&=8#bLdalyyV zp2>0tq6oC?GU^m8VbVL>v;!r|2UfFeUE!PUg&hp$qYM{bMtN7)D`oXnAP3sE%|}fM zTD4X+F%h|DN&zi@*JrcUNfU*Dpob7Wa=Tz9Ao)2n7w0ZNyj^kzqD;!J1NwwU8KkVL zj3i)F?Gh}PWxSDOsvL4xtXPxaVVk*S#E&GkrF4A)^M?hXoRC|Bk%(4EbAt2?D7QHl zxCNXkvzSvDnU|N2Nw8h9cjfp_`IU9`8#DVAeL>|)b`ijWIDBoQLc;_v&UX<~ zQ3Uj2Gs@YBE=7523B{X)aVUEk#P%RwptDpwG#`u>oDl&G)hfNAxikVxk%7QR?5zQW zM+w)1N}yI`fS2+Ro=>b~4Kl5O%vFJ`U@2-_QPveC9E`xc%>H+Km*%BMP!V7{ir4C| z=RsE>7=#O$orC#?7B(}yXpx;)5)soEz@xx4bt$xw0m9T#giBC^Kuw}sO*fMlLlc=( z{Kz*$X(kJZ88U>tmL&}Wz+TK~38YFm;zD5vJ;RkfPKpUT+JFm7CKT6dLsiD903{wg zx!wkz((9@;vr4WuVKyQdwKWS*Xvw^55zzH@K6xzj0Ib`T#V)H*)CQJ+Ns5CfLJeb= zh+}}MKpC?KxEP8=ma-%3;&64id|ifsg<8kOxRTRXBzo+%USNr$+oMJHT_u~1K%!Hm XP^Ct+YP>xd1oX!2!l{z||4w}X(s$J< literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/malformed-first-then-offer b/test/fuzz/qbit_corpora/pq_records/malformed-first-then-offer new file mode 100644 index 0000000000000000000000000000000000000000..fc1998efe30949677feff4d7ddbdb16665a27572 GIT binary patch literal 1576 zcmV+@2G{um@T$B0AqMbH^*q?uRB)0-2y z^TN-FrUQ{tFoUBoaf@>$d{s<*@X8ov`@d);HDFv?e+D+Onz6^?$;&{`V#Iksv3OPY z!sOA0lnJeSI|Oi2zt=WlmDos-@H)aUGNwFKUfG`q&bEDwhP}Im=2lGb{Kdd?)KZ# z!5|fBA$r&s&0`k)jDIjTq}8T*30t%`Y@T&C3SB zhu~n`Tb;Sk3Qnf7b43NfNLRxg%BG9RGjly2C^z7>uEb+hhIuoBLz(e1fLN0=40FL8 zn2j3SYOI@^yu3jsF<9ccV8aGwR}_Sh3ddp^K7Y|AFZ(ASc0JsfDLPw{mykw_n@-qB z$+}Y-+M^%rU^aWij;>)RcguO#wQ3jmrmw-l{=0_kC~o;C46<-}z}2To5VP+lu%5|n z=9hC`k%SwPB-1*-E*liFRAAW!P?IK%b-{X}x)rrXK4{Y{nV}6p#sjomAGjM?Wx;cK zxR}eM%@lFF2gq1&BQ0Dht?7dw1{OL!V8(J&UXQS#AJjuwQ%DO3KESLpyo!fx!_7mx zx4(-$$9QzUXut8$&Gw>@4U5V{@|%amIb3>qOgW@piNo3Bzi>viydX+-qY7|xLQ-6T z&q~QWDvKs;S?a0{nR7_#{Cm(TLNUczrOXs%q9YgQ6wDMcz>>buW?v*An8jAHz>%Sa z8NvFIHBDp#_Zf2Mj3c6oCuf*~YddiscB&Whd?X=FY#eTiQ*&(DhS}ss28A;5rXftp zj0g-c1ij=+e@fABX9U#y^>rif> z)C*P|0eHq@r^U9t69qLjT8dOSt{e_3xN`X@0J<@oG}VXYC}8UtI}6(+>;g&=8#bLd zalyyVp2>0tq6oC?GU^m8VbVL>v;!r|2UfFeUE!PUg&hp$qYM{bMtN7)D`oXnAP3sE z%|}fMTD4X+F%h|DN&zi@*JrcUNfU*Dpob7Wa=Tz9Ao)2n7w0ZNyj^kzqD;!J1NwwU z8KkVLj3i)F?Gh}PWxSDOsvL4xtXPxaVVk*S#E&GkrF4A)^M?hXoRC|Bk%(4EbAt2? zD7QHlxCNXkvzSvDnU|N2Nw8h9cjfp_`IU9`8#DVAeL>|)b`ijWIDBoQLc;_v z&UX<~Q3Uj2Gs@YBE=7523B{X)aVUEk#P%RwptDpwG#`u>oDl&G)hfNAxikVxk%7QR z?5zQWM+w)1N}yI`fS2+Ro=>b~4Kl5O%vFJ`U@2-_QPveC9E`xc%>H+Km*%BMP!V7{ zir4C|=RsE>7=#O$orC#?7B(}yXpx;)5)soEz@xx4bt$xw0m9T#giBC^Kuw}sO*fMl zLlc=({Kz*$X(kJZ88U>tmL&}Wz+TK~38YFm;zD5vJ;RkfPKpUT+JFm7CKT6dLsiD9 z03{wgx!wkz((9@;vr4WuVKyQdwKWS*Xvw^55zzH@K6xzj0Ib`T#V)H*)CQJ+Ns5Cf zLJeb=h+}}MKpC?KxEP8=ma-%3;&64id|ifsg<8kOxRTRXBzo+%USNr$+oMJHT_u~1 aK%!HmP^Ct+YP>xd1oX!2!l{z||4w~jOxJ(_ literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/minimal-unknown-records b/test/fuzz/qbit_corpora/pq_records/minimal-unknown-records new file mode 100644 index 0000000000..fd5fa385d5 --- /dev/null +++ b/test/fuzz/qbit_corpora/pq_records/minimal-unknown-records @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/fuzz/qbit_corpora/pq_records/non-canonical-length b/test/fuzz/qbit_corpora/pq_records/non-canonical-length new file mode 100644 index 0000000000000000000000000000000000000000..935388e0c4db09ab560a17736a7a8156a2d6c864 GIT binary patch literal 5 Mcmey%#PDGi00)Qyng9R* literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/offer b/test/fuzz/qbit_corpora/pq_records/offer new file mode 100644 index 0000000000000000000000000000000000000000..090c62bf1ead53b7fa4a8f0edfcfbd7f8a46cc22 GIT binary patch literal 1572 zcmV+<2HW}lAqMbH^*q?uRB)0-2y^TN-F zrUQ{tFoUBoaf@>$d{s<*@X8ov`@d);HDFv?e+D+Onz6^?$;&{`V#Iksv3OPY!sOA0 zlnJeSI|Oi2zt=WlmDos-@H)aUGNwFKUfG`q&bEDwhP}Im=2lGb{Kdd?)KZ#!5|fB zA$r&s&0`k)jDIjTq}8T*30t%`Y@T&C3SBhu~n` zTb;Sk3Qnf7b43NfNLRxg%BG9RGjly2C^z7>uEb+hhIuoBLz(e1fLN0=40FL8n2j3S zYOI@^yu3jsF<9ccV8aGwR}_Sh3ddp^K7Y|AFZ(ASc0JsfDLPw{mykw_n@-qB$+}Y- z+M^%rU^aWij;>)RcguO#wQ3jmrmw-l{=0_kC~o;C46<-}z}2To5VP+lu%5|n=9hC` zk%SwPB-1*-E*liFRAAW!P?IK%b-{X}x)rrXK4{Y{nV}6p#sjomAGjM?Wx;cKxR}eM z%@lFF2gq1&BQ0Dht?7dw1{OL!V8(J&UXQS#AJjuwQ%DO3KESLpyo!fx!_7mxx4(-$ z$9QzUXut8$&Gw>@4U5V{@|%amIb3>qOgW@piNo3Bzi>viydX+-qY7|xLQ-6T&q~QW zDvKs;S?a0{nR7_#{Cm(TLNUczrOXs%q9YgQ6wDMcz>>buW?v*An8jAHz>%Sa8NvFI zHBDp#_Zf2Mj3c6oCuf*~YddiscB&Whd?X=FY#eTiQ*&(DhS}ss28A;5rXftpj0g-c1ij=+e@fABX9U#y^>rif>)C*P| z0eHq@r^U9t69qLjT8dOSt{e_3xN`X@0J<@oG}VXYC}8UtI}6(+>;g&=8#bLdalyyV zp2>0tq6oC?GU^m8VbVL>v;!r|2UfFeUE!PUg&hp$qYM{bMtN7)D`oXnAP3sE%|}fM zTD4X+F%h|DN&zi@*JrcUNfU*Dpob7Wa=Tz9Ao)2n7w0ZNyj^kzqD;!J1NwwU8KkVL zj3i)F?Gh}PWxSDOsvL4xtXPxaVVk*S#E&GkrF4A)^M?hXoRC|Bk%(4EbAt2?D7QHl zxCNXkvzSvDnU|N2Nw8h9cjfp_`IU9`8#DVAeL>|)b`ijWIDBoQLc;_v&UX<~ zQ3Uj2Gs@YBE=7523B{X)aVUEk#P%RwptDpwG#`u>oDl&G)hfNAxikVxk%7QR?5zQW zM+w)1N}yI`fS2+Ro=>b~4Kl5O%vFJ`U@2-_QPveC9E`xc%>H+Km*%BMP!V7{ir4C| z=RsE>7=#O$orC#?7B(}yXpx;)5)soEz@xx4bt$xw0m9T#giBC^Kuw}sO*fMlLlc=( z{Kz*$X(kJZ88U>tmL&}Wz+TK~38YFm;zD5vJ;RkfPKpUT+JFm7CKT6dLsiD903{wg zx!wkz((9@;vr4WuVKyQdwKWS*Xvw^55zzH@K6xzj0Ib`T#V)H*)CQJ+Ns5CfLJeb= zh+}}MKpC?KxEP8=ma-%3;&64id|ifsg<8kOxRTRXBzo+%USNr$+oMJHT_u~1K%!Hm WP^Ct+YP>xd1oX!2!l{z||4x1P9MvcQ literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/offer-duplicate-unknown b/test/fuzz/qbit_corpora/pq_records/offer-duplicate-unknown new file mode 100644 index 0000000000000000000000000000000000000000..090ca6b92276b3952db8d07fdd1a01ba20791312 GIT binary patch literal 3148 zcmV-S472n7AqMa{7?Q}G#QWFJE1QnBW2S*bZBeX~0BskY$CMXTPZC2T8SBD5cQ{)F zb%N<4?A&e9HIZigV^i_X6F^5J_@iGSEmsP0y7V>n!Ar2$NUn$p;*b!3A}+I`D#3u6 zo1w3&_CnjqDN10hZp;Gwih^BK9M$I|y~)mm`j>=o2{C*OTC+&3*&*uthcI%JDRE12 z3<(=JGD${S5=At#+f79pckSV}48sV`m3X4kAW$++3a2FR!AvAoT#sr(SgDxix&ko5 zE8Hf1T&Z{sU_X&@B{SJ>qKTWCiD-+db(bVuazQV1$T~+Dg9aR8a)ZI1HClSu4JlSs z0hL*YqOXvPpC6)Sw4pB?qe`e?C)b#EsKc;!YEWcjc7&t^m+4~I<*e>7n>Qg^MS>@; z31vVd#W8x3l~G6Lhz)^<1v@~8F4)Mbz^B%bc}G!{3OAYt7pcY^O)I6uvub>r*vuYU z%%x)H%>V&hzER|WB5L%oG* zmh2Up1D^%}2NX1tflQEQC)9GSN_lbDD5K zlX{9`cr_Tt<}e4qx)8E}lufipDPw7>Nm_*CM<2SaPf$lPKoBJf&P8%=Ciz_gRs&q( z8acs3455fiRT;eYgFE_V!>WmAdtDe7N@6ghN+!Ytx@)u1V)P+3JDoOaT70hGX>9pUtL~AicDjGtMpryy;nWidY3dt4o*j~IjtdvVBcffd< zn0tIDarqN&Z7T|-Dr`9%gjV5trq;3L18&p%UjRp1#DI8LaC$orR+d)alN`^UH z1%gRO`Z#u|XTC2GW@=n$JBt|(*g>>&qARmmN%u;XfX=gUC_yL0MaNm!%*EzvOSw?A zr9g4?mc?-Ora_q;a9W!?@)H`iB}x(uI#O9^d?jpp3!*y0H0rQlG(tHCxzTL5V{yYH zJajU5DO3s?i~6UA^CZN?M#8l)bu^QHn1D^FS&WsWH0QaTq7+N_Q`4eWMup8k+ZA-Q zawlt(J6DoK`n665I^U!b^=6vobfCH!`{Z zTZ#yqh~+^7w=-``i>AfK#=N1LX%&+8TUWI7e2yrI=-9_A(wJ7*I);a&ZEVi>>Aq_B zTB$aCm;0*6B^=PqL`{JaSR}sP`zFJrFnEW7!Q7`Umc+kzsIVv!$CAUuawjj+P)O4w z3rS00STb(LZ90&mH%GGyb12J;j82ud7&#!+22*GflhgOWl;>@Xxr;~@g~kxBH02_1 zn!PuZh;?&x0MHkUymUNVXD4@!B5)9^h`g+54H3nG{UKX3(!$--V;$w8go}2`QAdDj zhbQN?F$l!bBCh?KRTn@i<#3A^AMq& z`Bb{_gA#txNL zXc3zT#1=g~LKt-oOrXG~cerHZw7%+-N_1WEi@4gdiM3%tOO%wdT@hshqCG3lAq>>H z%P;!!VbP4DcUYWqd+0?~5Gxt|AqMa+0a3Q%kakEvUO{c(im%DFyWa)L2FXfBi^3UG zYaM~Z|6&0m3$yRlWY>IJ6?epEVxa1bpX&ty^L8lkcvpM5yzeT_?&0SWR2`_QvlRRoe3m~sHVC;MYZ+kO?Ku-?nU`%)8EG(cg-T?T|@45x@t91_x| zME2W%f}ze8^$T1av*%YkfJ&lyArrC22|GEG+Q%!za0CtvWG4wPy>=LZiJl`=drg-Y zvNg#|SSQ8WG@1NKvJ1Y^$5r(;NS$|X73G%4(g)q-&hlbmwEz}#+7>VqvC|wrazT}7 z0S?xCZ9j8;|1n|UXmW`r{P0VWA`iRh3f^K%B^tBV1~ zGbwJ_+fH@dGYexI=Tjds=#kw#t@36aCaV*>g@MlOFaqMEIn**oGg!^YbuB1k*!!1i zqM{Y$eZCfO&EO1?RdT?XXb@4(sKh*$oF6reU!dwkeiu>6#m|G&re(J_fGWIm(Wg9N z4;bUR?Dm)b!E(|&DDIRPY2u^W3Yf6O&K2>gu`?Y?v;*k zgCWd55eZYTy3c_m3!GUO+vRyiRD+urcew1Rh!Gov`*nC1AVY%(bTMq(x`A1lc`>Le z)A}Q*8JDocVaOMf4MeZjs(3+JoW9%vuH!*wWS`$mO(+CWM?jT@pmk&xd}v!HuHk6A zS*JEG!jCh3io(~uT!Ir%L0#bjGOA3$Apz92c88mFkaihAu+1AW^XaI zpx+ipXNwDYvu!(EQ>_e*jHwP?rJ=TRXA-%-W&0RhG`jynR80YO<>Jkc!bvnYH^WB5 zFs5L#Lm7P8$l*kRk`_z&_P7CyhaCfRva?Z+#$_y7Y#1m6!v#O2*IN7FQs^jJwdTO8 zbR!YBAQ^U}kmmqQz!lB2Opzj$Hm1RWP|VAapf556hDTH$%1N-~nECNCN+>eC^cvK^hw@!H@UX}1i%#(TUwyp&OMTiG@KcOW{l$1&8 zFOBn?I$Rb}@M>JODE`4URQ>7s0ox9*>!;KvACzHiZ*rkBJaX z^$<3DR6$)FC3<2W`z3u%fO;X5dPH=Fd?pp~;eWRHeZA#Zy!tcTbscOuVI7kx+|`q` mM9q&w9tA5S=t7L2IWZ~gKtAFET>&Iqm)3Kq#TthL@c;n{XT8_} literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/offer-trailing-byte b/test/fuzz/qbit_corpora/pq_records/offer-trailing-byte new file mode 100644 index 0000000000000000000000000000000000000000..3fdbbddeaaf6b5184013fdf6caa4af45f7011302 GIT binary patch literal 1573 zcmV+=2HN@kAqMbH^*q?uRB)0-2y^TN-F zrUQ{tFoUBoaf@>$d{s<*@X8ov`@d);HDFv?e+D+Onz6^?$;&{`V#Iksv3OPY!sOA0 zlnJeSI|Oi2zt=WlmDos-@H)aUGNwFKUfG`q&bEDwhP}Im=2lGb{Kdd?)KZ#!5|fB zA$r&s&0`k)jDIjTq}8T*30t%`Y@T&C3SBhu~n` zTb;Sk3Qnf7b43NfNLRxg%BG9RGjly2C^z7>uEb+hhIuoBLz(e1fLN0=40FL8n2j3S zYOI@^yu3jsF<9ccV8aGwR}_Sh3ddp^K7Y|AFZ(ASc0JsfDLPw{mykw_n@-qB$+}Y- z+M^%rU^aWij;>)RcguO#wQ3jmrmw-l{=0_kC~o;C46<-}z}2To5VP+lu%5|n=9hC` zk%SwPB-1*-E*liFRAAW!P?IK%b-{X}x)rrXK4{Y{nV}6p#sjomAGjM?Wx;cKxR}eM z%@lFF2gq1&BQ0Dht?7dw1{OL!V8(J&UXQS#AJjuwQ%DO3KESLpyo!fx!_7mxx4(-$ z$9QzUXut8$&Gw>@4U5V{@|%amIb3>qOgW@piNo3Bzi>viydX+-qY7|xLQ-6T&q~QW zDvKs;S?a0{nR7_#{Cm(TLNUczrOXs%q9YgQ6wDMcz>>buW?v*An8jAHz>%Sa8NvFI zHBDp#_Zf2Mj3c6oCuf*~YddiscB&Whd?X=FY#eTiQ*&(DhS}ss28A;5rXftpj0g-c1ij=+e@fABX9U#y^>rif>)C*P| z0eHq@r^U9t69qLjT8dOSt{e_3xN`X@0J<@oG}VXYC}8UtI}6(+>;g&=8#bLdalyyV zp2>0tq6oC?GU^m8VbVL>v;!r|2UfFeUE!PUg&hp$qYM{bMtN7)D`oXnAP3sE%|}fM zTD4X+F%h|DN&zi@*JrcUNfU*Dpob7Wa=Tz9Ao)2n7w0ZNyj^kzqD;!J1NwwU8KkVL zj3i)F?Gh}PWxSDOsvL4xtXPxaVVk*S#E&GkrF4A)^M?hXoRC|Bk%(4EbAt2?D7QHl zxCNXkvzSvDnU|N2Nw8h9cjfp_`IU9`8#DVAeL>|)b`ijWIDBoQLc;_v&UX<~ zQ3Uj2Gs@YBE=7523B{X)aVUEk#P%RwptDpwG#`u>oDl&G)hfNAxikVxk%7QR?5zQW zM+w)1N}yI`fS2+Ro=>b~4Kl5O%vFJ`U@2-_QPveC9E`xc%>H+Km*%BMP!V7{ir4C| z=RsE>7=#O$orC#?7B(}yXpx;)5)soEz@xx4bt$xw0m9T#giBC^Kuw}sO*fMlLlc=( z{Kz*$X(kJZ88U>tmL&}Wz+TK~38YFm;zD5vJ;RkfPKpUT+JFm7CKT6dLsiD903{wg zx!wkz((9@;vr4WuVKyQdwKWS*Xvw^55zzH@K6xzj0Ib`T#V)H*)CQJ+Ns5CfLJeb= zh+}}MKpC?KxEP8=ma-%3;&64id|ifsg<8kOxRTRXBzo+%USNr$+oMJHT_u~1K%!Hm XP^Ct+YP>xd1oX!2!l{z||4x1V%}~}Q literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/one-past-end b/test/fuzz/qbit_corpora/pq_records/one-past-end new file mode 100644 index 0000000000..f04166c321 --- /dev/null +++ b/test/fuzz/qbit_corpora/pq_records/one-past-end @@ -0,0 +1 @@ +ðª \ No newline at end of file diff --git a/test/fuzz/qbit_corpora/pq_records/oversized-length b/test/fuzz/qbit_corpora/pq_records/oversized-length new file mode 100644 index 0000000000000000000000000000000000000000..dc20e4e3ead2014273a73bcbcc90631d889c2da1 GIT binary patch literal 7 Ocmeyz$iTq#VHE%gj{=+k literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/reserved-and-unknown-headers b/test/fuzz/qbit_corpora/pq_records/reserved-and-unknown-headers new file mode 100644 index 0000000000000000000000000000000000000000..b46a2bc79dd8453c09e36211bb0ddc638c530c12 GIT binary patch literal 7 OcmZQ%VEVX<@jn0t+X9~e literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/short-offer b/test/fuzz/qbit_corpora/pq_records/short-offer new file mode 100644 index 0000000000000000000000000000000000000000..df732639161642d50a9cb69394509d5f7146e910 GIT binary patch literal 1571 zcmV+;2Hg4mAO`SG^*q?uRB)0-2y^TN-F zrUQ{tFoUBoaf@>$d{s<*@X8ov`@d);HDFv?e+D+Onz6^?$;&{`V#Iksv3OPY!sOA0 zlnJeSI|Oi2zt=WlmDos-@H)aUGNwFKUfG`q&bEDwhP}Im=2lGb{Kdd?)KZ#!5|fB zA$r&s&0`k)jDIjTq}8T*30t%`Y@T&C3SBhu~n` zTb;Sk3Qnf7b43NfNLRxg%BG9RGjly2C^z7>uEb+hhIuoBLz(e1fLN0=40FL8n2j3S zYOI@^yu3jsF<9ccV8aGwR}_Sh3ddp^K7Y|AFZ(ASc0JsfDLPw{mykw_n@-qB$+}Y- z+M^%rU^aWij;>)RcguO#wQ3jmrmw-l{=0_kC~o;C46<-}z}2To5VP+lu%5|n=9hC` zk%SwPB-1*-E*liFRAAW!P?IK%b-{X}x)rrXK4{Y{nV}6p#sjomAGjM?Wx;cKxR}eM z%@lFF2gq1&BQ0Dht?7dw1{OL!V8(J&UXQS#AJjuwQ%DO3KESLpyo!fx!_7mxx4(-$ z$9QzUXut8$&Gw>@4U5V{@|%amIb3>qOgW@piNo3Bzi>viydX+-qY7|xLQ-6T&q~QW zDvKs;S?a0{nR7_#{Cm(TLNUczrOXs%q9YgQ6wDMcz>>buW?v*An8jAHz>%Sa8NvFI zHBDp#_Zf2Mj3c6oCuf*~YddiscB&Whd?X=FY#eTiQ*&(DhS}ss28A;5rXftpj0g-c1ij=+e@fABX9U#y^>rif>)C*P| z0eHq@r^U9t69qLjT8dOSt{e_3xN`X@0J<@oG}VXYC}8UtI}6(+>;g&=8#bLdalyyV zp2>0tq6oC?GU^m8VbVL>v;!r|2UfFeUE!PUg&hp$qYM{bMtN7)D`oXnAP3sE%|}fM zTD4X+F%h|DN&zi@*JrcUNfU*Dpob7Wa=Tz9Ao)2n7w0ZNyj^kzqD;!J1NwwU8KkVL zj3i)F?Gh}PWxSDOsvL4xtXPxaVVk*S#E&GkrF4A)^M?hXoRC|Bk%(4EbAt2?D7QHl zxCNXkvzSvDnU|N2Nw8h9cjfp_`IU9`8#DVAeL>|)b`ijWIDBoQLc;_v&UX<~ zQ3Uj2Gs@YBE=7523B{X)aVUEk#P%RwptDpwG#`u>oDl&G)hfNAxikVxk%7QR?5zQW zM+w)1N}yI`fS2+Ro=>b~4Kl5O%vFJ`U@2-_QPveC9E`xc%>H+Km*%BMP!V7{ir4C| z=RsE>7=#O$orC#?7B(}yXpx;)5)soEz@xx4bt$xw0m9T#giBC^Kuw}sO*fMlLlc=( z{Kz*$X(kJZ88U>tmL&}Wz+TK~38YFm;zD5vJ;RkfPKpUT+JFm7CKT6dLsiD903{wg zx!wkz((9@;vr4WuVKyQdwKWS*Xvw^55zzH@K6xzj0Ib`T#V)H*)CQJ+Ns5CfLJeb= zh+}}MKpC?KxEP8=ma-%3;&64id|ifsg<8kOxRTRXBzo+%USNr$+oMJHT_u~1K%!Hm VP^Ct+YP>xd1oX!2!l{z||4tjy)T#gg literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/truncated-wide-length b/test/fuzz/qbit_corpora/pq_records/truncated-wide-length new file mode 100644 index 0000000000000000000000000000000000000000..d74217a42a986d49d6a65628cc66276800d37d54 GIT binary patch literal 6 NcmZQ#T=kEE0RRQL0jvN3 literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/unknown-then-accept b/test/fuzz/qbit_corpora/pq_records/unknown-then-accept new file mode 100644 index 0000000000000000000000000000000000000000..50770534019a44fc4f6a115087c9e0de471d9b04 GIT binary patch literal 1578 zcmV+_2G#im0dZn!bp0U)@c95QULHV=nyCSq{HKxphUf0xQTkTd;(C4ugb)t3s-k>{sLd!a;`3@nO-^Lt)pqB#GqVssCljE}zv<&OTXv3h$QFs^TVKqm>#kI{R}qmxmbRgNN3P#`o^< z`1=c+dNw7n~Zi zE`sP9*B-9PDlMF#9Oj1p!bDgd7t9NhsoQIB=ER;^88dw{NrBKKB7Opf04QGf zm840=n3d<vgnAce&ljyx+4ZD+F90DJj(`Az}(flnIAc9ct~4pI@H&WB-!lH17nsYj?KiS`$%obyWzLh%u-!-*+jDyN<-k zoB-=4mb7zsbu#nnksF{r*`5e&yLx2Bp~HbxFb}>F;}Kb!D{dMT-(&22@N%W(3+!Vw;f6$lHplo0VQek*x)@{gPd0ZCsEZ#h4a+8fXIk0mkj3`R zPL-tb`cQ?W6zm`kA&NA^naW#`3OhCkLT~|}9^KuS19?%_3-iHJPF@$=Ax2sE0f-YW zGx3~hf7b}A1kxrhu$);03;~jPTvNK{XhbDu;y1h_@W}4PwQ0@nl_I8?qY~)l-N%Td zp6b%OhZ(Y%@?2m=&kQWiDJaJ&#Y~NfCCK1I#b*6$Ey?F4{YSS$)p6u0lr)5$Om~Ub z^>6SSI`19M*Y*L=#3$cAy!QmEav@~TEj;E)6jsg)#iS>j?0Mvlf+B#5z||k1j;_nN*SRN2PSjSP z=Jnf^Y3=y7T3^(<;B&^A?rcyX4O>_wR4PD+IvCZsfy#!=+Z>yvIB=Ec|FAuzv)#4Q zpyfGQA*@1(5LzRJ{uFoY>ck#@T~ZE(Pn8kYDiec~mG;APknN15Ym)CMQX;Xf->9RZ z8L;DXX1u(3CX@Sa2b-kc*{5150dMxN1gsyxOZFE&Sj>_ye0s(P4eColX`~>>=naem zO%aXg5zk-&0@)!d8J+I0M3Ujx+adwc)nVjHh5K{fC+pZn>1S1kU-DA#{B)kH^O$(I;(z+fy~BO zXLMBal(CUNBGP;xe+%HmyV9dory1S_o)_Wo0X#P!j2v8&rKYynja!5J}zld?$`KI~Y&T3-7z5i(-#GpqKi& ctT%z0oC*PullbG9=ii4v$;6lWaq@OOpMc67@&Et; literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/pq_records/zero-length b/test/fuzz/qbit_corpora/pq_records/zero-length new file mode 100644 index 0000000000000000000000000000000000000000..0548523376e5cb6fe2cb88ecc9f9c3adc77041ac GIT binary patch literal 7 OcmZSmSeTSq!T Date: Mon, 5 Oct 2026 14:25:43 -0400 Subject: [PATCH 4/8] net: negotiate hybrid v2 keys and require key confirmation Wire PQHandshake into V2Transport per #184 and #199 (E3). Production construction stays PQ-off (V2PQOptions{} in MakeTransport); part 6 adds the option, and with the switch off the wire bytes are today's. - New states: SendState::AWAITING_VERSION and RecvState::CONFIRM, with the transition checks and diagrams updated. - With PQMode::NEGOTIATE the ciphers retain the ECDH secret and start the transcript. A responder generates its key only once the initiator's whole public key has arrived, queues its terminator and the offer, and then sends nothing until the initiator's version packet is processed. An initiator queues only its terminator and holds its version packet. While holding, SetMessageToSend refuses and GetBytesToSend reports more=false. - ProcessReceivedPacketBytes takes the send lock around ProcessVersionContents, so the switch of all four ciphers and the session id is atomic under both locks; application packets take no extra lock. A legacy peer (no own record, or contents that do not parse) keeps the ECDH keys. A valid offer is answered with the accept under the ECDH keys, then both sides switch and append a 20-byte key confirmation. Packets are only ever appended to the send buffer. - In CONFIRM the peer's first hybrid packet must decrypt to length 0, checked as soon as its 3 length bytes arrive, then verify its tag with empty AAD and carry the ignore bit. It never becomes a message. - Off and fallback neither parse nor validate the peer's contents; a responder that could not offer never decapsulates. - Internal ML-KEM failures never assert. Key generation, key check and encapsulation failures continue as plain v2 with an empty version packet; a decapsulation failure after the initiator committed closes. The decapsulation key and the retained ECDH secret are wiped on switch, legacy outcome, every failure and in destructors. - Transport::Info gains transport_pq and transport_pq_status; until the confirmation the status is pending, the classification stays detecting and no session id is published. V2Transport::GetPQSnapshot exposes the negotiation's progress and first failure for part 5. - V2PQOptions::corrupt_shared_secret inverts byte 0 of the local ML-KEM secret before derivation, as the vectors' negative case models. net_tests gains v2_pq_pair, v2_pq_legacy_matrix (including two real transports in every mode pair), v2_pq_v1_detection, v2_pq_send_hold, v2_pq_switch_boundary, v2_pq_confirmation_length, _tag and _not_decoy, v2_pq_corrupt_ct, v2_pq_internal_errors and v2_pq_rekey, and the existing random-contents scenarios also run with the negotiation on against a legacy peer. The bidirectional v2 and v1v2 transport fuzz targets take a PQ mode and an ML-KEM entropy seed per side from the input and check that the keys are hybrid exactly when both sides negotiate; both become required qbit corpus targets with generated seeds, including in the nightly mutation list. Part of #184 (#199, E3). Co-Authored-By: Claude Opus 5.5 --- ci/test/03_test_script.sh | 3 +- doc/development/fuzzing.md | 7 +- src/bip324.h | 3 + src/net.cpp | 341 +++++- src/net.h | 126 +- src/test/fuzz/p2p_transport_serialization.cpp | 53 +- src/test/net_tests.cpp | 1048 ++++++++++++++++- test/fuzz/qbit_corpora/MANIFEST.json | 123 ++ test/fuzz/qbit_corpora/README.md | 17 +- test/fuzz/qbit_corpora/generate_seeds.py | 109 ++ test/fuzz/qbit_corpora/overlay.py | 2 + .../v1-silent-negotiating-responder | Bin 0 -> 114 bytes .../v1-version-to-negotiating-responder | Bin 0 -> 119 bytes ...rsion-to-negotiating-responder-max-garbage | Bin 0 -> 119 bytes .../v1-version-to-off-responder | Bin 0 -> 183 bytes .../p2p_transport_bidirectional_v2/both-off | Bin 0 -> 214 bytes .../both-off-interleaved | Bin 0 -> 390 bytes .../initiator-fallback-responder-off | Bin 0 -> 219 bytes .../initiator-fallback-responder-offers | Bin 0 -> 214 bytes ...ator-fallback-responder-offers-interleaved | Bin 0 -> 343 bytes .../initiator-negotiates-responder-off | Bin 0 -> 214 bytes ...iator-negotiates-responder-off-interleaved | Bin 0 -> 331 bytes .../initiator-off-responder-offers | Bin 0 -> 214 bytes ...initiator-off-responder-offers-interleaved | Bin 0 -> 359 bytes .../negotiate-both | Bin 0 -> 214 bytes .../negotiate-both-interleaved | Bin 0 -> 343 bytes .../negotiate-both-interleaved-long-garbage | Bin 0 -> 326 bytes .../negotiate-both-max-garbage | Bin 0 -> 214 bytes test/fuzz/test_qbit_fuzz_runner.py | 3 +- test/fuzz/test_runner.py | 2 + 30 files changed, 1763 insertions(+), 74 deletions(-) create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-silent-negotiating-responder create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-version-to-negotiating-responder create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-version-to-negotiating-responder-max-garbage create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-version-to-off-responder create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/both-off create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/both-off-interleaved create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-fallback-responder-off create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-fallback-responder-offers create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-fallback-responder-offers-interleaved create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-negotiates-responder-off create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-negotiates-responder-off-interleaved create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-off-responder-offers create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-off-responder-offers-interleaved create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both-interleaved create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both-interleaved-long-garbage create mode 100644 test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both-max-garbage diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh index a36230465f..b65992a5ed 100755 --- a/ci/test/03_test_script.sh +++ b/ci/test/03_test_script.sh @@ -371,6 +371,7 @@ if [ "$RUN_FUZZ_TESTS" = "true" ]; then --require_qbit_corpus \ --mutate_min_time="${QBIT_FUZZ_MUTATE_MIN_TIME}" \ "${DIR_FUZZ_IN}" \ - asert_chain_transition asert_edge_cases asert_math auxpow mlkem mlkem_backend_diff p2mr_script pq_records pqc + asert_chain_transition asert_edge_cases asert_math auxpow mlkem mlkem_backend_diff p2mr_script \ + p2p_transport_bidirectional_v1v2 p2p_transport_bidirectional_v2 pq_records pqc fi fi diff --git a/doc/development/fuzzing.md b/doc/development/fuzzing.md index cee35c7585..b27dbcf8c5 100644 --- a/doc/development/fuzzing.md +++ b/doc/development/fuzzing.md @@ -42,7 +42,8 @@ $ ctest --test-dir build_fuzz -R '^required_fuzz_targets$' --output-on-failure `qa-assets` has no inputs for `asert_chain_transition`, `asert_edge_cases`, `asert_math`, `auxpow`, `mlkem`, `mlkem_backend_diff`, `p2mr_script`, `pq_records` -or `pqc`. +or `pqc`, and its inputs for `p2p_transport_bidirectional_v2` and +`p2p_transport_bidirectional_v1v2` predate the hybrid v2 negotiation. Generated seeds for these targets, their manifest and the input format are described in [`test/fuzz/qbit_corpora/README.md`](../../test/fuzz/qbit_corpora/README.md). @@ -53,7 +54,7 @@ $ test/fuzz/qbit_corpora/overlay.py qa-assets/fuzz_corpora $ build_fuzz/test/fuzz/test_runner.py --require_qbit_corpus qa-assets/fuzz_corpora ``` -`--require_qbit_corpus` fails if any of the nine targets is not compiled or not +`--require_qbit_corpus` fails if any of the eleven targets is not compiled or not selected (including through `--exclude`), has no regular input files, or is not reported as replayed by the fuzz executable. The runner prints the number of input files present and replayed for each target. @@ -69,7 +70,7 @@ replay with a mutation phase of that many seconds per target. The phase starts from the corpus, writes new inputs to a temporary directory and leaves the corpus unchanged. It fails on non-libFuzzer builds. The nightly native fuzz job sets `QBIT_FUZZ_MUTATE_MIN_TIME`, which makes `ci/test/03_test_script.sh` run -this phase for the nine targets after the normal replay. +this phase for the eleven targets after the normal replay. The budget is libFuzzer's `-max_total_time`, which counts time spent loading the seed corpus (libFuzzer's `INITED` line) as well as mutation. A run can end diff --git a/src/bip324.h b/src/bip324.h index 6cd3c2dc6d..4d80becfc5 100644 --- a/src/bip324.h +++ b/src/bip324.h @@ -100,6 +100,9 @@ class BIP324Cipher /** Wipe the retained ECDH secret and reset the transcript; the current ciphers stay in use. */ void DiscardHybridSecret() noexcept; + /** Whether the retained ECDH secret or the transcript is still held. */ + bool HoldsHybridSecret() const noexcept { return m_retained_ecdh.has_value() || m_hybrid_transcript.has_value(); } + /** Determine whether this cipher is fully initialized. */ explicit operator bool() const noexcept { return m_send_l_cipher.has_value(); } diff --git a/src/net.cpp b/src/net.cpp index 9c0391fb99..405ca8f90e 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -729,7 +729,7 @@ V1Transport::V1Transport(const NodeId node_id) noexcept Transport::Info V1Transport::GetInfo() const noexcept { - return {.transport_type = TransportProtocolType::V1, .session_id = {}}; + return {.transport_type = TransportProtocolType::V1, .session_id = {}, .transport_pq = false, .transport_pq_status = PQStatus::V1}; } int V1Transport::readHeader(std::span msg_bytes) @@ -1000,10 +1000,13 @@ void V2Transport::StartSendingHandshake() noexcept // We cannot wipe m_send_garbage as it will still be used as AAD later in the handshake. } -V2Transport::V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::span ent32, std::vector garbage) noexcept +V2Transport::V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::span ent32, + std::vector garbage, V2PQOptions pq, PQRandomSource random, const PQKemOps& ops) noexcept : m_cipher{key, ent32}, m_initiating{initiating}, m_nodeid{nodeid}, m_v1_fallback{nodeid}, + m_pq_options{EffectivePQOptions(initiating, pq)}, m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1}, + m_pq{initiating, m_pq_options.mode, random, ops}, m_send_garbage{std::move(garbage)}, m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1} { @@ -1015,9 +1018,20 @@ V2Transport::V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::s } } -V2Transport::V2Transport(NodeId nodeid, bool initiating) noexcept +V2PQOptions V2Transport::EffectivePQOptions(bool initiating, V2PQOptions pq) noexcept +{ + // Inbound connections never fall back (#184): a responder in fallback mode is a caller bug. + if (!Assume(initiating || pq.mode != PQMode::FALLBACK)) pq.mode = PQMode::OFF; + return pq; +} + +V2Transport::V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::span ent32, std::vector garbage) noexcept + : V2Transport{nodeid, initiating, key, ent32, std::move(garbage), V2PQOptions{}, DefaultPQRandomSource(), DefaultPQKemOps()} {} + +V2Transport::V2Transport(NodeId nodeid, bool initiating, V2PQOptions pq) noexcept : V2Transport{nodeid, initiating, GenerateRandomKey(), - MakeByteSpan(GetRandHash()), GenerateRandomGarbage()} {} + MakeByteSpan(GetRandHash()), GenerateRandomGarbage(), + pq, DefaultPQRandomSource(), DefaultPQKemOps()} {} void V2Transport::SetReceiveState(RecvState recv_state) noexcept { @@ -1034,6 +1048,9 @@ void V2Transport::SetReceiveState(RecvState recv_state) noexcept Assume(recv_state == RecvState::VERSION); break; case RecvState::VERSION: + Assume(recv_state == RecvState::APP || recv_state == RecvState::CONFIRM); + break; + case RecvState::CONFIRM: Assume(recv_state == RecvState::APP); break; case RecvState::APP: @@ -1059,6 +1076,9 @@ void V2Transport::SetSendState(SendState send_state) noexcept Assume(send_state == SendState::V1 || send_state == SendState::AWAITING_KEY); break; case SendState::AWAITING_KEY: + Assume(send_state == SendState::READY || send_state == SendState::AWAITING_VERSION); + break; + case SendState::AWAITING_VERSION: Assume(send_state == SendState::READY); break; case SendState::READY: @@ -1144,33 +1164,49 @@ bool V2Transport::ProcessReceivedKeyBytes() noexcept // Other side's key has been fully received, and can now be Diffie-Hellman combined with // our key to initialize the encryption ciphers. - // Initialize the ciphers. + // Initialize the ciphers. The hybrid negotiation needs the ECDH secret and the transcript + // until the switch decision. EllSwiftPubKey ellswift(MakeByteSpan(m_recv_buffer)); + const bool negotiate{m_pq_options.mode == PQMode::NEGOTIATE}; LOCK(m_send_mutex); - m_cipher.Initialize(ellswift, m_initiating); + m_cipher.Initialize(ellswift, m_initiating, /*self_decrypt=*/false, /*retain_for_hybrid=*/negotiate); // Switch receiver state to GARB_GARBTERM. SetReceiveState(RecvState::GARB_GARBTERM); m_recv_buffer.clear(); - // Switch sender state to READY. - SetSendState(SendState::READY); - // Append the garbage terminator to the send buffer. m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::GARBAGE_TERMINATOR_LEN); std::copy(m_cipher.GetSendGarbageTerminator().begin(), m_cipher.GetSendGarbageTerminator().end(), MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::GARBAGE_TERMINATOR_LEN).begin()); - // Construct version packet in the send buffer, with the sent garbage data as AAD. - m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::EXPANSION + VERSION_CONTENTS.size()); - m_cipher.Encrypt( - /*contents=*/VERSION_CONTENTS, - /*aad=*/MakeByteSpan(m_send_garbage), - /*ignore=*/false, - /*output=*/MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::EXPANSION + VERSION_CONTENTS.size())); - // We no longer need the garbage. - ClearShrink(m_send_garbage); + if (!negotiate) { + // Switch sender state to READY, and send an empty version packet. + SetSendState(SendState::READY); + AppendVersionPacket(VERSION_CONTENTS); + } else if (m_initiating) { + // Hold the version packet until the responder's arrives: it answers their offer. + SetSendState(SendState::AWAITING_VERSION); + } else { + // Offer an encapsulation key in the version packet, and send nothing else until the + // initiator's version packet is processed. The key is generated only now that the + // initiator's whole public key has arrived. + PQHandshake::Record offer; + if (m_pq.MakeOffer(offer) == mlkem::Error::NONE) { + // Unreachable failure: the transcript was just started. + if (!Assume(m_cipher.AddVersionContents(offer))) return FailCipherState(); + SetSendState(SendState::AWAITING_VERSION); + AppendVersionPacket(offer); + m_pq.SetOfferSent(); + } else { + // A local fault: continue without the hybrid negotiation, as plain v2. + LogDebug(BCLog::NET, "V2 transport: no hybrid offer after an internal error (keygen), peer=%d\n", m_nodeid); + ClearHybridSecrets(); + SetSendState(SendState::READY); + AppendVersionPacket(VERSION_CONTENTS); + } + } } else { // We still have to receive more key bytes. } @@ -1207,7 +1243,8 @@ bool V2Transport::ProcessReceivedGarbageBytes() noexcept bool V2Transport::ProcessReceivedPacketBytes() noexcept { AssertLockHeld(m_recv_mutex); - Assume(m_recv_state == RecvState::VERSION || m_recv_state == RecvState::APP); + AssertLockNotHeld(m_send_mutex); + Assume(m_recv_state == RecvState::VERSION || m_recv_state == RecvState::CONFIRM || m_recv_state == RecvState::APP); // The maximum permitted contents length for a packet, consisting of: // - 0x00 byte: indicating long message type encoding @@ -1220,11 +1257,20 @@ bool V2Transport::ProcessReceivedPacketBytes() noexcept if (m_recv_buffer.size() == BIP324Cipher::LENGTH_LEN) { // Length descriptor received. m_recv_len = m_cipher.DecryptLength(MakeByteSpan(m_recv_buffer)); + if (m_recv_state == RecvState::CONFIRM && m_recv_len != 0) { + // The key confirmation is empty. With mismatched keys the length decrypts to a random + // value: fail now instead of waiting for bytes that may never come. + m_pq.SetFailure(PQFailure::CONFIRM_LENGTH); + LogDebug(BCLog::NET, "V2 transport error: key confirmation failed (length), peer=%d\n", m_nodeid); + return false; + } if (m_recv_len > MAX_CONTENTS_LEN) { LogDebug(BCLog::NET, "V2 transport error: packet too large (%u bytes), peer=%d\n", m_recv_len, m_nodeid); return false; } } else if (m_recv_buffer.size() > BIP324Cipher::LENGTH_LEN && m_recv_buffer.size() == m_recv_len + BIP324Cipher::EXPANSION) { + // The key confirmation is handled before, and never as, an ordinary decoy. + if (m_recv_state == RecvState::CONFIRM) return ProcessConfirmation(); // Ciphertext received, decrypt it into m_recv_decode_buffer. // Note that it is impossible to reach this branch without hitting the branch above first, // as GetMaxBytesToProcess only allows up to LENGTH_LEN into the buffer before that point. @@ -1248,11 +1294,14 @@ bool V2Transport::ProcessReceivedPacketBytes() noexcept // decoy, which we simply ignore, use the current state to decide what to do with it. if (!ignore) { switch (m_recv_state) { - case RecvState::VERSION: - // Version message received; transition to application phase. The contents is - // ignored, but can be used for future extensions. - SetReceiveState(RecvState::APP); + case RecvState::VERSION: { + // Version message received; act on its contents (or ignore them), and transition + // to the key confirmation or the application phase. The send lock makes a switch + // of all ciphers atomic. + LOCK(m_send_mutex); + if (!ProcessVersionContents(MakeByteSpan(m_recv_decode_buffer))) return false; break; + } case RecvState::APP: // Application message decrypted correctly. It can be extracted using GetMessage(). SetReceiveState(RecvState::APP_READY); @@ -1273,6 +1322,165 @@ bool V2Transport::ProcessReceivedPacketBytes() noexcept return true; } +bool V2Transport::ProcessConfirmation() noexcept +{ + AssertLockHeld(m_recv_mutex); + Assume(m_recv_state == RecvState::CONFIRM); + Assume(m_recv_len == 0 && m_recv_buffer.size() == PQ_CONFIRMATION_BYTES); + // The peer's first packet under the hybrid keys: an empty decoy, with empty AAD. + bool ignore{false}; + if (!m_cipher.Decrypt( + /*input=*/MakeByteSpan(m_recv_buffer).subspan(BIP324Cipher::LENGTH_LEN), + /*aad=*/{}, + /*ignore=*/ignore, + /*contents=*/{})) { + m_pq.SetFailure(PQFailure::CONFIRM_TAG); + LogDebug(BCLog::NET, "V2 transport error: key confirmation failed (tag), peer=%d\n", m_nodeid); + return false; + } + if (!ignore) { + m_pq.SetFailure(PQFailure::CONFIRM_NOT_DECOY); + LogDebug(BCLog::NET, "V2 transport error: key confirmation failed (not_decoy), peer=%d\n", m_nodeid); + return false; + } + // Feed the last 4 bytes of the Poly1305 authentication tag (and its timing) into our RNG. + RandAddEvent(ReadLE32(m_recv_buffer.data() + m_recv_buffer.size() - 4)); + m_pq.SetConfirmed(); + ClearShrink(m_recv_buffer); + SetReceiveState(RecvState::APP); + return true; +} + +bool V2Transport::ProcessVersionContents(std::span contents) noexcept +{ + AssertLockHeld(m_recv_mutex); + AssertLockHeld(m_send_mutex); + Assume(m_recv_state == RecvState::VERSION); + m_pq.SetVersionReceived(); + + // Without the negotiation (switch off, fallback, or a responder that could not offer), the + // contents are neither parsed nor validated. + if (m_send_state != SendState::AWAITING_VERSION) { + SetReceiveState(RecvState::APP); + return true; + } + Assume(m_pq_options.mode == PQMode::NEGOTIATE); + + // A legacy peer: empty contents, no own record, or contents that do not parse. Keep the ECDH + // keys; an initiator now sends its empty version packet. + const auto parsed{PQHandshake::ParseContents(contents)}; + if (parsed.kind != PQHandshake::ParseKind::OWN_RECORD) { + ClearHybridSecrets(); + SetSendState(SendState::READY); + if (m_initiating) AppendVersionPacket(VERSION_CONTENTS); + SetReceiveState(RecvState::APP); + return true; + } + + // The first own record is the responder's offer or the initiator's accept, and has one length. + const auto payload{m_pq.CheckRecordLength(parsed.payload)}; + if (!payload) { + if (m_pq.GetSnapshot().failure == PQFailure::CIPHER_STATE_INTERNAL) return FailCipherState(); + LogDebug(BCLog::NET, "V2 transport error: malformed hybrid record (%s), peer=%d\n", + m_initiating ? "ek_length" : "ct_length", m_nodeid); + ClearHybridSecrets(); + return false; + } + + // Both contents go into the transcript, the responder's first. The unreachable cipher state + // failures close, whatever was committed. + if (!Assume(m_cipher.AddVersionContents(contents))) return FailCipherState(); + mlkem::SharedSecret ss; + if (m_initiating) { + PQHandshake::Record accept; + const mlkem::Error result{m_pq.AcceptOffer(*payload, accept, ss)}; + if (result == mlkem::Error::INVALID_PUBLIC_KEY) { + LogDebug(BCLog::NET, "V2 transport error: malformed hybrid record (ek_modulus), peer=%d\n", m_nodeid); + ClearHybridSecrets(); + return false; + } + if (result != mlkem::Error::NONE) { + // A local fault before we committed to anything (AcceptOffer recorded which): + // continue as if the switch were off. + LogDebug(BCLog::NET, "V2 transport: no hybrid accept after an internal error, peer=%d\n", m_nodeid); + ClearHybridSecrets(); + SetSendState(SendState::READY); + AppendVersionPacket(VERSION_CONTENTS); + SetReceiveState(RecvState::APP); + return true; + } + if (!Assume(m_cipher.AddVersionContents(accept))) return FailCipherState(); + if (m_pq_options.corrupt_shared_secret) ss.Bytes()[0] ^= 0xff; + // The accept travels under the ECDH keys, then both directions switch. + SetSendState(SendState::READY); + AppendVersionPacket(accept); + } else { + // The initiator has committed to the hybrid keys, so every failure from here closes. + if (m_pq.DecapsulateAccept(*payload, ss) != mlkem::Error::NONE) { + LogDebug(BCLog::NET, "V2 transport error: hybrid key exchange failed (decaps), peer=%d\n", m_nodeid); + ClearHybridSecrets(); + return false; + } + if (m_pq_options.corrupt_shared_secret) ss.Bytes()[0] ^= 0xff; + SetSendState(SendState::READY); + } + + // Unreachable failure: both contents are in the transcript. + if (!Assume(m_cipher.SwitchToHybrid(std::as_bytes(ss.Bytes())))) return FailCipherState(); + m_pq.SetSwitched(); + // Our first packet under the hybrid keys is the key confirmation, and theirs must be too. + AppendConfirmationPacket(); + SetReceiveState(RecvState::CONFIRM); + return true; +} + +void V2Transport::AppendVersionPacket(std::span contents) noexcept +{ + AssertLockHeld(m_recv_mutex); + AssertLockHeld(m_send_mutex); + // Construct version packet in the send buffer, after any unsent bytes, with the sent garbage + // data as AAD. + m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::EXPANSION + contents.size()); + m_cipher.Encrypt( + /*contents=*/contents, + /*aad=*/MakeByteSpan(m_send_garbage), + /*ignore=*/false, + /*output=*/MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::EXPANSION + contents.size())); + // We no longer need the garbage. + ClearShrink(m_send_garbage); +} + +void V2Transport::AppendConfirmationPacket() noexcept +{ + AssertLockHeld(m_recv_mutex); + AssertLockHeld(m_send_mutex); + static_assert(BIP324Cipher::EXPANSION == PQ_CONFIRMATION_BYTES); + m_send_buffer.resize(m_send_buffer.size() + PQ_CONFIRMATION_BYTES); + m_cipher.Encrypt( + /*contents=*/{}, + /*aad=*/{}, + /*ignore=*/true, + /*output=*/MakeWritableByteSpan(m_send_buffer).last(PQ_CONFIRMATION_BYTES)); +} + +void V2Transport::ClearHybridSecrets() noexcept +{ + AssertLockHeld(m_recv_mutex); + AssertLockHeld(m_send_mutex); + m_pq.ClearSecrets(); + m_cipher.DiscardHybridSecret(); +} + +bool V2Transport::FailCipherState() noexcept +{ + AssertLockHeld(m_recv_mutex); + AssertLockHeld(m_send_mutex); + m_pq.SetFailure(PQFailure::CIPHER_STATE_INTERNAL); + LogDebug(BCLog::NET, "V2 transport error: hybrid key exchange failed (cipher_state), peer=%d\n", m_nodeid); + ClearHybridSecrets(); + return false; +} + size_t V2Transport::GetMaxBytesToProcess() noexcept { AssertLockHeld(m_recv_mutex); @@ -1297,8 +1505,9 @@ size_t V2Transport::GetMaxBytesToProcess() noexcept // Process garbage bytes one by one (because terminator may appear anywhere). return 1; case RecvState::VERSION: + case RecvState::CONFIRM: case RecvState::APP: - // These three states all involve decoding a packet. Process the length descriptor first, + // These states all involve decoding a packet. Process the length descriptor first, // so that we know where the current packet ends (and we don't process bytes from the next // packet or decoy yet). Then, process the ciphertext bytes of the current packet. if (m_recv_buffer.size() < BIP324Cipher::LENGTH_LEN) { @@ -1350,6 +1559,7 @@ bool V2Transport::ReceivedBytes(std::span& msg_bytes) noexcept m_recv_buffer.reserve(MAX_GARBAGE_LEN + BIP324Cipher::GARBAGE_TERMINATOR_LEN); break; case RecvState::VERSION: + case RecvState::CONFIRM: case RecvState::APP: { // During states where a packet is being received, as much as is expected but never // more than MAX_RESERVE_AHEAD bytes in addition to what is received so far. @@ -1389,12 +1599,25 @@ bool V2Transport::ReceivedBytes(std::span& msg_bytes) noexcept break; case RecvState::GARB_GARBTERM: - if (!ProcessReceivedGarbageBytes()) return false; + if (!ProcessReceivedGarbageBytes()) { + if (m_pq_options.mode == PQMode::NEGOTIATE) { + LOCK(m_send_mutex); + ClearHybridSecrets(); + } + return false; + } break; case RecvState::VERSION: + case RecvState::CONFIRM: case RecvState::APP: - if (!ProcessReceivedPacketBytes()) return false; + if (!ProcessReceivedPacketBytes()) { + if (m_pq_options.mode == PQMode::NEGOTIATE) { + LOCK(m_send_mutex); + ClearHybridSecrets(); + } + return false; + } break; case RecvState::APP_READY: @@ -1587,18 +1810,80 @@ Transport::Info V2Transport::GetInfo() const noexcept Transport::Info info; // Do not report v2 and session ID until the version packet has been received - // and verified (confirming that the other side very likely has the same keys as us). + // and verified (confirming that the other side very likely has the same keys as us), and after + // a switch to hybrid keys, until the peer's key confirmation has verified. if (m_recv_state != RecvState::KEY_MAYBE_V1 && m_recv_state != RecvState::KEY && - m_recv_state != RecvState::GARB_GARBTERM && m_recv_state != RecvState::VERSION) { + m_recv_state != RecvState::GARB_GARBTERM && m_recv_state != RecvState::VERSION && + m_recv_state != RecvState::CONFIRM) { info.transport_type = TransportProtocolType::V2; info.session_id = uint256(MakeUCharSpan(m_cipher.GetSessionID())); } else { info.transport_type = TransportProtocolType::DETECTING; } + info.transport_pq = m_pq.GetSnapshot().confirmed; + info.transport_pq_status = GetPQStatus(); return info; } +PQStatus V2Transport::GetPQStatus() const noexcept +{ + AssertLockHeld(m_recv_mutex); + switch (m_pq_options.mode) { + case PQMode::OFF: return PQStatus::OFF; + case PQMode::FALLBACK: return PQStatus::FALLBACK; + case PQMode::NEGOTIATE: break; + } + const PQHandshake::Snapshot snapshot{m_pq.GetSnapshot()}; + if (snapshot.confirmed) return PQStatus::HYBRID; + switch (snapshot.failure) { + case PQFailure::NONE: + // The peer's version packet had no usable offer or accept: the ECDH keys stay. + if (snapshot.version_received && !snapshot.switched) return PQStatus::LEGACY_PEER; + return PQStatus::PENDING; + case PQFailure::KEYGEN_INTERNAL: + case PQFailure::CHECK_EK_INTERNAL: + case PQFailure::ENCAPS_INTERNAL: + // A local fault before anything was committed: the connection continues as plain v2. + return PQStatus::OFF; + case PQFailure::EK_LENGTH: + case PQFailure::EK_MODULUS: + case PQFailure::CT_LENGTH: + case PQFailure::CONFIRM_LENGTH: + case PQFailure::CONFIRM_TAG: + case PQFailure::CONFIRM_NOT_DECOY: + case PQFailure::DECAPS_INTERNAL: + case PQFailure::CIPHER_STATE_INTERNAL: + // The connection closes. There is no failed status, so it reports pending until it is + // removed, local faults included. + return PQStatus::PENDING; + } + Assume(false); + return PQStatus::PENDING; +} + +PQHandshake::Snapshot V2Transport::GetPQSnapshot() const noexcept +{ + AssertLockNotHeld(m_recv_mutex); + LOCK(m_recv_mutex); + return m_pq.GetSnapshot(); +} + +bool V2Transport::HoldsHybridSecretsForTesting() const noexcept +{ + AssertLockNotHeld(m_recv_mutex); + LOCK(m_recv_mutex); + return m_pq.HasDecapsulationKey() || m_cipher.HoldsHybridSecret(); +} + +void V2Transport::DiscardHybridSecretForTesting() noexcept +{ + AssertLockNotHeld(m_recv_mutex); + AssertLockNotHeld(m_send_mutex); + LOCK2(m_recv_mutex, m_send_mutex); + m_cipher.DiscardHybridSecret(); +} + std::pair CConnman::SocketSendData(CNode& node) const { auto it = node.vSendMsg.begin(); diff --git a/src/net.h b/src/net.h index d41fc325ef..9fa593c0eb 100644 --- a/src/net.h +++ b/src/net.h @@ -7,6 +7,7 @@ #define QBIT_NET_H #include +#include #include #include #include @@ -264,6 +265,9 @@ class Transport { { TransportProtocolType transport_type; std::optional session_id; + /** Whether the session keys are hybrid: true only once the peer's key confirmation verified. */ + bool transport_pq{false}; + PQStatus transport_pq_status{PQStatus::OFF}; }; /** Retrieve information about this transport. */ @@ -451,12 +455,21 @@ class V1Transport final : public Transport bool ShouldReconnectV1() const noexcept override { return false; } }; +/** The hybrid post-quantum negotiation of one v2 connection (doc/design/pq-transport.md). */ +struct V2PQOptions { + PQMode mode{PQMode::OFF}; + /** Test only: invert byte 0 of the local ML-KEM shared secret before deriving keys, so the + * peer's key confirmation check fails. EK and CT are unchanged. */ + bool corrupt_shared_secret{false}; +}; + class V2Transport final : public Transport { private: - /** Contents of the version packet to send. BIP324 stipulates that senders should leave this - * empty, and receivers should ignore it. Future extensions can change what is sent as long as - * an empty version packet contents is interpreted as no extensions supported. */ + /** Contents of the version packet to send without an offer or accept. BIP324 stipulates that + * senders should leave this empty, and receivers should ignore it. Extensions (such as the + * hybrid negotiation, see PQHandshake) change what is sent, and an empty version packet + * contents is interpreted as no extensions supported. */ static constexpr std::array VERSION_CONTENTS = {}; /** The length of the V1 prefix to match bytes initially received by responders with to @@ -475,12 +488,12 @@ class V2Transport final : public Transport * * start(responder) * | - * | start(initiator) /---------\ - * | | | | - * v v v | - * KEY_MAYBE_V1 -> KEY -> GARB_GARBTERM -> VERSION -> APP -> APP_READY - * | - * \-------> V1 + * | start(initiator) /---------\ + * | | | | + * v v v | + * KEY_MAYBE_V1 -> KEY -> GARB_GARBTERM -> VERSION ------------> APP -> APP_READY + * | | ^ + * \-------> V1 \----> CONFIRM ----/ */ enum class RecvState : uint8_t { /** (Responder only) either v2 public key or v1 header. @@ -511,10 +524,20 @@ class V2Transport final : public Transport * first received packet in this state (whether it's a decoy or not) is expected to * authenticate the garbage received during the GARB_GARBTERM state as associated * authenticated data (AAD). The first non-decoy packet in this state is interpreted as - * version negotiation (currently, that means ignoring the contents, but it can be used for - * negotiating future extensions), and afterwards the state becomes APP. */ + * version negotiation. Without the hybrid negotiation its contents are ignored, and the + * state becomes APP. With it, the contents are an offer or an accept (or a legacy peer's), + * see ProcessVersionContents(): the state becomes CONFIRM if the keys switched to hybrid, + * and APP otherwise. */ VERSION, + /** Key confirmation (hybrid keys only). + * + * The first packet under the hybrid keys must be an empty decoy. Its length is checked as + * soon as its 3 bytes arrive, then its tag and ignore bit. If that fails, the connection + * aborts; otherwise the peer is confirmed, nothing is delivered and the state becomes + * APP. */ + CONFIRM, + /** Application packet. * * A packet is received, and decrypted/verified. If that succeeds, the state becomes @@ -543,9 +566,9 @@ class V2Transport final : public Transport * | start(initiator) * | | * v v - * MAYBE_V1 -> AWAITING_KEY -> READY - * | - * \-----> V1 + * MAYBE_V1 -> AWAITING_KEY ---------------------> READY + * | | ^ + * \-----> V1 \-----> AWAITING_VERSION ------/ */ enum class SendState : uint8_t { /** (Responder only) Not sending until v1 or v2 is detected. @@ -560,15 +583,26 @@ class V2Transport final : public Transport * * This is the initial state for initiators. The public key and garbage is sent out. When * the receiver receives the other side's public key and transitions to GARB_GARBTERM, the - * sender state becomes READY. */ + * sender state becomes READY, or AWAITING_VERSION with the hybrid negotiation. */ AWAITING_KEY, + /** (Hybrid negotiation only) Waiting for the other side's version packet. + * + * The ciphers are initialized and the garbage terminator is queued. A responder has also + * queued its version packet with the offer; an initiator holds its version packet, which + * answers the offer. Nothing else is sent, so no message can be provided, and bytes still + * in the send buffer go out. When the receiver processes the other side's version packet, + * the sender state becomes READY, and the version packet (initiator) and the key + * confirmation (after a switch to hybrid keys) are appended to the send buffer. */ + AWAITING_VERSION, + /** Normal sending state. * * In this state, the ciphers are initialized, so packets can be sent. When this state is - * entered, the garbage terminator and version packet are appended to the send buffer (in - * addition to the key and garbage which may still be there). In this state a message can be - * provided if the send buffer is empty. */ + * entered, the garbage terminator and version packet (and, after a switch to hybrid keys, + * the key confirmation) have been appended to the send buffer (in addition to the key and + * garbage which may still be there). In this state a message can be provided if the send + * buffer is empty. */ READY, /** This transport is using v1 fallback. @@ -585,6 +619,8 @@ class V2Transport final : public Transport const NodeId m_nodeid; /** Encapsulate a V1Transport to fall back to. */ V1Transport m_v1_fallback; + /** The hybrid negotiation options of this connection. */ + const V2PQOptions m_pq_options; /** Lock for receiver-side fields. */ mutable Mutex m_recv_mutex ACQUIRED_BEFORE(m_send_mutex); @@ -599,6 +635,8 @@ class V2Transport final : public Transport std::vector m_recv_decode_buffer GUARDED_BY(m_recv_mutex); /** Current receiver state. */ RecvState m_recv_state GUARDED_BY(m_recv_mutex); + /** The hybrid negotiation state: KEM secrets and progress. */ + PQHandshake m_pq GUARDED_BY(m_recv_mutex); /** Lock for sending-side fields. If both sending and receiving fields are accessed, * m_recv_mutex must be acquired before m_send_mutex. */ @@ -632,8 +670,32 @@ class V2Transport final : public Transport bool ProcessReceivedKeyBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, !m_send_mutex); /** Process bytes in m_recv_buffer, while in GARB_GARBTERM state. */ bool ProcessReceivedGarbageBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex); - /** Process bytes in m_recv_buffer, while in VERSION/APP state. */ - bool ProcessReceivedPacketBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex); + /** Process bytes in m_recv_buffer, while in VERSION/CONFIRM/APP state. */ + bool ProcessReceivedPacketBytes() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, !m_send_mutex); + /** Process the peer's key confirmation in m_recv_buffer, while in CONFIRM state. */ + bool ProcessConfirmation() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex); + /** Act on the contents of the peer's version packet, and leave the VERSION state. Returns + * false if the connection must close. */ + bool ProcessVersionContents(std::span contents) noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, m_send_mutex); + /** Append our version packet to the send buffer, under the current (ECDH) keys, with our + * garbage as AAD, and wipe the garbage. */ + void AppendVersionPacket(std::span contents) noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, m_send_mutex); + /** Append our key confirmation (an empty decoy, empty AAD) under the hybrid keys. */ + void AppendConfirmationPacket() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, m_send_mutex); + /** Wipe the decapsulation key and the retained ECDH secret, keeping the current keys. */ + void ClearHybridSecrets() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, m_send_mutex); + /** Close after an inconsistent hybrid cipher state, a local bug: record and log + * CIPHER_STATE_INTERNAL, wipe, and return false. */ + bool FailCipherState() noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex, m_send_mutex); + /** The options this side uses: a responder never falls back, so its FALLBACK becomes OFF. */ + static V2PQOptions EffectivePQOptions(bool initiating, V2PQOptions pq) noexcept; + /** The status getpeerinfo reports for the hybrid negotiation. OFF and FALLBACK follow the + * mode. With the negotiation on: HYBRID once the peer's confirmation verified, LEGACY_PEER + * when the peer's version packet carried no usable offer or accept, OFF after a key + * generation, key check or encapsulation fault that continued as plain v2, and PENDING + * otherwise. There is no failed status: a connection closing on any other failure, local + * faults included, reports PENDING until it is removed. */ + PQStatus GetPQStatus() const noexcept EXCLUSIVE_LOCKS_REQUIRED(m_recv_mutex); public: static constexpr uint32_t MAX_GARBAGE_LEN = 4095; @@ -642,12 +704,24 @@ class V2Transport final : public Transport * * @param[in] nodeid the node's NodeId (only for debug log output). * @param[in] initiating whether we are the initiator side. + * @param[in] pq the hybrid negotiation options (off by default). Only an initiator + * falls back: PQMode::FALLBACK on a responder is a caller bug + * (Assume), and it runs and reports as PQMode::OFF. */ - V2Transport(NodeId nodeid, bool initiating) noexcept; + V2Transport(NodeId nodeid, bool initiating, V2PQOptions pq = {}) noexcept; - /** Construct a V2 transport with specified keys and garbage (test use only). */ + /** Construct a V2 transport with specified keys and garbage, without the hybrid negotiation + * (test use only). */ V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::span ent32, std::vector garbage) noexcept; + /** Construct a V2 transport with specified keys, garbage, hybrid negotiation options, ML-KEM + * entropy and operations (test use only). ops must have static lifetime. A responder's + * PQMode::FALLBACK is handled as in the constructor above. */ + V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::span ent32, + std::vector garbage, V2PQOptions pq, PQRandomSource random, const PQKemOps& ops) noexcept; + V2Transport(NodeId nodeid, bool initiating, const CKey& key, std::span ent32, + std::vector garbage, V2PQOptions pq, PQRandomSource random, const PQKemOps&& ops) = delete; + // Receive side functions. bool ReceivedMessageComplete() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex); bool ReceivedBytes(std::span& msg_bytes) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex, !m_send_mutex); @@ -662,6 +736,14 @@ class V2Transport final : public Transport // Miscellaneous functions. bool ShouldReconnectV1() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex, !m_send_mutex); Info GetInfo() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex); + /** The hybrid negotiation's progress and first failure. */ + PQHandshake::Snapshot GetPQSnapshot() const noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex); + + /** Test only: whether the decapsulation key or the retained ECDH secret is still held. */ + bool HoldsHybridSecretsForTesting() const noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex); + /** Test only: discard the retained ECDH secret and transcript, as an inconsistent cipher state + * would. Every later use of them fails closed. */ + void DiscardHybridSecretForTesting() noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex, !m_send_mutex); }; struct CNodeOptions diff --git a/src/test/fuzz/p2p_transport_serialization.cpp b/src/test/fuzz/p2p_transport_serialization.cpp index 88432ec100..9c6bfc272f 100644 --- a/src/test/fuzz/p2p_transport_serialization.cpp +++ b/src/test/fuzz/p2p_transport_serialization.cpp @@ -2,7 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include +#include +#include #include #include #include @@ -13,10 +16,12 @@ #include #include +#include #include #include #include #include +#include #include namespace { @@ -336,8 +341,23 @@ std::unique_ptr MakeV1Transport(NodeId nodeid) noexcept return std::make_unique(nodeid); } +/** One side's ML-KEM entropy, SHA256(seed || counter), with the seed from the fuzzer input. */ +struct FuzzPQEntropy { + std::array seed{}; + uint32_t counter{0}; + + static void Fill(void* context, std::span out) noexcept + { + auto& self{*static_cast(context)}; + uint8_t counter_le[4]; + WriteLE32(counter_le, self.counter++); + CSHA256().Write(self.seed.data(), self.seed.size()).Write(counter_le, sizeof(counter_le)).Finalize(out.data()); + } +}; + template -std::unique_ptr MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider) +std::unique_ptr MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider, + FuzzPQEntropy& entropy, PQMode& mode) { // Retrieve key auto key = ConsumePrivateKey(provider); @@ -366,8 +386,15 @@ std::unique_ptr MakeV2Transport(NodeId nodeid, bool initiator, RNG& r CSHA256().Write(UCharCast(ent.data()), ent.size()) .Write(garb.data(), garb.size()) .Finalize(UCharCast(ent.data())); - - return std::make_unique(nodeid, initiator, key, ent, std::move(garb)); + // Retrieve the hybrid negotiation mode (only an initiator falls back), and the seed of this + // side's ML-KEM entropy. + mode = initiator ? provider.PickValueInArray({PQMode::OFF, PQMode::NEGOTIATE, PQMode::FALLBACK}) : + provider.PickValueInArray({PQMode::OFF, PQMode::NEGOTIATE}); + const auto pq_seed{provider.ConsumeBytes(entropy.seed.size())}; + std::ranges::copy(pq_seed, entropy.seed.begin()); + + return std::make_unique(nodeid, initiator, key, ent, std::move(garb), V2PQOptions{.mode = mode}, + PQRandomSource{.fill32 = &FuzzPQEntropy::Fill, .context = &entropy}, DefaultPQKemOps()); } } // namespace @@ -385,22 +412,32 @@ FUZZ_TARGET(p2p_transport_bidirectional, .init = initialize_p2p_transport_serial FUZZ_TARGET(p2p_transport_bidirectional_v2, .init = initialize_p2p_transport_serialization) { - // Test with two V2 transports talking to each other. + // Test with two V2 transports talking to each other, each with or without the hybrid + // negotiation. FuzzedDataProvider provider{buffer.data(), buffer.size()}; InsecureRandomContext rng(provider.ConsumeIntegral()); - auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider); - auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider); + FuzzPQEntropy entropy1, entropy2; + PQMode mode1, mode2; + auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider, entropy1, mode1); + auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider, entropy2, mode2); if (!t1 || !t2) return; SimulationTest(*t1, *t2, rng, provider); + // The keys are hybrid, and confirmed on both sides, exactly when both sides negotiate. + const bool hybrid{mode1 == PQMode::NEGOTIATE && mode2 == PQMode::NEGOTIATE}; + assert(t1->GetInfo().transport_pq == hybrid); + assert(t2->GetInfo().transport_pq == hybrid); } FUZZ_TARGET(p2p_transport_bidirectional_v1v2, .init = initialize_p2p_transport_serialization) { - // Test with a V1 initiator talking to a V2 responder. + // Test with a V1 initiator talking to a V2 responder, with or without the hybrid negotiation. FuzzedDataProvider provider{buffer.data(), buffer.size()}; InsecureRandomContext rng(provider.ConsumeIntegral()); + FuzzPQEntropy entropy; + PQMode mode; auto t1 = MakeV1Transport(NodeId{0}); - auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider); + auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider, entropy, mode); if (!t1 || !t2) return; SimulationTest(*t1, *t2, rng, provider); + assert(!t2->GetInfo().transport_pq); } diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 9ba54b5748..c5f584fdf1 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -1064,6 +1064,7 @@ class V2TransportTester V2Transport m_transport; //!< V2Transport being tested BIP324Cipher m_cipher; //!< Cipher to help with the other side bool m_test_initiator; //!< Whether m_transport is the initiator (true) or responder (false) + PQHandshake m_peer_pq; //!< The other side's hybrid negotiation state std::vector m_sent_garbage; //!< The garbage we've sent to m_transport. std::vector m_recv_garbage; //!< The garbage we've received from m_transport. @@ -1074,11 +1075,21 @@ class V2TransportTester public: /** Construct a tester object. test_initiator: whether the tested transport is initiator. */ - explicit V2TransportTester(FastRandomContext& rng, bool test_initiator) + explicit V2TransportTester(FastRandomContext& rng, bool test_initiator, V2PQOptions pq = {}) : m_rng{rng}, - m_transport{0, test_initiator}, + m_transport{0, test_initiator, pq}, m_cipher{GenerateRandomTestKey(m_rng), MakeByteSpan(m_rng.rand256())}, - m_test_initiator(test_initiator) {} + m_test_initiator(test_initiator), + m_peer_pq{!test_initiator, PQMode::NEGOTIATE} {} + + /** Construct a tester object whose transport uses the given ML-KEM entropy and operations. */ + V2TransportTester(FastRandomContext& rng, bool test_initiator, V2PQOptions pq, PQRandomSource random, const PQKemOps& ops) + : m_rng{rng}, + m_transport{0, test_initiator, GenerateRandomTestKey(rng), MakeByteSpan(rng.rand256()), + rng.randbytes(rng.randrange(V2Transport::MAX_GARBAGE_LEN + 1)), pq, random, ops}, + m_cipher{GenerateRandomTestKey(m_rng), MakeByteSpan(m_rng.rand256())}, + m_test_initiator(test_initiator), + m_peer_pq{!test_initiator, PQMode::NEGOTIATE} {} /** Data type returned by Interact: * @@ -1145,6 +1156,43 @@ class V2TransportTester /** Expose the cipher. */ BIP324Cipher& GetCipher() { return m_cipher; } + /** Expose the transport being tested. */ + V2Transport& GetTransport() { return m_transport; } + + /** The bytes scheduled to be sent to the transport. */ + std::vector& ToSend() { return m_to_send; } + + /** The bytes received from the transport and not processed yet. */ + std::vector& Received() { return m_received; } + + /** Deliver scheduled bytes to the transport, all in one call or one byte per call. Returns false + * (leaving the rest scheduled) at the first transport error. */ + bool Deliver(bool byte_by_byte = false) + { + while (!m_to_send.empty()) { + std::span to_send{m_to_send}; + if (byte_by_byte) to_send = to_send.first(1); + const size_t old_len{to_send.size()}; + const bool ret{m_transport.ReceivedBytes(to_send)}; + m_to_send.erase(m_to_send.begin(), m_to_send.begin() + (old_len - to_send.size())); + if (!ret) return false; + BOOST_REQUIRE(to_send.empty() || m_transport.ReceivedMessageComplete()); + if (!to_send.empty()) return true; + } + return true; + } + + /** Take every byte the transport has to send. */ + void Collect() + { + while (true) { + const auto& [bytes, _more, _msg_type] = m_transport.GetBytesToSend(false); + if (bytes.empty()) break; + m_received.insert(m_received.end(), bytes.begin(), bytes.end()); + m_transport.MarkBytesSent(bytes.size()); + } + } + /** Schedule bytes to be sent to the transport. */ void Send(std::span data) { @@ -1202,12 +1250,13 @@ class V2TransportTester * Many other V2TransportTester functions cannot be called until after ReceiveKey() has been * called, as no encryption keys are set up before that point. */ - void ReceiveKey() + void ReceiveKey(bool retain_for_hybrid = false) { // When processing a key, enough bytes need to have been received already. BOOST_REQUIRE(m_received.size() >= EllSwiftPubKey::size()); // Initialize the cipher using it (acting as the opposite side of the tested transport). - m_cipher.Initialize(MakeByteSpan(m_received).first(EllSwiftPubKey::size()), !m_test_initiator); + m_cipher.Initialize(MakeByteSpan(m_received).first(EllSwiftPubKey::size()), !m_test_initiator, + /*self_decrypt=*/false, retain_for_hybrid); // Strip the processed bytes off the front of the receive buffer. m_received.erase(m_received.begin(), m_received.begin() + EllSwiftPubKey::size()); } @@ -1306,6 +1355,78 @@ class V2TransportTester BOOST_CHECK(contents.empty()); } + /** Expect a version packet to have been received, process it and return its contents (only + * after ReceiveKey). */ + std::vector ReceiveVersionContents() + { + return ReceivePacket(/*aad=*/MakeByteSpan(m_recv_garbage)); + } + + /** Expect a key confirmation: exactly one empty decoy packet (only after a switch). */ + void ReceiveConfirmation() + { + BOOST_REQUIRE(m_received.size() >= PQ_CONFIRMATION_BYTES); + const auto packet{MakeByteSpan(std::span{m_received}.first(PQ_CONFIRMATION_BYTES))}; + BOOST_CHECK_EQUAL(m_cipher.DecryptLength(packet.first(BIP324Cipher::LENGTH_LEN)), 0U); + bool ignore{false}; + BOOST_CHECK(m_cipher.Decrypt(packet.subspan(BIP324Cipher::LENGTH_LEN), /*aad=*/{}, ignore, /*contents=*/{})); + BOOST_CHECK(ignore); + m_received.erase(m_received.begin(), m_received.begin() + PQ_CONFIRMATION_BYTES); + } + + /** Schedule our key confirmation, an empty decoy (only after a switch). */ + void SendConfirmation() { SendPacket(/*content=*/{}, /*aad=*/{}, /*ignore=*/true); } + + /** As a hybrid responder (after ReceiveKey(true) and SendGarbageTerm): schedule our version + * packet with an offer, followed by extra contents. */ + void SendOffer(std::span extra = {}) + { + PQHandshake::Record offer; + BOOST_REQUIRE(m_peer_pq.MakeOffer(offer) == mlkem::Error::NONE); + std::vector contents{offer.begin(), offer.end()}; + contents.insert(contents.end(), extra.begin(), extra.end()); + BOOST_REQUIRE(m_cipher.AddVersionContents(contents)); + SendVersion(MakeUCharSpan(contents)); + } + + /** As a hybrid responder: process the transport's version packet, which must carry a valid + * accept, and switch keys; invert_ss inverts byte 0 of our ML-KEM secret first. */ + void ReceiveAcceptAndSwitch(bool invert_ss = false) + { + const auto contents{ReceiveVersionContents()}; + BOOST_REQUIRE(m_cipher.AddVersionContents(MakeByteSpan(contents))); + const auto parsed{PQHandshake::ParseContents(MakeByteSpan(contents))}; + BOOST_REQUIRE(parsed.kind == PQHandshake::ParseKind::OWN_RECORD); + const auto ct{m_peer_pq.CheckRecordLength(parsed.payload)}; + BOOST_REQUIRE(ct); + mlkem::SharedSecret ss; + BOOST_REQUIRE(m_peer_pq.DecapsulateAccept(*ct, ss) == mlkem::Error::NONE); + if (invert_ss) ss.Bytes()[0] ^= 0xff; + BOOST_REQUIRE(m_cipher.SwitchToHybrid(std::as_bytes(ss.Bytes()))); + } + + /** As a hybrid initiator (after ReceiveKey(true) and SendGarbageTerm): process the transport's + * version packet, which must carry a valid offer, schedule our version packet with the accept + * under the ECDH keys, and switch keys. damage_ct flips a ciphertext bit after hashing it; + * invert_ss inverts byte 0 of our ML-KEM secret before switching. */ + void AcceptOfferAndSwitch(bool damage_ct = false, bool invert_ss = false) + { + const auto contents{ReceiveVersionContents()}; + BOOST_REQUIRE(m_cipher.AddVersionContents(MakeByteSpan(contents))); + const auto parsed{PQHandshake::ParseContents(MakeByteSpan(contents))}; + BOOST_REQUIRE(parsed.kind == PQHandshake::ParseKind::OWN_RECORD); + const auto ek{m_peer_pq.CheckRecordLength(parsed.payload)}; + BOOST_REQUIRE(ek); + PQHandshake::Record accept; + mlkem::SharedSecret ss; + BOOST_REQUIRE(m_peer_pq.AcceptOffer(*ek, accept, ss) == mlkem::Error::NONE); + BOOST_REQUIRE(m_cipher.AddVersionContents(accept)); + if (damage_ct) accept[4 + m_rng.randrange(mlkem::CIPHERTEXT_BYTES)] ^= std::byte(1 << m_rng.randrange(8)); + SendVersion(MakeUCharSpan(accept)); + if (invert_ss) ss.Bytes()[0] ^= 0xff; + BOOST_REQUIRE(m_cipher.SwitchToHybrid(std::as_bytes(ss.Bytes()))); + } + /** Expect application packet to have been received, with specified short id and payload. * (only after ReceiveKey). */ void ReceiveMessage(uint8_t short_id, std::span payload) @@ -1448,8 +1569,11 @@ BOOST_AUTO_TEST_CASE(v2transport_test) BOOST_CHECK(!ret); } - // Various valid but unusual scenarios. - for (int i = 0; i < 50; ++i) { + // Various valid but unusual scenarios. The last 50 have the hybrid negotiation on, with a legacy + // peer. + for (int i = 0; i < 100; ++i) { + /** Whether the tested transport negotiates hybrid keys. */ + const bool pq{i >= 50}; /** Whether an initiator or responder is being tested. */ bool initiator = m_rng.randbool(); /** Use either 0 bytes or the maximum possible (4095 bytes) garbage length. */ @@ -1458,11 +1582,15 @@ BOOST_AUTO_TEST_CASE(v2transport_test) unsigned num_ignore_version = m_rng.randrange(10); /** What data to send in the version packet (ignored by BIP324 peers, but reserved for future extensions). */ auto ver_data = m_rng.randbytes(m_rng.randbool() ? 0 : m_rng.randrange(1000)); + // A legacy peer's random contents never carry an own record. + while (pq && PQHandshake::ParseContents(MakeByteSpan(ver_data)).kind == PQHandshake::ParseKind::OWN_RECORD) { + ver_data = m_rng.randbytes(m_rng.randrange(1000)); + } /** Whether to immediately send key and garbage out (required for responders, optional otherwise). */ bool send_immediately = !initiator || m_rng.randbool(); /** How many decoy packets to send before the first and second real message. */ unsigned num_decoys_1 = m_rng.randrange(1000), num_decoys_2 = m_rng.randrange(1000); - V2TransportTester tester(m_rng, initiator); + V2TransportTester tester(m_rng, initiator, {.mode = pq ? PQMode::NEGOTIATE : PQMode::OFF}); if (send_immediately) { tester.SendKey(); tester.SendGarbage(garb_len); @@ -1484,7 +1612,13 @@ BOOST_AUTO_TEST_CASE(v2transport_test) ret = tester.Interact(); BOOST_REQUIRE(ret && ret->empty()); tester.ReceiveGarbage(); - tester.ReceiveVersion(); + if (pq && !initiator) { + // The tested responder offered; the legacy peer ignores the offer. + const auto contents{tester.ReceiveVersionContents()}; + BOOST_CHECK(PQHandshake::ParseContents(MakeByteSpan(contents)).kind == PQHandshake::ParseKind::OWN_RECORD); + } else { + tester.ReceiveVersion(); + } tester.CompareSessionIDs(); for (unsigned d = 0; d < num_decoys_1; ++d) { auto decoy_data = m_rng.randbytes(m_rng.randrange(1000)); @@ -1509,6 +1643,9 @@ BOOST_AUTO_TEST_CASE(v2transport_test) BOOST_CHECK(!(*ret)[2]); BOOST_CHECK((*ret)[3] && (*ret)[3]->m_type == "foobar" && (*ret)[3]->m_recv.empty()); tester.ReceiveMessage("barfoo", {}); + const auto info{tester.GetTransport().GetInfo()}; + BOOST_CHECK(!info.transport_pq); + BOOST_CHECK(info.transport_pq_status == (pq ? PQStatus::LEGACY_PEER : PQStatus::OFF)); } // Too long garbage (initiator). @@ -2041,4 +2178,897 @@ BOOST_AUTO_TEST_CASE(pq_handshake_negotiation) } } +namespace { + +constexpr V2PQOptions PQ_ON{.mode = PQMode::NEGOTIATE}; + +/** Drive a tester, as the hybrid peer, through the handshake with its transport: both sides + * switch, and the tester checks the transport's key confirmation. The tester's own confirmation + * is scheduled (not delivered) if send_confirmation. Returns the ECDH session id. */ +uint256 HybridHandshake(V2TransportTester& tester, bool test_initiator, bool send_confirmation = true) +{ + uint256 ecdh_session_id; + if (test_initiator) { + // The transport initiates and holds its version packet; we (the responder) offer at once. + auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->empty()); + tester.SendKey(); + tester.SendGarbage(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + ecdh_session_id = uint256(MakeUCharSpan(tester.GetCipher().GetSessionID())); + tester.SendGarbageTerm(); + tester.SendOffer(); + ret = tester.Interact(); + BOOST_REQUIRE(ret && ret->empty()); + tester.ReceiveGarbage(); + tester.ReceiveAcceptAndSwitch(); + } else { + // The transport responds with an offer; we (the initiator) accept it. + tester.SendKey(); + tester.SendGarbage(); + auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->empty()); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + ecdh_session_id = uint256(MakeUCharSpan(tester.GetCipher().GetSessionID())); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(); + ret = tester.Interact(); + BOOST_REQUIRE(ret && ret->empty()); + } + tester.ReceiveConfirmation(); + if (send_confirmation) tester.SendConfirmation(); + return ecdh_session_id; +} + +void CheckPQ(V2Transport& transport, PQStatus status, PQFailure failure = PQFailure::NONE) +{ + const auto info{transport.GetInfo()}; + BOOST_CHECK(info.transport_pq_status == status); + BOOST_CHECK_EQUAL(info.transport_pq, status == PQStatus::HYBRID); + BOOST_CHECK(transport.GetPQSnapshot().failure == failure); +} + +/** Exchange count messages each way between two connected transports, fragmenting the bytes at + * random, and check that each arrives intact and in order. */ +void ExchangeMessages(FastRandomContext& rng, V2Transport& initiator, V2Transport& responder, int count) +{ + const std::array transports{&initiator, &responder}; + std::array, 2> to_send; + std::array, 2> expected; + std::array, 2> in_flight; + for (int side = 0; side < 2; ++side) { + for (int i = 0; i < count; ++i) { + CSerializedNetMsg msg; + msg.m_type = i == 0 ? "version" : "ping"; + msg.data = rng.randbytes(rng.randrange(1000)); + expected[side].push_back(msg.Copy()); + to_send[side].push_back(std::move(msg)); + } + } + bool progress{true}; + while (progress) { + progress = false; + for (int side = 0; side < 2; ++side) { + if (!to_send[side].empty() && transports[side]->SetMessageToSend(to_send[side].front())) { + to_send[side].pop_front(); + progress = true; + } + const auto& [bytes, _more, _type] = transports[side]->GetBytesToSend(!to_send[side].empty()); + if (!bytes.empty()) { + const size_t n{1 + rng.randrange(bytes.size())}; + in_flight[side].insert(in_flight[side].end(), bytes.begin(), bytes.begin() + n); + transports[side]->MarkBytesSent(n); + progress = true; + } + if (!in_flight[side].empty()) { + std::span received{std::span{in_flight[side]}.first(1 + rng.randrange(in_flight[side].size()))}; + const size_t old_size{received.size()}; + BOOST_REQUIRE(transports[!side]->ReceivedBytes(received)); + in_flight[side].erase(in_flight[side].begin(), in_flight[side].begin() + (old_size - received.size())); + if (received.size() != old_size) progress = true; + } + while (transports[!side]->ReceivedMessageComplete()) { + bool reject{false}; + const CNetMessage msg{transports[!side]->GetReceivedMessage({}, reject)}; + BOOST_REQUIRE(!reject && !expected[side].empty()); + BOOST_CHECK_EQUAL(msg.m_type, expected[side].front().m_type); + BOOST_CHECK(std::ranges::equal(MakeUCharSpan(msg.m_recv), expected[side].front().data)); + expected[side].pop_front(); + progress = true; + } + } + } + BOOST_CHECK(expected[0].empty() && expected[1].empty()); +} + +} // namespace + +BOOST_AUTO_TEST_CASE(v2_pq_pair) +{ + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + const uint256 ecdh_session_id{HybridHandshake(tester, test_initiator, /*send_confirmation=*/false)}; + + // Switched, not confirmed: pending, still detecting, and no session id. + auto info{transport.GetInfo()}; + BOOST_CHECK(info.transport_type == TransportProtocolType::DETECTING); + BOOST_CHECK(!info.session_id); + CheckPQ(transport, PQStatus::PENDING); + auto snapshot{transport.GetPQSnapshot()}; + BOOST_CHECK(snapshot.version_received && snapshot.switched && !snapshot.confirmed); + BOOST_CHECK(snapshot.offer == (test_initiator ? PQOfferState::RECEIVED : PQOfferState::SENT)); + + // The transport sends application messages without waiting for our confirmation. + const auto msg_data_1{m_rng.randbytes(m_rng.randrange(100000))}; + tester.AddMessage("barfoo", msg_data_1); + auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->empty()); + tester.ReceiveMessage("barfoo", msg_data_1); + + // It delivers ours only after our confirmation. + const auto msg_data_2{m_rng.randbytes(m_rng.randrange(100000))}; + tester.SendConfirmation(); + tester.SendMessage(uint8_t(4), msg_data_2); // cmpctblock short id + tester.SendMessage("tx", msg_data_1); + ret = tester.Interact(); + BOOST_REQUIRE(ret && ret->size() == 2); + BOOST_CHECK((*ret)[0] && (*ret)[0]->m_type == "cmpctblock" && std::ranges::equal((*ret)[0]->m_recv, MakeByteSpan(msg_data_2))); + BOOST_CHECK((*ret)[1] && (*ret)[1]->m_type == "tx" && std::ranges::equal((*ret)[1]->m_recv, MakeByteSpan(msg_data_1))); + + // Hybrid: v2 with the hybrid session id, which both sides share. + info = transport.GetInfo(); + BOOST_CHECK(info.transport_type == TransportProtocolType::V2); + CheckPQ(transport, PQStatus::HYBRID); + tester.CompareSessionIDs(); + BOOST_CHECK(info.session_id && *info.session_id != ecdh_session_id); + snapshot = transport.GetPQSnapshot(); + BOOST_CHECK(snapshot.switched && snapshot.confirmed); + } + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_legacy_matrix) +{ + // The tested transport (on, off or in fallback) against a scripted peer (hybrid or legacy), in + // both roles. A legacy peer sends its version packet and an application burst as soon as it + // can, without waiting for ours. + for (const bool test_initiator : {true, false}) { + for (const PQMode local : {PQMode::OFF, PQMode::NEGOTIATE, PQMode::FALLBACK}) { + // Inbound connections never fall back. + if (local == PQMode::FALLBACK && !test_initiator) continue; + for (const bool peer_hybrid : {false, true}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator << " local=" << int(local) << " peer_hybrid=" << peer_hybrid) + { + g_kem_calls = {}; + TestPQEntropy entropy{m_rng.rand256()}; + V2TransportTester tester(m_rng, test_initiator, {.mode = local}, entropy.Source(), COUNTING_KEM_OPS); + auto& transport{tester.GetTransport()}; + const bool hybrid{local == PQMode::NEGOTIATE && peer_hybrid}; + const bool offered{local == PQMode::NEGOTIATE && !test_initiator}; + const auto burst_1{m_rng.randbytes(m_rng.randrange(10000))}; + const auto burst_2{m_rng.randbytes(m_rng.randrange(1000))}; + const auto send_burst = [&] { + tester.SendMessage(uint8_t(14), burst_1); // inv short id + tester.SendMessage("foobar", burst_2); + }; + + if (test_initiator) { + auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->empty()); + } + tester.SendKey(); + tester.SendGarbage(); + if (!test_initiator) { + auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->empty()); + } + tester.ReceiveKey(/*retain_for_hybrid=*/peer_hybrid); + tester.SendGarbageTerm(); + if (!peer_hybrid) { + tester.SendVersion(); + send_burst(); + } else if (test_initiator) { + // A hybrid responder offers at once. + tester.SendOffer(); + } + auto ret{tester.Interact()}; + BOOST_REQUIRE(ret); + BOOST_CHECK_EQUAL(ret->size(), peer_hybrid ? 0U : 2U); + tester.ReceiveGarbage(); + if (!peer_hybrid) { + // A legacy peer ignores an offer. + const auto contents{tester.ReceiveVersionContents()}; + BOOST_CHECK(offered ? PQHandshake::ParseContents(MakeByteSpan(contents)).kind == PQHandshake::ParseKind::OWN_RECORD : contents.empty()); + } else if (test_initiator) { + if (hybrid) { + tester.ReceiveAcceptAndSwitch(); + tester.ReceiveConfirmation(); + tester.SendConfirmation(); + } else { + BOOST_CHECK(tester.ReceiveVersionContents().empty()); + tester.GetCipher().DiscardHybridSecret(); + } + send_burst(); + } else { + // A hybrid initiator answers the responder's version packet. + if (hybrid) { + tester.AcceptOfferAndSwitch(); + tester.SendConfirmation(); + } else { + BOOST_CHECK(tester.ReceiveVersionContents().empty()); + tester.GetCipher().DiscardHybridSecret(); + tester.SendVersion(); + } + send_burst(); + } + if (peer_hybrid) { + ret = tester.Interact(); + BOOST_REQUIRE(ret && ret->size() == 2); + if (hybrid && !test_initiator) tester.ReceiveConfirmation(); + } + BOOST_CHECK((*ret)[0] && (*ret)[0]->m_type == "inv" && std::ranges::equal((*ret)[0]->m_recv, MakeByteSpan(burst_1))); + BOOST_CHECK((*ret)[1] && (*ret)[1]->m_type == "foobar" && std::ranges::equal((*ret)[1]->m_recv, MakeByteSpan(burst_2))); + + // The transport's messages arrive under the keys the peer expects. + tester.AddMessage("barfoo", burst_2); + ret = tester.Interact(); + BOOST_REQUIRE(ret && ret->empty()); + tester.ReceiveMessage("barfoo", burst_2); + BOOST_CHECK(tester.Received().empty()); + tester.CompareSessionIDs(); + const PQStatus status{local == PQMode::OFF ? PQStatus::OFF : + local == PQMode::FALLBACK ? PQStatus::FALLBACK : + hybrid ? PQStatus::HYBRID : PQStatus::LEGACY_PEER}; + CheckPQ(transport, status); + + // Off and fallback neither generate, parse nor validate anything; a responder + // never decapsulates without an accept. + BOOST_CHECK_EQUAL(g_kem_calls.keygen, offered ? 1 : 0); + BOOST_CHECK_EQUAL(g_kem_calls.check, hybrid && test_initiator ? 1 : 0); + BOOST_CHECK_EQUAL(g_kem_calls.encaps, hybrid && test_initiator ? 1 : 0); + BOOST_CHECK_EQUAL(g_kem_calls.decaps, hybrid && !test_initiator ? 1 : 0); + } + } + } + } + + // Two transports in every combination of modes. + for (const PQMode initiator_mode : {PQMode::OFF, PQMode::NEGOTIATE, PQMode::FALLBACK}) { + for (const PQMode responder_mode : {PQMode::OFF, PQMode::NEGOTIATE}) { + BOOST_TEST_CONTEXT("initiator=" << int(initiator_mode) << " responder=" << int(responder_mode)) + { + V2Transport initiator{0, /*initiating=*/true, {.mode = initiator_mode}}; + V2Transport responder{1, /*initiating=*/false, {.mode = responder_mode}}; + ExchangeMessages(m_rng, initiator, responder, 5); + const auto initiator_info{initiator.GetInfo()}, responder_info{responder.GetInfo()}; + BOOST_CHECK(initiator_info.transport_type == TransportProtocolType::V2); + BOOST_CHECK(initiator_info.session_id && initiator_info.session_id == responder_info.session_id); + const bool hybrid{initiator_mode == PQMode::NEGOTIATE && responder_mode == PQMode::NEGOTIATE}; + const auto expected = [&](PQMode mode) { + if (mode == PQMode::OFF) return PQStatus::OFF; + if (mode == PQMode::FALLBACK) return PQStatus::FALLBACK; + return hybrid ? PQStatus::HYBRID : PQStatus::LEGACY_PEER; + }; + CheckPQ(initiator, expected(initiator_mode)); + CheckPQ(responder, expected(responder_mode)); + } + } + } + + // A responder never falls back: its FALLBACK is a caller bug (Assume), and it runs and reports + // as off. Debug builds abort on the Assume instead. + if constexpr (!G_FUZZING_BUILD && !G_ABORT_ON_FAILED_ASSUME) { + V2Transport initiator{0, /*initiating=*/true, PQ_ON}; + V2Transport responder{1, /*initiating=*/false, {.mode = PQMode::FALLBACK}}; + ExchangeMessages(m_rng, initiator, responder, 2); + CheckPQ(initiator, PQStatus::LEGACY_PEER); + CheckPQ(responder, PQStatus::OFF); + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_v1_detection) +{ + // A responder with the negotiation on detects v1 peers and peers from another network as + // before, without generating a key. + g_kem_calls = {}; + { + TestPQEntropy entropy{m_rng.rand256()}; + V2TransportTester tester(m_rng, false, PQ_ON, entropy.Source(), COUNTING_KEM_OPS); + tester.SendV1Version(Params().MessageStart()); + BOOST_CHECK(tester.Interact()); + const auto info{tester.GetTransport().GetInfo()}; + BOOST_CHECK(info.transport_type == TransportProtocolType::V1); + BOOST_CHECK(info.transport_pq_status == PQStatus::V1); + BOOST_CHECK(!info.transport_pq); + } + { + TestPQEntropy entropy{m_rng.rand256()}; + V2TransportTester tester(m_rng, false, PQ_ON, entropy.Source(), COUNTING_KEM_OPS); + tester.SendV1Version(CChainParams::Main()->MessageStart()); + BOOST_CHECK(!tester.Interact()); + } + BOOST_CHECK_EQUAL(g_kem_calls.total(), 0); + + // An initiator asks for the v1 retry exactly as before: after sending at least 24 bytes and + // before receiving anything. + for (const PQMode mode : {PQMode::OFF, PQMode::NEGOTIATE, PQMode::FALLBACK}) { + V2TransportTester tester(m_rng, true, {.mode = mode}); + auto& transport{tester.GetTransport()}; + BOOST_CHECK(!transport.ShouldReconnectV1()); + BOOST_REQUIRE(tester.Interact()); + BOOST_CHECK(transport.ShouldReconnectV1()); + tester.SendKey(); + tester.ToSend().resize(1); + BOOST_REQUIRE(tester.Deliver()); + BOOST_CHECK(!transport.ShouldReconnectV1()); + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_send_hold) +{ + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + if (test_initiator) tester.Collect(); + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Deliver()); + + // A responder's offer counts as sent once it is queued. + BOOST_CHECK(transport.GetPQSnapshot().offer == (test_initiator ? PQOfferState::NONE : PQOfferState::SENT)); + + // Holding: the terminator (initiator) or the key, garbage, terminator and offer + // (responder) is queued. Send part of it, and look at all of it. + auto [bytes, more, _type] = transport.GetBytesToSend(/*have_next_message=*/true); + BOOST_CHECK(!more); + BOOST_REQUIRE(bytes.size() > (test_initiator ? 0U : PQ_RECORD_BYTES)); + const std::vector queued{bytes.begin(), bytes.end()}; + tester.Received().insert(tester.Received().end(), queued.begin(), queued.end()); + const size_t sent{test_initiator ? 1 + m_rng.randrange(queued.size() - 1) : queued.size() - 1 - m_rng.randrange(PQ_RECORD_BYTES)}; + transport.MarkBytesSent(sent); + + // No message is taken, and nothing more will be sendable. + CSerializedNetMsg msg; + msg.m_type = "ping"; + msg.data = m_rng.randbytes(8); + const auto data{msg.data}; + BOOST_CHECK(!transport.SetMessageToSend(msg)); + BOOST_CHECK(msg.m_type == "ping" && msg.data == data); + for (const bool have_next : {false, true}) { + const auto& [rest, rest_more, _rest_type] = transport.GetBytesToSend(have_next); + BOOST_CHECK(!rest_more); + BOOST_CHECK(std::ranges::equal(rest, std::span{queued}.subspan(sent))); + } + + // The peer's version packet: the transport appends after the unsent bytes. + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + if (test_initiator) { + tester.SendOffer(); + } else { + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(); + } + BOOST_REQUIRE(tester.Deliver()); + const auto& [after, after_more, _after_type] = transport.GetBytesToSend(/*have_next_message=*/true); + BOOST_CHECK(after_more); + const size_t appended{(test_initiator ? BIP324Cipher::EXPANSION + PQ_RECORD_BYTES : 0) + PQ_CONFIRMATION_BYTES}; + BOOST_REQUIRE_EQUAL(after.size(), queued.size() - sent + appended); + BOOST_CHECK(std::ranges::equal(after.first(queued.size() - sent), std::span{queued}.subspan(sent))); + tester.Received().insert(tester.Received().end(), after.end() - appended, after.end()); + transport.MarkBytesSent(after.size()); + if (test_initiator) { + tester.ReceiveGarbage(); + tester.ReceiveAcceptAndSwitch(); + } + tester.ReceiveConfirmation(); + + // Now the held message goes out under the hybrid keys. + BOOST_CHECK(transport.SetMessageToSend(msg)); + tester.Collect(); + tester.ReceiveMessage(uint8_t(18), data); // ping short id + } + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_switch_boundary) +{ + for (const bool test_initiator : {true, false}) { + for (const bool byte_by_byte : {false, true}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator << " byte_by_byte=" << byte_by_byte) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + const auto payload{m_rng.randbytes(m_rng.randrange(1000))}; + if (test_initiator) { + // The offer (ECDH keys), then confirmation and message (hybrid keys). + tester.Collect(); + tester.SendKey(); + tester.SendGarbage(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.SendOffer(); + BOOST_REQUIRE(tester.Deliver(byte_by_byte)); + tester.Collect(); + tester.ReceiveGarbage(); + tester.ReceiveAcceptAndSwitch(); + tester.ReceiveConfirmation(); + } else { + // The accept (ECDH keys), confirmation and message (hybrid keys), coalesced. + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Deliver(byte_by_byte)); + tester.Collect(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(); + } + tester.SendConfirmation(); + const size_t before_message{tester.ToSend().size()}; + tester.SendMessage(uint8_t(14), payload); // inv short id + std::vector message{tester.ToSend().begin() + before_message, tester.ToSend().end()}; + tester.ToSend().resize(before_message); + + // Up to the end of the confirmation: confirmed, and no message. + BOOST_REQUIRE(tester.Deliver(byte_by_byte)); + BOOST_CHECK(!transport.ReceivedMessageComplete()); + CheckPQ(transport, PQStatus::HYBRID); + if (!test_initiator) { + tester.Collect(); + tester.ReceiveConfirmation(); + } + + // Then the message, under the hybrid keys. + tester.Send(message); + BOOST_REQUIRE(tester.Deliver(byte_by_byte)); + BOOST_REQUIRE(transport.ReceivedMessageComplete()); + bool reject{false}; + const CNetMessage msg{transport.GetReceivedMessage({}, reject)}; + BOOST_CHECK(!reject && msg.m_type == "inv" && std::ranges::equal(msg.m_recv, MakeByteSpan(payload))); + BOOST_CHECK(!transport.ReceivedMessageComplete()); + tester.CompareSessionIDs(); + } + } + } + + // All at once: the accept, the confirmation and a message coalesced in one delivery. + { + V2TransportTester tester(m_rng, false, PQ_ON); + auto& transport{tester.GetTransport()}; + const auto payload{m_rng.randbytes(100)}; + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Deliver()); + tester.Collect(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(); + tester.SendConfirmation(); + tester.SendMessage(uint8_t(14), payload); + BOOST_REQUIRE(tester.Deliver()); + BOOST_REQUIRE(transport.ReceivedMessageComplete()); + bool reject{false}; + const CNetMessage msg{transport.GetReceivedMessage({}, reject)}; + BOOST_CHECK(!reject && msg.m_type == "inv" && std::ranges::equal(msg.m_recv, MakeByteSpan(payload))); + CheckPQ(transport, PQStatus::HYBRID); + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_confirmation_length) +{ + for (const bool test_initiator : {true, false}) { + for (const size_t length : {size_t{1}, size_t{17}, size_t{4'000'014}}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator << " length=" << length) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + HybridHandshake(tester, test_initiator, /*send_confirmation=*/false); + // An authenticated decoy with a nonzero length, of which only the length arrives. + tester.SendPacket(std::vector(length), /*aad=*/{}, /*ignore=*/true); + tester.ToSend().resize(BIP324Cipher::LENGTH_LEN); + BOOST_CHECK(!tester.Deliver(/*byte_by_byte=*/true)); + BOOST_CHECK(tester.ToSend().empty()); + CheckPQ(transport, PQStatus::PENDING, PQFailure::CONFIRM_LENGTH); + BOOST_CHECK(!transport.ShouldReconnectV1()); + } + } + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_confirmation_tag) +{ + for (const bool test_initiator : {true, false}) { + for (const bool damage_header : {false, true}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator << " damage_header=" << damage_header) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + HybridHandshake(tester, test_initiator, /*send_confirmation=*/false); + tester.SendConfirmation(); + BOOST_REQUIRE_EQUAL(tester.ToSend().size(), PQ_CONFIRMATION_BYTES); + tester.ToSend()[damage_header ? BIP324Cipher::LENGTH_LEN : PQ_CONFIRMATION_BYTES - 1] ^= 1 << m_rng.randrange(8); + // Nothing fails before byte 20. + std::vector last{tester.ToSend().back()}; + tester.ToSend().pop_back(); + BOOST_REQUIRE(tester.Deliver(/*byte_by_byte=*/true)); + CheckPQ(transport, PQStatus::PENDING); + tester.Send(last); + BOOST_CHECK(!tester.Deliver()); + CheckPQ(transport, PQStatus::PENDING, PQFailure::CONFIRM_TAG); + } + } + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_confirmation_not_decoy) +{ + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + HybridHandshake(tester, test_initiator, /*send_confirmation=*/false); + // An authenticated empty packet without the ignore bit (an empty application message). + tester.SendPacket(/*content=*/{}, /*aad=*/{}, /*ignore=*/false); + BOOST_CHECK(!tester.Deliver(/*byte_by_byte=*/true)); + BOOST_CHECK(tester.ToSend().empty()); + BOOST_CHECK(!transport.ReceivedMessageComplete()); + CheckPQ(transport, PQStatus::PENDING, PQFailure::CONFIRM_NOT_DECOY); + } + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_corrupt_ct) +{ + // A ciphertext damaged in transit, of the right length: the responder switches to different + // keys, and the first packet under them fails. No continuation with ECDH keys. + { + V2TransportTester tester(m_rng, false, PQ_ON); + auto& transport{tester.GetTransport()}; + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(/*damage_ct=*/true); + tester.SendConfirmation(); + BOOST_CHECK(!tester.Interact()); + const auto snapshot{transport.GetPQSnapshot()}; + BOOST_CHECK(snapshot.switched && !snapshot.confirmed); + BOOST_CHECK(snapshot.failure == PQFailure::CONFIRM_LENGTH || snapshot.failure == PQFailure::CONFIRM_TAG); + BOOST_CHECK(transport.GetInfo().transport_pq_status == PQStatus::PENDING); + } + + // The local fault injection: the transport inverts byte 0 of its ML-KEM secret. Each side + // rejects the other's confirmation, and an initiator does not retry with v1. + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator) + { + V2TransportTester tester(m_rng, test_initiator, {.mode = PQMode::NEGOTIATE, .corrupt_shared_secret = true}); + auto& transport{tester.GetTransport()}; + if (test_initiator) { + BOOST_REQUIRE(tester.Interact()); + tester.SendKey(); + tester.SendGarbage(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.SendOffer(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveGarbage(); + tester.ReceiveAcceptAndSwitch(); + } else { + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(); + BOOST_REQUIRE(tester.Interact()); + } + // The transport's confirmation does not verify under our honest keys. + BOOST_REQUIRE_EQUAL(tester.Received().size(), PQ_CONFIRMATION_BYTES); + const auto packet{MakeByteSpan(tester.Received())}; + bool ignore{false}; + BOOST_CHECK(tester.GetCipher().DecryptLength(packet.first(BIP324Cipher::LENGTH_LEN)) != 0 || + !tester.GetCipher().Decrypt(packet.subspan(BIP324Cipher::LENGTH_LEN), {}, ignore, {})); + tester.SendConfirmation(); + BOOST_CHECK(!tester.Interact()); + const auto snapshot{transport.GetPQSnapshot()}; + BOOST_CHECK(snapshot.switched && !snapshot.confirmed); + BOOST_CHECK(snapshot.failure == PQFailure::CONFIRM_LENGTH || snapshot.failure == PQFailure::CONFIRM_TAG); + BOOST_CHECK(!transport.ShouldReconnectV1()); + } + } + + // The injected fault is exactly byte 0 of the ML-KEM secret inverted, EK and CT untouched: a + // peer that inverts the same byte derives the transport's keys. pq_transport_vectors.json + // models the same change in a negative vector. + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator) + { + V2TransportTester tester(m_rng, test_initiator, {.mode = PQMode::NEGOTIATE, .corrupt_shared_secret = true}); + auto& transport{tester.GetTransport()}; + if (test_initiator) { + BOOST_REQUIRE(tester.Interact()); + tester.SendKey(); + tester.SendGarbage(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.SendOffer(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveGarbage(); + tester.ReceiveAcceptAndSwitch(/*invert_ss=*/true); + } else { + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(/*damage_ct=*/false, /*invert_ss=*/true); + BOOST_REQUIRE(tester.Interact()); + } + tester.ReceiveConfirmation(); + tester.SendConfirmation(); + BOOST_REQUIRE(tester.Interact()); + CheckPQ(transport, PQStatus::HYBRID); + tester.CompareSessionIDs(); + } + } + UniValue doc; + BOOST_REQUIRE(doc.read(json_tests::pq_transport_vectors)); + int modeled{0}; + for (const UniValue& neg : doc["negative_vectors"].getValues()) { + const UniValue& vec{doc["vectors"][neg["vector"].getInt()]}; + auto ss{ParseHex(vec["ss_mlkem"].get_str())}; + ss[0] ^= 0xff; + const UniValue& contents{neg["transcript_contents"]}; + if (HexStr(ss) == neg["ss_mlkem"].get_str() && contents[0].get_str() == vec["contents_responder"].get_str() && + contents[1].get_str() == vec["contents_initiator"].get_str()) { + ++modeled; + } + } + BOOST_CHECK_EQUAL(modeled, 1); +} + +BOOST_AUTO_TEST_CASE(v2_pq_internal_errors) +{ + using mlkem::InjectResultForTesting; + using mlkem::Operation; + namespace upstream = mlkem::upstream; + + // Failures before anything is committed: an empty version packet, and plain v2 continues. + for (const auto& [op, failure] : {std::pair{Operation::KEYGEN, PQFailure::KEYGEN_INTERNAL}, + std::pair{Operation::CHECK_PUBLIC_KEY, PQFailure::CHECK_EK_INTERNAL}, + std::pair{Operation::ENCAPS, PQFailure::ENCAPS_INTERNAL}}) { + const bool test_initiator{op != Operation::KEYGEN}; + BOOST_TEST_CONTEXT("failure=" << int(failure)) + { + g_kem_calls = {}; + TestPQEntropy entropy{m_rng.rand256()}; + V2TransportTester tester(m_rng, test_initiator, PQ_ON, entropy.Source(), COUNTING_KEM_OPS); + auto& transport{tester.GetTransport()}; + if (test_initiator) { + BOOST_REQUIRE(tester.Interact()); + tester.SendKey(); + tester.SendGarbage(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.SendOffer(); + InjectResultForTesting inject{op, upstream::ERR_FAIL}; + BOOST_REQUIRE(tester.Interact()); + } else { + tester.SendKey(); + tester.SendGarbage(); + { + InjectResultForTesting inject{op, upstream::ERR_FAIL}; + BOOST_REQUIRE(tester.Interact()); + } + // No offer was queued. + BOOST_CHECK(transport.GetPQSnapshot().offer == PQOfferState::NONE); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + } + tester.ReceiveGarbage(); + BOOST_CHECK(tester.ReceiveVersionContents().empty()); + tester.GetCipher().DiscardHybridSecret(); + if (!test_initiator) { + // An unsolicited own record (an accept to an offer never made) is ignored and + // never decapsulated. + tester.SendVersion(MakeUCharSpan(MakeRecord(PQ_MLKEM1024, MakeByteSpan(m_rng.randbytes(mlkem::CIPHERTEXT_BYTES))))); + } + const auto payload{m_rng.randbytes(100)}; + tester.SendMessage(uint8_t(14), payload); + tester.AddMessage("barfoo", payload); + const auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->size() == 1); + BOOST_CHECK((*ret)[0] && (*ret)[0]->m_type == "inv" && std::ranges::equal((*ret)[0]->m_recv, MakeByteSpan(payload))); + tester.ReceiveMessage("barfoo", payload); + tester.CompareSessionIDs(); + CheckPQ(transport, PQStatus::OFF, failure); + BOOST_CHECK(!transport.GetPQSnapshot().switched); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); + BOOST_CHECK_EQUAL(g_kem_calls.decaps, 0); + BOOST_CHECK_EQUAL(g_kem_calls.keygen, test_initiator ? 0 : 1); + } + } + + // Decapsulation fails after the initiator committed to its accept: close. There is no failed + // status, so the closing connection reports pending. + { + V2TransportTester tester(m_rng, false, PQ_ON); + auto& transport{tester.GetTransport()}; + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(); + tester.SendConfirmation(); + InjectResultForTesting inject{Operation::DECAPS, upstream::ERR_FAIL}; + BOOST_CHECK(!tester.Interact()); + const auto snapshot{transport.GetPQSnapshot()}; + BOOST_CHECK(snapshot.version_received && !snapshot.switched); + CheckPQ(transport, PQStatus::PENDING, PQFailure::DECAPS_INTERNAL); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); + } + + // An inconsistent cipher state (a local bug, behind Assume) closes in both roles, also before + // the initiator committed to anything. Debug builds abort on the Assume instead. + if constexpr (!G_FUZZING_BUILD && !G_ABORT_ON_FAILED_ASSUME) { + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("cipher_state test_initiator=" << test_initiator) + { + g_kem_calls = {}; + TestPQEntropy entropy{m_rng.rand256()}; + V2TransportTester tester(m_rng, test_initiator, PQ_ON, entropy.Source(), COUNTING_KEM_OPS); + auto& transport{tester.GetTransport()}; + if (test_initiator) tester.Collect(); + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Deliver()); + tester.Collect(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + transport.DiscardHybridSecretForTesting(); + if (test_initiator) { + tester.SendOffer(); + } else { + tester.ReceiveGarbage(); + tester.AcceptOfferAndSwitch(); + } + BOOST_CHECK(!tester.Deliver()); + // Nothing more is queued: no version packet from the initiator, no confirmation. + BOOST_CHECK(std::get<0>(transport.GetBytesToSend(false)).empty()); + BOOST_CHECK(!transport.GetPQSnapshot().switched); + CheckPQ(transport, PQStatus::PENDING, PQFailure::CIPHER_STATE_INTERNAL); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); + BOOST_CHECK_EQUAL(g_kem_calls.check + g_kem_calls.encaps + g_kem_calls.decaps, 0); + } + } + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_malformed_record) +{ + // The first own record of the peer's version packet has the wrong length, or (offer only) a + // key that fails the modulus check: close with that failure, wipe both secrets, and queue + // nothing, without any encapsulation or decapsulation. + const auto valid_offer{PQHandshake::SerializeRecord(TestEncapsulationKey())}; + const auto bad_payload = [&](size_t size) { return MakeRecord(PQ_MLKEM1024, MakeByteSpan(m_rng.randbytes(size))); }; + struct Case { + std::string name; + bool test_initiator; + std::vector contents; + PQFailure failure; + }; + std::vector cases{ + {"ek 1567", true, bad_payload(mlkem::PUBLIC_KEY_BYTES - 1), PQFailure::EK_LENGTH}, + {"ek 1569", true, bad_payload(mlkem::PUBLIC_KEY_BYTES + 1), PQFailure::EK_LENGTH}, + {"ek modulus", true, MakeRecord(PQ_MLKEM1024, std::vector(mlkem::PUBLIC_KEY_BYTES, std::byte{0xff})), PQFailure::EK_MODULUS}, + {"malformed first, valid second", true, Concat({bad_payload(2), valid_offer}), PQFailure::EK_LENGTH}, + }; + for (const size_t size : {size_t{0}, mlkem::CIPHERTEXT_BYTES - 1, mlkem::CIPHERTEXT_BYTES + 1, size_t{3000}}) { + cases.push_back({strprintf("ct %u", size), false, bad_payload(size), PQFailure::CT_LENGTH}); + } + for (const auto& c : cases) { + BOOST_TEST_CONTEXT(c.name) + { + g_kem_calls = {}; + TestPQEntropy entropy{m_rng.rand256()}; + V2TransportTester tester(m_rng, c.test_initiator, PQ_ON, entropy.Source(), COUNTING_KEM_OPS); + auto& transport{tester.GetTransport()}; + if (c.test_initiator) tester.Collect(); + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Deliver()); + // Everything queued so far: the key, garbage and terminator, and a responder's offer. + tester.Collect(); + BOOST_CHECK(transport.HoldsHybridSecretsForTesting()); + tester.ReceiveKey(); + tester.SendGarbageTerm(); + if (!c.test_initiator) { + tester.ReceiveGarbage(); + BOOST_CHECK(PQHandshake::ParseContents(MakeByteSpan(tester.ReceiveVersionContents())).kind == PQHandshake::ParseKind::OWN_RECORD); + } + tester.SendVersion(MakeUCharSpan(c.contents)); + BOOST_CHECK(!tester.Deliver()); + BOOST_CHECK(std::get<0>(transport.GetBytesToSend(false)).empty()); + const auto snapshot{transport.GetPQSnapshot()}; + BOOST_CHECK(snapshot.version_received && !snapshot.switched); + CheckPQ(transport, PQStatus::PENDING, c.failure); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); + BOOST_CHECK_EQUAL(g_kem_calls.check, c.failure == PQFailure::EK_MODULUS ? 1 : 0); + BOOST_CHECK_EQUAL(g_kem_calls.encaps + g_kem_calls.decaps, 0); + BOOST_CHECK_EQUAL(g_kem_calls.keygen, c.test_initiator ? 0 : 1); + } + } +} + +BOOST_AUTO_TEST_CASE(v2_pq_rekey) +{ + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + // 222 decoys before our version packet: the transport's ECDH receive counter is at 223, + // one packet before a rekey, when it switches. + constexpr int DECOYS{BIP324Cipher::REKEY_INTERVAL - 2}; + if (test_initiator) { + BOOST_REQUIRE(tester.Interact()); + tester.SendKey(); + tester.SendGarbage(); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + for (int i = 0; i < DECOYS; ++i) tester.SendVersion(m_rng.randbytes(m_rng.randrange(10)), /*vers_ignore=*/true); + tester.SendOffer(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveGarbage(); + tester.ReceiveAcceptAndSwitch(); + } else { + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Interact()); + tester.ReceiveKey(/*retain_for_hybrid=*/true); + tester.SendGarbageTerm(); + tester.ReceiveGarbage(); + for (int i = 0; i < DECOYS; ++i) tester.SendVersion(m_rng.randbytes(m_rng.randrange(10)), /*vers_ignore=*/true); + tester.AcceptOfferAndSwitch(); + BOOST_REQUIRE(tester.Interact()); + } + tester.ReceiveConfirmation(); + tester.SendConfirmation(); + + // More than two rekey intervals of hybrid packets each way. + constexpr int MESSAGES{2 * BIP324Cipher::REKEY_INTERVAL + 2}; + std::vector> payloads; + for (int i = 0; i < MESSAGES; ++i) { + payloads.push_back(m_rng.randbytes(m_rng.randrange(100))); + tester.SendMessage(uint8_t(14), payloads.back()); + tester.AddMessage("barfoo", payloads.back()); + } + const auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->size() == MESSAGES); + for (int i = 0; i < MESSAGES; ++i) { + BOOST_CHECK((*ret)[i] && (*ret)[i]->m_type == "inv" && std::ranges::equal((*ret)[i]->m_recv, MakeByteSpan(payloads[i]))); + tester.ReceiveMessage("barfoo", payloads[i]); + } + CheckPQ(transport, PQStatus::HYBRID); + tester.CompareSessionIDs(); + } + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/fuzz/qbit_corpora/MANIFEST.json b/test/fuzz/qbit_corpora/MANIFEST.json index 772ebb2a53..ec6acbdfd0 100644 --- a/test/fuzz/qbit_corpora/MANIFEST.json +++ b/test/fuzz/qbit_corpora/MANIFEST.json @@ -415,6 +415,129 @@ "semantics": "Tagged generation selector 0x0F followed by the legacy-checksigpqc-default-valid layout." } ], + "p2p_transport_bidirectional_v1v2": [ + { + "file": "v1-version-to-negotiating-responder", + "format": "fdp", + "size": 119, + "sha256": "35e6f63d5ac205b1e4d48389eeea77e0d1e55d798fb7a156233754548fca1a19", + "semantics": "A v1 initiator's version message reaches a responder with the negotiation on: v1 detected, no key generated." + }, + { + "file": "v1-version-to-off-responder", + "format": "fdp", + "size": 183, + "sha256": "19e798b8d20825d67726f8067d7283dddb834821e7ae0e7fd4940f790ac43ad2", + "semantics": "A v1 initiator's version message reaches a responder with the negotiation off: v1 detected." + }, + { + "file": "v1-version-to-negotiating-responder-max-garbage", + "format": "fdp", + "size": 119, + "sha256": "69c2c84bdbe6b60ec78bab5775ce5e65ff6eab9529aee686530b93625438bbc7", + "semantics": "A v1 initiator's version message reaches a negotiating responder configured with 4,095 bytes of garbage, which it never sends: v1 detected." + }, + { + "file": "v1-silent-negotiating-responder", + "format": "fdp", + "size": 114, + "sha256": "28ff6ed50c2b9700aaee84e470ec094fc48d2698e55cc4282cda42dd9d12530c", + "semantics": "A v1 initiator that sends nothing: the negotiating responder stays undecided and sends nothing." + } + ], + "p2p_transport_bidirectional_v2": [ + { + "file": "negotiate-both", + "format": "fdp", + "size": 214, + "sha256": "513f1a34d1c2e612ef36e2d5c34f8c48776acff59ad10169392b114925bf0581", + "semantics": "Both sides negotiate, no garbage; the final flush completes the handshake: hybrid keys, confirmed both ways." + }, + { + "file": "negotiate-both-max-garbage", + "format": "fdp", + "size": 214, + "sha256": "d49337112b58a48f382feb53e798c218a9bd7d6459d0f83054c0af71924a3b79", + "semantics": "Both sides negotiate with 4,095 bytes of garbage each, so the held version packet's AAD is the longest; the final flush completes the handshake." + }, + { + "file": "negotiate-both-interleaved", + "format": "fdp", + "size": 343, + "sha256": "8b793b1259c0a61cf02a467c4302840314635f52e593f5f920976992d8dfc9ef", + "semantics": "Both sides negotiate, 64 and 17 bytes of garbage; a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake: hybrid." + }, + { + "file": "negotiate-both-interleaved-long-garbage", + "format": "fdp", + "size": 326, + "sha256": "978e1df57d71dc44cca090e6fe25415e2e1f4e5050cacfc16610bce1d484875a", + "semantics": "Both sides negotiate, 1,000 and 64 bytes of garbage; a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake: hybrid." + }, + { + "file": "initiator-negotiates-responder-off", + "format": "fdp", + "size": 214, + "sha256": "9306059152bd46f04435b2b6dbe8bafe816102d4810cb2927a8d8af6f249fa83", + "semantics": "The initiator holds its version packet for an empty one; the final flush completes the handshake: legacy_peer, ECDH keys." + }, + { + "file": "initiator-negotiates-responder-off-interleaved", + "format": "fdp", + "size": 331, + "sha256": "bd1fbf465f64843526c618a09ed968336afb0ef65d95add1b2bf088fc0a75f2a", + "semantics": "The initiator negotiates, the responder is off; a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake: ECDH keys." + }, + { + "file": "initiator-off-responder-offers", + "format": "fdp", + "size": 214, + "sha256": "0e8c0e8cc82faf65135f7a440a25f65fcfffbfdc11b01ab35573ec3d35695f55", + "semantics": "The responder offers to an initiator that is off and ignores it; the final flush completes the handshake: ECDH keys." + }, + { + "file": "initiator-off-responder-offers-interleaved", + "format": "fdp", + "size": 359, + "sha256": "3b68416cfaec3415673d47174ec0512b788cbe3062109708336b8ade9929e445", + "semantics": "The responder offers to an initiator that is off; a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake: ECDH keys." + }, + { + "file": "initiator-fallback-responder-offers", + "format": "fdp", + "size": 214, + "sha256": "e1cd4020339b10060518069f2434f25dea38db0fe8cfa0e80885a946d32d7461", + "semantics": "An initiator in fallback sends plain v2 and never parses the offer; the final flush completes the handshake: ECDH keys." + }, + { + "file": "both-off", + "format": "fdp", + "size": 214, + "sha256": "3ad084a8e2f8494a595d9c44ebd9b72db47efda77c76d531c197ca6d686334f0", + "semantics": "Today's v2 on both sides; the final flush completes the handshake." + }, + { + "file": "both-off-interleaved", + "format": "fdp", + "size": 390, + "sha256": "9378972edb1ddf9b65d7ae33ec515901e11aa937ec1ebd355c4834d96dd6192a", + "semantics": "Today's v2 on both sides; a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake." + }, + { + "file": "initiator-fallback-responder-off", + "format": "fdp", + "size": 219, + "sha256": "f315c712be3bad0f7bf8eb9484ffa876675bc4bd90932dac84b13ac051e746ad", + "semantics": "Plain v2 on both sides; the final flush completes the handshake." + }, + { + "file": "initiator-fallback-responder-offers-interleaved", + "format": "fdp", + "size": 343, + "sha256": "cec2460db80a9d17fcd35bb12215b7517034cf9d732b0e328f6a6e2026fe7aff", + "semantics": "An initiator in fallback never parses the offer; a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake: ECDH keys." + } + ], "pq_records": [ { "file": "offer", diff --git a/test/fuzz/qbit_corpora/README.md b/test/fuzz/qbit_corpora/README.md index 1c3eb778d4..596101201e 100644 --- a/test/fuzz/qbit_corpora/README.md +++ b/test/fuzz/qbit_corpora/README.md @@ -1,7 +1,8 @@ # qbit fuzz seed corpora Flat binary seed inputs for the qbit-specific fuzz targets that have no -upstream `qa-assets` corpus: +upstream `qa-assets` corpus, and for the v2 transport targets, whose +`qa-assets` inputs predate the hybrid negotiation: | Target | Directory | | --- | --- | @@ -12,6 +13,8 @@ upstream `qa-assets` corpus: | `mlkem` | [`mlkem/`](mlkem) | | `mlkem_backend_diff` | [`mlkem_backend_diff/`](mlkem_backend_diff) | | `p2mr_script` | [`p2mr_script/`](p2mr_script) | +| `p2p_transport_bidirectional_v1v2` | [`p2p_transport_bidirectional_v1v2/`](p2p_transport_bidirectional_v1v2) | +| `p2p_transport_bidirectional_v2` | [`p2p_transport_bidirectional_v2/`](p2p_transport_bidirectional_v2) | | `pq_records` | [`pq_records/`](pq_records) | | `pqc` | [`pqc/`](pqc) | @@ -70,6 +73,16 @@ of the wrong length, duplicate and unknown records, and framing errors that make the whole contents invalid. The harness checks the parser against an independent reading of the grammar. +`p2p_transport_bidirectional_v2` and `p2p_transport_bidirectional_v1v2` use +their harness's untagged layout: each v2 side's key, garbage and ellswift +entropy, then its hybrid negotiation mode (off, negotiate or fallback for an +initiator; off or negotiate for a responder, which never falls back) and the +32-byte seed of its ML-KEM entropy, then the simulation steps. The seeds cover +every pair of modes and the v1 detection; some end before the simulation loop, +so the final flush completes the handshake, and some add a fixed pattern that +drives the loop first. The v2 harness checks that the keys are hybrid on both +sides exactly when both negotiate. + ## Use in CI [`overlay.py`](overlay.py) copies each seed to @@ -83,6 +96,6 @@ from a reused corpus by hand if they are no longer wanted. A target path that is not a directory, or a symlink, directory or other non-regular file at a managed name, is an error; all target paths and managed names are checked before any file is written. -`test/fuzz/test_runner.py --require_qbit_corpus` then fails unless all nine +`test/fuzz/test_runner.py --require_qbit_corpus` then fails unless all eleven targets are selected, each has at least one regular input file, and the fuzz binary reports replaying them. diff --git a/test/fuzz/qbit_corpora/generate_seeds.py b/test/fuzz/qbit_corpora/generate_seeds.py index b86e2383dc..cf37a338aa 100755 --- a/test/fuzz/qbit_corpora/generate_seeds.py +++ b/test/fuzz/qbit_corpora/generate_seeds.py @@ -712,6 +712,113 @@ def pq_records_cases() -> list[dict]: ] +# --- p2p_transport_bidirectional_v2, p2p_transport_bidirectional_v1v2 --------- + +SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +V2_MAX_GARBAGE_LEN = 4095 +PQ_MODES = {"off": 0, "negotiate": 1, "fallback": 2} +# A responder never falls back, so the harness picks its mode from the first two only. +PQ_RESPONDER_MODES = 2 +SIMULATION_MAX_MESSAGE = 75000 +SIMULATION_ACTIONS = 6 + + +def transport_key(label: str) -> bytes: + key = hashlib.sha256(f"qbit fuzz seed key/{label}".encode()).digest() + assert 0 < int.from_bytes(key, "big") < SECP256K1_ORDER + return key + + +def v2_transport_side(enc: FdpEncoder, label: str, garbage_len: int, mode: str, initiator: bool) -> None: + """MakeV2Transport: key, compression flag, garbage, ellswift entropy, PQ mode and ML-KEM seed.""" + enc.raw(transport_key(label)) + enc.boolean(True) + enc.integral(garbage_len, SIZE_T, 0, V2_MAX_GARBAGE_LEN) + if garbage_len <= 64: + enc.raw(pattern(garbage_len, 0x31)) + enc.raw(hashlib.sha256(f"qbit fuzz seed ent/{label}".encode()).digest()) + if not initiator and PQ_MODES[mode] >= PQ_RESPONDER_MODES: + raise ValueError(f"{label}: a responder cannot be in {mode} mode") + enc.pick(PQ_MODES[mode], len(PQ_MODES) if initiator else PQ_RESPONDER_MODES) + enc.raw(hashlib.sha256(f"qbit fuzz seed mlkem/{label}".encode()).digest()) + + +def transport_v2_input(name: str, initiator: tuple[int, str], responder: tuple[int, str], tail: int) -> bytes: + enc = FdpEncoder() + enc.integral(int.from_bytes(hashlib.sha256(name.encode()).digest()[:8], "big"), UINT64) + v2_transport_side(enc, f"{name}/initiator", *initiator, initiator=True) + v2_transport_side(enc, f"{name}/responder", *responder, initiator=False) + # Both sides' first message ("version") sizes. + enc.integral(1000, UINT32, 0, SIMULATION_MAX_MESSAGE) + enc.integral(200, UINT32, 0, SIMULATION_MAX_MESSAGE) + # Further bytes drive the interleaving loop from the back; the final flush completes the rest. + enc.raw(pattern(tail, 0x47)) + return enc.build() + + +def transport_v2_cases() -> list[dict]: + flush = "the final flush completes the handshake" + loop = "a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake" + cases = [ + ("negotiate-both", (0, "negotiate"), (0, "negotiate"), 0, + f"Both sides negotiate, no garbage; {flush}: hybrid keys, confirmed both ways."), + ("negotiate-both-max-garbage", (V2_MAX_GARBAGE_LEN, "negotiate"), (V2_MAX_GARBAGE_LEN, "negotiate"), 0, + f"Both sides negotiate with 4,095 bytes of garbage each, so the held version packet's AAD is the longest; {flush}."), + ("negotiate-both-interleaved", (64, "negotiate"), (17, "negotiate"), 48, + f"Both sides negotiate, 64 and 17 bytes of garbage; {loop}: hybrid."), + ("negotiate-both-interleaved-long-garbage", (1000, "negotiate"), (64, "negotiate"), 48, + f"Both sides negotiate, 1,000 and 64 bytes of garbage; {loop}: hybrid."), + ("initiator-negotiates-responder-off", (0, "negotiate"), (0, "off"), 0, + f"The initiator holds its version packet for an empty one; {flush}: legacy_peer, ECDH keys."), + ("initiator-negotiates-responder-off-interleaved", (64, "negotiate"), (5, "off"), 48, + f"The initiator negotiates, the responder is off; {loop}: ECDH keys."), + ("initiator-off-responder-offers", (0, "off"), (0, "negotiate"), 0, + f"The responder offers to an initiator that is off and ignores it; {flush}: ECDH keys."), + ("initiator-off-responder-offers-interleaved", (33, "off"), (64, "negotiate"), 48, + f"The responder offers to an initiator that is off; {loop}: ECDH keys."), + ("initiator-fallback-responder-offers", (0, "fallback"), (0, "negotiate"), 0, + f"An initiator in fallback sends plain v2 and never parses the offer; {flush}: ECDH keys."), + ("both-off", (0, "off"), (0, "off"), 0, f"Today's v2 on both sides; {flush}."), + ("both-off-interleaved", (64, "off"), (64, "off"), 48, f"Today's v2 on both sides; {loop}."), + ("initiator-fallback-responder-off", (2, "fallback"), (3, "off"), 0, f"Plain v2 on both sides; {flush}."), + ("initiator-fallback-responder-offers-interleaved", (64, "fallback"), (17, "negotiate"), 48, + f"An initiator in fallback never parses the offer; {loop}: ECDH keys."), + ] + return [case(name, "fdp", transport_v2_input(name, initiator, responder, tail), semantics) + for name, initiator, responder, tail, semantics in cases] + + +def transport_v1v2_input(name: str, responder: tuple[int, str], v1_message: bool) -> bytes: + enc = FdpEncoder() + enc.integral(int.from_bytes(hashlib.sha256(name.encode()).digest()[:8], "big"), UINT64) + v2_transport_side(enc, f"{name}/responder", *responder, initiator=False) + enc.integral(500, UINT32, 0, SIMULATION_MAX_MESSAGE) + enc.integral(300, UINT32, 0, SIMULATION_MAX_MESSAGE) + if v1_message: + # One loop step: the v1 initiator takes its version message (the next message's type index + # and size are drawn when it is queued); the final flush delivers it. + enc.pick(0, SIMULATION_ACTIONS) + enc.integral(0, UINT8) + enc.integral(100, UINT32, 0, SIMULATION_MAX_MESSAGE) + return enc.build() + + +def transport_v1v2_cases() -> list[dict]: + return [ + case("v1-version-to-negotiating-responder", "fdp", transport_v1v2_input("v1-negotiate", (0, "negotiate"), True), + "A v1 initiator's version message reaches a responder with the negotiation on: v1 detected, no key " + "generated."), + case("v1-version-to-off-responder", "fdp", transport_v1v2_input("v1-off", (64, "off"), True), + "A v1 initiator's version message reaches a responder with the negotiation off: v1 detected."), + case("v1-version-to-negotiating-responder-max-garbage", "fdp", + transport_v1v2_input("v1-negotiate-max-garbage", (V2_MAX_GARBAGE_LEN, "negotiate"), True), + "A v1 initiator's version message reaches a negotiating responder configured with 4,095 bytes of garbage, " + "which it never sends: v1 detected."), + case("v1-silent-negotiating-responder", "fdp", transport_v1v2_input("v1-silent", (0, "negotiate"), False), + "A v1 initiator that sends nothing: the negotiating responder stays undecided and sends nothing."), + ] + + def case(name: str, fmt: str, data: bytes, semantics: str) -> dict: return {"name": name, "format": fmt, "data": data, "semantics": semantics} @@ -724,6 +831,8 @@ def case(name: str, fmt: str, data: bytes, semantics: str) -> dict: "mlkem": mlkem_cases, "mlkem_backend_diff": mlkem_cases, "p2mr_script": p2mr_cases, + "p2p_transport_bidirectional_v1v2": transport_v1v2_cases, + "p2p_transport_bidirectional_v2": transport_v2_cases, "pq_records": pq_records_cases, "pqc": pqc_cases, } diff --git a/test/fuzz/qbit_corpora/overlay.py b/test/fuzz/qbit_corpora/overlay.py index 62c87b1a0a..7ca88ae8a2 100755 --- a/test/fuzz/qbit_corpora/overlay.py +++ b/test/fuzz/qbit_corpora/overlay.py @@ -32,6 +32,8 @@ "mlkem", "mlkem_backend_diff", "p2mr_script", + "p2p_transport_bidirectional_v1v2", + "p2p_transport_bidirectional_v2", "pq_records", "pqc", ) diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-silent-negotiating-responder b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-silent-negotiating-responder new file mode 100644 index 0000000000000000000000000000000000000000..afd6e5ba6947ec93e359d34c0f760c1316786294 GIT binary patch literal 114 zcmV-&0FD3UqJiOdOSwKn-=CWEFLMhAeh=tP<@G{U@5as)Igl1WR-i{Ng+Wo|;?bi` z#lz#*;`Aw5+rDYC>(Cpj6DHk|@-|8&>yqjz1UCat&ZqNJpcz?ejvFCh?^|wa)M&yi U0RZ#?009610qcUC>EC3c5-5c?N&o-= literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-version-to-negotiating-responder b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-version-to-negotiating-responder new file mode 100644 index 0000000000000000000000000000000000000000..3312cdb7dfb53c26a8e7b8e0c463b6eede2e7f8f GIT binary patch literal 119 zcmV--0EqvT2rq__Ml<0H5*Z6nI=@5*U@bf_Xr4tPtnK$M8Aqf4&D1}t;<_ z?kem|2}_|gjjL~nP#$>C1YFW;&TD}MP{53ostMrd<3 zO$+O5lPooA!l<{Ri0~(R$oR~z3~c=POcj1t)y?bMsYfc^kA|rQZi2WL>9OjP{5q~= Z00000ECB%Y0RRF24*{yiY^uuIri~BxJoEqn literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-version-to-off-responder b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v1v2/v1-version-to-off-responder new file mode 100644 index 0000000000000000000000000000000000000000..e54445aacb7901c7aba426803b6fb2a38f6c155a GIT binary patch literal 183 zcmV;o07(DHSk&LZMo}Eeb~2k5zf!wR+-8D6z*q4r;M&InI3><8ZmZ}eVxQa@R+G>T zN{7S&K6|+JGHL+BO-Wynz(hp3D#sffoy7x3h z%99LOLYR|yoQi7^)8DZmE-&We*;ha==J>gxiOerH^)%kmJ67;6lmMqM?4j*?C78Vm lyb=_lmP;}~C&WEu00000ECB%Y0RR9%00HTQE+G_1AW<4-QiK2i literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/both-off b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/both-off new file mode 100644 index 0000000000000000000000000000000000000000..41fb37c2e40159ddd880391114aecb328bf2d421 GIT binary patch literal 214 zcmV;{04e{UXi1S4p>2Q0UD#&Od4A^u6~hi&&%{)4ccgG7Q=# z+70eLkX`ZOss{iZ?~T-Th_I9Z)&kU;K8~Ovlu8b6e%ztH`x0JZ2D$IH3DLb3LQem3 zjFYSRxcdWNiE(h1k%cCxhBw_E_{mwznywi>ni_8Y!Ll>FiNJX_fQ||b{6lmm=H6|WoSPO}}ze5>Th008I%00000 Q0RR910pC#O)$_fT%=3F{rvLx| literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/both-off-interleaved b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/both-off-interleaved new file mode 100644 index 0000000000000000000000000000000000000000..2ba2427ab569368ca6195b90c5944a76ea394fb7 GIT binary patch literal 390 zcmX?h!F1d6JcGx7TV}>>I?=_^`DnSU{Wawm_1~8!Y}cJRiHR#%8p=3$6e&dN4+m?~L$mLD|AYLn3YZtup{cgRNoe=3X&Xf$~c=TM|nh(H>L z8bQM5*gJF10=d-wHMCz?XUx<>j;-ZwC78WnwczbLWif86k_uQFa&?XgUP4uQcSRW} zWy&cW3*ls;Ja0OfsbBPho)_!fi#2?9&*+zx-E(9Ua2)Fy1UEOiL~EsXllC7@B_uKVY7IwQmSSl@Q)S=4K#sp8^r*9==f)d?I0IdfNLBTSsBw1 zY1D4@>NS5%v3sb-+s>uJ5RaO)ld z%1EW-;;Bp6n8%TYSS5E@>6%{7au=nu>_ZOcUg4XR!L31SNbMILu>V5H008I%00961 Q0RjL30Tcs1XjT!DQ39=G=l}o! literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-fallback-responder-offers-interleaved b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-fallback-responder-offers-interleaved new file mode 100644 index 0000000000000000000000000000000000000000..8e9fec4edeb924c27d96a264acccdffb41b0d44e GIT binary patch literal 343 zcmV-d0jU1l5$E5mak~(4$Jr@fa4(*7^3oZMFvT zN{7S&K6|+JGHL+BO-Wynz(hp3D#sffoy7x3^ z(dM~p$gW9H`_%G`NB#1=t3fO?7)bF`V1=~r9N0ZY3|9C}OLhp)qVZudZmZ}eVxQa@ zR+G>TN{7S&!30@Fj@LS-kGCm<@hnR%K%86XeQb^WcTatE-}kgyAfqcs`a0AZMm8|* zlvIZ)?n4akjQsbV0U`qvWoS-IM})!tJ9xG7FKnsiBVe7{7gUkX3rK~+|2%oN^Du3y p=OkgC+Za`n&kRY1!vH;c$N&K70{{UL009C(00E^}(HD-m62f2MqwW9z literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-negotiates-responder-off b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/initiator-negotiates-responder-off new file mode 100644 index 0000000000000000000000000000000000000000..2f95347f744b26ba77bf824b875bfb9f86a07658 GIT binary patch literal 214 zcmV;{04e_&W3b`417ob4Cf`zb#=OL&u`mg1M3@#9vY*-UWAf24u5;^Oxj;T9!pbk|hia|T@p^o|%%e47jz=ZQahhxxX|%BAsZ57Zx(aRd<~stkUKFx+#r zooz9}yY4|o?zl`49#Xc4q@c0^Q3y*gugz6Uv9v;od(55=nK9t(nFuqbpJKkB=Jqot zBM!!)%8ZuzM@9ymazLgJ4(#>98@F=Myb2K|ME2(dOC7CN62%D_QiCDL008I%00000 Q0RaF20g4`20nh|Q+hbr~!T$?uJT*mn`X-ApB4Qa+c9CLJE`w6ZmZ}eVxQa@R+G>T zN{7S&K6|+JGHL+BO-Wynz(hp3D#sffoy7x5I z%W@i^xV~FR&wnexnKeo_8t;g{O`=L>{>xJuga*R$Ok5~?Jf4kN`xRVX(IC0d*4e3s z3aE;O*&3st35k`^^V6R;j?)!rjPFZk*YirDCrg!(ZuN9MuMmL;ufPv6ZmZ}e?}fnx zzSs51XJ85(^ed8!FYv^=o<(jZ{1Op+fJHv9S8F|>v`*y zq1HXcBm0JqJ6Wo3u!0)#bV_`4Jn>RPUNTke3b(|)l{RiEP>E?KWMWHR*S_;lV?ZVX zMe*SYB`m;7%~Lt(_zA1^bS_o>Q5OBG`#9U`)=Y(+KxfVAuA5Gr=TLv9D0JwUAf>g| zWhEdPNZIoStvFFPOW2mkIyWiR&{njp|;=_m-U)GX8Dbo8E3P*>2746}!3KRK0t6N%?Nm!^VgyHh*|s zyKY@Kjgw8O{N>}J^RLE{pab~4JB~8h*X;aeT(I_)ddk9k(h*ZH@%wZhV|J+B_1z?I z-+lJLipld5r`7Gb%3bh$y_f9j$F+AST`u3Z%VxsF_g9on3x5O@IDcElIdwL_PVV0? zS!=cKS!zGab1U_H?fb~R<={W-imjjYb5=f6PMCX3JY?cIZjaVO|7|L_eKyEl^;{)! o-ffA{N#}Vy+YU3>R-IsAc)`rT=)l0ppvb`ZoKJdkarLQX0QDBEivR!s literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both new file mode 100644 index 0000000000000000000000000000000000000000..3ea9a529f02f169c4f083ca83a1232e1ad2fb893 GIT binary patch literal 214 zcmV;{04e_t%+#1^B1s!t0_x*J()3t%3(W$?3iRE!lPq05ctsaUwU*^wvn}zyqvYBcP5ix7F2GM=7Pt|NT(`7;gfZUe{2008I%00961 Q0RaF20p0Adbj%J`XXt`v+W-In literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both-interleaved b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both-interleaved new file mode 100644 index 0000000000000000000000000000000000000000..d8e8e79fc2a237173037482913fd9708250ef466 GIT binary patch literal 343 zcmaE`@HK06YSOX)dm5gocUTLmUvZbId@sVP{z^eS$G3{xFmLq>)uj1%B*P|O;Pq-h z!f02$kgX@k^YL0SpWZwLSeVI;m_sxrg7I)l;^KpMyBIe~jrQxfT!2cx@ zA}J!D)<>Rn*g3&b>Jp3e^!zC=zQ(UqQaJgmxBE9I$9>6z@0Fk2GnHu3mCuwD=H3zynRt%dqxH~#o62pU4RTjKS4o_A nTOxGQc^=QU!wj}nCm0xBFf%X;GB7eaFfiKrwh5n$sPP8?Sl2rT;jc|2VM>woa{x&&NEyfwK6|@s}$Ex))ZmZ}eVxQa@R+G>TN{7S&K6|+J zGHL+BO-Wynz(hp3D#sffoy7x5dfLTXUYq-zT zYL?(zIkIuDO3TIPa>dz#2c%$klu;0z%4eQd5kl~dDi7hJ zNA4^~gu(thc(w8`Y^mlWV4d0*RFTdLNQJ`xJbAYBFm0;mBw?Q07*&$b3`vH=06luh Y008I%00BS%0RiX(0VXPT2APJdx0lD6rvLx| literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both-max-garbage b/test/fuzz/qbit_corpora/p2p_transport_bidirectional_v2/negotiate-both-max-garbage new file mode 100644 index 0000000000000000000000000000000000000000..6f09e28e944ad0bee00f88f50657d6b2e65f0ac4 GIT binary patch literal 214 zcmV;{04e_~2;1n`v@qA=lxZNl!2}pGe(|_8jT&Hx*dzPa;BWh2uo33CeT zeJNgX?9o>;cqXBXaX&6qK6Z_wSJV}{pz7LW21e}QR(BDyI889G_oZO=Vmf}%nHr(5 z5XG+;quwHrYFTFFHSScgguo4fMRia@6H(r(de~5c^xBlw=Aj1=ayN!$Qh2k9&E2xd z@c86APib&Ilooc18fVy=t;fO$%C)z*P1#o%F>e;%a*@NOQNHC@f^|a3008I%00I9G Q0RjIH0U`V}lN|2-n? Date: Mon, 5 Oct 2026 16:24:16 -0400 Subject: [PATCH 5/8] fuzz: add a stateful malicious-peer target for PQ negotiation Add the p2p_v2_pq_malicious_peer target (#199, G4): a peer that completes the ECDH part of BIP324 honestly, so every packet it sends authenticates, and drives the hybrid negotiation of a V2Transport from the attacker's side. The tested transport runs in either role and in each mode (a responder never falls back), built with E3's deterministic test constructor and counting ML-KEM operations. The fuzzer scripts the peer step by step: its key, terminator, decoys (also carrying own records), version packets (honest, duplicate and unknown records, trailing corruption, malformed lengths, a key that fails the modulus check, blind, damaged and unsolicited ciphertexts, an accept without the peer's own switch), confirmation variants, application packets, unauthenticated bytes, partial delivery and collection, messages for the transport to send, and a disconnect at any point. The peer reads everything the transport sends and switches to hybrid keys itself when it played its part honestly. The variant generator follows the E3 verifier's malpeer harness. Checked after every step: no assertion or hang; GetBytesToSend stays append-only and its 'more' predictions hold; refused messages stay untouched and a holding initiator takes none; nothing is delivered between the switch and the peer's confirmation; the transport sends no decoy but its confirmation, which decrypts to length 0 under the peer's keys when both switched; only the same keys confirm, with equal session ids, and an honest exchange delivered in full confirms; off and fallback make no ML-KEM call, and a responder decapsulates only an accept to its own offer; once the peer's version packet is processed, or the transport failed, no hybrid secret remains; a close under the hybrid keys before confirmation is a confirmation failure. The target joins the required qbit seed corpora with 16 generated seeds, each a script whose outcome is documented in the manifest: honest exchanges in both roles (with maximum garbage, extra records and fragmented delivery), every malformed record and confirmation failure, a damaged ciphertext, a legacy peer, a fallback initiator, an unsolicited accept and a disconnect while the offer is pending. Part of #184 (#199, G4). Co-Authored-By: Claude Opus 5.5 --- ci/test/03_test_script.sh | 2 +- ...h-ef5ec86784262f7cd94f46933e5f6760bee654b5 | Bin 0 -> 85 bytes doc/development/fuzzing.md | 11 +- src/test/fuzz/CMakeLists.txt | 1 + src/test/fuzz/p2p_v2_pq_malicious_peer.cpp | 639 ++++++++++++++++++ test/fuzz/qbit_corpora/MANIFEST.json | 114 ++++ test/fuzz/qbit_corpora/README.md | 15 +- test/fuzz/qbit_corpora/generate_seeds.py | 94 +++ test/fuzz/qbit_corpora/overlay.py | 1 + .../initiator-confirmation-length | Bin 0 -> 86 bytes .../initiator-confirmation-tag | Bin 0 -> 86 bytes .../initiator-ek-length | Bin 0 -> 83 bytes .../initiator-ek-modulus | Bin 0 -> 83 bytes .../initiator-fallback-ignores-offer | Bin 0 -> 88 bytes .../p2p_v2_pq_malicious_peer/initiator-hybrid | Bin 0 -> 90 bytes .../initiator-hybrid-extras-fragmented | Bin 0 -> 309 bytes .../initiator-noncompliant-responder | Bin 0 -> 85 bytes .../responder-confirmation-not-decoy | Bin 0 -> 86 bytes .../responder-corrupt-ct | Bin 0 -> 90 bytes .../responder-ct-length | Bin 0 -> 83 bytes .../responder-disconnect-after-offer | Bin 0 -> 81 bytes .../p2p_v2_pq_malicious_peer/responder-hybrid | Bin 0 -> 90 bytes .../responder-hybrid-extras | Bin 0 -> 97 bytes .../responder-legacy-peer-burst | Bin 0 -> 89 bytes .../responder-off-unsolicited-accept | Bin 0 -> 87 bytes test/fuzz/test_qbit_fuzz_runner.py | 3 +- test/fuzz/test_runner.py | 1 + 27 files changed, 873 insertions(+), 8 deletions(-) create mode 100644 crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 create mode 100644 src/test/fuzz/p2p_v2_pq_malicious_peer.cpp create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-confirmation-length create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-confirmation-tag create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-ek-length create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-ek-modulus create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-fallback-ignores-offer create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-hybrid create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-hybrid-extras-fragmented create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-noncompliant-responder create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-confirmation-not-decoy create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-corrupt-ct create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-ct-length create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-disconnect-after-offer create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-hybrid create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-hybrid-extras create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-legacy-peer-burst create mode 100644 test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-off-unsolicited-accept diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh index b65992a5ed..d4f2fbe36f 100755 --- a/ci/test/03_test_script.sh +++ b/ci/test/03_test_script.sh @@ -372,6 +372,6 @@ if [ "$RUN_FUZZ_TESTS" = "true" ]; then --mutate_min_time="${QBIT_FUZZ_MUTATE_MIN_TIME}" \ "${DIR_FUZZ_IN}" \ asert_chain_transition asert_edge_cases asert_math auxpow mlkem mlkem_backend_diff p2mr_script \ - p2p_transport_bidirectional_v1v2 p2p_transport_bidirectional_v2 pq_records pqc + p2p_transport_bidirectional_v1v2 p2p_transport_bidirectional_v2 p2p_v2_pq_malicious_peer pq_records pqc fi fi diff --git a/crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 b/crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 new file mode 100644 index 0000000000000000000000000000000000000000..6d959d383c44c7c3587ffaef1880e59230157834 GIT binary patch literal 85 zcmV-b0IL6)+2nmkc{lTLP0;rO^-UTsS`pn4cF(k5RsFur64D*Ng_zILrKpusWqreB r_*Hc&)Wsu<7$EPs`HpF#PW*=m0s;jC0RRY^a*p2S)>d->00003S_~(q literal 0 HcmV?d00001 diff --git a/doc/development/fuzzing.md b/doc/development/fuzzing.md index b27dbcf8c5..bd99a5eff0 100644 --- a/doc/development/fuzzing.md +++ b/doc/development/fuzzing.md @@ -41,9 +41,10 @@ $ ctest --test-dir build_fuzz -R '^required_fuzz_targets$' --output-on-failure ## qbit seed corpora `qa-assets` has no inputs for `asert_chain_transition`, `asert_edge_cases`, -`asert_math`, `auxpow`, `mlkem`, `mlkem_backend_diff`, `p2mr_script`, `pq_records` -or `pqc`, and its inputs for `p2p_transport_bidirectional_v2` and -`p2p_transport_bidirectional_v1v2` predate the hybrid v2 negotiation. +`asert_math`, `auxpow`, `mlkem`, `mlkem_backend_diff`, `p2mr_script`, +`p2p_v2_pq_malicious_peer`, `pq_records` or `pqc`, and its inputs for +`p2p_transport_bidirectional_v2` and `p2p_transport_bidirectional_v1v2` predate +the hybrid v2 negotiation. Generated seeds for these targets, their manifest and the input format are described in [`test/fuzz/qbit_corpora/README.md`](../../test/fuzz/qbit_corpora/README.md). @@ -54,7 +55,7 @@ $ test/fuzz/qbit_corpora/overlay.py qa-assets/fuzz_corpora $ build_fuzz/test/fuzz/test_runner.py --require_qbit_corpus qa-assets/fuzz_corpora ``` -`--require_qbit_corpus` fails if any of the eleven targets is not compiled or not +`--require_qbit_corpus` fails if any of the twelve targets is not compiled or not selected (including through `--exclude`), has no regular input files, or is not reported as replayed by the fuzz executable. The runner prints the number of input files present and replayed for each target. @@ -70,7 +71,7 @@ replay with a mutation phase of that many seconds per target. The phase starts from the corpus, writes new inputs to a temporary directory and leaves the corpus unchanged. It fails on non-libFuzzer builds. The nightly native fuzz job sets `QBIT_FUZZ_MUTATE_MIN_TIME`, which makes `ci/test/03_test_script.sh` run -this phase for the eleven targets after the normal replay. +this phase for the twelve targets after the normal replay. The budget is libFuzzer's `-max_total_time`, which counts time spent loading the seed corpus (libFuzzer's `INITED` line) as well as mutation. A run can end diff --git a/src/test/fuzz/CMakeLists.txt b/src/test/fuzz/CMakeLists.txt index 0f1313c238..26e6304502 100644 --- a/src/test/fuzz/CMakeLists.txt +++ b/src/test/fuzz/CMakeLists.txt @@ -79,6 +79,7 @@ add_executable(fuzz p2p_handshake.cpp p2p_headers_presync.cpp p2p_transport_serialization.cpp + p2p_v2_pq_malicious_peer.cpp p2mr_script.cpp pcp.cpp package_eval.cpp diff --git a/src/test/fuzz/p2p_v2_pq_malicious_peer.cpp b/src/test/fuzz/p2p_v2_pq_malicious_peer.cpp new file mode 100644 index 0000000000..781ef4f292 --- /dev/null +++ b/src/test/fuzz/p2p_v2_pq_malicious_peer.cpp @@ -0,0 +1,639 @@ +// Copyright (c) 2026-present The qbit core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +/** How often each ML-KEM operation ran for the current input. */ +struct KemCalls { + int keygen{0}, check{0}, encaps{0}, decaps{0}; +}; +KemCalls g_kem_calls; + +const PQKemOps COUNTING_KEM_OPS{ + .keygen = [](std::span seed, mlkem::PublicKey& ek, mlkem::DecapsulationKey& dk) noexcept { + ++g_kem_calls.keygen; + return mlkem::KeyGen(seed, ek, dk); + }, + .check_public_key = [](std::span ek) noexcept { + ++g_kem_calls.check; + return mlkem::CheckPublicKey(ek); + }, + .encaps = [](std::span ek, std::span coins, + mlkem::Ciphertext& ct, mlkem::SharedSecret& ss) noexcept { + ++g_kem_calls.encaps; + return mlkem::Encaps(ek, coins, ct, ss); + }, + .decaps = [](const mlkem::DecapsulationKey& dk, std::span ct, mlkem::SharedSecret& ss) noexcept { + ++g_kem_calls.decaps; + return mlkem::Decaps(dk, ct, ss); + }, +}; + +/** ML-KEM entropy from a generator seeded by the fuzzer input. */ +struct Entropy { + InsecureRandomContext rng; + + explicit Entropy(uint64_t seed) : rng{seed} {} + + static void Fill(void* context, std::span out) noexcept + { + static_cast(context)->rng.fillrand(std::as_writable_bytes(out)); + } + + PQRandomSource Source() { return {.fill32 = &Fill, .context = this}; } +}; + +std::vector Record(uint8_t header, std::span payload) +{ + std::vector out; + VectorWriter writer{out, 0}; + WriteCompactSize(writer, 1 + payload.size()); + out.push_back(header); + out.insert(out.end(), payload.begin(), payload.end()); + return out; +} + +void Append(std::vector& out, std::span bytes) +{ + out.insert(out.end(), bytes.begin(), bytes.end()); +} + +/** What the peer's first non-decoy packet, the one the transport takes as its version packet, was. */ +enum class PeerVersion { NONE, HONEST_OFFER, HONEST_ACCEPT, OTHER }; + +/** + * A malicious peer of a V2Transport. It completes the ECDH part honestly, so every packet it sends + * authenticates, and chooses the negotiation plaintext, packet boundaries, delivery and timing. + * The peer reads everything the transport sends, and switches to hybrid keys itself when it played + * its part of the negotiation honestly. + */ +class MaliciousPeer +{ + FuzzedDataProvider& m_provider; + InsecureRandomContext m_rng; + const bool m_test_initiator; + const PQMode m_mode; + std::vector m_transport_garbage, m_peer_garbage; + Entropy m_transport_entropy, m_peer_entropy; + V2Transport m_transport; + BIP324Cipher m_cipher; + PQHandshake m_peer_pq; + + // The peer side. + std::vector m_to_transport; //!< scheduled, not yet delivered + bool m_key_sent{false}, m_terminator_sent{false}, m_sent_aad{false}; + PeerVersion m_peer_version{PeerVersion::NONE}; + std::optional m_offer; + bool m_accepted{false}; + bool m_switched{false}; + bool m_raw_sent{false}; //!< we sent unauthenticated bytes + bool m_sent_between{false}; //!< we sent a packet after our version packet and before our switch + std::optional m_confirmation_honest; //!< whether our first hybrid packet is a valid confirmation + + // What the transport sent. + std::vector m_from_transport; //!< received, not yet processed + bool m_have_key{false}, m_garbage_read{false}, m_recv_aad_used{false}, m_lost_sync{false}; + std::optional m_recv_len; + std::optional> m_transport_version; + bool m_confirmation_read{false}; + + // The transport's state as observed. + bool m_closed{false}; + std::vector m_unsent; //!< bytes GetBytesToSend reported and were not marked sent + std::optional m_expect_more, m_expect_more_next, m_last_more, m_last_more_next; + +public: + MaliciousPeer(FuzzedDataProvider& provider, bool test_initiator, PQMode mode, const CKey& transport_key, + const CKey& peer_key, size_t transport_garbage_len, size_t peer_garbage_len, uint64_t seed) + : m_provider{provider}, + m_rng{seed}, + m_test_initiator{test_initiator}, + m_mode{mode}, + m_transport_garbage{m_rng.randbytes(transport_garbage_len)}, + m_peer_garbage{m_rng.randbytes(peer_garbage_len)}, + m_transport_entropy{m_rng.rand64()}, + m_peer_entropy{m_rng.rand64()}, + m_transport{0, test_initiator, transport_key, MakeByteSpan(m_rng.rand256()), m_transport_garbage, + V2PQOptions{.mode = mode}, m_transport_entropy.Source(), COUNTING_KEM_OPS}, + m_cipher{peer_key, MakeByteSpan(m_rng.rand256())}, + m_peer_pq{!test_initiator, PQMode::NEGOTIATE, m_peer_entropy.Source(), DefaultPQKemOps()} {} + + /** Check GetBytesToSend: stable between calls, append-only, and as predicted by 'more'. */ + Transport::BytesToSend CheckBytesToSend() + { + const auto& [bytes, more, type] = m_transport.GetBytesToSend(/*have_next_message=*/false); + const auto& [bytes_next, more_next, type_next] = m_transport.GetBytesToSend(/*have_next_message=*/true); + assert(std::ranges::equal(bytes, bytes_next)); + assert(type == type_next); + if (more) assert(more_next); + assert(m_unsent.size() <= bytes.size()); + assert(std::ranges::equal(m_unsent, bytes.first(m_unsent.size()))); + if (m_expect_more) assert(!bytes.empty() == *m_expect_more); + m_unsent.assign(bytes.begin(), bytes.end()); + m_last_more = more; + m_last_more_next = more_next; + return {bytes, more, type}; + } + + /** Take up to max bytes the transport has to send, and process them as the peer. */ + void Collect(size_t max) + { + const auto& [bytes, more, type] = CheckBytesToSend(); + const size_t n{std::min(max, bytes.size())}; + if (n == 0) return; + m_from_transport.insert(m_from_transport.end(), bytes.begin(), bytes.begin() + n); + m_transport.MarkBytesSent(n); + m_unsent.erase(m_unsent.begin(), m_unsent.begin() + n); + if (m_unsent.empty()) { + m_expect_more = m_last_more; + m_expect_more_next = m_last_more_next; + } + CheckBytesToSend(); + ReadTransportOutput(); + } + + /** Deliver up to max scheduled bytes to the transport, taking every complete message. */ + void Deliver(size_t max) + { + size_t n{std::min(max, m_to_transport.size())}; + while (n > 0 && !m_closed) { + std::span bytes{std::span{m_to_transport}.first(n)}; + const bool ok{m_transport.ReceivedBytes(bytes)}; + const size_t used{n - bytes.size()}; + m_to_transport.erase(m_to_transport.begin(), m_to_transport.begin() + used); + n -= used; + // Received bytes can make bytes to send appear. + if (m_expect_more == false) m_expect_more = std::nullopt; + if (m_expect_more_next == false) m_expect_more_next = std::nullopt; + if (!ok) { + OnClose(); + return; + } + const bool message{TakeMessages()}; + if (used == 0 && !message) break; + } + } + + bool TakeMessages() + { + bool any{false}; + while (m_transport.ReceivedMessageComplete()) { + // Nothing is delivered between the switch and the peer's confirmation. + const auto snapshot{m_transport.GetPQSnapshot()}; + assert(!snapshot.switched || snapshot.confirmed); + bool reject{false}; + (void)m_transport.GetReceivedMessage({}, reject); + any = true; + } + return any; + } + + void Flush() + { + while (true) { + const size_t before{m_to_transport.size() + m_unsent.size()}; + Deliver(m_to_transport.size()); + Collect(std::numeric_limits::max()); + if (m_to_transport.size() + m_unsent.size() == before || (m_closed && m_unsent.empty())) break; + } + } + + void OnClose() + { + m_closed = true; + // A failed transport keeps no hybrid secrets, never reports hybrid keys, and a failure + // under the hybrid keys before the peer's confirmation is a confirmation failure. + assert(!m_transport.HoldsHybridSecretsForTesting()); + const auto snapshot{m_transport.GetPQSnapshot()}; + assert(!m_transport.GetInfo().transport_pq || snapshot.confirmed); + if (snapshot.switched && !snapshot.confirmed) { + assert(snapshot.failure == PQFailure::CONFIRM_LENGTH || snapshot.failure == PQFailure::CONFIRM_TAG || + snapshot.failure == PQFailure::CONFIRM_NOT_DECOY); + } + } + + /** Packets need the ciphers, and must follow our key and garbage, or the transport would take + * them as our key. */ + bool CanSendPackets() const { return m_key_sent && m_have_key; } + + /** Schedule a packet to the transport; the first one authenticates our garbage. */ + void SendPacket(std::span contents, bool ignore, PeerVersion kind = PeerVersion::OTHER) + { + std::vector packet(contents.size() + BIP324Cipher::EXPANSION); + m_cipher.Encrypt(MakeByteSpan(contents), m_sent_aad ? std::span{} : MakeByteSpan(m_peer_garbage), ignore, + MakeWritableByteSpan(packet)); + m_sent_aad = true; + if (m_peer_version != PeerVersion::NONE && !m_switched) m_sent_between = true; + if (m_switched && !m_confirmation_honest) m_confirmation_honest = contents.empty() && ignore; + if (!ignore && m_peer_version == PeerVersion::NONE) m_peer_version = kind; + Append(m_to_transport, packet); + } + + void SendKey() + { + if (m_key_sent) return; + Append(m_to_transport, MakeUCharSpan(m_cipher.GetOurPubKey())); + Append(m_to_transport, m_peer_garbage); + m_key_sent = true; + } + + void SendTerminator() + { + if (!m_key_sent || !m_have_key || m_terminator_sent) return; + Append(m_to_transport, MakeUCharSpan(m_cipher.GetSendGarbageTerminator())); + m_terminator_sent = true; + } + + void SendDecoy(uint8_t kind) + { + if (!CanSendPackets()) return; + std::vector contents; + switch (kind) { + case 0: contents = m_rng.randbytes(m_rng.randrange(64)); break; + case 1: contents = Record(PQ_MLKEM1024, m_rng.randbytes(mlkem::CIPHERTEXT_BYTES)); break; + case 2: break; + default: contents = m_rng.randbytes(m_rng.randrange(2000)); break; + } + SendPacket(contents, /*ignore=*/true); + } + + /** The peer's version packet: an offer (the transport initiates) or an accept, honest or not. */ + void SendVersion(uint8_t variant) + { + if (!CanSendPackets()) return; + const bool first{m_peer_version == PeerVersion::NONE}; + std::vector contents; + PeerVersion kind{PeerVersion::OTHER}; + mlkem::SharedSecret ss; + bool switch_after{false}; + const auto random_record = [&](size_t size) { return Record(PQ_MLKEM1024, m_rng.randbytes(size)); }; + if (m_test_initiator) { + // We are the responder: offer variants. + const auto offer = [&]() -> std::span { + if (!m_offer) { + PQHandshake::Record record; + assert(m_peer_pq.MakeOffer(record) == mlkem::Error::NONE); + m_offer = record; + } + return *m_offer; + }; + switch (variant) { + case 0: break; + case 1: contents = m_rng.randbytes(m_rng.randrange(2000)); break; + case 2: contents = random_record(m_rng.randrange(1600)); break; + case 3: contents = Record(PQ_MLKEM1024, std::vector(mlkem::PUBLIC_KEY_BYTES, 0xff)); break; + case 4: Append(contents, MakeUCharSpan(offer())); Append(contents, std::vector{0xfd, 0x00}); break; + case 5: Append(contents, MakeUCharSpan(offer())); kind = PeerVersion::HONEST_OFFER; break; + case 6: + Append(contents, Record(0x01, m_rng.randbytes(m_rng.randrange(20)))); + Append(contents, MakeUCharSpan(offer())); + Append(contents, random_record(m_rng.randrange(20))); + kind = PeerVersion::HONEST_OFFER; + break; + case 7: Append(contents, Record(0xf1, m_rng.randbytes(m_rng.randrange(20)))); break; + case 8: contents = random_record(mlkem::PUBLIC_KEY_BYTES - 1); break; + default: + Append(contents, MakeUCharSpan(offer())); + Append(contents, random_record(mlkem::PUBLIC_KEY_BYTES)); + kind = PeerVersion::HONEST_OFFER; + break; + } + // A transport that sent its version packet before ours does not negotiate, so we + // have already dropped the transcript. + if (first && !m_transport_version) assert(m_cipher.AddVersionContents(MakeByteSpan(contents))); + } else { + // We are the initiator: accept variants, answering the transport's offer if it made one. + PQHandshake::Record accept; + bool have_accept{false}; + if (!m_accepted && m_transport_version && first) { + const auto parsed{PQHandshake::ParseContents(MakeByteSpan(*m_transport_version))}; + if (parsed.kind == PQHandshake::ParseKind::OWN_RECORD) { + const auto ek{m_peer_pq.CheckRecordLength(parsed.payload)}; + assert(ek); + assert(m_peer_pq.AcceptOffer(*ek, accept, ss) == mlkem::Error::NONE); + m_accepted = have_accept = true; + } + } + switch (variant) { + case 0: break; + case 1: contents = m_rng.randbytes(m_rng.randrange(2000)); break; + case 2: contents = random_record(m_rng.randrange(1600)); break; + case 3: contents = random_record(mlkem::CIPHERTEXT_BYTES); break; // blind + case 4: + if (have_accept) { + Append(contents, MakeUCharSpan(accept)); + contents[4 + m_rng.randrange(mlkem::CIPHERTEXT_BYTES)] ^= 1 << m_rng.randrange(8); + } + break; + case 5: + case 6: + if (have_accept) { + if (variant == 6) Append(contents, Record(0x02, m_rng.randbytes(m_rng.randrange(20)))); + Append(contents, MakeUCharSpan(accept)); + if (variant == 6) Append(contents, random_record(m_rng.randrange(20))); + kind = PeerVersion::HONEST_ACCEPT; + switch_after = true; + } + break; + case 7: + // An honest accept, but we never switch: a non-compliant peer. + if (have_accept) Append(contents, MakeUCharSpan(accept)); + break; + case 8: contents = random_record(mlkem::CIPHERTEXT_BYTES + 1); break; + default: + if (have_accept) { + Append(contents, MakeUCharSpan(accept)); + Append(contents, std::vector{0xfe, 0x00}); + } + break; + } + if (switch_after) { + assert(m_cipher.AddVersionContents(MakeByteSpan(*m_transport_version))); + assert(m_cipher.AddVersionContents(MakeByteSpan(contents))); + } + } + SendPacket(contents, /*ignore=*/false, kind); + if (switch_after && first) { + assert(m_cipher.SwitchToHybrid(std::as_bytes(ss.Bytes()))); + m_switched = true; + } + } + + void SendConfirmation(uint8_t variant) + { + if (!CanSendPackets()) return; + switch (variant) { + case 0: SendPacket({}, /*ignore=*/true); break; + case 1: SendPacket(m_rng.randbytes(1 + m_rng.randrange(30)), /*ignore=*/true); break; + case 2: SendPacket({}, /*ignore=*/false); break; + default: { + const bool first_hybrid{m_switched && !m_confirmation_honest}; + SendPacket({}, /*ignore=*/true); + if (first_hybrid) m_confirmation_honest = false; + // Damage the length (variant 3) or the header and tag (otherwise). + const size_t pos{m_to_transport.size() - BIP324Cipher::EXPANSION + + (variant == 3 ? m_rng.randrange(BIP324Cipher::LENGTH_LEN) : BIP324Cipher::LENGTH_LEN + m_rng.randrange(17))}; + m_to_transport[pos] ^= 1 << m_rng.randrange(8); + break; + } + } + } + + void SendApplication(uint8_t variant) + { + if (!CanSendPackets()) return; + switch (variant) { + case 0: SendPacket(std::vector{18, 1, 2, 3, 4, 5, 6, 7, 8}, /*ignore=*/false); break; // ping + case 1: { + std::vector contents(1 + CMessageHeader::MESSAGE_TYPE_SIZE); + std::ranges::copy(std::string_view{"foobar"}, contents.begin() + 1); + SendPacket(contents, /*ignore=*/false); + break; + } + case 2: SendPacket({}, /*ignore=*/false); break; + default: SendPacket(m_rng.randbytes(m_rng.randrange(100)), /*ignore=*/false); break; + } + } + + /** Unauthenticated bytes, after our key (bytes in place of the key would only make the ECDH + * part dishonest). */ + void SendRaw(uint8_t size) + { + if (!m_key_sent) return; + Append(m_to_transport, m_rng.randbytes(size)); + m_raw_sent = true; + } + + /** Give the transport a message to send; refused messages stay untouched. */ + void QueueMessage(uint8_t variant) + { + static const std::array TYPES{"ping", "pong", "version", "barfoo"}; + CSerializedNetMsg msg; + msg.m_type = TYPES[variant % TYPES.size()]; + msg.data = m_rng.randbytes(m_rng.randrange(200)); + const CSerializedNetMsg copy{msg.Copy()}; + const auto snapshot{m_transport.GetPQSnapshot()}; + const bool holding{m_test_initiator && m_mode == PQMode::NEGOTIATE && !snapshot.version_received}; + const bool queued{m_transport.SetMessageToSend(msg)}; + if (!queued) assert(msg.m_type == copy.m_type && msg.data == copy.data); + // An initiator holding its version packet takes no message. + if (holding) assert(!queued); + m_expect_more = m_expect_more_next; + m_expect_more_next = std::nullopt; + CheckBytesToSend(); + } + + /** Process what the transport sent: its key, garbage and terminator, then its packets. */ + void ReadTransportOutput() + { + if (!m_have_key) { + if (m_from_transport.size() < EllSwiftPubKey::size()) return; + m_cipher.Initialize(EllSwiftPubKey{MakeByteSpan(m_from_transport).first(EllSwiftPubKey::size())}, !m_test_initiator, + /*self_decrypt=*/false, /*retain_for_hybrid=*/true); + m_from_transport.erase(m_from_transport.begin(), m_from_transport.begin() + EllSwiftPubKey::size()); + m_have_key = true; + } + if (!m_garbage_read) { + const size_t len{m_transport_garbage.size() + BIP324Cipher::GARBAGE_TERMINATOR_LEN}; + if (m_from_transport.size() < len) return; + assert(std::ranges::equal(std::span{m_from_transport}.first(m_transport_garbage.size()), m_transport_garbage)); + assert(std::ranges::equal(MakeByteSpan(m_from_transport).subspan(m_transport_garbage.size(), BIP324Cipher::GARBAGE_TERMINATOR_LEN), + m_cipher.GetReceiveGarbageTerminator())); + m_from_transport.erase(m_from_transport.begin(), m_from_transport.begin() + len); + m_garbage_read = true; + } + while (!m_lost_sync) { + if (!m_recv_len) { + if (m_from_transport.size() < BIP324Cipher::LENGTH_LEN) return; + m_recv_len = m_cipher.DecryptLength(MakeByteSpan(m_from_transport).first(BIP324Cipher::LENGTH_LEN)); + // Key agreement: when both sides switched, the transport's first hybrid packet is its + // confirmation, so its length decrypts to 0 under our keys. + if (m_switched && !m_confirmation_read && m_transport.GetPQSnapshot().switched) assert(*m_recv_len == 0); + } + const size_t size{*m_recv_len + BIP324Cipher::EXPANSION}; + if (m_from_transport.size() < size) return; + std::vector contents(*m_recv_len); + bool ignore{false}; + const bool ok{m_cipher.Decrypt(MakeByteSpan(m_from_transport).subspan(BIP324Cipher::LENGTH_LEN, size - BIP324Cipher::LENGTH_LEN), + m_recv_aad_used ? std::span{} : MakeByteSpan(m_transport_garbage), ignore, + MakeWritableByteSpan(contents))}; + m_recv_aad_used = true; + m_recv_len.reset(); + m_from_transport.erase(m_from_transport.begin(), m_from_transport.begin() + size); + if (!ok) { + // Only keys that differ can fail: one side switched and the other did not. + assert(m_switched != m_transport.GetPQSnapshot().switched); + m_lost_sync = true; + return; + } + if (!m_transport_version) { + // The transport sends no decoy before its version packet. + assert(!ignore); + m_transport_version = contents; + const auto parsed{PQHandshake::ParseContents(MakeByteSpan(contents))}; + const bool own_record{parsed.kind == PQHandshake::ParseKind::OWN_RECORD}; + if (!m_test_initiator) { + // A responder offers at once exactly when it negotiates. + assert(own_record == (m_mode == PQMode::NEGOTIATE)); + } else { + // An initiator accepts exactly an honest offer, with the negotiation on. + assert(own_record == (m_mode == PQMode::NEGOTIATE && m_peer_version == PeerVersion::HONEST_OFFER)); + if (own_record) { + assert(m_cipher.AddVersionContents(MakeByteSpan(contents))); + const auto ct{m_peer_pq.CheckRecordLength(parsed.payload)}; + assert(ct); + mlkem::SharedSecret ss; + assert(m_peer_pq.DecapsulateAccept(*ct, ss) == mlkem::Error::NONE); + assert(m_cipher.SwitchToHybrid(std::as_bytes(ss.Bytes()))); + m_switched = true; + } else { + m_cipher.DiscardHybridSecret(); + } + } + } else if (m_switched && !m_confirmation_read) { + // The transport's first packet under the hybrid keys is its key confirmation. + assert(ignore && contents.empty()); + m_confirmation_read = true; + } else { + // The transport sends no other decoys. + assert(!ignore); + } + } + } + + /** Invariants that hold between any two steps. */ + void CheckState() + { + const auto info{m_transport.GetInfo()}; + if (info.transport_pq_status == PQStatus::V1) return; + const auto snapshot{m_transport.GetPQSnapshot()}; + assert(info.transport_pq == snapshot.confirmed); + if (snapshot.confirmed) { + // Only a peer with the same keys can have produced a valid confirmation. + assert(m_switched && info.transport_pq_status == PQStatus::HYBRID); + assert(info.session_id && std::ranges::equal(MakeUCharSpan(*info.session_id), MakeUCharSpan(m_cipher.GetSessionID()))); + } + if (snapshot.switched && !snapshot.confirmed) { + assert(info.transport_type == TransportProtocolType::DETECTING && !info.session_id); + assert(info.transport_pq_status == PQStatus::PENDING); + } + switch (m_mode) { + case PQMode::OFF: + case PQMode::FALLBACK: + // Neither generates, parses nor validates anything. + assert(!snapshot.switched && snapshot.offer == PQOfferState::NONE); + assert(g_kem_calls.keygen + g_kem_calls.check + g_kem_calls.encaps + g_kem_calls.decaps == 0); + assert(info.transport_pq_status == (m_mode == PQMode::OFF ? PQStatus::OFF : PQStatus::FALLBACK)); + break; + case PQMode::NEGOTIATE: + assert(g_kem_calls.keygen <= (m_test_initiator ? 0 : 1)); + assert(g_kem_calls.check + g_kem_calls.encaps <= (m_test_initiator ? 2 : 0)); + // A responder decapsulates only an accept to the offer it sent. + assert(g_kem_calls.decaps <= ((!m_test_initiator && snapshot.offer == PQOfferState::SENT) ? 1 : 0)); + break; + } + // Once the peer's version packet is processed, or the transport failed, no hybrid secret + // remains. + if (snapshot.version_received || m_closed) assert(!m_transport.HoldsHybridSecretsForTesting()); + } + + /** Run the fuzzer's script of steps, then deliver and take everything left. */ + void Run() + { + LIMITED_WHILE(m_provider.remaining_bytes(), 300) + { + bool stop{false}; + CallOneOf( + m_provider, + [&] { SendKey(); }, + [&] { SendTerminator(); }, + [&] { SendDecoy(m_provider.ConsumeIntegralInRange(0, 3)); }, + [&] { SendVersion(m_provider.ConsumeIntegralInRange(0, 9)); }, + [&] { SendConfirmation(m_provider.ConsumeIntegralInRange(0, 4)); }, + [&] { SendApplication(m_provider.ConsumeIntegralInRange(0, 3)); }, + [&] { Deliver(m_provider.ConsumeIntegralInRange(1, 1 << 16)); }, + [&] { Collect(m_provider.ConsumeIntegralInRange(1, 1 << 16)); }, + [&] { Flush(); }, + [&] { QueueMessage(m_provider.ConsumeIntegralInRange(0, 3)); }, + [&] { SendRaw(m_provider.ConsumeIntegralInRange(1, 64)); }, + // The peer disconnects. + [&] { stop = true; }); + CheckState(); + if (stop) return; + } + Flush(); + CheckState(); + // An honest exchange with a valid confirmation, delivered in full, confirms (later damaged + // packets may still close the connection). + const auto snapshot{m_transport.GetPQSnapshot()}; + if (m_switched && snapshot.switched && !m_raw_sent && !m_sent_between && m_confirmation_honest == true) { + assert(snapshot.confirmed); + } + } +}; + +CKey KeyFrom(FuzzedDataProvider& provider) +{ + auto bytes{provider.ConsumeBytes(32)}; + bytes.resize(32); + CKey key; + key.Set(bytes.begin(), bytes.end(), /*fCompressedIn=*/true); + return key; +} + +void initialize_p2p_v2_pq_malicious_peer() +{ + static ECC_Context ecc_context{}; + SelectParams(ChainType::REGTEST); +} + +} // namespace + +FUZZ_TARGET(p2p_v2_pq_malicious_peer, .init = initialize_p2p_v2_pq_malicious_peer) +{ + FuzzedDataProvider provider{buffer.data(), buffer.size()}; + g_kem_calls = {}; + // The tested transport's role and mode (a responder never falls back), both keys, both + // garbage lengths and the seed of everything else that is random. + const uint8_t flags{provider.ConsumeIntegral()}; + const bool test_initiator{(flags & 1) != 0}; + const PQMode mode{test_initiator ? std::array{PQMode::OFF, PQMode::NEGOTIATE, PQMode::FALLBACK}[(flags >> 1) % 3] : + std::array{PQMode::OFF, PQMode::NEGOTIATE}[(flags >> 1) % 2]}; + const CKey transport_key{KeyFrom(provider)}; + const CKey peer_key{KeyFrom(provider)}; + if (!transport_key.IsValid() || !peer_key.IsValid()) return; + const size_t transport_garbage_len{provider.ConsumeIntegralInRange(0, V2Transport::MAX_GARBAGE_LEN)}; + const size_t peer_garbage_len{provider.ConsumeIntegralInRange(0, V2Transport::MAX_GARBAGE_LEN)}; + const uint64_t seed{provider.ConsumeIntegral()}; + MaliciousPeer peer{provider, test_initiator, mode, transport_key, peer_key, transport_garbage_len, peer_garbage_len, seed}; + peer.Run(); +} diff --git a/test/fuzz/qbit_corpora/MANIFEST.json b/test/fuzz/qbit_corpora/MANIFEST.json index ec6acbdfd0..f2bc3b70d3 100644 --- a/test/fuzz/qbit_corpora/MANIFEST.json +++ b/test/fuzz/qbit_corpora/MANIFEST.json @@ -538,6 +538,120 @@ "semantics": "An initiator in fallback never parses the offer; a fixed 48-byte pattern drives the interleaving loop, then the final flush completes the handshake: ECDH keys." } ], + "p2p_v2_pq_malicious_peer": [ + { + "file": "initiator-hybrid", + "format": "fdp", + "size": 90, + "sha256": "96925cdab372557e025a9cac0dc3b5e3ca71d1b4b9499077b0e7f1d5a6e9c211", + "semantics": "The transport initiates; we offer honestly and switch on its accept: hybrid, confirmed, messages both ways." + }, + { + "file": "responder-hybrid", + "format": "fdp", + "size": 90, + "sha256": "796086e85f8379e80d9f7cdb75f60544d260051be35af905e966c33773bde94c", + "semantics": "The transport offers; we accept honestly and switch: hybrid, confirmed, messages both ways." + }, + { + "file": "initiator-hybrid-extras-fragmented", + "format": "fdp", + "size": 309, + "sha256": "f45f8b17efcd2bfc33d6ade02c70da02d30dbdd2bfdf1d19a1fe69a65ef16eec", + "semantics": "Maximum garbage both ways, own records inside decoys, an offer between unknown and duplicate records, delivered in 97-byte and then 1-byte pieces: hybrid." + }, + { + "file": "responder-hybrid-extras", + "format": "fdp", + "size": 97, + "sha256": "bb33f24d2a9e2696ee9428633897934427610f8681bc2e65e941ec010a88afcc", + "semantics": "Decoys, then an accept between unknown and duplicate records: hybrid." + }, + { + "file": "initiator-ek-length", + "format": "fdp", + "size": 83, + "sha256": "1727485faecda04ec7f676fe38b5dd837d06554f4bc76cc0e0b91a3665860467", + "semantics": "A 1,567-byte offer: the transport closes with ek_length and never sends its version packet." + }, + { + "file": "initiator-ek-modulus", + "format": "fdp", + "size": 83, + "sha256": "032bd86ffdea759d3b842e38891187a844f2d27713e5d5704fff6086d06bca4f", + "semantics": "An offer whose coefficients are all above q: the transport closes with ek_modulus." + }, + { + "file": "responder-ct-length", + "format": "fdp", + "size": 83, + "sha256": "42661de0c7b810020c0c76d463661a203c262faab397d6f3e9c09dd40fc7281a", + "semantics": "A 1,569-byte accept: the transport closes with ct_length." + }, + { + "file": "responder-corrupt-ct", + "format": "fdp", + "size": 90, + "sha256": "f7ba2b5a427de4fb48e9fe038386188f5a28d1ff8d3d1a270878135bcbf46c0d", + "semantics": "An accept with one ciphertext bit flipped: the transport switches to other keys and our confirmation fails." + }, + { + "file": "responder-disconnect-after-offer", + "format": "fdp", + "size": 81, + "sha256": "612b7d3ab9f066d1a40a408047aac95b3d78ddd9408a716ac0ecf52ea5b34737", + "semantics": "The transport offers and we disconnect before sending our version packet." + }, + { + "file": "initiator-confirmation-length", + "format": "fdp", + "size": 86, + "sha256": "2ca916c42ec30c4e55391852d1e45b46666d312e15ebd821ff4d1da9aa3db3ec", + "semantics": "An honest exchange, then a nonzero-length first hybrid packet: the transport fails it at byte 3." + }, + { + "file": "initiator-confirmation-tag", + "format": "fdp", + "size": 86, + "sha256": "23721132e8dba8e0d357b1f99ca1a51fd44e6b8a2e8b79309f629d8fb1bc23b4", + "semantics": "An honest offer followed by a duplicate record, then a confirmation with a damaged header or tag." + }, + { + "file": "responder-confirmation-not-decoy", + "format": "fdp", + "size": 86, + "sha256": "2b7d387a3b986e3dfea29386069868d90441e69bf02e869adf920b4fcc79e185", + "semantics": "An honest exchange, then an empty first hybrid packet without the ignore bit." + }, + { + "file": "responder-legacy-peer-burst", + "format": "fdp", + "size": 89, + "sha256": "079588a0165c1ca65c75ac701e625169401a08909479b05ca704b1f18a6a3102", + "semantics": "We ignore the offer like a legacy peer and send application messages at once: plain v2, legacy_peer." + }, + { + "file": "initiator-fallback-ignores-offer", + "format": "fdp", + "size": 88, + "sha256": "549406bb7e9c49617e70b98e2bb336f2a8424d6e1fb153a8009a527eca77bc91", + "semantics": "An initiator in fallback sends plain v2 at once and never parses our offer: no ML-KEM operation." + }, + { + "file": "initiator-noncompliant-responder", + "format": "fdp", + "size": 85, + "sha256": "e2156d55b4e66ccf22a17b4c4d7bb0d66d6b8000f2db1322111c7f6dd122bc9c", + "semantics": "We offer and then send a decoy under the ECDH keys where our confirmation belongs: the transport fails it." + }, + { + "file": "responder-off-unsolicited-accept", + "format": "fdp", + "size": 87, + "sha256": "600263bb14d01a4deefa4ec87a235eae35154b976a7866f594bd4635bb7578b4", + "semantics": "A responder that is off gets an accept-shaped record it never asked for: ignored, plain v2." + } + ], "pq_records": [ { "file": "offer", diff --git a/test/fuzz/qbit_corpora/README.md b/test/fuzz/qbit_corpora/README.md index 596101201e..426a8218c6 100644 --- a/test/fuzz/qbit_corpora/README.md +++ b/test/fuzz/qbit_corpora/README.md @@ -15,6 +15,7 @@ upstream `qa-assets` corpus, and for the v2 transport targets, whose | `p2mr_script` | [`p2mr_script/`](p2mr_script) | | `p2p_transport_bidirectional_v1v2` | [`p2p_transport_bidirectional_v1v2/`](p2p_transport_bidirectional_v1v2) | | `p2p_transport_bidirectional_v2` | [`p2p_transport_bidirectional_v2/`](p2p_transport_bidirectional_v2) | +| `p2p_v2_pq_malicious_peer` | [`p2p_v2_pq_malicious_peer/`](p2p_v2_pq_malicious_peer) | | `pq_records` | [`pq_records/`](pq_records) | | `pqc` | [`pqc/`](pqc) | @@ -83,6 +84,18 @@ so the final flush completes the handshake, and some add a fixed pattern that drives the loop first. The v2 harness checks that the keys are hybrid on both sides exactly when both negotiate. +`p2p_v2_pq_malicious_peer` drives a v2 transport from a malicious peer that +completes the ECDH part honestly, so every packet it sends authenticates, and +chooses the negotiation plaintext. Its layout: the tested transport's role and +mode, both keys, both garbage lengths and a seed (everything else random comes +from it), then a script of steps, each a choice byte and a fixed-width +parameter: send our key, terminator, a decoy, a version packet variant, a +confirmation variant or an application packet; deliver or take some bytes; +flush; give the transport a message; send unauthenticated bytes; disconnect. +The seeds script honest hybrid exchanges in both roles, each malformed record +and confirmation failure, a damaged ciphertext, a legacy peer, a fallback +initiator, an unsolicited record and a disconnect while the offer is pending. + ## Use in CI [`overlay.py`](overlay.py) copies each seed to @@ -96,6 +109,6 @@ from a reused corpus by hand if they are no longer wanted. A target path that is not a directory, or a symlink, directory or other non-regular file at a managed name, is an error; all target paths and managed names are checked before any file is written. -`test/fuzz/test_runner.py --require_qbit_corpus` then fails unless all eleven +`test/fuzz/test_runner.py --require_qbit_corpus` then fails unless all twelve targets are selected, each has at least one regular input file, and the fuzz binary reports replaying them. diff --git a/test/fuzz/qbit_corpora/generate_seeds.py b/test/fuzz/qbit_corpora/generate_seeds.py index cf37a338aa..a0e741cb8a 100755 --- a/test/fuzz/qbit_corpora/generate_seeds.py +++ b/test/fuzz/qbit_corpora/generate_seeds.py @@ -819,6 +819,99 @@ def transport_v1v2_cases() -> list[dict]: ] +# --- p2p_v2_pq_malicious_peer --------------------------------------------------- + +# The harness's steps, in its CallOneOf order, and the range of each step's parameter. +MALPEER_STEPS = { + "key": None, "terminator": None, "decoy": (UINT8, 0, 3), "version": (UINT8, 0, 9), + "confirmation": (UINT8, 0, 4), "application": (UINT8, 0, 3), "deliver": (UINT32, 1, 1 << 16), + "collect": (UINT32, 1, 1 << 16), "flush": None, "queue": (UINT8, 0, 3), "raw": (UINT8, 1, 64), + "disconnect": None, +} +MALPEER_INITIATOR_MODES = {"off": 0, "negotiate": 1, "fallback": 2} +MALPEER_RESPONDER_MODES = {"off": 0, "negotiate": 1} + + +def malpeer_input(name: str, test_initiator: bool, mode: str, steps: list, garbage: tuple[int, int] = (0, 0)) -> bytes: + """The tested transport's role and mode, both keys, both garbage lengths, a seed, then the steps.""" + enc = FdpEncoder() + modes = MALPEER_INITIATOR_MODES if test_initiator else MALPEER_RESPONDER_MODES + enc.integral((modes[mode] << 1) | int(test_initiator), UINT8) + enc.raw(transport_key(f"{name}/transport")) + enc.raw(transport_key(f"{name}/peer")) + enc.integral(garbage[0], UINT16, 0, V2_MAX_GARBAGE_LEN) + enc.integral(garbage[1], UINT16, 0, V2_MAX_GARBAGE_LEN) + enc.integral(int.from_bytes(hashlib.sha256(name.encode()).digest()[:8], "big"), UINT64) + names = list(MALPEER_STEPS) + for step in steps: + step_name, param = (step, None) if isinstance(step, str) else step + enc.pick(names.index(step_name), len(names)) + spec = MALPEER_STEPS[step_name] + if spec is not None: + type_name, lo, hi = spec + enc.integral(param, type_name, lo, hi) + return enc.build() + + +def malpeer_cases() -> list[dict]: + # Version variants (see SendVersion): as responder 5, 6 and 9 are honest offers, 3 a key that + # fails the modulus check, 8 a 1,567-byte key; as initiator 5 and 6 are honest accepts after + # which we switch, 3 a blind ciphertext, 4 a damaged one, 7 an honest accept without our + # switch, 8 a 1,569-byte ciphertext. Confirmation variants: 0 honest, 1 nonzero length, 2 not + # a decoy, 4 damaged tag. + as_responder = ["flush", "key", "terminator"] # the transport initiates + as_initiator = ["key", "flush", "terminator"] # the transport responds and offers + finish = [("confirmation", 0), ("application", 0), ("queue", 0), "flush"] + cases = [ + ("initiator-hybrid", True, "negotiate", as_responder + [("version", 5), "flush"] + finish, (0, 0), + "The transport initiates; we offer honestly and switch on its accept: hybrid, confirmed, messages both ways."), + ("responder-hybrid", False, "negotiate", as_initiator + [("version", 5), "flush"] + finish, (0, 0), + "The transport offers; we accept honestly and switch: hybrid, confirmed, messages both ways."), + ("initiator-hybrid-extras-fragmented", True, "negotiate", + as_responder + [("decoy", 1), ("decoy", 2), ("version", 6)] + [("deliver", 97)] * 40 + + ["flush", ("confirmation", 0), ("decoy", 0), ("application", 1)] + [("deliver", 1)] * 30 + + [("collect", 5), ("queue", 1), "flush"], (V2_MAX_GARBAGE_LEN, V2_MAX_GARBAGE_LEN), + "Maximum garbage both ways, own records inside decoys, an offer between unknown and duplicate records, " + "delivered in 97-byte and then 1-byte pieces: hybrid."), + ("responder-hybrid-extras", False, "negotiate", + as_initiator + [("decoy", 0), ("decoy", 1), ("version", 6), ("collect", 3), "flush"] + finish, + (V2_MAX_GARBAGE_LEN, 17), "Decoys, then an accept between unknown and duplicate records: hybrid."), + ("initiator-ek-length", True, "negotiate", as_responder + [("version", 8), "flush"], (0, 0), + "A 1,567-byte offer: the transport closes with ek_length and never sends its version packet."), + ("initiator-ek-modulus", True, "negotiate", as_responder + [("version", 3), "flush"], (0, 0), + "An offer whose coefficients are all above q: the transport closes with ek_modulus."), + ("responder-ct-length", False, "negotiate", as_initiator + [("version", 8), "flush"], (0, 0), + "A 1,569-byte accept: the transport closes with ct_length."), + ("responder-corrupt-ct", False, "negotiate", as_initiator + [("version", 4), "flush"] + finish, (0, 0), + "An accept with one ciphertext bit flipped: the transport switches to other keys and our confirmation fails."), + ("responder-disconnect-after-offer", False, "negotiate", as_initiator + ["disconnect"], (0, 0), + "The transport offers and we disconnect before sending our version packet."), + ("initiator-confirmation-length", True, "negotiate", + as_responder + [("version", 5), "flush", ("confirmation", 1), "flush"], (0, 0), + "An honest exchange, then a nonzero-length first hybrid packet: the transport fails it at byte 3."), + ("initiator-confirmation-tag", True, "negotiate", + as_responder + [("version", 9), "flush", ("confirmation", 4), "flush"], (0, 0), + "An honest offer followed by a duplicate record, then a confirmation with a damaged header or tag."), + ("responder-confirmation-not-decoy", False, "negotiate", + as_initiator + [("version", 5), "flush", ("confirmation", 2), "flush"], (0, 0), + "An honest exchange, then an empty first hybrid packet without the ignore bit."), + ("responder-legacy-peer-burst", False, "negotiate", + as_initiator + [("version", 0), ("application", 0), ("application", 1), ("queue", 0), "flush"], (0, 64), + "We ignore the offer like a legacy peer and send application messages at once: plain v2, legacy_peer."), + ("initiator-fallback-ignores-offer", True, "fallback", + as_responder + ["flush", ("version", 5), ("application", 0), ("queue", 0), "flush"], (5, 5), + "An initiator in fallback sends plain v2 at once and never parses our offer: no ML-KEM operation."), + ("initiator-noncompliant-responder", True, "negotiate", + as_responder + [("version", 5), ("decoy", 2), "flush"], (0, 0), + "We offer and then send a decoy under the ECDH keys where our confirmation belongs: the transport fails it."), + ("responder-off-unsolicited-accept", False, "off", + as_initiator + [("version", 3), ("application", 0), ("queue", 2), "flush"], (0, 0), + "A responder that is off gets an accept-shaped record it never asked for: ignored, plain v2."), + ] + return [case(name, "fdp", malpeer_input(name, ti, mode, steps, garbage), semantics) + for name, ti, mode, steps, garbage, semantics in cases] + + def case(name: str, fmt: str, data: bytes, semantics: str) -> dict: return {"name": name, "format": fmt, "data": data, "semantics": semantics} @@ -833,6 +926,7 @@ def case(name: str, fmt: str, data: bytes, semantics: str) -> dict: "p2mr_script": p2mr_cases, "p2p_transport_bidirectional_v1v2": transport_v1v2_cases, "p2p_transport_bidirectional_v2": transport_v2_cases, + "p2p_v2_pq_malicious_peer": malpeer_cases, "pq_records": pq_records_cases, "pqc": pqc_cases, } diff --git a/test/fuzz/qbit_corpora/overlay.py b/test/fuzz/qbit_corpora/overlay.py index 7ca88ae8a2..72d8b52a2a 100755 --- a/test/fuzz/qbit_corpora/overlay.py +++ b/test/fuzz/qbit_corpora/overlay.py @@ -34,6 +34,7 @@ "p2mr_script", "p2p_transport_bidirectional_v1v2", "p2p_transport_bidirectional_v2", + "p2p_v2_pq_malicious_peer", "pq_records", "pqc", ) diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-confirmation-length b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-confirmation-length new file mode 100644 index 0000000000000000000000000000000000000000..c8f70a8224a515b8fb5ae03a352f0444342d54f3 GIT binary patch literal 86 zcmV-c0IC0X)z~6X$zEPVvo*+B4y|AP2S~`N(ytMoez2i4_;N+@D|2l sc^}5=yvH8Kkqd(Oy_6O)9-Tymvd)uZF8t4qa@WVl)gR6Ol0 sA37V^e*h{QF~$0wh4emKCBIn+1Ox~P0|5XC-IJeAnGlV30000011s7j0ssI2 literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-ek-length b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-ek-length new file mode 100644 index 0000000000000000000000000000000000000000..bc3d7f074b742db5be97c5453797d279d8f7ba33 GIT binary patch literal 83 zcmV-Z0IdHzF~J4$V^yk-Te_3#P97(%7!$vwHk#Uj>i{2~65`DEP(w&}jt_U*W%Rsb poy5sM8;5tr_}!+wXs_>GrS8KB2m=8C2#bRv5!ahg>Hq)$00Wu@CHVjV literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-ek-modulus b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-ek-modulus new file mode 100644 index 0000000000000000000000000000000000000000..bc37e00bae2ac6bc8e086d6061da34bdfa9461c8 GIT binary patch literal 83 zcmV-Z0IdI!b_5c6?00SfxBtHNE literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-fallback-ignores-offer b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-fallback-ignores-offer new file mode 100644 index 0000000000000000000000000000000000000000..5bb104af2988e95962fd313b8bec0a4f563cc7f4 GIT binary patch literal 88 zcmV-e0H^+qK5_%5*XM91hFwQF)=bRGH|51 Q&lB_%wr}{)|DT^30CXNV761SM literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-noncompliant-responder b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/initiator-noncompliant-responder new file mode 100644 index 0000000000000000000000000000000000000000..6d959d383c44c7c3587ffaef1880e59230157834 GIT binary patch literal 85 zcmV-b0IL6)+2nmkc{lTLP0;rO^-UTsS`pn4cF(k5RsFur64D*Ng_zILrKpusWqreB r_*Hc&)Wsu<7$EPs`HpF#PW*=m0s;jC0RRY^a*p2S)>d->00003S_~(q literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-confirmation-not-decoy b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-confirmation-not-decoy new file mode 100644 index 0000000000000000000000000000000000000000..9deee9b3677d5c19981253084c5538298468e1f9 GIT binary patch literal 86 zcmV-c0IB~w>$C8_arq-DCa45f-t*uESSD{{5nm~x8r2MZIogp$%h^3>8eQ}Sm4rdQ sbjo$x4-xxS=*365K_uBmCc^Xx0t5&J0|5vC!-i>FHHTsk000000<26TMF0Q* literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-corrupt-ct b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-corrupt-ct new file mode 100644 index 0000000000000000000000000000000000000000..1e18ff61a12bd4e76e491b796b1c3df380056a11 GIT binary patch literal 90 zcmV-g0Hy!Y{JEcKgW*28jW{M}R+^&Hi-|q3aONSdRb-6FSx`f?7?#9JIA4EyO-JW$ wq*T8ys9kdJ>YAVF4?LGAtl8!W00{sE00amG0|5vC|J+Fyn;*ktf%Qsi_ItK?cBL~Cmt^XJDs-C~X00c|`+Akl` literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-disconnect-after-offer b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-disconnect-after-offer new file mode 100644 index 0000000000000000000000000000000000000000..afec4d1436fed27196268b6b7861942b5f6b7d8c GIT binary patch literal 81 zcmV-X0IvV!)Zq|G<+C{^*IURjKzq^>CD`RNm3Y;w-7h?u> nMCV%w&&-8Y6e4!yq8Q%-Ww7-N0SEx1QRcR`ZqsD|00002-*h4D literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-hybrid b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-hybrid new file mode 100644 index 0000000000000000000000000000000000000000..9f72a775ea06cfcd9b5daf411bbf0102b6c6f156 GIT binary patch literal 90 zcmV-g0HyzEq_n4aoYp<{X5~|KspedsN)Kg|(RE7b>)UoI6jr$RQJga0)hXB~eip=w wh=_?NweXR2_sQehqxqe6tbOPR00{sE00amH0|5vC(!0pgUxjUi000000(i zNk{+wd-F|%rW3N3Aoor+nD_Gt00{sE00amE00#yG0RjL50SExT;x_KcomvhN0RImH D3wtQ8 literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-legacy-peer-burst b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-legacy-peer-burst new file mode 100644 index 0000000000000000000000000000000000000000..b13b5a4c9655091a3fe99036a8bdfdcb38ffba99 GIT binary patch literal 89 zcmV-f0H*)76gtO4OUR;18&9#WqVE3ao}AO1%{Y8A4Gps*8`(WrS!JhsAq7!=S literal 0 HcmV?d00001 diff --git a/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-off-unsolicited-accept b/test/fuzz/qbit_corpora/p2p_v2_pq_malicious_peer/responder-off-unsolicited-accept new file mode 100644 index 0000000000000000000000000000000000000000..d38420d8cb95651639bb7c9404ff3c9ce3e4c0d9 GIT binary patch literal 87 zcmY#>quUmFd5TTP*_0RG57s%`E_H31-toZv$;-u?#RBxtrq7p-m~k_5YtFe|mqYqD rm|LEN^Q`Fj{@`c*fo|80{B9gfoD8hY%#0ii)mo`a4jt__3?Kjidzd83 literal 0 HcmV?d00001 diff --git a/test/fuzz/test_qbit_fuzz_runner.py b/test/fuzz/test_qbit_fuzz_runner.py index 64fc21c038..23240a3664 100755 --- a/test/fuzz/test_qbit_fuzz_runner.py +++ b/test/fuzz/test_qbit_fuzz_runner.py @@ -32,7 +32,8 @@ RUNNER = SRC_DIR / "test" / "fuzz" / "test_runner.py" CORPORA_DIR = SRC_DIR / "test" / "fuzz" / "qbit_corpora" REQUIRED = ("asert_chain_transition", "asert_edge_cases", "asert_math", "auxpow", "mlkem", "mlkem_backend_diff", "p2mr_script", - "p2p_transport_bidirectional_v1v2", "p2p_transport_bidirectional_v2", "pq_records", "pqc") + "p2p_transport_bidirectional_v1v2", "p2p_transport_bidirectional_v2", + "p2p_v2_pq_malicious_peer", "pq_records", "pqc") INTEGRATION = {} STUB_SOURCE = r''' diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index 4db8ca88fc..6de6a411e4 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -29,6 +29,7 @@ "p2mr_script", "p2p_transport_bidirectional_v1v2", "p2p_transport_bidirectional_v2", + "p2p_v2_pq_malicious_peer", "pq_records", "pqc", ) From d0aa7ffba14f5e52f883495fbd43482f8d1d6e2a Mon Sep 17 00:00:00 2001 From: Dan Hepworth Date: Tue, 6 Oct 2026 12:57:10 -0400 Subject: [PATCH 6/8] fuzz: drop a stray libFuzzer artifact and ignore new ones The G4 commit added crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 at the repository root by accident. It is an input for p2p_v2_pq_malicious_peer left over from one of the harness false positives fixed during G4's development, not a transport defect: it replays cleanly on that target and on pq_records, bip324_cipher_roundtrip and both bidirectional transport targets. libFuzzer writes its crash, leak, out-of-memory, slow-unit and timeout artifacts to the working directory, so ignore those prefixes at the root. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 7 +++++++ crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 | Bin 85 -> 0 bytes 2 files changed, 7 insertions(+) delete mode 100644 crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 diff --git a/.gitignore b/.gitignore index 65201c99d0..1a053bda74 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,11 @@ target/ /guix-build-* +# libFuzzer artifacts, written to the working directory +/crash-* +/leak-* +/oom-* +/slow-unit-* +/timeout-* + /ci/scratch/ diff --git a/crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 b/crash-ef5ec86784262f7cd94f46933e5f6760bee654b5 deleted file mode 100644 index 6d959d383c44c7c3587ffaef1880e59230157834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85 zcmV-b0IL6)+2nmkc{lTLP0;rO^-UTsS`pn4cF(k5RsFur64D*Ng_zILrKpusWqreB r_*Hc&)Wsu<7$EPs`HpF#PW*=m0s;jC0RRY^a*p2S)>d->00003S_~(q From 70b1c7863de5f2a4974f99fa64b99aab6c9bd823 Mon Sep 17 00:00:00 2001 From: Dan Hepworth Date: Tue, 6 Oct 2026 12:57:23 -0400 Subject: [PATCH 7/8] test: check hybrid secret wipes, close paths and pending status Close the test gaps the #230 deep review's mutants exposed; each mutant below survived bip324_tests and net_tests before and is now caught. - The switch wipes the retained ECDH secret and the transcript (hybrid_api_order, v2_pq_pair, v2_pq_legacy_matrix), and a legacy outcome wipes them and the decapsulation key (v2_pq_legacy_matrix, in every arm and for every pair of modes). - v2_pq_close_wipes: a missing garbage terminator, an oversized version packet length and a damaged version packet tag close with both secrets wiped at once, in both roles. These closes come before the peer's version packet authenticated: no PQ failure, pending, and a responder's offer counts as sent. - The status is pending from the start of the handshake until the peer's version packet is processed, not legacy_peer (v2_pq_pair, v2_pq_legacy_matrix, v2_pq_close_wipes). - A decapsulation failure closes on the initiator's version packet itself, with no confirmation queued, instead of one packet later. Co-Authored-By: Claude Opus 5.5 --- src/test/bip324_tests.cpp | 7 ++++ src/test/net_tests.cpp | 84 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/test/bip324_tests.cpp b/src/test/bip324_tests.cpp index ee36045246..3ed4464e49 100644 --- a/src/test/bip324_tests.cpp +++ b/src/test/bip324_tests.cpp @@ -659,6 +659,7 @@ BOOST_AUTO_TEST_CASE(hybrid_api_order) BIP324Cipher initiator(key_i, ent_i), responder(key_r, ent_r); InitializePair(initiator, responder, /*retain_for_hybrid=*/false); const Bytes session_id = ToBytes(initiator.GetSessionID()); + BOOST_CHECK(!initiator.HoldsHybridSecret()); BOOST_CHECK(!initiator.AddVersionContents(contents_r)); BOOST_CHECK(!initiator.SwitchToHybrid(ss)); BOOST_CHECK(std::ranges::equal(initiator.GetSessionID(), session_id)); @@ -682,8 +683,11 @@ BOOST_AUTO_TEST_CASE(hybrid_api_order) BOOST_CHECK(responder.AddVersionContents(contents_i)); BOOST_CHECK(!responder.AddVersionContents({})); + // The switch wipes the retained secret and the transcript. + BOOST_CHECK(initiator.HoldsHybridSecret() && responder.HoldsHybridSecret()); BOOST_CHECK(initiator.SwitchToHybrid(ss)); BOOST_CHECK(responder.SwitchToHybrid(ss)); + BOOST_CHECK(!initiator.HoldsHybridSecret() && !responder.HoldsHybridSecret()); const Bytes hybrid_session_id = ToBytes(initiator.GetSessionID()); BOOST_CHECK(hybrid_session_id != ecdh_session_id); BOOST_CHECK(std::ranges::equal(responder.GetSessionID(), hybrid_session_id)); @@ -702,9 +706,12 @@ BOOST_AUTO_TEST_CASE(hybrid_api_order) const Bytes ecdh_session_id = ToBytes(initiator.GetSessionID()); BOOST_CHECK(initiator.AddVersionContents(contents_r)); BOOST_CHECK(initiator.AddVersionContents(contents_i)); + BOOST_CHECK(initiator.HoldsHybridSecret()); initiator.DiscardHybridSecret(); + BOOST_CHECK(!initiator.HoldsHybridSecret()); initiator.DiscardHybridSecret(); responder.DiscardHybridSecret(); + BOOST_CHECK(!responder.HoldsHybridSecret()); BOOST_CHECK(!initiator.SwitchToHybrid(ss)); BOOST_CHECK(!initiator.AddVersionContents(contents_i)); BOOST_CHECK(!responder.AddVersionContents(contents_r)); diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index c5f584fdf1..8743655755 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -2291,9 +2291,13 @@ BOOST_AUTO_TEST_CASE(v2_pq_pair) { V2TransportTester tester(m_rng, test_initiator, PQ_ON); auto& transport{tester.GetTransport()}; + // Pending from the start of the handshake. + CheckPQ(transport, PQStatus::PENDING); + BOOST_CHECK(!transport.GetPQSnapshot().version_received); const uint256 ecdh_session_id{HybridHandshake(tester, test_initiator, /*send_confirmation=*/false)}; - // Switched, not confirmed: pending, still detecting, and no session id. + // Switched, not confirmed: pending, still detecting, and no session id. The switch + // wiped the decapsulation key and the retained ECDH secret. auto info{transport.GetInfo()}; BOOST_CHECK(info.transport_type == TransportProtocolType::DETECTING); BOOST_CHECK(!info.session_id); @@ -2301,6 +2305,7 @@ BOOST_AUTO_TEST_CASE(v2_pq_pair) auto snapshot{transport.GetPQSnapshot()}; BOOST_CHECK(snapshot.version_received && snapshot.switched && !snapshot.confirmed); BOOST_CHECK(snapshot.offer == (test_initiator ? PQOfferState::RECEIVED : PQOfferState::SENT)); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); // The transport sends application messages without waiting for our confirmation. const auto msg_data_1{m_rng.randbytes(m_rng.randrange(100000))}; @@ -2355,6 +2360,10 @@ BOOST_AUTO_TEST_CASE(v2_pq_legacy_matrix) tester.SendMessage(uint8_t(14), burst_1); // inv short id tester.SendMessage("foobar", burst_2); }; + // Until the peer's version packet is processed, the negotiation is pending. + const PQStatus initial_status{local == PQMode::OFF ? PQStatus::OFF : + local == PQMode::FALLBACK ? PQStatus::FALLBACK : PQStatus::PENDING}; + CheckPQ(transport, initial_status); if (test_initiator) { auto ret{tester.Interact()}; @@ -2368,6 +2377,13 @@ BOOST_AUTO_TEST_CASE(v2_pq_legacy_matrix) } tester.ReceiveKey(/*retain_for_hybrid=*/peer_hybrid); tester.SendGarbageTerm(); + // After the key exchange, before the peer's version packet: still pending, and + // only the negotiation holds secrets for the switch. + auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->empty()); + CheckPQ(transport, initial_status); + BOOST_CHECK(!transport.GetPQSnapshot().version_received); + BOOST_CHECK_EQUAL(transport.HoldsHybridSecretsForTesting(), local == PQMode::NEGOTIATE); if (!peer_hybrid) { tester.SendVersion(); send_burst(); @@ -2375,7 +2391,7 @@ BOOST_AUTO_TEST_CASE(v2_pq_legacy_matrix) // A hybrid responder offers at once. tester.SendOffer(); } - auto ret{tester.Interact()}; + ret = tester.Interact(); BOOST_REQUIRE(ret); BOOST_CHECK_EQUAL(ret->size(), peer_hybrid ? 0U : 2U); tester.ReceiveGarbage(); @@ -2424,6 +2440,8 @@ BOOST_AUTO_TEST_CASE(v2_pq_legacy_matrix) local == PQMode::FALLBACK ? PQStatus::FALLBACK : hybrid ? PQStatus::HYBRID : PQStatus::LEGACY_PEER}; CheckPQ(transport, status); + // The switch and a legacy outcome both wiped what the switch needed. + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); // Off and fallback neither generate, parse nor validate anything; a responder // never decapsulates without an accept. @@ -2455,6 +2473,8 @@ BOOST_AUTO_TEST_CASE(v2_pq_legacy_matrix) }; CheckPQ(initiator, expected(initiator_mode)); CheckPQ(responder, expected(responder_mode)); + BOOST_CHECK(!initiator.HoldsHybridSecretsForTesting()); + BOOST_CHECK(!responder.HoldsHybridSecretsForTesting()); } } } @@ -2903,8 +2923,9 @@ BOOST_AUTO_TEST_CASE(v2_pq_internal_errors) } } - // Decapsulation fails after the initiator committed to its accept: close. There is no failed - // status, so the closing connection reports pending. + // Decapsulation fails after the initiator committed to its accept: close at once, on the + // version packet, without queuing a confirmation. There is no failed status, so the closing + // connection reports pending. { V2TransportTester tester(m_rng, false, PQ_ON); auto& transport{tester.GetTransport()}; @@ -2915,9 +2936,10 @@ BOOST_AUTO_TEST_CASE(v2_pq_internal_errors) tester.SendGarbageTerm(); tester.ReceiveGarbage(); tester.AcceptOfferAndSwitch(); - tester.SendConfirmation(); InjectResultForTesting inject{Operation::DECAPS, upstream::ERR_FAIL}; - BOOST_CHECK(!tester.Interact()); + BOOST_CHECK(!tester.Deliver()); + BOOST_CHECK(tester.ToSend().empty()); + BOOST_CHECK(std::get<0>(transport.GetBytesToSend(false)).empty()); const auto snapshot{transport.GetPQSnapshot()}; BOOST_CHECK(snapshot.version_received && !snapshot.switched); CheckPQ(transport, PQStatus::PENDING, PQFailure::DECAPS_INTERNAL); @@ -3016,6 +3038,56 @@ BOOST_AUTO_TEST_CASE(v2_pq_malformed_record) } } +BOOST_AUTO_TEST_CASE(v2_pq_close_wipes) +{ + // A close the transport detects before the peer's version packet authenticated (a missing + // garbage terminator, or a version packet that is too long or fails its tag) wipes the + // retained ECDH secret and a responder's decapsulation key at once. + enum class Close { GARBAGE_TERMINATOR, VERSION_LENGTH, VERSION_TAG }; + for (const bool test_initiator : {true, false}) { + for (const Close close : {Close::GARBAGE_TERMINATOR, Close::VERSION_LENGTH, Close::VERSION_TAG}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator << " close=" << int(close)) + { + V2TransportTester tester(m_rng, test_initiator, PQ_ON); + auto& transport{tester.GetTransport()}; + if (test_initiator) tester.Collect(); + tester.SendKey(); + BOOST_REQUIRE(tester.Deliver()); + tester.Collect(); + tester.ReceiveKey(); + if (close == Close::GARBAGE_TERMINATOR) { + tester.SendGarbage(V2Transport::MAX_GARBAGE_LEN + BIP324Cipher::GARBAGE_TERMINATOR_LEN); + } else { + tester.SendGarbage(); + tester.SendGarbageTerm(); + const size_t version_start{tester.ToSend().size()}; + if (close == Close::VERSION_LENGTH) { + // Only the length of a version packet one byte over the maximum arrives. + tester.SendVersion(std::vector(4'000'014)); + tester.ToSend().resize(version_start + BIP324Cipher::LENGTH_LEN); + } else { + tester.SendVersion(); + tester.ToSend().back() ^= 1 << m_rng.randrange(8); + } + } + // Nothing fails before the last byte, and the secrets for the switch are held. + const std::vector last{tester.ToSend().back()}; + tester.ToSend().pop_back(); + BOOST_REQUIRE(tester.Deliver()); + BOOST_CHECK(transport.HoldsHybridSecretsForTesting()); + tester.Send(last); + BOOST_CHECK(!tester.Deliver()); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); + // No PQ failure: the close is the transport's, before the peer's version packet. + const auto snapshot{transport.GetPQSnapshot()}; + BOOST_CHECK(!snapshot.version_received && !snapshot.switched); + BOOST_CHECK(snapshot.offer == (test_initiator ? PQOfferState::NONE : PQOfferState::SENT)); + CheckPQ(transport, PQStatus::PENDING); + } + } + } +} + BOOST_AUTO_TEST_CASE(v2_pq_rekey) { for (const bool test_initiator : {true, false}) { From ac16fb688805a9ff05ed0dab6f157460882c9822 Mon Sep 17 00:00:00 2001 From: Dan Hepworth Date: Tue, 6 Oct 2026 12:57:36 -0400 Subject: [PATCH 8/8] net: wipe hybrid secrets whenever the peer's version is processed ProcessVersionContents returned early, without wiping, when this side was not awaiting the peer's version packet: the negotiation off, in fallback, or a responder that could not offer. Every such path in this PR wipes (or never retains) before it sends its version packet, so this is correct today. But a later path that declines the negotiation after Initialize(..., retain_for_hybrid=true) would keep the ECDH secret for the whole connection; part 5's load shedding first had exactly that bug. Wipe in that branch too, a no-op unless such a path kept a secret, so that no hybrid secret outlives the peer's version packet by construction. A test-only option, V2PQOptions::retain_without_negotiation, retains the secret with the negotiation off to stand in for such a path; production passes default options. v2_pq_version_wipes checks the wipe in both roles and that plain v2 continues. v2_pq_internal_errors now also checks that the keygen failure wipes at once, which the new wipe would otherwise mask until the initiator's version packet arrives. Co-Authored-By: Claude Opus 5.5 --- src/net.cpp | 7 +++++-- src/net.h | 3 +++ src/test/net_tests.cpp | 45 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 405ca8f90e..2c330ce0dc 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1169,7 +1169,8 @@ bool V2Transport::ProcessReceivedKeyBytes() noexcept EllSwiftPubKey ellswift(MakeByteSpan(m_recv_buffer)); const bool negotiate{m_pq_options.mode == PQMode::NEGOTIATE}; LOCK(m_send_mutex); - m_cipher.Initialize(ellswift, m_initiating, /*self_decrypt=*/false, /*retain_for_hybrid=*/negotiate); + m_cipher.Initialize(ellswift, m_initiating, /*self_decrypt=*/false, + /*retain_for_hybrid=*/negotiate || m_pq_options.retain_without_negotiation); // Switch receiver state to GARB_GARBTERM. SetReceiveState(RecvState::GARB_GARBTERM); @@ -1359,8 +1360,10 @@ bool V2Transport::ProcessVersionContents(std::span contents) no m_pq.SetVersionReceived(); // Without the negotiation (switch off, fallback, or a responder that could not offer), the - // contents are neither parsed nor validated. + // contents are neither parsed nor validated. Nothing is held for a switch past this point, + // whichever path declined it (a no-op unless that path kept a secret). if (m_send_state != SendState::AWAITING_VERSION) { + ClearHybridSecrets(); SetReceiveState(RecvState::APP); return true; } diff --git a/src/net.h b/src/net.h index 9fa593c0eb..8f061ed795 100644 --- a/src/net.h +++ b/src/net.h @@ -458,6 +458,9 @@ class V1Transport final : public Transport /** The hybrid post-quantum negotiation of one v2 connection (doc/design/pq-transport.md). */ struct V2PQOptions { PQMode mode{PQMode::OFF}; + /** Test only: retain the ECDH secret and the transcript at the key exchange even without the + * negotiation, as a path that declines it afterwards without wiping them would. */ + bool retain_without_negotiation{false}; /** Test only: invert byte 0 of the local ML-KEM shared secret before deriving keys, so the * peer's key confirmation check fails. EK and CT are unchanged. */ bool corrupt_shared_secret{false}; diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 8743655755..5f0c37ea2a 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -2894,8 +2894,10 @@ BOOST_AUTO_TEST_CASE(v2_pq_internal_errors) InjectResultForTesting inject{op, upstream::ERR_FAIL}; BOOST_REQUIRE(tester.Interact()); } - // No offer was queued. + // No offer was queued, and the retained secret is wiped at once, not only when the + // initiator's version packet arrives. BOOST_CHECK(transport.GetPQSnapshot().offer == PQOfferState::NONE); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); tester.ReceiveKey(/*retain_for_hybrid=*/true); tester.SendGarbageTerm(); } @@ -3088,6 +3090,47 @@ BOOST_AUTO_TEST_CASE(v2_pq_close_wipes) } } +BOOST_AUTO_TEST_CASE(v2_pq_version_wipes) +{ + // However a transport came to send its version packet without the negotiation, processing the + // peer's version packet wipes anything still held for a switch. A test option retains the ECDH + // secret and the transcript with the negotiation off, as a path that declined it after the key + // exchange without wiping them would. + for (const bool test_initiator : {true, false}) { + BOOST_TEST_CONTEXT("test_initiator=" << test_initiator) + { + V2TransportTester tester(m_rng, test_initiator, {.mode = PQMode::OFF, .retain_without_negotiation = true}); + auto& transport{tester.GetTransport()}; + if (test_initiator) tester.Collect(); + tester.SendKey(); + tester.SendGarbage(); + BOOST_REQUIRE(tester.Deliver()); + tester.Collect(); + tester.ReceiveKey(); + tester.SendGarbageTerm(); + BOOST_REQUIRE(tester.Deliver()); + // The transport sent its empty version packet, and still holds the retained secret. + BOOST_CHECK(transport.HoldsHybridSecretsForTesting()); + tester.ReceiveGarbage(); + tester.ReceiveVersion(); + tester.SendVersion(); + BOOST_REQUIRE(tester.Deliver()); + BOOST_CHECK(!transport.HoldsHybridSecretsForTesting()); + + // The connection continues as plain v2. + const auto payload{m_rng.randbytes(100)}; + tester.SendMessage("foobar", payload); + tester.AddMessage("barfoo", payload); + const auto ret{tester.Interact()}; + BOOST_REQUIRE(ret && ret->size() == 1); + BOOST_CHECK((*ret)[0] && (*ret)[0]->m_type == "foobar" && std::ranges::equal((*ret)[0]->m_recv, MakeByteSpan(payload))); + tester.ReceiveMessage("barfoo", payload); + tester.CompareSessionIDs(); + CheckPQ(transport, PQStatus::OFF); + } + } +} + BOOST_AUTO_TEST_CASE(v2_pq_rekey) { for (const bool test_initiator : {true, false}) {