diff options
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/meshbay-hub/tests/test_keyring_vectors.py | 142 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/vectors/gen_keyring_vectors.js | 232 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/vectors/keyring.json | 342 |
3 files changed, 716 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_keyring_vectors.py b/packages/meshbay-hub/tests/test_keyring_vectors.py new file mode 100644 index 0000000..3928965 --- /dev/null +++ b/packages/meshbay-hub/tests/test_keyring_vectors.py @@ -0,0 +1,142 @@ +""" +The keypair-bundle and transcript vectors (`tests/vectors/keyring.json`). + +One file that every implementation of the bundle format and of the signed +transcripts has to reproduce: the page (`keyderive.js`), the desktop keyring +(`keyring.js`, `transcripts.js`), the Python reference below, and the Android +keyring, whose unit tests read the same file. Pairwise parity tests grow with +the square of the implementations; a shared file grows by one consumer. + +Two things are checked here. The file is what the shipped desktop code writes, +so it cannot drift from the code it describes. And the specification +(`docs/MESHBAY_DESIGN.md` §3.7, written out with argon2-cffi and +`cryptography`, sharing nothing with the generator) arrives at the same bytes. +""" + +import base64 +import hashlib +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +VECTORS = Path(__file__).resolve().parent / "vectors" +FILE = VECTORS / "keyring.json" +GENERATOR = VECTORS / "gen_keyring_vectors.js" +KEYRING = Path(__file__).resolve().parents[2] / "meshbay-client" / "src" / "keyring.js" + +try: + from argon2.low_level import Type, hash_secret_raw + HAVE_ARGON2 = True +except ImportError: + HAVE_ARGON2 = False + + +def _vectors() -> dict: + return json.loads(FILE.read_text(encoding="utf-8")) + + +@pytest.mark.skipif(shutil.which("node") is None or not KEYRING.exists(), + reason="node or the desktop client sources are unavailable") +def test_the_file_is_what_the_shipped_keyring_writes(): + """Regenerated from keyring.js and transcripts.js, byte for byte. A change + to either that alters a bundle or a transcript fails here first — which is + the moment to decide whether it is a format change every client must make.""" + out = subprocess.run(["node", str(GENERATOR)], capture_output=True, text=True, + timeout=120, check=True).stdout + assert json.loads(out) == _vectors(), ( + "keyring.js or transcripts.js no longer produce tests/vectors/keyring.json; " + "if the format change is deliberate, regenerate it and update every client") + + +def _hkdf(ikm: bytes, info: str) -> bytes: + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.hkdf import HKDF + return HKDF(hashes.SHA256(), 32, None, info.encode()).derive(ikm) + + +@pytest.fixture(scope="module") +def spec(): + """The chain from the specification: passphrase → Argon2id → M → keys.""" + if not HAVE_ARGON2: + pytest.skip("argon2-cffi is unavailable") + v = _vectors() + i, p = v["input"], v["kdf"]["argon2_params"] + salt = hashlib.sha256(f"meshbay:bundle:v2:{i['username']}".encode()).digest()[:16] + a = hash_secret_raw(i["password"].encode(), salt, time_cost=p["passes"], + memory_cost=p["memory"], parallelism=p["parallelism"], + hash_len=p["tagLength"], type=Type.ID) + m = _hkdf(a + base64.b64decode(i["pepperB64"]), f"meshbay:bundle-master:v3|{i['userId']}") + return {"v": v, "salt": salt, "a": a, "m": m, + "node_key": _hkdf(m, f"meshbay:bundle:v3|node|{i['nodePk']}"), + "recovery_key": _hkdf(bytes.fromhex(v["kdf"]["mnemonic_bytes_hex"]), + f"meshbay:recovery:v1:{i['username']}")} + + +def test_the_specification_derives_the_same_keys(spec): + k = spec["v"]["kdf"] + assert spec["salt"].hex() == k["salt_hex"] + assert spec["a"].hex() == k["argon2_hex"] + assert spec["m"].hex() == k["master_hex"] + assert hashlib.sha256(spec["m"]).hexdigest()[:16] == k["master_fingerprint"] + assert spec["node_key"].hex() == k["node_key_hex"] + playlist = _hkdf(spec["m"], "meshbay:playlists:v2") + assert base64.b64encode(playlist).decode() == k["playlist_key_b64"] + assert spec["recovery_key"].hex() == k["recovery_key_hex"] + + +def test_the_specification_seals_the_same_bundles(spec): + """MBK3 = magic ‖ pepper version ‖ nonce ‖ AES-GCM(JSON, aad). With the nonce + pinned, sealing is deterministic, so every client must write these bytes.""" + from cryptography.hazmat.primitives.ciphers.aead import AESGCM + v = spec["v"] + i, b = v["input"], v["bundles"] + nonce = bytes.fromhex(i["fixedNonceHex"]) + plain, aad = b["sealed_json_plaintext"].encode(), b["aad"].encode() + + def seal(key: bytes, version: int) -> str: + return base64.b64encode(b"MBK3" + bytes([version]) + nonce + + AESGCM(key).encrypt(nonce, plain, aad)).decode() + + assert seal(spec["node_key"], i["pepperVersion"]) == b["fixed_nonce_bundle_b64"] + assert seal(spec["recovery_key"], 0) == b["recovery_fixed_nonce_b64"] + raw = base64.b64decode(b["legacy_mbk2_b64"]) # TRANSITIONAL: MBK2, no AAD + assert AESGCM(spec["a"]).decrypt(raw[4:16], raw[16:], None) == plain + + +def test_the_identity_signs_and_agrees_as_recorded(): + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + from cryptography.hazmat.primitives.asymmetric.x25519 import ( + X25519PrivateKey, + X25519PublicKey, + ) + v = _vectors() + i = v["input"] + ed = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(i["edSeedHex"])) + x = X25519PrivateKey.from_private_bytes(bytes.fromhex(i["xSeedHex"])) + + def raw(k) -> str: + return base64.b64encode(k.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw)).decode() + + assert raw(ed) == v["identity"]["public"]["pkEdB64"] + assert raw(x) == v["identity"]["public"]["pkXB64"] + peer = X25519PublicKey.from_public_bytes(bytes.fromhex(i["peerXPubHex"])) + assert base64.b64encode(x.exchange(peer)).decode() == v["agreement"]["shared_b64"] + for kind, t in v["transcripts"].items(): + sig = base64.b64encode(ed.sign(bytes.fromhex(t["transcript_hex"]))).decode() + assert sig == t["signature_b64"], kind + + +def test_every_signed_kind_and_a_refusal_for_each_check_is_recorded(): + """A consumer that passes an empty list proves nothing; pin what is there.""" + v = _vectors() + assert set(v["transcripts"]) == {"join", "device_hello", "device_request", + "device_add", "device_revoke", "chat", "admin"} + labels = {r["label"] for r in v["refusals"]} + assert {"another node", "another account", "stale timestamp", "unknown kind", + "an op that widens sharing (gone from MNP 6.0)"} <= labels + assert all(r["error"].startswith("Refused: ") for r in v["refusals"]) diff --git a/packages/meshbay-hub/tests/vectors/gen_keyring_vectors.js b/packages/meshbay-hub/tests/vectors/gen_keyring_vectors.js new file mode 100644 index 0000000..055070f --- /dev/null +++ b/packages/meshbay-hub/tests/vectors/gen_keyring_vectors.js @@ -0,0 +1,232 @@ +// Golden vectors for the keypair bundle and the transcripts, produced by the +// code the desktop application ships: meshbay-client's keyring.js and +// transcripts.js, with the vendored Argon2 the page also runs. +// +// node gen_keyring_vectors.js > keyring.json +// +// Every implementation of the bundle format and of the transcripts — the page, +// the desktop keyring, the Python reference, the Android keyring — must +// reproduce this file (test_keyring_vectors.py, and the Android unit tests). +// It is regenerated deliberately, for a format change, never by a test. The +// output is deterministic: nonces and the clock are pinned. + +'use strict'; + +const path = require('node:path'); +const crypto = require('node:crypto'); + +const REPO = path.resolve(__dirname, '..', '..', '..', '..'); +const CLIENT = path.join(REPO, 'packages/meshbay-client/src'); +const VENDOR = path.join(REPO, 'packages/meshbay-hub/src/meshbay_hub/static/vendor'); + +const { createKeyring } = require(path.join(CLIENT, 'keyring.js')); +const { transcriptFor } = require(path.join(CLIENT, 'transcripts.js')); +const { wasmArgon2 } = require(path.join(CLIENT, 'argon2-wasm.js')); + +const b64 = (b) => Buffer.from(b).toString('base64'); +const hex = (b) => Buffer.from(b).toString('hex'); +const hkdf = (ikm, info) => Buffer.from( + crypto.hkdfSync('sha256', ikm, Buffer.alloc(0), Buffer.from(info), 32)); + +// Fixed inputs. Invented values only. +const NOW = 1790000000; // a fixed "now" for transcript timestamps +const INPUT = { + username: 'vector-user', + userId: '0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0', + password: 'a passphrase used only for vectors', + pepperB64: b64(Buffer.alloc(32, 7)), + pepperVersion: 3, + nodePk: b64(Buffer.alloc(32, 0x11)), + otherNodePk: b64(Buffer.alloc(32, 0x22)), + groupId: 'grp_vector-01', + mnemonic: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', + edSeedHex: '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', + xSeedHex: '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a', + peerXPubHex: 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f', + fixedNonceHex: '000102030405060708090a0b', + legacyNonceHex: '0b0a09080706050403020100', + now: NOW, +}; + +const pkcs8 = (prefixHex, seedHex) => + Buffer.concat([Buffer.from(prefixHex, 'hex'), Buffer.from(seedHex, 'hex')]); +const ED_PKCS8 = pkcs8('302e020100300506032b657004220420', INPUT.edSeedHex); +const X_PKCS8 = pkcs8('302e020100300506032b656e04220420', INPUT.xSeedHex); + +function withFixedRandom(bytesHex, fn) { + const real = crypto.randomBytes; + crypto.randomBytes = (n) => { + const b = Buffer.from(bytesHex, 'hex'); + if (b.length !== n) throw new Error(`fixed random of ${b.length}, asked ${n}`); + return b; + }; + try { return fn(); } finally { crypto.randomBytes = real; } +} + +function withNow(seconds, fn) { + const real = Date.now; + Date.now = () => seconds * 1000; + try { return fn(); } finally { Date.now = real; } +} + +(async () => { + const argon2 = wasmArgon2(VENDOR); + let store = {}; + const ring = createKeyring({ + argon2, + load: () => JSON.parse(JSON.stringify(store)), + save: (o) => { store = JSON.parse(JSON.stringify(o)); }, + }); + + // 1. KDF chain. + const salt = crypto.createHash('sha256') + .update(`meshbay:bundle:v2:${INPUT.username}`).digest().subarray(0, 16); + const ARGON2 = { memory: 131072, passes: 3, parallelism: 1, tagLength: 32 }; + await ring.deriveSession({ + password: INPUT.password, username: INPUT.username, userId: INPUT.userId, + pepperB64: INPUT.pepperB64, pepperVersion: INPUT.pepperVersion, + }); + const m = Buffer.from(store.masters[INPUT.userId].m, 'base64'); + const a = Buffer.from(store.masters[INPUT.userId].legacy, 'base64'); + const kdf = { + argon2_params: ARGON2, + salt_hex: hex(salt), + argon2_hex: hex(a), + master_hex: hex(m), + master_fingerprint: ring.currentFingerprint(INPUT.userId), + node_key_hex: hex(hkdf(m, `meshbay:bundle:v3|node|${INPUT.nodePk}`)), + playlist_key_b64: ring.playlistKey(INPUT.userId), + recovery_key_hex: null, // filled below + }; + + // 2. A fixed identity, placed in the store as mint() would leave it. + store.identities[INPUT.userId] = { + [INPUT.nodePk]: { ed: b64(ED_PKCS8), x: b64(X_PKCS8), sealedWith: null }, + }; + const identity = { + ed_pkcs8_b64: b64(ED_PKCS8), + x_pkcs8_b64: b64(X_PKCS8), + public: ring.identity(INPUT.userId, INPUT.nodePk), + }; + + // 3. Bundles. + const fixed = withFixedRandom(INPUT.fixedNonceHex, + () => ring.sealBundle(INPUT.userId, INPUT.nodePk)); + const recovery = withFixedRandom(INPUT.fixedNonceHex, + () => ring.sealRecovery(INPUT.userId, INPUT.nodePk, INPUT.mnemonic, INPUT.username)); + + // The recovery key, re-derived the way keyring.js does (fromMnemonic + hkdf). + const B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + let bits = 0; let value = 0; const raw = []; + for (const ch of INPUT.mnemonic.replace(/[^A-Za-z2-7]/g, '').toUpperCase()) { + value = (value << 5) | B32.indexOf(ch); bits += 5; + if (bits >= 8) { raw.push((value >>> (bits - 8)) & 0xff); bits -= 8; } + } + kdf.mnemonic_bytes_hex = hex(Buffer.from(raw.slice(0, 32))); + kdf.recovery_key_hex = hex(hkdf(Buffer.from(raw.slice(0, 32)), + `meshbay:recovery:v1:${INPUT.username}`)); + + // MBK2 (TRANSITIONAL): "MBK2" ‖ nonce ‖ AES-GCM(JSON) under the Argon2 key, no AAD. + const legacyNonce = Buffer.from(INPUT.legacyNonceHex, 'hex'); + const c = crypto.createCipheriv('aes-256-gcm', a, legacyNonce); + const plain = JSON.stringify({ skEd: b64(ED_PKCS8), skX: b64(X_PKCS8) }); + const legacy = b64(Buffer.concat([ + Buffer.from('MBK2'), legacyNonce, c.update(plain), c.final(), c.getAuthTag()])); + + // Each must open through the shipped keyring, into a fresh store. + const check = async (label, bundleEnc, extra = {}) => { + let s2 = {}; + const r2 = createKeyring({ argon2, load: () => JSON.parse(JSON.stringify(s2)), + save: (o) => { s2 = JSON.parse(JSON.stringify(o)); } }); + await r2.deriveSession({ password: INPUT.password, username: INPUT.username, + userId: INPUT.userId, pepperB64: INPUT.pepperB64, pepperVersion: INPUT.pepperVersion }); + const pub = r2.openBundle(INPUT.userId, INPUT.nodePk, { bundleEnc, ...extra }); + if (pub.pkEdB64 !== identity.public.pkEdB64 || pub.pkXB64 !== identity.public.pkXB64) { + throw new Error(`${label} opened to another identity`); + } + return true; + }; + await check('fixed', fixed.bundle); + await check('legacy', legacy); + // The recovery copy, through the fallback: a passphrase copy that does not open. + await check('recovery', b64(Buffer.concat([Buffer.from('MBK3'), Buffer.alloc(30, 1)])), { + recoveryEnc: recovery, recoveryMnemonic: INPUT.mnemonic, username: INPUT.username }); + + const bundles = { + sealed_json_plaintext: plain, + aad: `meshbay:bundle:v3|${INPUT.userId}|${INPUT.nodePk}`, + fixed_nonce_bundle_b64: fixed.bundle, + fixed_nonce_fingerprint: fixed.fingerprint, + recovery_fixed_nonce_b64: recovery, + legacy_mbk2_b64: legacy, + }; + + // 4. Agreement. + const agreement = { + peer_x_pub_b64: b64(Buffer.from(INPUT.peerXPubHex, 'hex')), + shared_b64: ring.shared(INPUT.userId, INPUT.nodePk, b64(Buffer.from(INPUT.peerXPubHex, 'hex'))), + }; + + // 5. Transcripts: every kind, then refusals. + const ctx = { userId: INPUT.userId, nodePk: INPUT.nodePk, ...identity.public }; + delete ctx.sealedWith; + const nonceNode = b64(Buffer.alloc(32, 0x33)); + const kinds = { + join: { nodePk: INPUT.nodePk, groupId: INPUT.groupId, userId: INPUT.userId, nonceNode, ts: NOW }, + device_hello: { nodePk: INPUT.nodePk, groupId: INPUT.groupId, userId: INPUT.userId, nonceNode, ts: NOW - 30 }, + device_request: { nodePk: INPUT.nodePk, userId: INPUT.userId, + codeHash: 'ab'.repeat(32), nonceNode, ts: NOW + 30 }, + device_add: { nodePk: INPUT.nodePk, userId: INPUT.userId, + pkEd: b64(Buffer.alloc(32, 0x44)), pkX: b64(Buffer.alloc(32, 0x55)), nonceNode, ts: NOW }, + device_revoke: { nodePk: INPUT.nodePk, userId: INPUT.userId, + pkEd: b64(Buffer.alloc(32, 0x44)), nonceNode, ts: NOW }, + chat: { groupId: INPUT.groupId, epoch: 4, nonce: b64(Buffer.alloc(24, 0x66)), + ct: b64(Buffer.from('ciphertext of a chat line, opaque here')) }, + admin: { op: 'apps_enabled', nodePk: INPUT.nodePk, groupId: INPUT.groupId, + subject: '{"apps":["chat","videos"],"note":"é ü 漢"}', + nonce: b64(Buffer.alloc(16, 0x77)), ts: NOW }, + }; + const transcripts = {}; + for (const [kind, fields] of Object.entries(kinds)) { + const bytes = withNow(NOW, () => transcriptFor(kind, fields, ctx)); + const sig = withNow(NOW, () => ring.signAs(INPUT.userId, INPUT.nodePk, kind, fields)); + transcripts[kind] = { fields, transcript_hex: hex(bytes), signature_b64: sig }; + } + + const refusals = []; + const refuse = (label, kind, fields) => { + try { + withNow(NOW, () => transcriptFor(kind, fields, ctx)); + throw new Error(`vector "${label}" was NOT refused by transcripts.js`); + } catch (e) { + if (/NOT refused/.test(e.message)) throw e; + refusals.push({ label, kind, fields, error: e.message }); + } + }; + refuse('another node', 'join', { ...kinds.join, nodePk: INPUT.otherNodePk }); + refuse('another account', 'join', { ...kinds.join, userId: '00000000-0000-4000-8000-000000000000' }); + refuse('stale timestamp', 'join', { ...kinds.join, ts: NOW - 601 }); + refuse('future timestamp', 'device_hello', { ...kinds.device_hello, ts: NOW + 601 }); + refuse('fractional timestamp', 'join', { ...kinds.join, ts: NOW + 0.5 }); + refuse('bad group id', 'join', { ...kinds.join, groupId: 'a/b' }); + refuse('short node nonce', 'join', { ...kinds.join, nonceNode: b64(Buffer.alloc(15)) }); + refuse('not base64', 'join', { ...kinds.join, nonceNode: 'not*base64' }); + refuse('bad request hash', 'device_request', { ...kinds.device_request, codeHash: 'AB'.repeat(32) }); + refuse('device key wrong length', 'device_add', { ...kinds.device_add, pkEd: b64(Buffer.alloc(31)) }); + refuse('negative epoch', 'chat', { ...kinds.chat, epoch: -1 }); + refuse('chat nonce too short', 'chat', { ...kinds.chat, nonce: b64(Buffer.alloc(11)) }); + refuse('bad op', 'admin', { ...kinds.admin, op: 'Drop-Table' }); + refuse('an op that widens sharing (gone from MNP 6.0)', 'admin', { ...kinds.admin, op: 'root_add' }); + refuse('a well-formed op not on the list', 'admin', { ...kinds.admin, op: 'set_app_setting' }); + refuse('subject too long', 'admin', { ...kinds.admin, subject: 'x'.repeat(16385) }); + refuse('unknown kind', 'sign_anything', { bytes: 'AAAA' }); + + const out = { + _about: 'Generated by gen_keyring_vectors.js from meshbay-client keyring.js and ' + + 'transcripts.js with the vendored Argon2. Every implementation of the bundle ' + + 'format and the transcripts must reproduce every field.', + input: INPUT, + kdf, identity, bundles, agreement, transcripts, refusals, + }; + process.stdout.write(JSON.stringify(out, null, 2) + '\n'); +})().catch((e) => { console.error(e); process.exit(1); }); diff --git a/packages/meshbay-hub/tests/vectors/keyring.json b/packages/meshbay-hub/tests/vectors/keyring.json new file mode 100644 index 0000000..84ecff0 --- /dev/null +++ b/packages/meshbay-hub/tests/vectors/keyring.json @@ -0,0 +1,342 @@ +{ + "_about": "Generated by gen_keyring_vectors.js from meshbay-client keyring.js and transcripts.js with the vendored Argon2. Every implementation of the bundle format and the transcripts must reproduce every field.", + "input": { + "username": "vector-user", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "password": "a passphrase used only for vectors", + "pepperB64": "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc=", + "pepperVersion": 3, + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "otherNodePk": "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=", + "groupId": "grp_vector-01", + "mnemonic": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", + "edSeedHex": "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + "xSeedHex": "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + "peerXPubHex": "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", + "fixedNonceHex": "000102030405060708090a0b", + "legacyNonceHex": "0b0a09080706050403020100", + "now": 1790000000 + }, + "kdf": { + "argon2_params": { + "memory": 131072, + "passes": 3, + "parallelism": 1, + "tagLength": 32 + }, + "salt_hex": "2244e8b97822de2b9e210e22301700ff", + "argon2_hex": "95e8531f721421b13bba6e6585de932654d26e5428793f8734b4e7da74b14a7c", + "master_hex": "ecb972b6454304630cecffe115a9f679905ce98457c741f7d534f575322b1cef", + "master_fingerprint": "292fe17f616a1ef6", + "node_key_hex": "948aa161c470cd16e944cbc1dfc1a375a76a17761ad80014423d64cf2401bd2f", + "playlist_key_b64": "Gyd4KTER2IS4dHieFp9DkiHExSP3hjLT/SMXF0TBUno=", + "recovery_key_hex": "612b9cf5cc507ba1483555d7748dc7e20734374994baa092fca605df3600a8c3", + "mnemonic_bytes_hex": "00443214c74254b635cf84653a56d7c675be77df00443214c74254b635cf8465" + }, + "identity": { + "ed_pkcs8_b64": "MC4CAQAwBQYDK2VwBCIEIJ1hsZ3v/VpguoRK9JLsLMREScVpezJpGXA7rAMcrn9g", + "x_pkcs8_b64": "MC4CAQAwBQYDK2VuBCIEIHcHbQpzGKV9PBbBclGyZkXfTC+H68CZKrF3+6UduSwq", + "public": { + "pkEdB64": "11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=", + "pkXB64": "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo=", + "sealedWith": null + } + }, + "bundles": { + "sealed_json_plaintext": "{\"skEd\":\"MC4CAQAwBQYDK2VwBCIEIJ1hsZ3v/VpguoRK9JLsLMREScVpezJpGXA7rAMcrn9g\",\"skX\":\"MC4CAQAwBQYDK2VuBCIEIHcHbQpzGKV9PBbBclGyZkXfTC+H68CZKrF3+6UduSwq\"}", + "aad": "meshbay:bundle:v3|0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0|ERERERERERERERERERERERERERERERERERERERERERE=", + "fixed_nonce_bundle_b64": "TUJLMwMAAQIDBAUGBwgJCgu/0e64MEzT13AuLRE9rV3gw4cF1jPwKvIl8h0OsNnW1UsbNLdQcWg+SVVgNSOfR2SneoWBbieI1Gypb/9osJ7gkWHfOcvlMqa0AdjoS3ww/Tf0/hL/LLB5B04w1oujnfsvhCL6zaPZtjyPZ5PUc6Ks7AqXmJZtuqSN33qEjZoQH9xQKNb6LUVJrWTjSUl2NZ02Y1mIVOd6Y9Af421u/vn41FIxwg==", + "fixed_nonce_fingerprint": "292fe17f616a1ef6", + "recovery_fixed_nonce_b64": "TUJLMwAAAQIDBAUGBwgJCgvqVgf86f7WSRXeERiv5CqFqgsFVR5vXLcYOKVOWblJErRq4D3pWWM1UMSyWOEjv0Q88dukHmm/ncpvUV24rtrBwQ3a2o0O3Zu4QQxKispflVoCuYIakbwh8dSF5Qh7Pgdat2TpWO0/OekZpvXv6kUdiMFviB1qKSkzE0od+qgdh0Wokqb5cvD9Mxr5CQkrNlMVkGs+O73ITEIUkDlDHNNSr+2GMg==", + "legacy_mbk2_b64": "TUJLMgsKCQgHBgUEAwIBAOAz0yDaxedAe3ervmsyggv0fyDeHyTeMdfuBhO3mEHtfub86kZFyk6RG+SUuZHd2uReHxdA+6sZhexlTb32eK7Fg2MfsAhXlVdO6p0JS+LT2Dx4IV8KV7f8En1n5RE7ppy2QtMjqKzi56nzY0uihtNDmgnaQgas4anOMFJDzONfR6ek8f5DQWAKxDc/AKb8jf+DnhOY2F3J5NNzl4swIXe60FND" + }, + "agreement": { + "peer_x_pub_b64": "3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08=", + "shared_b64": "Sl2dW6TOLeFyjjv0gDUPJeB+IclH0Z4zdvCbPB4WF0I=" + }, + "transcripts": { + "join": { + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000 + }, + "transcript_hex": "6d6573686261793a6a6f696e3a76310000002c455245524552455245524552455245524552455245524552455245524552455245524552455245524552453d0000000d6772705f766563746f722d30310000002430663165326433632d346235612d343936382d383737362d6135623463336432653166300000002c3131715941594b7843726656532f3754795751484f6737686376506170694d6c727749616150634855526f3d0000002c6853447743596b777031523069333363744437335767322f4f67306d4f427230363653706a717162546d6f3d0000002033333333333333333333333333333333333333333333333333333333333333330000000a31373930303030303030", + "signature_b64": "PjmA9stj7pqTWdaHGgNQjiouiC3V6YktCa7ebXy7aZVUIeeoKT5nf7hqhkkNV0hUUvYZoWsu9rD14TwVZI6pBw==" + }, + "device_hello": { + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1789999970 + }, + "transcript_hex": "6d6573686261793a6465766963655f68656c6c6f3a76310000002c455245524552455245524552455245524552455245524552455245524552455245524552455245524552453d0000000d6772705f766563746f722d30310000002430663165326433632d346235612d343936382d383737362d6135623463336432653166300000002c3131715941594b7843726656532f3754795751484f6737686376506170694d6c727749616150634855526f3d0000002033333333333333333333333333333333333333333333333333333333333333330000000a31373839393939393730", + "signature_b64": "IVOAHoLco3TvqaRdN4lvv8YGmE4PQu54njs23luu/j51vOdXmkbAVS9HYBrSmPhVPxc9UW5B/KY9vdzHYnMZBg==" + }, + "device_request": { + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "codeHash": "abababababababababababababababababababababababababababababababab", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000030 + }, + "transcript_hex": "6d6573686261793a6465766963655f7265713a76310000002c455245524552455245524552455245524552455245524552455245524552455245524552455245524552453d0000002430663165326433632d346235612d343936382d383737362d6135623463336432653166300000002c3131715941594b7843726656532f3754795751484f6737686376506170694d6c727749616150634855526f3d0000002c6853447743596b777031523069333363744437335767322f4f67306d4f427230363653706a717162546d6f3d00000040616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261626162616261620000002033333333333333333333333333333333333333333333333333333333333333330000000a31373930303030303330", + "signature_b64": "wi1DvdWqKYSvrmweN8U8E/IGe5akrEO1nXClVjBoKsr2geksMrxnOrkAFSO2Ecf7js4hT2OxoYgVBzjiRdV0AA==" + }, + "device_add": { + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "pkEd": "REREREREREREREREREREREREREREREREREREREREREQ=", + "pkX": "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000 + }, + "transcript_hex": "6d6573686261793a6465766963655f6164643a76310000002c455245524552455245524552455245524552455245524552455245524552455245524552455245524552453d0000002430663165326433632d346235612d343936382d383737362d6135623463336432653166300000002c524552455245524552455245524552455245524552455245524552455245524552455245524552455245513d0000002c565656565656565656565656565656565656565656565656565656565656565656565656565656565656553d0000002033333333333333333333333333333333333333333333333333333333333333330000000a31373930303030303030", + "signature_b64": "D+tRe/2fbscnA3wdhHsnEfUCu0U/JszfIhFvYC8lEy8AqiD7osn3GWotQIMGSO3FoQz62WJHZsAdUaelgQJUCg==" + }, + "device_revoke": { + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "pkEd": "REREREREREREREREREREREREREREREREREREREREREQ=", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000 + }, + "transcript_hex": "6d6573686261793a6465766963655f7265766f6b653a76310000002c455245524552455245524552455245524552455245524552455245524552455245524552455245524552453d0000002430663165326433632d346235612d343936382d383737362d6135623463336432653166300000002c524552455245524552455245524552455245524552455245524552455245524552455245524552455245513d0000002033333333333333333333333333333333333333333333333333333333333333330000000a31373930303030303030", + "signature_b64": "NmVrQU3Fb0rms+wYQI9TIdc7Ry0H/G9CLU5stNNnGe6/WU29bSU8V81FXk6ze5dOfi0ezz4YmWrem7cRAR5MBg==" + }, + "chat": { + "fields": { + "groupId": "grp_vector-01", + "epoch": 4, + "nonce": "ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZm", + "ct": "Y2lwaGVydGV4dCBvZiBhIGNoYXQgbGluZSwgb3BhcXVlIGhlcmU=" + }, + "transcript_hex": "6d6573686261793a636861743a76310000000d6772705f766563746f722d3031000000013400000020d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a000000186666666666666666666666666666666666666666666666660000002663697068657274657874206f6620612063686174206c696e652c206f70617175652068657265", + "signature_b64": "Ogv5d2lhr0ABJacfp0XEbUH7jO8lIplbCZLj1jL5bt8kHyPvKpRDruZkCg+vo9sOFWjMuAlIzQALuS6zE62JBA==" + }, + "admin": { + "fields": { + "op": "apps_enabled", + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "subject": "{\"apps\":[\"chat\",\"videos\"],\"note\":\"é ü 漢\"}", + "nonce": "d3d3d3d3d3d3d3d3d3d3dw==", + "ts": 1790000000 + }, + "transcript_hex": "6d6573686261793a61646d696e3a76310000000c617070735f656e61626c65640000002c455245524552455245524552455245524552455245524552455245524552455245524552455245524552453d0000000d6772705f766563746f722d30310000002d7b2261707073223a5b2263686174222c22766964656f73225d2c226e6f7465223a22c3a920c3bc20e6bca2227d00000010777777777777777777777777777777770000000a31373930303030303030", + "signature_b64": "ocx6tu6SKu3U8mEQUWhNNIZieGDfYquLdtBfez0vqb2EkfFzPfD1yraP3OhlvZYTIZfnWfzJ1R9y/sRN6vZgAg==" + } + }, + "refusals": [ + { + "label": "another node", + "kind": "join", + "fields": { + "nodePk": "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000 + }, + "error": "Refused: another node" + }, + { + "label": "another account", + "kind": "join", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "00000000-0000-4000-8000-000000000000", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000 + }, + "error": "Refused: another account" + }, + { + "label": "stale timestamp", + "kind": "join", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1789999399 + }, + "error": "Refused: the timestamp is not now" + }, + { + "label": "future timestamp", + "kind": "device_hello", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000601 + }, + "error": "Refused: the timestamp is not now" + }, + { + "label": "fractional timestamp", + "kind": "join", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000.5 + }, + "error": "Refused: the timestamp is not now" + }, + { + "label": "bad group id", + "kind": "join", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "a/b", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000 + }, + "error": "Refused: not a group id" + }, + { + "label": "short node nonce", + "kind": "join", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "AAAAAAAAAAAAAAAAAAAA", + "ts": 1790000000 + }, + "error": "Refused: the node nonce has the wrong length" + }, + { + "label": "not base64", + "kind": "join", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "nonceNode": "not*base64", + "ts": 1790000000 + }, + "error": "Refused: the node nonce is not base64" + }, + { + "label": "bad request hash", + "kind": "device_request", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "codeHash": "ABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000030 + }, + "error": "Refused: not a request hash" + }, + { + "label": "device key wrong length", + "kind": "device_add", + "fields": { + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "userId": "0f1e2d3c-4b5a-4968-8776-a5b4c3d2e1f0", + "pkEd": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "pkX": "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVU=", + "nonceNode": "MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM=", + "ts": 1790000000 + }, + "error": "Refused: the device key has the wrong length" + }, + { + "label": "negative epoch", + "kind": "chat", + "fields": { + "groupId": "grp_vector-01", + "epoch": -1, + "nonce": "ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZm", + "ct": "Y2lwaGVydGV4dCBvZiBhIGNoYXQgbGluZSwgb3BhcXVlIGhlcmU=" + }, + "error": "Refused: not an epoch" + }, + { + "label": "chat nonce too short", + "kind": "chat", + "fields": { + "groupId": "grp_vector-01", + "epoch": 4, + "nonce": "AAAAAAAAAAAAAAA=", + "ct": "Y2lwaGVydGV4dCBvZiBhIGNoYXQgbGluZSwgb3BhcXVlIGhlcmU=" + }, + "error": "Refused: the message nonce has the wrong length" + }, + { + "label": "bad op", + "kind": "admin", + "fields": { + "op": "Drop-Table", + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "subject": "{\"apps\":[\"chat\",\"videos\"],\"note\":\"é ü 漢\"}", + "nonce": "d3d3d3d3d3d3d3d3d3d3dw==", + "ts": 1790000000 + }, + "error": "Refused: not an operation" + }, + { + "label": "an op that widens sharing (gone from MNP 6.0)", + "kind": "admin", + "fields": { + "op": "root_add", + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "subject": "{\"apps\":[\"chat\",\"videos\"],\"note\":\"é ü 漢\"}", + "nonce": "d3d3d3d3d3d3d3d3d3d3dw==", + "ts": 1790000000 + }, + "error": "Refused: not an operation" + }, + { + "label": "a well-formed op not on the list", + "kind": "admin", + "fields": { + "op": "set_app_setting", + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "subject": "{\"apps\":[\"chat\",\"videos\"],\"note\":\"é ü 漢\"}", + "nonce": "d3d3d3d3d3d3d3d3d3d3dw==", + "ts": 1790000000 + }, + "error": "Refused: not an operation" + }, + { + "label": "subject too long", + "kind": "admin", + "fields": { + "op": "apps_enabled", + "nodePk": "ERERERERERERERERERERERERERERERERERERERERERE=", + "groupId": "grp_vector-01", + "subject": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "nonce": "d3d3d3d3d3d3d3d3d3d3dw==", + "ts": 1790000000 + }, + "error": "Refused: the subject is too long" + }, + { + "label": "unknown kind", + "kind": "sign_anything", + "fields": { + "bytes": "AAAA" + }, + "error": "Refused: nothing is signed as \"sign_anything\"" + } + ] +} |