xoonya 3.1.17 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -74
- package/RELEASE_NOTES.md +1 -1
- package/START_HERE.md +38 -134
- package/bin/xoonya-first-run.js +22 -10
- package/bin/xoonya.js +61 -29
- package/lib/native_verify.js +1918 -0
- package/package.json +19 -42
- package/scripts/verify_release_candidate.js +243 -116
- package/scripts/verify_vectors.js +351 -104
- package/verify.js +95 -0
- package/.env.example +0 -10
- package/CARRIED_HANDOFF_DEMO.md +0 -60
- package/COMPATIBILITY_NOTE.md +0 -27
- package/CROSS_RUNTIME_POSTURE.md +0 -77
- package/EXAMPLE_PATHS.md +0 -53
- package/FINAL_CONTRACT.md +0 -445
- package/PARTNER_READY_PATH.md +0 -67
- package/PUBLIC_DEMO_SCRIPT.md +0 -79
- package/SERVICE_CONTRACT.md +0 -207
- package/TERMINOLOGY.md +0 -55
- package/WHY_XYTARA_XOONYA.md +0 -43
- package/XNYA_CONFORMANCE_MARK.md +0 -78
- package/XNYA_REFERENCE_IMPLEMENTATIONS.md +0 -157
- package/XOONYA_DEFAULT_PATH_MODEL.md +0 -63
- package/XOONYA_FOLLOWTHROUGH_TRUTH_MODEL.md +0 -63
- package/XOONYA_NATIVE_ATOM_SPEC.md +0 -184
- package/XOONYA_NATIVE_PROOF_FORMAT.md +0 -570
- package/XOONYA_NATIVE_STANDARD_GOVERNANCE.md +0 -119
- package/XOONYA_PROOF_TRUTH_MODEL.md +0 -58
- package/bin/xoonya-release.js +0 -1367
- package/bin/xoonya-review.js +0 -70
- package/examples/integration_boundary_walkthrough.js +0 -37
- package/examples/package_quickstart.js +0 -1007
- package/examples/partner_proof_walkthrough.js +0 -68
- package/examples/proof_review_walkthrough.js +0 -66
- package/examples/review_carried_handoff.js +0 -72
- package/index.js +0 -31
- package/integrations/PROOF_INTEGRATION_PROCESS.md +0 -45
- package/integrations/examples/example-third-party-verifier-registration.record.json +0 -22
- package/integrations/registration-record.schema.json +0 -99
- package/integrations/registry-snapshot.schema.json +0 -36
- package/integrations/registry.js +0 -636
- package/integrations/views.js +0 -36
- package/lib/adapter_certification_contract.js +0 -90
- package/lib/announcement_pack.js +0 -61
- package/lib/artifact_distribution_lane_contract.js +0 -54
- package/lib/asyncapi_contract.js +0 -135
- package/lib/auth_interop_contract.js +0 -69
- package/lib/authority_binding_lifecycle_contract.js +0 -182
- package/lib/bridge_intelligence.js +0 -102
- package/lib/cloudevents_contract.js +0 -79
- package/lib/crypto_agility_matrix.js +0 -131
- package/lib/default_path_contract.js +0 -380
- package/lib/ecosystem_entry.js +0 -64
- package/lib/erc8004_proof_lane_contract.js +0 -64
- package/lib/event_system_lane_contract.js +0 -76
- package/lib/external_proof_import_lane_contract.js +0 -87
- package/lib/feature_control_lane_contract.js +0 -54
- package/lib/followthrough_truth_contract.js +0 -214
- package/lib/framework_lane_contract.js +0 -76
- package/lib/governance_disputes_summary.js +0 -36
- package/lib/governance_intelligence.js +0 -108
- package/lib/identity_interop_contract.js +0 -83
- package/lib/integration_matrix_contract.js +0 -81
- package/lib/launch_narrative.js +0 -53
- package/lib/machine_identity_registry_contract.js +0 -199
- package/lib/major_rails_proof_lane_contract.js +0 -87
- package/lib/offline_verifiability_recovery_contract.js +0 -155
- package/lib/openapi_contract.js +0 -227
- package/lib/operational_receipt_contract.js +0 -165
- package/lib/outreach_proof.js +0 -71
- package/lib/phase_10_closeout_pack.js +0 -45
- package/lib/phase_10_completion_pack.js +0 -76
- package/lib/phase_10_decision_record_pack.js +0 -54
- package/lib/phase_10_decision_resolution_pack.js +0 -53
- package/lib/phase_10_demand_signal_adapters_proof_pack.js +0 -74
- package/lib/phase_10_ecosystem_backlog_triage_proof_pack.js +0 -74
- package/lib/phase_10_freeze_baseline_pack.js +0 -67
- package/lib/phase_10_freeze_review_pack.js +0 -64
- package/lib/phase_10_gate_pack.js +0 -57
- package/lib/phase_10_long_tail_continuity_pack.js +0 -80
- package/lib/phase_10_long_tail_ecosystem_pack.js +0 -68
- package/lib/phase_10_niche_extension_registry_proof_pack.js +0 -74
- package/lib/phase_1_claude_mcp_pack.js +0 -60
- package/lib/phase_1_claude_mcp_proof_pack.js +0 -57
- package/lib/phase_1_ecosystem_pack.js +0 -51
- package/lib/phase_1_openai_codex_pack.js +0 -60
- package/lib/phase_1_openai_codex_proof_pack.js +0 -57
- package/lib/phase_2_base_proof_pack.js +0 -71
- package/lib/phase_2_base_usdc_proof_pack.js +0 -72
- package/lib/phase_2_closeout_pack.js +0 -65
- package/lib/phase_2_completion_pack.js +0 -113
- package/lib/phase_2_decision_record_pack.js +0 -66
- package/lib/phase_2_decision_resolution_pack.js +0 -69
- package/lib/phase_2_eth_proof_pack.js +0 -70
- package/lib/phase_2_evm_base_proof_pack.js +0 -73
- package/lib/phase_2_evm_proof_pack.js +0 -71
- package/lib/phase_2_flip_preview_pack.js +0 -60
- package/lib/phase_2_freeze_review_pack.js +0 -99
- package/lib/phase_2_gate_pack.js +0 -77
- package/lib/phase_2_payment_rails_pack.js +0 -71
- package/lib/phase_2_usdc_proof_pack.js +0 -71
- package/lib/phase_3_brc_proof_pack.js +0 -81
- package/lib/phase_3_bsv_teranode_proof_pack.js +0 -81
- package/lib/phase_3_closeout_pack.js +0 -62
- package/lib/phase_3_completion_pack.js +0 -92
- package/lib/phase_3_decision_record_pack.js +0 -64
- package/lib/phase_3_decision_resolution_pack.js +0 -69
- package/lib/phase_3_freeze_baseline_pack.js +0 -67
- package/lib/phase_3_freeze_review_pack.js +0 -80
- package/lib/phase_3_gate_pack.js +0 -76
- package/lib/phase_3_metanet_proof_pack.js +0 -81
- package/lib/phase_3_native_bsv_pack.js +0 -89
- package/lib/phase_3_native_continuity_pack.js +0 -82
- package/lib/phase_4_attestation_proof_pack.js +0 -75
- package/lib/phase_4_closeout_pack.js +0 -45
- package/lib/phase_4_completion_pack.js +0 -82
- package/lib/phase_4_decision_record_pack.js +0 -54
- package/lib/phase_4_decision_resolution_pack.js +0 -45
- package/lib/phase_4_erc8004_proof_pack.js +0 -76
- package/lib/phase_4_freeze_baseline_pack.js +0 -67
- package/lib/phase_4_freeze_review_pack.js +0 -68
- package/lib/phase_4_gate_pack.js +0 -58
- package/lib/phase_4_identity_registry_proof_pack.js +0 -75
- package/lib/phase_4_identity_trust_pack.js +0 -73
- package/lib/phase_4_trust_continuity_pack.js +0 -86
- package/lib/phase_5_antelope_proof_pack.js +0 -76
- package/lib/phase_5_closeout_pack.js +0 -44
- package/lib/phase_5_completion_pack.js +0 -82
- package/lib/phase_5_decision_record_pack.js +0 -54
- package/lib/phase_5_decision_resolution_pack.js +0 -45
- package/lib/phase_5_freeze_baseline_pack.js +0 -67
- package/lib/phase_5_freeze_review_pack.js +0 -68
- package/lib/phase_5_gate_pack.js +0 -58
- package/lib/phase_5_major_rails_continuity_pack.js +0 -86
- package/lib/phase_5_major_rails_pack.js +0 -74
- package/lib/phase_5_proton_xpr_proof_pack.js +0 -76
- package/lib/phase_5_solana_proof_pack.js +0 -76
- package/lib/phase_6_autogen_proof_pack.js +0 -74
- package/lib/phase_6_closeout_pack.js +0 -46
- package/lib/phase_6_completion_pack.js +0 -77
- package/lib/phase_6_decision_record_pack.js +0 -54
- package/lib/phase_6_decision_resolution_pack.js +0 -53
- package/lib/phase_6_framework_continuity_pack.js +0 -81
- package/lib/phase_6_framework_runtime_pack.js +0 -68
- package/lib/phase_6_freeze_baseline_pack.js +0 -68
- package/lib/phase_6_freeze_review_pack.js +0 -65
- package/lib/phase_6_gate_pack.js +0 -59
- package/lib/phase_6_langchain_proof_pack.js +0 -74
- package/lib/phase_6_langgraph_proof_pack.js +0 -74
- package/lib/phase_6_semantic_kernel_proof_pack.js +0 -74
- package/lib/phase_7_closeout_pack.js +0 -45
- package/lib/phase_7_completion_pack.js +0 -85
- package/lib/phase_7_decision_record_pack.js +0 -53
- package/lib/phase_7_decision_resolution_pack.js +0 -53
- package/lib/phase_7_event_system_continuity_pack.js +0 -89
- package/lib/phase_7_event_system_pack.js +0 -76
- package/lib/phase_7_freeze_baseline_pack.js +0 -74
- package/lib/phase_7_freeze_review_pack.js +0 -65
- package/lib/phase_7_gate_pack.js +0 -58
- package/lib/phase_7_kafka_proof_pack.js +0 -76
- package/lib/phase_7_mqtt_proof_pack.js +0 -76
- package/lib/phase_7_nats_proof_pack.js +0 -76
- package/lib/phase_7_webhook_event_bus_proof_pack.js +0 -76
- package/lib/phase_8_closeout_pack.js +0 -46
- package/lib/phase_8_completion_pack.js +0 -82
- package/lib/phase_8_decision_record_pack.js +0 -54
- package/lib/phase_8_decision_resolution_pack.js +0 -53
- package/lib/phase_8_external_receipt_import_proof_pack.js +0 -74
- package/lib/phase_8_external_result_import_proof_pack.js +0 -74
- package/lib/phase_8_freeze_baseline_pack.js +0 -71
- package/lib/phase_8_freeze_review_pack.js +0 -64
- package/lib/phase_8_gate_pack.js +0 -58
- package/lib/phase_8_proof_bridge_continuity_pack.js +0 -86
- package/lib/phase_8_proof_bridges_pack.js +0 -72
- package/lib/phase_8_proof_bundle_normalization_proof_pack.js +0 -74
- package/lib/phase_9_closeout_pack.js +0 -46
- package/lib/phase_9_completion_pack.js +0 -82
- package/lib/phase_9_custody_refs_proof_pack.js +0 -74
- package/lib/phase_9_decision_record_pack.js +0 -54
- package/lib/phase_9_decision_resolution_pack.js +0 -53
- package/lib/phase_9_freeze_baseline_pack.js +0 -71
- package/lib/phase_9_freeze_review_pack.js +0 -64
- package/lib/phase_9_gate_pack.js +0 -58
- package/lib/phase_9_operator_accounting_bridges_proof_pack.js +0 -74
- package/lib/phase_9_treasury_connectivity_pack.js +0 -72
- package/lib/phase_9_treasury_continuity_pack.js +0 -86
- package/lib/phase_9_treasury_destinations_proof_pack.js +0 -74
- package/lib/phase_program_pack.js +0 -47
- package/lib/proof_bridge_lane_contract.js +0 -82
- package/lib/proof_truth_contract.js +0 -224
- package/lib/provenance_lane_contract.js +0 -54
- package/lib/provider_lane_contract.js +0 -67
- package/lib/public_surface_policy_contract.js +0 -111
- package/lib/publish_plan.js +0 -51
- package/lib/release_candidate.js +0 -50
- package/lib/release_center.js +0 -1037
- package/lib/release_history.js +0 -206
- package/lib/release_manifest.js +0 -114
- package/lib/release_pack.js +0 -391
- package/lib/result_package_trust_lane_contract.js +0 -74
- package/lib/review_intelligence.js +0 -100
- package/lib/role_freeze.js +0 -436
- package/lib/shared_signals_lane_contract.js +0 -54
- package/lib/signed_audit_provenance_contract.js +0 -93
- package/lib/telemetry_lane_contract.js +0 -54
- package/lib/treasury_proof_lane_contract.js +0 -83
- package/lib/trust_intelligence.js +0 -106
- package/lib/trust_portability_boundary_contract.js +0 -66
- package/lib/trust_portability_pack.js +0 -224
- package/lib/verification_service_pack.js +0 -149
- package/proof_sdk.js +0 -6692
- package/reference/python/README.md +0 -35
- package/reference/python/requirements-optional.txt +0 -1
- package/reference/python/verify_vectors.py +0 -223
- package/reference/python/xnya_reference.py +0 -810
- package/reference/rust/Cargo.toml +0 -11
- package/reference/rust/README.md +0 -28
- package/reference/rust/src/lib.rs +0 -87
- package/scripts/registry_cli.js +0 -157
- package/scripts/verify_all.js +0 -603
- package/scripts/verify_control_plane_contracts.js +0 -34
- package/scripts/verify_control_plane_routes.js +0 -70
- package/scripts/verify_examples.js +0 -19
- package/scripts/verify_external_proof_import_gate.js +0 -84
- package/scripts/verify_external_proof_signature.js +0 -142
- package/scripts/verify_forgery_resistance.js +0 -281
- package/scripts/verify_integrations.js +0 -170
- package/scripts/verify_native_hybrid_design_gate.js +0 -114
- package/scripts/verify_paid_execute.js +0 -133
- package/scripts/verify_paywall_flag.js +0 -86
- package/scripts/verify_reference_promotion_gate.js +0 -74
- package/scripts/verify_reference_python.js +0 -49
- package/scripts/verify_reference_python_crypto.js +0 -87
- package/scripts/verify_reference_rust_scaffold.js +0 -50
- package/scripts/verify_service.js +0 -3038
- package/scripts/verify_tooling.js +0 -1251
- package/scripts/verify_trust_portability.js +0 -56
- package/scripts/verify_verification_services.js +0 -56
- package/server.js +0 -4430
|
@@ -0,0 +1,1918 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Standalone verifier for xoonya proof bundles, native receipts, XNYA atoms,
|
|
4
|
+
// payload profiles, and line-rate frames.
|
|
5
|
+
//
|
|
6
|
+
// This module VERIFIES only. It recomputes digests and checks signatures that a
|
|
7
|
+
// proof already carries; it never emits receipts, signs material, or builds
|
|
8
|
+
// proof bundles. The canonicalization rules (sorted-key JSON, SHA-256, SPKI key
|
|
9
|
+
// fingerprints, base64url signatures) are byte-for-byte the rules the hosted
|
|
10
|
+
// xoonya proof engine uses, so a bundle that verifies here verifies there and
|
|
11
|
+
// vice versa. Node built-ins only (node:crypto).
|
|
12
|
+
//
|
|
13
|
+
// What a valid result means: the proof is well-formed and bound to the canonical
|
|
14
|
+
// event, and (for signed / hybrid methods) the carried public key really signed
|
|
15
|
+
// it. It does not establish who controls that key, custody, settlement, or
|
|
16
|
+
// external chain truth; supply `trusted_public_key_refs` / `expected_*_ref`
|
|
17
|
+
// options to bind a key to an identity you already trust.
|
|
18
|
+
|
|
19
|
+
const crypto = require("crypto");
|
|
20
|
+
|
|
21
|
+
const MAGIC_HEX = "50524654";
|
|
22
|
+
const XOONYA_NATIVE_ATOM_MAGIC_ASCII = "XNYA";
|
|
23
|
+
const XOONYA_NATIVE_ATOM_MAGIC_HEX = "584e5941";
|
|
24
|
+
const VERSION_V1_HEX = "01";
|
|
25
|
+
const VERSION_V2_HEX = "02";
|
|
26
|
+
const VALID_TYPES = new Set(["01", "02", "03"]);
|
|
27
|
+
const ZERO_HASH32 = "00".repeat(32);
|
|
28
|
+
const ZERO_CONTEXT12 = "00".repeat(12);
|
|
29
|
+
const PROOF_ENVELOPE_VERSION = "proof-envelope-v1";
|
|
30
|
+
const CANONICAL_EVENT_BUNDLE_VERSION = "canonical-event-bundle/v1";
|
|
31
|
+
const CANONICAL_RECEIPT_VERSION = "receipt/v1";
|
|
32
|
+
const PROOF_BUNDLE_VERSION = "proof-bundle/v1";
|
|
33
|
+
const VERIFICATION_METHOD_NONE = "none";
|
|
34
|
+
const LEGACY_OPRETURN_METHOD = "legacy-opreturn-v1";
|
|
35
|
+
const DETACHED_ASSERTION_METHOD = "detached-assertion-v1";
|
|
36
|
+
const XOONYA_NATIVE_SHA256_METHOD = "xoonya-native-sha256-v1";
|
|
37
|
+
const JWT_ASSERTION_METHOD = "jwt-assertion-v1";
|
|
38
|
+
const DID_VC_METHOD = "did-vc-v1";
|
|
39
|
+
const SD_JWT_VC_METHOD = "sd-jwt-vc-v1";
|
|
40
|
+
const SIGNED_ENVELOPE_METHOD = "signed-envelope-v1";
|
|
41
|
+
const PARTNER_PROOF_ENVELOPE_METHOD = "partner-proof-envelope-v1";
|
|
42
|
+
const XOONYA_NATIVE_PROOF_FORMAT_VERSION = "xoonya-native-proof/v1";
|
|
43
|
+
const XOONYA_NATIVE_SIGNED_METHOD = "xoonya-native-signed-v1";
|
|
44
|
+
const XOONYA_NATIVE_ANCHORED_METHOD = "xoonya-native-anchored-v1";
|
|
45
|
+
const XOONYA_NATIVE_HYBRID_METHOD = "xoonya-native-hybrid-v1";
|
|
46
|
+
const XOONYA_NATIVE_ATOM_V1 = "xoonya-native-atom/v1";
|
|
47
|
+
const XOONYA_NATIVE_ATOM_V2 = "xoonya-native-atom/v2";
|
|
48
|
+
const XOONYA_NATIVE_PAYLOAD_PROFILE_V1 = "xoonya-native-payload-profile/v1";
|
|
49
|
+
const XOONYA_NATIVE_LINE_RATE_FRAME_V1 = "xoonya-native-line-rate-frame/v1";
|
|
50
|
+
const XNYA_DS_AGENT_V1 = "xoonya:agent:v1:";
|
|
51
|
+
const XNYA_DS_PAYLOAD_V1 = "xoonya:payload:v1:";
|
|
52
|
+
const XNYA_DS_AGENT_V2 = "xoonya:agent:v2:";
|
|
53
|
+
const XNYA_DS_PAYLOAD_V2 = "xoonya:payload:v2:";
|
|
54
|
+
const XNYA_DS_PAYLOAD_PROFILE_V1 = "xoonya:payload-profile:v1:";
|
|
55
|
+
const XNYA_DS_LINE_RATE_FRAME_V1 = "xoonya:line-rate-frame:v1:";
|
|
56
|
+
const XNYA_NATIVE_ATOM_ERROR_CODES = Object.freeze({
|
|
57
|
+
ATOM_HEX_NOT_HEX: "atom_hex_not_hex",
|
|
58
|
+
ATOM_UNSUPPORTED_LENGTH: "atom_unsupported_length",
|
|
59
|
+
ATOM_BAD_MAGIC: "atom_bad_magic",
|
|
60
|
+
ATOM_UNSUPPORTED_RECORD_TYPE: "atom_unsupported_record_type",
|
|
61
|
+
ATOM_VERSION_LENGTH_MISMATCH: "atom_version_length_mismatch",
|
|
62
|
+
IDENTITY_UNSUPPORTED_TYPE_PREFIX: "identity_unsupported_type_prefix",
|
|
63
|
+
IDENTITY_MISSING_RAW_BYTES: "identity_missing_raw_bytes",
|
|
64
|
+
AGENT_ID_MISMATCH: "agent_id_mismatch",
|
|
65
|
+
PAYLOAD_HASH_MISMATCH: "payload_hash_mismatch"
|
|
66
|
+
});
|
|
67
|
+
const PROOF_VERIFICATION_METHODS = Object.freeze({
|
|
68
|
+
[LEGACY_OPRETURN_METHOD]: Object.freeze({
|
|
69
|
+
id: LEGACY_OPRETURN_METHOD,
|
|
70
|
+
family: "legacy_receipt",
|
|
71
|
+
verification_posture: "compatibility_verified",
|
|
72
|
+
description: "Verifies the compatibility OP_RETURN receipt and requires the receipt signature to match it."
|
|
73
|
+
}),
|
|
74
|
+
[DETACHED_ASSERTION_METHOD]: Object.freeze({
|
|
75
|
+
id: DETACHED_ASSERTION_METHOD,
|
|
76
|
+
family: "detached_assertion",
|
|
77
|
+
verification_posture: "structure_verified_external_signature",
|
|
78
|
+
description: "Accepts an explicit detached assertion signature after canonical receipt checks; cryptographic verification is external to this helper."
|
|
79
|
+
}),
|
|
80
|
+
[XOONYA_NATIVE_SHA256_METHOD]: Object.freeze({
|
|
81
|
+
id: XOONYA_NATIVE_SHA256_METHOD,
|
|
82
|
+
family: "xoonya_native",
|
|
83
|
+
verification_posture: "native_deterministic_digest",
|
|
84
|
+
description: "Verifies xoonya's native deterministic receipt digest format for method-agnostic proof bundles."
|
|
85
|
+
}),
|
|
86
|
+
[XOONYA_NATIVE_SIGNED_METHOD]: Object.freeze({
|
|
87
|
+
id: XOONYA_NATIVE_SIGNED_METHOD,
|
|
88
|
+
family: "xoonya_native",
|
|
89
|
+
verification_posture: "native_signature_verified",
|
|
90
|
+
description: "Verifies xoonya's native signed proof envelope with explicit signature-suite and public-key binding."
|
|
91
|
+
}),
|
|
92
|
+
[XOONYA_NATIVE_ANCHORED_METHOD]: Object.freeze({
|
|
93
|
+
id: XOONYA_NATIVE_ANCHORED_METHOD,
|
|
94
|
+
family: "xoonya_native",
|
|
95
|
+
verification_posture: "native_anchor_binding_verified",
|
|
96
|
+
description: "Verifies xoonya's native anchor-binding proof envelope for chain, batch, finality, or external attestation references."
|
|
97
|
+
}),
|
|
98
|
+
[JWT_ASSERTION_METHOD]: Object.freeze({
|
|
99
|
+
id: JWT_ASSERTION_METHOD,
|
|
100
|
+
family: "jwt",
|
|
101
|
+
verification_posture: "structure_verified_external_signature",
|
|
102
|
+
description: "Accepts a compact JWT assertion after canonical receipt checks; JWT signature verification is external to this helper."
|
|
103
|
+
}),
|
|
104
|
+
[DID_VC_METHOD]: Object.freeze({
|
|
105
|
+
id: DID_VC_METHOD,
|
|
106
|
+
family: "did_vc",
|
|
107
|
+
verification_posture: "structure_verified_external_signature",
|
|
108
|
+
description: "Accepts a DID/VC proof envelope after canonical receipt checks; DID resolution and VC proof verification are external to this helper."
|
|
109
|
+
}),
|
|
110
|
+
[SD_JWT_VC_METHOD]: Object.freeze({
|
|
111
|
+
id: SD_JWT_VC_METHOD,
|
|
112
|
+
family: "sd_jwt_vc",
|
|
113
|
+
verification_posture: "structure_verified_external_signature",
|
|
114
|
+
description: "Accepts an SD-JWT VC disclosure envelope after canonical receipt checks; disclosure and issuer verification are external to this helper."
|
|
115
|
+
}),
|
|
116
|
+
[SIGNED_ENVELOPE_METHOD]: Object.freeze({
|
|
117
|
+
id: SIGNED_ENVELOPE_METHOD,
|
|
118
|
+
family: "signed_envelope",
|
|
119
|
+
verification_posture: "structure_verified_external_signature",
|
|
120
|
+
description: "Accepts a signed envelope after canonical receipt checks; envelope-specific signature verification is external to this helper."
|
|
121
|
+
}),
|
|
122
|
+
[PARTNER_PROOF_ENVELOPE_METHOD]: Object.freeze({
|
|
123
|
+
id: PARTNER_PROOF_ENVELOPE_METHOD,
|
|
124
|
+
family: "partner_proof_envelope",
|
|
125
|
+
verification_posture: "structure_verified_external_signature",
|
|
126
|
+
description: "Accepts partner-specific proof envelopes after canonical receipt checks; partner verifier policy is external to this helper."
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
const NATIVE_HYBRID_METHOD_DESCRIPTOR = Object.freeze({
|
|
130
|
+
id: XOONYA_NATIVE_HYBRID_METHOD,
|
|
131
|
+
family: "xoonya_native",
|
|
132
|
+
verification_posture: "native_hybrid_signature_verified",
|
|
133
|
+
description: "Verifies xoonya's native hybrid proof envelope with a classical and PQ signature bound to one deterministic signing material."
|
|
134
|
+
});
|
|
135
|
+
const NATIVE_SIGNATURE_SUITES = Object.freeze({
|
|
136
|
+
ed25519: Object.freeze({
|
|
137
|
+
id: "ed25519",
|
|
138
|
+
maturity: "implemented",
|
|
139
|
+
public_key_format: "spki-pem",
|
|
140
|
+
verification_posture: "native_verified",
|
|
141
|
+
key_type: "ed25519"
|
|
142
|
+
}),
|
|
143
|
+
secp256k1: Object.freeze({
|
|
144
|
+
id: "secp256k1",
|
|
145
|
+
maturity: "implemented",
|
|
146
|
+
public_key_format: "spki-pem",
|
|
147
|
+
verification_posture: "native_verified",
|
|
148
|
+
key_type: "ec",
|
|
149
|
+
curve: "secp256k1",
|
|
150
|
+
signing_hash: "sha256"
|
|
151
|
+
}),
|
|
152
|
+
p256: Object.freeze({
|
|
153
|
+
id: "p256",
|
|
154
|
+
maturity: "implemented",
|
|
155
|
+
public_key_format: "spki-pem",
|
|
156
|
+
verification_posture: "native_verified",
|
|
157
|
+
key_type: "ec",
|
|
158
|
+
curve: "prime256v1",
|
|
159
|
+
signing_hash: "sha256"
|
|
160
|
+
}),
|
|
161
|
+
"webauthn-cose": Object.freeze({
|
|
162
|
+
id: "webauthn-cose",
|
|
163
|
+
maturity: "planned",
|
|
164
|
+
public_key_format: "cose-key",
|
|
165
|
+
verification_posture: "registry_declared_not_yet_verified"
|
|
166
|
+
}),
|
|
167
|
+
dilithium: Object.freeze({
|
|
168
|
+
id: "dilithium",
|
|
169
|
+
maturity: "planned_pqc",
|
|
170
|
+
status: "deprecated_placeholder",
|
|
171
|
+
deprecated_alias_of: "ml-dsa-65",
|
|
172
|
+
public_key_format: "suite_defined",
|
|
173
|
+
verification_posture: "registry_declared_not_yet_verified",
|
|
174
|
+
description: "Legacy pre-standard placeholder name. Use the implemented FIPS 204 suite `ml-dsa-65` instead; this entry never verifies."
|
|
175
|
+
}),
|
|
176
|
+
falcon: Object.freeze({
|
|
177
|
+
id: "falcon",
|
|
178
|
+
maturity: "planned_pqc",
|
|
179
|
+
status: "deprecated_placeholder",
|
|
180
|
+
deprecated_alias_of: null,
|
|
181
|
+
public_key_format: "suite_defined",
|
|
182
|
+
verification_posture: "registry_declared_not_yet_verified",
|
|
183
|
+
description: "Legacy pre-standard placeholder name (FN-DSA is not yet registered). This entry never verifies."
|
|
184
|
+
}),
|
|
185
|
+
"sphincs-plus": Object.freeze({
|
|
186
|
+
id: "sphincs-plus",
|
|
187
|
+
maturity: "planned_pqc",
|
|
188
|
+
status: "deprecated_placeholder",
|
|
189
|
+
deprecated_alias_of: "slh-dsa-sha2-128f",
|
|
190
|
+
public_key_format: "suite_defined",
|
|
191
|
+
verification_posture: "registry_declared_not_yet_verified",
|
|
192
|
+
description: "Legacy pre-standard placeholder name. Use the implemented FIPS 205 suite `slh-dsa-sha2-128f` instead; this entry never verifies."
|
|
193
|
+
}),
|
|
194
|
+
"ml-dsa-65": Object.freeze({
|
|
195
|
+
id: "ml-dsa-65",
|
|
196
|
+
maturity: "implemented",
|
|
197
|
+
public_key_format: "spki-pem",
|
|
198
|
+
verification_posture: "native_verified",
|
|
199
|
+
key_type: "ml-dsa-65"
|
|
200
|
+
}),
|
|
201
|
+
"slh-dsa-sha2-128f": Object.freeze({
|
|
202
|
+
id: "slh-dsa-sha2-128f",
|
|
203
|
+
maturity: "implemented",
|
|
204
|
+
public_key_format: "spki-pem",
|
|
205
|
+
verification_posture: "native_verified",
|
|
206
|
+
key_type: "slh-dsa-sha2-128f"
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
const XOONYA_NATIVE_IDENTITY_TYPES = Object.freeze({
|
|
210
|
+
"01": Object.freeze({
|
|
211
|
+
prefix: "01",
|
|
212
|
+
id: "secp256k1-public-key",
|
|
213
|
+
canonical_material: "compressed-sec1-public-key"
|
|
214
|
+
}),
|
|
215
|
+
"02": Object.freeze({
|
|
216
|
+
prefix: "02",
|
|
217
|
+
id: "device-identifier",
|
|
218
|
+
canonical_material: "raw-device-identifier-bytes"
|
|
219
|
+
}),
|
|
220
|
+
"03": Object.freeze({
|
|
221
|
+
prefix: "03",
|
|
222
|
+
id: "did-string",
|
|
223
|
+
canonical_material: "utf8-did-string-no-bom-no-normalization"
|
|
224
|
+
}),
|
|
225
|
+
"04": Object.freeze({
|
|
226
|
+
prefix: "04",
|
|
227
|
+
id: "opaque-agent-key",
|
|
228
|
+
canonical_material: "opaque-agent-key-bytes"
|
|
229
|
+
}),
|
|
230
|
+
"05": Object.freeze({
|
|
231
|
+
prefix: "05",
|
|
232
|
+
id: "dilithium-public-key",
|
|
233
|
+
canonical_material: "suite-defined-pqc-public-key-bytes",
|
|
234
|
+
status: "legacy_placeholder",
|
|
235
|
+
description: "Legacy placeholder prefix reserved before FIPS 204 finalization; the implemented PQ suite is `ml-dsa-65`. No verifier binds this prefix to a live suite."
|
|
236
|
+
}),
|
|
237
|
+
"06": Object.freeze({
|
|
238
|
+
prefix: "06",
|
|
239
|
+
id: "falcon-public-key",
|
|
240
|
+
canonical_material: "suite-defined-pqc-public-key-bytes",
|
|
241
|
+
status: "legacy_placeholder",
|
|
242
|
+
description: "Legacy placeholder prefix reserved before FN-DSA standardization. No verifier binds this prefix to a live suite."
|
|
243
|
+
}),
|
|
244
|
+
"07": Object.freeze({
|
|
245
|
+
prefix: "07",
|
|
246
|
+
id: "sphincs-plus-public-key",
|
|
247
|
+
canonical_material: "suite-defined-pqc-public-key-bytes",
|
|
248
|
+
status: "legacy_placeholder",
|
|
249
|
+
description: "Legacy placeholder prefix reserved before FIPS 205 finalization; the implemented PQ suite is `slh-dsa-sha2-128f`. No verifier binds this prefix to a live suite."
|
|
250
|
+
})
|
|
251
|
+
});
|
|
252
|
+
const EXTERNAL_PROOF_IMPORT_METHODS = Object.freeze([
|
|
253
|
+
DETACHED_ASSERTION_METHOD,
|
|
254
|
+
JWT_ASSERTION_METHOD,
|
|
255
|
+
DID_VC_METHOD,
|
|
256
|
+
SD_JWT_VC_METHOD,
|
|
257
|
+
SIGNED_ENVELOPE_METHOD,
|
|
258
|
+
PARTNER_PROOF_ENVELOPE_METHOD
|
|
259
|
+
]);
|
|
260
|
+
const VERIFICATION_CLASS_CRYPTOGRAPHIC = "cryptographic";
|
|
261
|
+
const VERIFICATION_CLASS_DIGEST = "digest";
|
|
262
|
+
const VERIFICATION_CLASS_STRUCTURAL = "structural";
|
|
263
|
+
const VERIFICATION_CLASS_ANCHORED_BINDING = "anchored_binding";
|
|
264
|
+
const PROOF_VERIFICATION_CLASSES = Object.freeze({
|
|
265
|
+
[VERIFICATION_CLASS_CRYPTOGRAPHIC]: Object.freeze({
|
|
266
|
+
id: VERIFICATION_CLASS_CRYPTOGRAPHIC,
|
|
267
|
+
cryptographically_verified: true,
|
|
268
|
+
strict_mode_accepted: true,
|
|
269
|
+
methods: Object.freeze([XOONYA_NATIVE_SIGNED_METHOD, XOONYA_NATIVE_HYBRID_METHOD]),
|
|
270
|
+
description: "A native classical and/or PQ signature was checked against the public key carried in the proof. Identity binding still requires a verifier-supplied trust root."
|
|
271
|
+
}),
|
|
272
|
+
[VERIFICATION_CLASS_DIGEST]: Object.freeze({
|
|
273
|
+
id: VERIFICATION_CLASS_DIGEST,
|
|
274
|
+
cryptographically_verified: false,
|
|
275
|
+
strict_mode_accepted: false,
|
|
276
|
+
methods: Object.freeze([XOONYA_NATIVE_SHA256_METHOD, LEGACY_OPRETURN_METHOD]),
|
|
277
|
+
description: "Only an unkeyed deterministic digest was recomputed. Anyone can produce a valid digest; this proves integrity and replayability, not authorship."
|
|
278
|
+
}),
|
|
279
|
+
[VERIFICATION_CLASS_ANCHORED_BINDING]: Object.freeze({
|
|
280
|
+
id: VERIFICATION_CLASS_ANCHORED_BINDING,
|
|
281
|
+
cryptographically_verified: false,
|
|
282
|
+
strict_mode_accepted: false,
|
|
283
|
+
methods: Object.freeze([XOONYA_NATIVE_ANCHORED_METHOD]),
|
|
284
|
+
description: "An unkeyed digest binding to declared anchor refs was recomputed. External anchor truth and authorship are not verified by xoonya."
|
|
285
|
+
}),
|
|
286
|
+
[VERIFICATION_CLASS_STRUCTURAL]: Object.freeze({
|
|
287
|
+
id: VERIFICATION_CLASS_STRUCTURAL,
|
|
288
|
+
cryptographically_verified: false,
|
|
289
|
+
strict_mode_accepted: false,
|
|
290
|
+
methods: Object.freeze(EXTERNAL_PROOF_IMPORT_METHODS.slice()),
|
|
291
|
+
description: "The envelope shape, method registration, and canonical receipt binding were checked. The external signature itself must be verified by the external verifier stack."
|
|
292
|
+
})
|
|
293
|
+
});
|
|
294
|
+
const STRICT_DIGEST_ONLY_REASON = "digest_only_not_cryptographically_verified";
|
|
295
|
+
const STRICT_EXTERNAL_VERIFICATION_REASON = "external_verification_required";
|
|
296
|
+
const NATIVE_PUBLIC_KEY_NOT_TRUSTED_REASON = "native_public_key_not_trusted";
|
|
297
|
+
const NATIVE_HYBRID_PUBLIC_KEY_NOT_TRUSTED_REASON = "native_hybrid_public_key_not_trusted";
|
|
298
|
+
const PROOF_VERIFICATION_STRICT_REJECTION_CODES = Object.freeze([
|
|
299
|
+
STRICT_DIGEST_ONLY_REASON,
|
|
300
|
+
STRICT_EXTERNAL_VERIFICATION_REASON
|
|
301
|
+
]);
|
|
302
|
+
const PROOF_VERIFICATION_TRUST_ROOT_REJECTION_CODES = Object.freeze([
|
|
303
|
+
NATIVE_PUBLIC_KEY_NOT_TRUSTED_REASON,
|
|
304
|
+
NATIVE_HYBRID_PUBLIC_KEY_NOT_TRUSTED_REASON
|
|
305
|
+
]);
|
|
306
|
+
const STRICT_VERIFY_ENV = "XOONYA_STRICT_VERIFY";
|
|
307
|
+
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// Primitives and canonicalization
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
function sha256Hex(buf) {
|
|
313
|
+
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function hash160Hex(buf) {
|
|
317
|
+
const sha = crypto.createHash("sha256").update(buf).digest();
|
|
318
|
+
return crypto.createHash("ripemd160").update(sha).digest("hex");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function assertHex(hex, expectedLen, label) {
|
|
322
|
+
const normalized = String(hex || "").trim().toLowerCase();
|
|
323
|
+
if (!/^[a-f0-9]+$/.test(normalized) || normalized.length !== expectedLen) {
|
|
324
|
+
throw new Error(`${label} must be ${expectedLen / 2} byte hex`);
|
|
325
|
+
}
|
|
326
|
+
return normalized;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function assertVariableHex(hex, label) {
|
|
330
|
+
const normalized = String(hex || "").trim().toLowerCase();
|
|
331
|
+
if (!/^[a-f0-9]+$/.test(normalized) || normalized.length === 0 || normalized.length % 2 !== 0) {
|
|
332
|
+
throw new Error(`${label} must be non-empty byte-aligned hex`);
|
|
333
|
+
}
|
|
334
|
+
return normalized;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Deterministic JSON: arrays in order, object keys sorted, JSON.stringify scalars.
|
|
338
|
+
function canonicalize(value) {
|
|
339
|
+
if (Array.isArray(value)) {
|
|
340
|
+
return `[${value.map((item) => canonicalize(item)).join(",")}]`;
|
|
341
|
+
}
|
|
342
|
+
if (value && typeof value === "object") {
|
|
343
|
+
const keys = Object.keys(value).sort();
|
|
344
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`).join(",")}}`;
|
|
345
|
+
}
|
|
346
|
+
return JSON.stringify(value);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function canonicalJsonSha256Hex(value) {
|
|
350
|
+
return sha256Hex(Buffer.from(canonicalize(value), "utf8"));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function base64urlDecode(value, label) {
|
|
354
|
+
const normalized = normalizeString(value);
|
|
355
|
+
if (!/^[A-Za-z0-9_-]+$/.test(normalized)) {
|
|
356
|
+
throw new Error(`${label} must be base64url`);
|
|
357
|
+
}
|
|
358
|
+
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
|
359
|
+
return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function readUInt64String(buf) {
|
|
363
|
+
return buf.readBigUInt64BE(0).toString();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function normalizeString(value) {
|
|
367
|
+
return String(value == null ? "" : value).trim();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function normalizeStringArray(values, label) {
|
|
371
|
+
if (values == null) return undefined;
|
|
372
|
+
if (!Array.isArray(values)) {
|
|
373
|
+
throw new Error(`${label} must be an array`);
|
|
374
|
+
}
|
|
375
|
+
const normalized = values
|
|
376
|
+
.map((value) => normalizeString(value))
|
|
377
|
+
.filter(Boolean);
|
|
378
|
+
return normalized.length ? normalized : undefined;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function normalizePlainObject(value, label) {
|
|
382
|
+
if (value == null) return undefined;
|
|
383
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
384
|
+
throw new Error(`${label} must be an object`);
|
|
385
|
+
}
|
|
386
|
+
return value;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Canonical event bundle
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
|
|
393
|
+
function canonicalizeEvent(event) {
|
|
394
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) {
|
|
395
|
+
throw new Error("event must be an object");
|
|
396
|
+
}
|
|
397
|
+
const normalized = {
|
|
398
|
+
version: normalizeString(event.version || CANONICAL_EVENT_BUNDLE_VERSION),
|
|
399
|
+
domain_id: normalizeString(event.domain_id),
|
|
400
|
+
sequence: normalizeString(event.sequence),
|
|
401
|
+
actor_id: normalizeString(event.actor_id),
|
|
402
|
+
adapter_source: normalizeString(event.adapter_source),
|
|
403
|
+
event_type: normalizeString(event.event_type),
|
|
404
|
+
event_hash: normalizeString(event.event_hash).toLowerCase()
|
|
405
|
+
};
|
|
406
|
+
Object.entries(normalized).forEach(([key, value]) => {
|
|
407
|
+
if (!value) {
|
|
408
|
+
throw new Error(`event.${key} required`);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
if (normalized.version !== CANONICAL_EVENT_BUNDLE_VERSION) {
|
|
412
|
+
throw new Error(`event.version must be ${CANONICAL_EVENT_BUNDLE_VERSION}`);
|
|
413
|
+
}
|
|
414
|
+
assertHex(normalized.event_hash, 64, "event.event_hash");
|
|
415
|
+
|
|
416
|
+
const timestampNs = normalizeString(event.timestamp_ns);
|
|
417
|
+
if (timestampNs) {
|
|
418
|
+
normalized.timestamp_ns = timestampNs;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const parentRefs = normalizeStringArray(event.parent_refs, "event.parent_refs");
|
|
422
|
+
if (parentRefs) {
|
|
423
|
+
normalized.parent_refs = parentRefs;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const capabilityRefs = normalizeStringArray(event.capability_refs, "event.capability_refs");
|
|
427
|
+
if (capabilityRefs) {
|
|
428
|
+
normalized.capability_refs = capabilityRefs;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const attestationRef = normalizeString(event.attestation_ref);
|
|
432
|
+
if (attestationRef) {
|
|
433
|
+
normalized.attestation_ref = attestationRef;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const semanticSlots = normalizePlainObject(event.semantic_slots, "event.semantic_slots");
|
|
437
|
+
if (semanticSlots) {
|
|
438
|
+
normalized.semantic_slots = semanticSlots;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return normalized;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function hashEvent(event) {
|
|
445
|
+
return canonicalJsonSha256Hex(canonicalizeEvent(event));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function buildCanonicalReceiptSigningMaterial(receipt) {
|
|
449
|
+
return {
|
|
450
|
+
agent_identity: receipt.agent_identity,
|
|
451
|
+
domain_id: receipt.domain_id,
|
|
452
|
+
event_commitment: receipt.event_commitment,
|
|
453
|
+
receipt_id: receipt.receipt_id,
|
|
454
|
+
sequence: receipt.sequence,
|
|
455
|
+
version: receipt.version
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ---------------------------------------------------------------------------
|
|
460
|
+
// Registries
|
|
461
|
+
// ---------------------------------------------------------------------------
|
|
462
|
+
|
|
463
|
+
function listProofVerificationMethods() {
|
|
464
|
+
return Object.values(PROOF_VERIFICATION_METHODS).map((method) => ({ ...method }));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function getProofVerificationMethod(methodId) {
|
|
468
|
+
const normalized = normalizeString(methodId);
|
|
469
|
+
const method = PROOF_VERIFICATION_METHODS[normalized];
|
|
470
|
+
return method ? { ...method } : null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function listNativeSignatureSuites() {
|
|
474
|
+
return Object.values(NATIVE_SIGNATURE_SUITES).map((suite) => ({ ...suite }));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function getNativeSignatureSuite(suiteId) {
|
|
478
|
+
const normalized = normalizeString(suiteId);
|
|
479
|
+
const suite = NATIVE_SIGNATURE_SUITES[normalized];
|
|
480
|
+
return suite ? { ...suite } : null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function listImplementedClassicalNativeSignatureSuites() {
|
|
484
|
+
return listNativeSignatureSuites().filter((suite) => suite.maturity === "implemented" && !suite.id.startsWith("ml-dsa-") && !suite.id.startsWith("slh-dsa-"));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function listImplementedPqNativeSignatureSuites() {
|
|
488
|
+
return listNativeSignatureSuites().filter((suite) => suite.maturity === "implemented" && (suite.id.startsWith("ml-dsa-") || suite.id.startsWith("slh-dsa-")));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function buildNativeHybridSuiteId(classicalSuiteId, pqSuiteId) {
|
|
492
|
+
const classicalSuite = getNativeSignatureSuite(classicalSuiteId);
|
|
493
|
+
if (!classicalSuite || classicalSuite.maturity !== "implemented") {
|
|
494
|
+
throw new Error("native hybrid classical signature suite not implemented");
|
|
495
|
+
}
|
|
496
|
+
if (classicalSuite.id.startsWith("ml-dsa-") || classicalSuite.id.startsWith("slh-dsa-")) {
|
|
497
|
+
throw new Error("native hybrid classical signature suite must be classical");
|
|
498
|
+
}
|
|
499
|
+
const pqSuite = getNativeSignatureSuite(pqSuiteId);
|
|
500
|
+
if (!pqSuite || pqSuite.maturity !== "implemented") {
|
|
501
|
+
throw new Error("native hybrid pq signature suite not implemented");
|
|
502
|
+
}
|
|
503
|
+
if (!pqSuite.id.startsWith("ml-dsa-") && !pqSuite.id.startsWith("slh-dsa-")) {
|
|
504
|
+
throw new Error("native hybrid pq signature suite must be pq");
|
|
505
|
+
}
|
|
506
|
+
return `${classicalSuite.id}+${pqSuite.id}`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function parseNativeHybridSuite(hybridSuiteId) {
|
|
510
|
+
const normalized = normalizeString(hybridSuiteId);
|
|
511
|
+
const [classicalSuiteId, pqSuiteId, extra] = normalized.split("+");
|
|
512
|
+
if (!classicalSuiteId || !pqSuiteId || extra) {
|
|
513
|
+
throw new Error("native hybrid signature suite unsupported");
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
id: buildNativeHybridSuiteId(classicalSuiteId, pqSuiteId),
|
|
517
|
+
classical_suite: getNativeSignatureSuite(classicalSuiteId),
|
|
518
|
+
pq_suite: getNativeSignatureSuite(pqSuiteId)
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function listImplementedNativeHybridSuites() {
|
|
523
|
+
const suites = [];
|
|
524
|
+
for (const classicalSuite of listImplementedClassicalNativeSignatureSuites()) {
|
|
525
|
+
for (const pqSuite of listImplementedPqNativeSignatureSuites()) {
|
|
526
|
+
suites.push({
|
|
527
|
+
id: buildNativeHybridSuiteId(classicalSuite.id, pqSuite.id),
|
|
528
|
+
classical_suite: classicalSuite.id,
|
|
529
|
+
pq_suite: pqSuite.id,
|
|
530
|
+
posture: "dual_signature_verified"
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return suites;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function listxoonyaNativeIdentityTypes() {
|
|
538
|
+
return Object.values(XOONYA_NATIVE_IDENTITY_TYPES).map((entry) => ({ ...entry }));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function getxoonyaNativeIdentityType(prefixHex) {
|
|
542
|
+
const normalized = normalizeString(prefixHex).toLowerCase();
|
|
543
|
+
const entry = XOONYA_NATIVE_IDENTITY_TYPES[normalized];
|
|
544
|
+
return entry ? { ...entry } : null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function isExternalAssertionMethod(algorithm) {
|
|
548
|
+
return EXTERNAL_PROOF_IMPORT_METHODS.includes(algorithm);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
// Native proof material (recomputed by the verifier, never signed here)
|
|
553
|
+
// ---------------------------------------------------------------------------
|
|
554
|
+
|
|
555
|
+
function expectedNativeDigestSignature(receipt, canonicalEvent) {
|
|
556
|
+
const material = {
|
|
557
|
+
method: XOONYA_NATIVE_SHA256_METHOD,
|
|
558
|
+
receipt_signing_material: buildCanonicalReceiptSigningMaterial(receipt),
|
|
559
|
+
event_commitment: hashEvent(canonicalEvent)
|
|
560
|
+
};
|
|
561
|
+
return `${XOONYA_NATIVE_SHA256_METHOD}:${sha256Hex(Buffer.from(canonicalize(material), "utf8"))}`;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function normalizePublicKeyPem(value) {
|
|
565
|
+
const pem = String(value || "").replace(/\r\n/g, "\n").trim();
|
|
566
|
+
if (!pem.includes("BEGIN PUBLIC KEY")) {
|
|
567
|
+
throw new Error("native public key must be SPKI PEM");
|
|
568
|
+
}
|
|
569
|
+
return `${pem}\n`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function nativePublicKeyRefFromPem(publicKeyPem, signatureSuite = "ed25519") {
|
|
573
|
+
const suite = getNativeSignatureSuite(signatureSuite);
|
|
574
|
+
if (!suite) {
|
|
575
|
+
throw new Error("native signature suite unsupported");
|
|
576
|
+
}
|
|
577
|
+
if (suite.maturity !== "implemented") {
|
|
578
|
+
throw new Error("native signature suite not implemented");
|
|
579
|
+
}
|
|
580
|
+
const publicKey = crypto.createPublicKey(normalizePublicKeyPem(publicKeyPem));
|
|
581
|
+
const der = publicKey.export({ type: "spki", format: "der" });
|
|
582
|
+
return `${suite.id}-spki-sha256:${sha256Hex(der)}`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function nativeHybridPublicKeyRefFromPems(classicalPublicKeyPem, pqPublicKeyPem, hybridSuiteId) {
|
|
586
|
+
parseNativeHybridSuite(hybridSuiteId);
|
|
587
|
+
const classicalDer = crypto.createPublicKey(normalizePublicKeyPem(classicalPublicKeyPem)).export({ type: "spki", format: "der" });
|
|
588
|
+
const pqDer = crypto.createPublicKey(normalizePublicKeyPem(pqPublicKeyPem)).export({ type: "spki", format: "der" });
|
|
589
|
+
return `${hybridSuiteId}-hybrid-spki-sha256:${sha256Hex(Buffer.concat([classicalDer, pqDer]))}`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function nativeSigningHashForSuite(suite) {
|
|
593
|
+
return suite && suite.signing_hash ? suite.signing_hash : null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function assertNativeKeyMatchesSuite(keyObject, suite, label) {
|
|
597
|
+
if (suite && suite.key_type && keyObject.asymmetricKeyType !== suite.key_type) {
|
|
598
|
+
throw new Error(`${label} key type must be ${suite.key_type}`);
|
|
599
|
+
}
|
|
600
|
+
if (suite && suite.curve) {
|
|
601
|
+
const details = keyObject.asymmetricKeyDetails || {};
|
|
602
|
+
if (details.namedCurve !== suite.curve) {
|
|
603
|
+
throw new Error(`${label} curve must be ${suite.curve}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function buildNativeSignedProofMaterial(receipt, canonicalEvent, proofDescriptor) {
|
|
609
|
+
return {
|
|
610
|
+
identity_ref: proofDescriptor.identity_ref,
|
|
611
|
+
method: XOONYA_NATIVE_SIGNED_METHOD,
|
|
612
|
+
public_key_ref: proofDescriptor.public_key_ref,
|
|
613
|
+
receipt_signing_material: buildCanonicalReceiptSigningMaterial(receipt),
|
|
614
|
+
event_commitment: hashEvent(canonicalEvent),
|
|
615
|
+
signature_suite: proofDescriptor.signature_suite,
|
|
616
|
+
standard_version: XOONYA_NATIVE_PROOF_FORMAT_VERSION
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function buildNativeHybridProofMaterial(receipt, canonicalEvent, proofDescriptor) {
|
|
621
|
+
return {
|
|
622
|
+
identity_ref: proofDescriptor.identity_ref,
|
|
623
|
+
method: XOONYA_NATIVE_HYBRID_METHOD,
|
|
624
|
+
hybrid_suite: proofDescriptor.hybrid_suite,
|
|
625
|
+
hybrid_public_key_ref: proofDescriptor.hybrid_public_key_ref,
|
|
626
|
+
classical_public_key_ref: proofDescriptor.classical_public_key_ref,
|
|
627
|
+
pq_public_key_ref: proofDescriptor.pq_public_key_ref,
|
|
628
|
+
receipt_signing_material: buildCanonicalReceiptSigningMaterial(receipt),
|
|
629
|
+
event_commitment: hashEvent(canonicalEvent),
|
|
630
|
+
standard_version: XOONYA_NATIVE_PROOF_FORMAT_VERSION
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Runtime capability probe: can this Node build verify the classical and PQ
|
|
635
|
+
// halves of a hybrid suite? Uses a throwaway key pair; nothing is persisted.
|
|
636
|
+
const NATIVE_HYBRID_RUNTIME_SUPPORT_CACHE = new Map();
|
|
637
|
+
|
|
638
|
+
function runtimeSupportsNativeHybridSuite(hybridSuiteId) {
|
|
639
|
+
const cacheKey = normalizeString(hybridSuiteId);
|
|
640
|
+
if (NATIVE_HYBRID_RUNTIME_SUPPORT_CACHE.has(cacheKey)) {
|
|
641
|
+
return NATIVE_HYBRID_RUNTIME_SUPPORT_CACHE.get(cacheKey);
|
|
642
|
+
}
|
|
643
|
+
const supported = probeNativeHybridRuntimeSupport(cacheKey);
|
|
644
|
+
NATIVE_HYBRID_RUNTIME_SUPPORT_CACHE.set(cacheKey, supported);
|
|
645
|
+
return supported;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function probeNativeHybridRuntimeSupport(hybridSuiteId) {
|
|
649
|
+
try {
|
|
650
|
+
const hybridSuite = parseNativeHybridSuite(hybridSuiteId);
|
|
651
|
+
const classicalPair = hybridSuite.classical_suite.id === "secp256k1"
|
|
652
|
+
? crypto.generateKeyPairSync("ec", { namedCurve: "secp256k1" })
|
|
653
|
+
: hybridSuite.classical_suite.id === "p256"
|
|
654
|
+
? crypto.generateKeyPairSync("ec", { namedCurve: "P-256" })
|
|
655
|
+
: crypto.generateKeyPairSync(hybridSuite.classical_suite.key_type);
|
|
656
|
+
const pqPair = crypto.generateKeyPairSync(hybridSuite.pq_suite.key_type);
|
|
657
|
+
const probe = Buffer.from(`xoonya-hybrid-runtime-probe:${hybridSuiteId}`, "utf8");
|
|
658
|
+
const classicalSignature = crypto.sign(nativeSigningHashForSuite(hybridSuite.classical_suite), probe, classicalPair.privateKey);
|
|
659
|
+
const pqSignature = crypto.sign(nativeSigningHashForSuite(hybridSuite.pq_suite), probe, pqPair.privateKey);
|
|
660
|
+
return (
|
|
661
|
+
crypto.verify(nativeSigningHashForSuite(hybridSuite.classical_suite), probe, classicalPair.publicKey, classicalSignature) &&
|
|
662
|
+
crypto.verify(nativeSigningHashForSuite(hybridSuite.pq_suite), probe, pqPair.publicKey, pqSignature)
|
|
663
|
+
);
|
|
664
|
+
} catch (_error) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function normalizeNativeAnchorRefs(anchorRefs, label = "native anchors") {
|
|
670
|
+
if (!Array.isArray(anchorRefs) || anchorRefs.length === 0) {
|
|
671
|
+
throw new Error(`${label} must be a non-empty array`);
|
|
672
|
+
}
|
|
673
|
+
if (anchorRefs.length > 32) {
|
|
674
|
+
throw new Error(`${label} must contain at most 32 entries`);
|
|
675
|
+
}
|
|
676
|
+
const normalized = anchorRefs.map((anchor, index) => {
|
|
677
|
+
if (!anchor || typeof anchor !== "object" || Array.isArray(anchor)) {
|
|
678
|
+
throw new Error(`${label}[${index}] must be an object`);
|
|
679
|
+
}
|
|
680
|
+
const entry = {
|
|
681
|
+
anchor_id: normalizeString(anchor.anchor_id || anchor.id),
|
|
682
|
+
anchor_kind: normalizeString(anchor.anchor_kind || anchor.kind),
|
|
683
|
+
anchor_ref: normalizeString(anchor.anchor_ref || anchor.ref),
|
|
684
|
+
anchor_hash_sha256: normalizeString(anchor.anchor_hash_sha256 || anchor.hash_sha256 || anchor.anchor_hash).toLowerCase()
|
|
685
|
+
};
|
|
686
|
+
if (!entry.anchor_id) throw new Error(`${label}[${index}].anchor_id required`);
|
|
687
|
+
if (!entry.anchor_kind) throw new Error(`${label}[${index}].anchor_kind required`);
|
|
688
|
+
if (!entry.anchor_ref) throw new Error(`${label}[${index}].anchor_ref required`);
|
|
689
|
+
entry.anchor_hash_sha256 = assertHex(entry.anchor_hash_sha256, 64, `${label}[${index}].anchor_hash_sha256`);
|
|
690
|
+
|
|
691
|
+
const network = normalizeString(anchor.network);
|
|
692
|
+
if (network) entry.network = network;
|
|
693
|
+
const finalityState = normalizeString(anchor.finality_state);
|
|
694
|
+
if (finalityState) entry.finality_state = finalityState;
|
|
695
|
+
const observedAtIso = normalizeString(anchor.observed_at_iso);
|
|
696
|
+
if (observedAtIso) entry.observed_at_iso = observedAtIso;
|
|
697
|
+
const providerRef = normalizeString(anchor.provider_ref);
|
|
698
|
+
if (providerRef) entry.provider_ref = providerRef;
|
|
699
|
+
const inclusionProofRef = normalizeString(anchor.inclusion_proof_ref);
|
|
700
|
+
if (inclusionProofRef) entry.inclusion_proof_ref = inclusionProofRef;
|
|
701
|
+
return entry;
|
|
702
|
+
});
|
|
703
|
+
return normalized.sort((a, b) => canonicalize(a).localeCompare(canonicalize(b)));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function buildNativeAnchorBindingMaterial(receipt, canonicalEvent, anchorRefs) {
|
|
707
|
+
const normalizedAnchors = normalizeNativeAnchorRefs(anchorRefs);
|
|
708
|
+
return {
|
|
709
|
+
anchor_refs: normalizedAnchors,
|
|
710
|
+
base_method: XOONYA_NATIVE_SHA256_METHOD,
|
|
711
|
+
event_commitment: hashEvent(canonicalEvent),
|
|
712
|
+
method: XOONYA_NATIVE_ANCHORED_METHOD,
|
|
713
|
+
receipt_signing_material: buildCanonicalReceiptSigningMaterial(receipt),
|
|
714
|
+
standard_version: XOONYA_NATIVE_PROOF_FORMAT_VERSION
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ---------------------------------------------------------------------------
|
|
719
|
+
// Verification options, trust roots, verification classes
|
|
720
|
+
// ---------------------------------------------------------------------------
|
|
721
|
+
|
|
722
|
+
function strictVerifyEnabledByEnvironment() {
|
|
723
|
+
const raw = normalizeString(process.env[STRICT_VERIFY_ENV]).toLowerCase();
|
|
724
|
+
return raw === "1" || raw === "true";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function resolveProofVerificationOptions(options) {
|
|
728
|
+
const source = options && typeof options === "object" && !Array.isArray(options) ? options : {};
|
|
729
|
+
const trustedPublicKeyRefs = source.trusted_public_key_refs &&
|
|
730
|
+
typeof source.trusted_public_key_refs === "object" &&
|
|
731
|
+
!Array.isArray(source.trusted_public_key_refs)
|
|
732
|
+
? source.trusted_public_key_refs
|
|
733
|
+
: null;
|
|
734
|
+
const expectedPublicKeyRef = normalizeString(source.expected_public_key_ref);
|
|
735
|
+
const expectedClassicalPublicKeyRef = normalizeString(source.expected_classical_public_key_ref);
|
|
736
|
+
const expectedPqPublicKeyRef = normalizeString(source.expected_pq_public_key_ref);
|
|
737
|
+
const expectedHybridPublicKeyRef = normalizeString(source.expected_hybrid_public_key_ref);
|
|
738
|
+
return {
|
|
739
|
+
__resolved_verification_options: true,
|
|
740
|
+
strict: source.strict === true || strictVerifyEnabledByEnvironment(),
|
|
741
|
+
trust_root_applied: Boolean(
|
|
742
|
+
trustedPublicKeyRefs ||
|
|
743
|
+
expectedPublicKeyRef ||
|
|
744
|
+
expectedClassicalPublicKeyRef ||
|
|
745
|
+
expectedPqPublicKeyRef ||
|
|
746
|
+
expectedHybridPublicKeyRef
|
|
747
|
+
),
|
|
748
|
+
expected_public_key_ref: expectedPublicKeyRef,
|
|
749
|
+
expected_classical_public_key_ref: expectedClassicalPublicKeyRef,
|
|
750
|
+
expected_pq_public_key_ref: expectedPqPublicKeyRef,
|
|
751
|
+
expected_hybrid_public_key_ref: expectedHybridPublicKeyRef,
|
|
752
|
+
trusted_public_key_refs: trustedPublicKeyRefs
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function normalizeTrustedPublicKeyRefList(value) {
|
|
757
|
+
if (value == null) return [];
|
|
758
|
+
const list = Array.isArray(value) ? value : [value];
|
|
759
|
+
return list.map((entry) => normalizeString(entry)).filter(Boolean);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function lookupTrustedPublicKeyEntry(trustedPublicKeyRefs, identityRef, agentIdentity) {
|
|
763
|
+
if (!trustedPublicKeyRefs) return undefined;
|
|
764
|
+
for (const candidate of [normalizeString(identityRef), normalizeString(agentIdentity)]) {
|
|
765
|
+
if (candidate && Object.prototype.hasOwnProperty.call(trustedPublicKeyRefs, candidate)) {
|
|
766
|
+
return trustedPublicKeyRefs[candidate];
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return undefined;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function trustedPublicKeyRefsForLane(entry, lane) {
|
|
773
|
+
if (entry == null) return { declared: false, refs: [] };
|
|
774
|
+
if (typeof entry === "string" || Array.isArray(entry)) {
|
|
775
|
+
return lane === "classical"
|
|
776
|
+
? { declared: true, refs: normalizeTrustedPublicKeyRefList(entry) }
|
|
777
|
+
: { declared: false, refs: [] };
|
|
778
|
+
}
|
|
779
|
+
if (typeof entry !== "object") return { declared: false, refs: [] };
|
|
780
|
+
const laneValue = lane === "classical"
|
|
781
|
+
? (entry.classical != null ? entry.classical : (entry.classical_public_key_ref != null ? entry.classical_public_key_ref : entry.public_key_ref))
|
|
782
|
+
: lane === "pq"
|
|
783
|
+
? (entry.pq != null ? entry.pq : entry.pq_public_key_ref)
|
|
784
|
+
: (entry.hybrid != null ? entry.hybrid : entry.hybrid_public_key_ref);
|
|
785
|
+
if (laneValue == null) return { declared: false, refs: [] };
|
|
786
|
+
return { declared: true, refs: normalizeTrustedPublicKeyRefList(laneValue) };
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function nativeSignedPublicKeyTrusted(verification, publicKeyRef, identityRef, agentIdentity) {
|
|
790
|
+
if (!verification || !verification.trust_root_applied) return true;
|
|
791
|
+
let constrained = false;
|
|
792
|
+
if (verification.expected_public_key_ref) {
|
|
793
|
+
constrained = true;
|
|
794
|
+
if (publicKeyRef !== verification.expected_public_key_ref) return false;
|
|
795
|
+
}
|
|
796
|
+
if (verification.expected_classical_public_key_ref) {
|
|
797
|
+
constrained = true;
|
|
798
|
+
if (publicKeyRef !== verification.expected_classical_public_key_ref) return false;
|
|
799
|
+
}
|
|
800
|
+
if (verification.trusted_public_key_refs) {
|
|
801
|
+
constrained = true;
|
|
802
|
+
const entry = lookupTrustedPublicKeyEntry(verification.trusted_public_key_refs, identityRef, agentIdentity);
|
|
803
|
+
const lane = trustedPublicKeyRefsForLane(entry, "classical");
|
|
804
|
+
if (!lane.declared || !lane.refs.includes(publicKeyRef)) return false;
|
|
805
|
+
}
|
|
806
|
+
return constrained;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function nativeHybridPublicKeyTrusted(verification, refs, identityRef, agentIdentity) {
|
|
810
|
+
if (!verification || !verification.trust_root_applied) return true;
|
|
811
|
+
let constrained = false;
|
|
812
|
+
if (verification.expected_public_key_ref) {
|
|
813
|
+
constrained = true;
|
|
814
|
+
if (refs.classical !== verification.expected_public_key_ref && refs.hybrid !== verification.expected_public_key_ref) return false;
|
|
815
|
+
}
|
|
816
|
+
if (verification.expected_classical_public_key_ref) {
|
|
817
|
+
constrained = true;
|
|
818
|
+
if (refs.classical !== verification.expected_classical_public_key_ref) return false;
|
|
819
|
+
}
|
|
820
|
+
if (verification.expected_pq_public_key_ref) {
|
|
821
|
+
constrained = true;
|
|
822
|
+
if (refs.pq !== verification.expected_pq_public_key_ref) return false;
|
|
823
|
+
}
|
|
824
|
+
if (verification.expected_hybrid_public_key_ref) {
|
|
825
|
+
constrained = true;
|
|
826
|
+
if (refs.hybrid !== verification.expected_hybrid_public_key_ref) return false;
|
|
827
|
+
}
|
|
828
|
+
if (verification.trusted_public_key_refs) {
|
|
829
|
+
const entry = lookupTrustedPublicKeyEntry(verification.trusted_public_key_refs, identityRef, agentIdentity);
|
|
830
|
+
const classicalLane = trustedPublicKeyRefsForLane(entry, "classical");
|
|
831
|
+
const pqLane = trustedPublicKeyRefsForLane(entry, "pq");
|
|
832
|
+
const hybridLane = trustedPublicKeyRefsForLane(entry, "hybrid");
|
|
833
|
+
if (!classicalLane.declared && !pqLane.declared && !hybridLane.declared) return false;
|
|
834
|
+
if (classicalLane.declared && !classicalLane.refs.includes(refs.classical) && !classicalLane.refs.includes(refs.hybrid)) return false;
|
|
835
|
+
if (pqLane.declared && !pqLane.refs.includes(refs.pq)) return false;
|
|
836
|
+
if (hybridLane.declared && !hybridLane.refs.includes(refs.hybrid)) return false;
|
|
837
|
+
constrained = true;
|
|
838
|
+
}
|
|
839
|
+
return constrained;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function verificationClassForMethod(algorithm) {
|
|
843
|
+
const normalized = normalizeString(algorithm);
|
|
844
|
+
for (const entry of Object.values(PROOF_VERIFICATION_CLASSES)) {
|
|
845
|
+
if (entry.methods.includes(normalized)) return entry.id;
|
|
846
|
+
}
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function listProofVerificationClasses() {
|
|
851
|
+
return Object.values(PROOF_VERIFICATION_CLASSES).map((entry) => ({ ...entry, methods: entry.methods.slice() }));
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function finalizeProofMethodVerification(result, algorithm, verification) {
|
|
855
|
+
const verificationClass = verificationClassForMethod(algorithm);
|
|
856
|
+
const classEntry = verificationClass ? PROOF_VERIFICATION_CLASSES[verificationClass] : null;
|
|
857
|
+
const decorated = {
|
|
858
|
+
...result,
|
|
859
|
+
verification_class: verificationClass,
|
|
860
|
+
cryptographically_verified: result.valid === true && Boolean(classEntry && classEntry.cryptographically_verified),
|
|
861
|
+
strict: verification.strict === true,
|
|
862
|
+
trust_root_applied: verification.trust_root_applied === true
|
|
863
|
+
};
|
|
864
|
+
if (decorated.valid === true && decorated.strict && classEntry && !classEntry.strict_mode_accepted) {
|
|
865
|
+
return {
|
|
866
|
+
...decorated,
|
|
867
|
+
valid: false,
|
|
868
|
+
reason: verificationClass === VERIFICATION_CLASS_STRUCTURAL
|
|
869
|
+
? STRICT_EXTERNAL_VERIFICATION_REASON
|
|
870
|
+
: STRICT_DIGEST_ONLY_REASON,
|
|
871
|
+
cryptographically_verified: false
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
return decorated;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// ---------------------------------------------------------------------------
|
|
878
|
+
// Native method verifiers
|
|
879
|
+
// ---------------------------------------------------------------------------
|
|
880
|
+
|
|
881
|
+
function verifyNativeSignedReceiptProof(receipt, ceb, verification = null) {
|
|
882
|
+
try {
|
|
883
|
+
const proof = receipt.native_proof;
|
|
884
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)) {
|
|
885
|
+
return { valid: false, reason: "native_proof_missing" };
|
|
886
|
+
}
|
|
887
|
+
if (normalizeString(proof.method) !== XOONYA_NATIVE_SIGNED_METHOD) {
|
|
888
|
+
return { valid: false, reason: "native_proof_method_mismatch" };
|
|
889
|
+
}
|
|
890
|
+
const signatureSuite = normalizeString(proof.signature_suite);
|
|
891
|
+
const suite = getNativeSignatureSuite(signatureSuite);
|
|
892
|
+
if (!suite) {
|
|
893
|
+
return { valid: false, reason: "native_signature_suite_unsupported" };
|
|
894
|
+
}
|
|
895
|
+
if (suite.maturity !== "implemented") {
|
|
896
|
+
return { valid: false, reason: "native_signature_suite_not_implemented" };
|
|
897
|
+
}
|
|
898
|
+
const identityRef = normalizeString(proof.identity_ref);
|
|
899
|
+
if (!identityRef) {
|
|
900
|
+
return { valid: false, reason: "native_identity_ref_missing" };
|
|
901
|
+
}
|
|
902
|
+
const publicKeyMaterial = proof.public_key_material;
|
|
903
|
+
if (!publicKeyMaterial || typeof publicKeyMaterial !== "object" || Array.isArray(publicKeyMaterial)) {
|
|
904
|
+
return { valid: false, reason: "native_public_key_material_missing" };
|
|
905
|
+
}
|
|
906
|
+
if (normalizeString(publicKeyMaterial.format) !== suite.public_key_format) {
|
|
907
|
+
return { valid: false, reason: "native_public_key_format_mismatch" };
|
|
908
|
+
}
|
|
909
|
+
const publicKeyPem = normalizePublicKeyPem(publicKeyMaterial.value);
|
|
910
|
+
const publicKey = crypto.createPublicKey(publicKeyPem);
|
|
911
|
+
assertNativeKeyMatchesSuite(publicKey, suite, "native public key");
|
|
912
|
+
const expectedPublicKeyRef = nativePublicKeyRefFromPem(publicKeyPem, suite.id);
|
|
913
|
+
if (normalizeString(proof.public_key_ref) !== expectedPublicKeyRef) {
|
|
914
|
+
return { valid: false, reason: "native_public_key_ref_mismatch" };
|
|
915
|
+
}
|
|
916
|
+
const proofMaterial = proof.proof_material;
|
|
917
|
+
if (!proofMaterial || typeof proofMaterial !== "object" || Array.isArray(proofMaterial)) {
|
|
918
|
+
return { valid: false, reason: "native_proof_material_missing" };
|
|
919
|
+
}
|
|
920
|
+
const signature = normalizeString(proofMaterial.signature);
|
|
921
|
+
if (!signature) {
|
|
922
|
+
return { valid: false, reason: "native_signature_missing" };
|
|
923
|
+
}
|
|
924
|
+
const expectedCompactSignature = `${XOONYA_NATIVE_SIGNED_METHOD}:${suite.id}:${signature}`;
|
|
925
|
+
if (normalizeString(receipt.signature) !== expectedCompactSignature) {
|
|
926
|
+
return { valid: false, reason: "signature_native_signed_mismatch" };
|
|
927
|
+
}
|
|
928
|
+
const signingMaterial = buildNativeSignedProofMaterial(receipt, ceb, {
|
|
929
|
+
identity_ref: identityRef,
|
|
930
|
+
public_key_ref: expectedPublicKeyRef,
|
|
931
|
+
signature_suite: suite.id
|
|
932
|
+
});
|
|
933
|
+
const signingBytes = Buffer.from(canonicalize(signingMaterial), "utf8");
|
|
934
|
+
const expectedSigningHash = sha256Hex(signingBytes);
|
|
935
|
+
if (normalizeString(proofMaterial.signing_material_sha256).toLowerCase() !== expectedSigningHash) {
|
|
936
|
+
return { valid: false, reason: "native_signing_material_hash_mismatch" };
|
|
937
|
+
}
|
|
938
|
+
const signatureBytes = base64urlDecode(signature, "native signature");
|
|
939
|
+
const verified = crypto.verify(nativeSigningHashForSuite(suite), signingBytes, publicKey, signatureBytes);
|
|
940
|
+
if (!verified) {
|
|
941
|
+
return { valid: false, reason: "native_signature_invalid" };
|
|
942
|
+
}
|
|
943
|
+
if (!nativeSignedPublicKeyTrusted(verification, expectedPublicKeyRef, identityRef, receipt.agent_identity)) {
|
|
944
|
+
return { valid: false, reason: NATIVE_PUBLIC_KEY_NOT_TRUSTED_REASON, public_key_ref: expectedPublicKeyRef };
|
|
945
|
+
}
|
|
946
|
+
return {
|
|
947
|
+
valid: true,
|
|
948
|
+
verification_state: "verified_native_signature",
|
|
949
|
+
signature_suite: suite.id,
|
|
950
|
+
public_key_ref: expectedPublicKeyRef
|
|
951
|
+
};
|
|
952
|
+
} catch (error) {
|
|
953
|
+
return { valid: false, reason: error.message };
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function verifyNativeHybridReceiptProof(receipt, ceb, verification = null) {
|
|
958
|
+
try {
|
|
959
|
+
const proof = receipt.native_proof;
|
|
960
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)) {
|
|
961
|
+
return { valid: false, reason: "native_proof_missing" };
|
|
962
|
+
}
|
|
963
|
+
if (normalizeString(proof.method) !== XOONYA_NATIVE_HYBRID_METHOD) {
|
|
964
|
+
return { valid: false, reason: "native_proof_method_mismatch" };
|
|
965
|
+
}
|
|
966
|
+
const hybridSuite = parseNativeHybridSuite(proof.hybrid_suite);
|
|
967
|
+
if (!runtimeSupportsNativeHybridSuite(hybridSuite.id)) {
|
|
968
|
+
return { valid: false, reason: "native_hybrid_runtime_validator_unavailable" };
|
|
969
|
+
}
|
|
970
|
+
const identityRef = normalizeString(proof.identity_ref);
|
|
971
|
+
if (!identityRef) {
|
|
972
|
+
return { valid: false, reason: "native_identity_ref_missing" };
|
|
973
|
+
}
|
|
974
|
+
const classicalPublicKeyMaterial = proof.classical_public_key_material;
|
|
975
|
+
const pqPublicKeyMaterial = proof.pq_public_key_material;
|
|
976
|
+
if (!classicalPublicKeyMaterial || typeof classicalPublicKeyMaterial !== "object" || Array.isArray(classicalPublicKeyMaterial)) {
|
|
977
|
+
return { valid: false, reason: "native_hybrid_classical_public_key_material_missing" };
|
|
978
|
+
}
|
|
979
|
+
if (!pqPublicKeyMaterial || typeof pqPublicKeyMaterial !== "object" || Array.isArray(pqPublicKeyMaterial)) {
|
|
980
|
+
return { valid: false, reason: "native_hybrid_pq_public_key_material_missing" };
|
|
981
|
+
}
|
|
982
|
+
if (normalizeString(classicalPublicKeyMaterial.format) !== hybridSuite.classical_suite.public_key_format) {
|
|
983
|
+
return { valid: false, reason: "native_hybrid_classical_public_key_format_mismatch" };
|
|
984
|
+
}
|
|
985
|
+
if (normalizeString(pqPublicKeyMaterial.format) !== hybridSuite.pq_suite.public_key_format) {
|
|
986
|
+
return { valid: false, reason: "native_hybrid_pq_public_key_format_mismatch" };
|
|
987
|
+
}
|
|
988
|
+
const classicalPublicKeyPem = normalizePublicKeyPem(classicalPublicKeyMaterial.value);
|
|
989
|
+
const pqPublicKeyPem = normalizePublicKeyPem(pqPublicKeyMaterial.value);
|
|
990
|
+
const classicalPublicKey = crypto.createPublicKey(classicalPublicKeyPem);
|
|
991
|
+
const pqPublicKey = crypto.createPublicKey(pqPublicKeyPem);
|
|
992
|
+
assertNativeKeyMatchesSuite(classicalPublicKey, hybridSuite.classical_suite, "native hybrid classical public key");
|
|
993
|
+
assertNativeKeyMatchesSuite(pqPublicKey, hybridSuite.pq_suite, "native hybrid pq public key");
|
|
994
|
+
const expectedClassicalPublicKeyRef = nativePublicKeyRefFromPem(classicalPublicKeyPem, hybridSuite.classical_suite.id);
|
|
995
|
+
const expectedPqPublicKeyRef = nativePublicKeyRefFromPem(pqPublicKeyPem, hybridSuite.pq_suite.id);
|
|
996
|
+
const expectedHybridPublicKeyRef = nativeHybridPublicKeyRefFromPems(classicalPublicKeyPem, pqPublicKeyPem, hybridSuite.id);
|
|
997
|
+
if (normalizeString(proof.classical_public_key_ref) !== expectedClassicalPublicKeyRef) {
|
|
998
|
+
return { valid: false, reason: "native_hybrid_classical_public_key_ref_mismatch" };
|
|
999
|
+
}
|
|
1000
|
+
if (normalizeString(proof.pq_public_key_ref) !== expectedPqPublicKeyRef) {
|
|
1001
|
+
return { valid: false, reason: "native_hybrid_pq_public_key_ref_mismatch" };
|
|
1002
|
+
}
|
|
1003
|
+
if (normalizeString(proof.hybrid_public_key_ref) !== expectedHybridPublicKeyRef) {
|
|
1004
|
+
return { valid: false, reason: "native_hybrid_public_key_ref_mismatch" };
|
|
1005
|
+
}
|
|
1006
|
+
const proofMaterial = proof.proof_material;
|
|
1007
|
+
if (!proofMaterial || typeof proofMaterial !== "object" || Array.isArray(proofMaterial)) {
|
|
1008
|
+
return { valid: false, reason: "native_proof_material_missing" };
|
|
1009
|
+
}
|
|
1010
|
+
const classicalSignature = normalizeString(proofMaterial.classical_signature);
|
|
1011
|
+
const pqSignature = normalizeString(proofMaterial.pq_signature);
|
|
1012
|
+
if (!classicalSignature) {
|
|
1013
|
+
return { valid: false, reason: "native_hybrid_classical_signature_missing" };
|
|
1014
|
+
}
|
|
1015
|
+
if (!pqSignature) {
|
|
1016
|
+
return { valid: false, reason: "native_hybrid_pq_signature_missing" };
|
|
1017
|
+
}
|
|
1018
|
+
const expectedCompactSignature = `${XOONYA_NATIVE_HYBRID_METHOD}:${hybridSuite.id}:${classicalSignature}.${pqSignature}`;
|
|
1019
|
+
if (normalizeString(receipt.signature) !== expectedCompactSignature) {
|
|
1020
|
+
return { valid: false, reason: "signature_native_hybrid_mismatch" };
|
|
1021
|
+
}
|
|
1022
|
+
const signingMaterial = buildNativeHybridProofMaterial(receipt, ceb, {
|
|
1023
|
+
identity_ref: identityRef,
|
|
1024
|
+
hybrid_suite: hybridSuite.id,
|
|
1025
|
+
hybrid_public_key_ref: expectedHybridPublicKeyRef,
|
|
1026
|
+
classical_public_key_ref: expectedClassicalPublicKeyRef,
|
|
1027
|
+
pq_public_key_ref: expectedPqPublicKeyRef
|
|
1028
|
+
});
|
|
1029
|
+
const signingBytes = Buffer.from(canonicalize(signingMaterial), "utf8");
|
|
1030
|
+
const expectedSigningHash = sha256Hex(signingBytes);
|
|
1031
|
+
if (normalizeString(proofMaterial.signing_material_sha256).toLowerCase() !== expectedSigningHash) {
|
|
1032
|
+
return { valid: false, reason: "native_hybrid_signing_material_hash_mismatch" };
|
|
1033
|
+
}
|
|
1034
|
+
const classicalSignatureBytes = base64urlDecode(classicalSignature, "native hybrid classical signature");
|
|
1035
|
+
const pqSignatureBytes = base64urlDecode(pqSignature, "native hybrid pq signature");
|
|
1036
|
+
if (!crypto.verify(nativeSigningHashForSuite(hybridSuite.classical_suite), signingBytes, classicalPublicKey, classicalSignatureBytes)) {
|
|
1037
|
+
return { valid: false, reason: "native_hybrid_classical_signature_invalid" };
|
|
1038
|
+
}
|
|
1039
|
+
if (!crypto.verify(nativeSigningHashForSuite(hybridSuite.pq_suite), signingBytes, pqPublicKey, pqSignatureBytes)) {
|
|
1040
|
+
return { valid: false, reason: "native_hybrid_pq_signature_invalid" };
|
|
1041
|
+
}
|
|
1042
|
+
const publicKeyRefs = {
|
|
1043
|
+
classical: expectedClassicalPublicKeyRef,
|
|
1044
|
+
pq: expectedPqPublicKeyRef,
|
|
1045
|
+
hybrid: expectedHybridPublicKeyRef
|
|
1046
|
+
};
|
|
1047
|
+
if (!nativeHybridPublicKeyTrusted(verification, publicKeyRefs, identityRef, receipt.agent_identity)) {
|
|
1048
|
+
return {
|
|
1049
|
+
valid: false,
|
|
1050
|
+
reason: NATIVE_HYBRID_PUBLIC_KEY_NOT_TRUSTED_REASON,
|
|
1051
|
+
public_key_ref: expectedHybridPublicKeyRef,
|
|
1052
|
+
classical_public_key_ref: expectedClassicalPublicKeyRef,
|
|
1053
|
+
pq_public_key_ref: expectedPqPublicKeyRef
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
valid: true,
|
|
1058
|
+
verification_state: "verified_native_hybrid_signature",
|
|
1059
|
+
signature_suite: hybridSuite.id,
|
|
1060
|
+
public_key_ref: expectedHybridPublicKeyRef,
|
|
1061
|
+
classical_public_key_ref: expectedClassicalPublicKeyRef,
|
|
1062
|
+
pq_public_key_ref: expectedPqPublicKeyRef
|
|
1063
|
+
};
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
return { valid: false, reason: error.message };
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function verifyNativeAnchoredReceiptProof(receipt, ceb, bundle) {
|
|
1070
|
+
try {
|
|
1071
|
+
const proof = receipt.native_proof;
|
|
1072
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)) {
|
|
1073
|
+
return { valid: false, reason: "native_proof_missing" };
|
|
1074
|
+
}
|
|
1075
|
+
if (normalizeString(proof.method) !== XOONYA_NATIVE_ANCHORED_METHOD) {
|
|
1076
|
+
return { valid: false, reason: "native_proof_method_mismatch" };
|
|
1077
|
+
}
|
|
1078
|
+
if (normalizeString(proof.base_method) !== XOONYA_NATIVE_SHA256_METHOD) {
|
|
1079
|
+
return { valid: false, reason: "native_anchor_base_method_unsupported" };
|
|
1080
|
+
}
|
|
1081
|
+
if (!bundle || !Array.isArray(bundle.anchors)) {
|
|
1082
|
+
return { valid: false, reason: "native_anchor_bundle_missing" };
|
|
1083
|
+
}
|
|
1084
|
+
const proofAnchors = normalizeNativeAnchorRefs(proof.anchor_refs);
|
|
1085
|
+
const bundleAnchors = normalizeNativeAnchorRefs(bundle.anchors);
|
|
1086
|
+
if (canonicalize(proofAnchors) !== canonicalize(bundleAnchors)) {
|
|
1087
|
+
return { valid: false, reason: "native_anchor_bundle_mismatch" };
|
|
1088
|
+
}
|
|
1089
|
+
const proofMaterial = proof.proof_material;
|
|
1090
|
+
if (!proofMaterial || typeof proofMaterial !== "object" || Array.isArray(proofMaterial)) {
|
|
1091
|
+
return { valid: false, reason: "native_anchor_proof_material_missing" };
|
|
1092
|
+
}
|
|
1093
|
+
const expectedAnchorRoot = canonicalJsonSha256Hex(proofAnchors);
|
|
1094
|
+
if (normalizeString(proofMaterial.anchor_root_sha256).toLowerCase() !== expectedAnchorRoot) {
|
|
1095
|
+
return { valid: false, reason: "native_anchor_root_mismatch" };
|
|
1096
|
+
}
|
|
1097
|
+
const bindingMaterial = buildNativeAnchorBindingMaterial(receipt, ceb, proofAnchors);
|
|
1098
|
+
const bindingHash = sha256Hex(Buffer.from(canonicalize(bindingMaterial), "utf8"));
|
|
1099
|
+
if (normalizeString(proofMaterial.binding_material_sha256).toLowerCase() !== bindingHash) {
|
|
1100
|
+
return { valid: false, reason: "native_anchor_binding_hash_mismatch" };
|
|
1101
|
+
}
|
|
1102
|
+
if (normalizeString(receipt.signature) !== `${XOONYA_NATIVE_ANCHORED_METHOD}:${bindingHash}`) {
|
|
1103
|
+
return { valid: false, reason: "signature_native_anchored_mismatch" };
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
valid: true,
|
|
1107
|
+
verification_state: "verified_native_anchor_binding",
|
|
1108
|
+
anchor_count: proofAnchors.length,
|
|
1109
|
+
anchor_root_sha256: expectedAnchorRoot
|
|
1110
|
+
};
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
return { valid: false, reason: error.message };
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// ---------------------------------------------------------------------------
|
|
1117
|
+
// Receipt method dispatch and proof bundle verification
|
|
1118
|
+
// ---------------------------------------------------------------------------
|
|
1119
|
+
|
|
1120
|
+
function verifyReceiptProofMethod(receipt, ceb, bundle, options = {}) {
|
|
1121
|
+
const verification = options && options.__resolved_verification_options === true
|
|
1122
|
+
? options
|
|
1123
|
+
: resolveProofVerificationOptions(options);
|
|
1124
|
+
const algorithm = normalizeString(receipt.algorithm);
|
|
1125
|
+
return finalizeProofMethodVerification(
|
|
1126
|
+
verifyReceiptProofMethodInner(receipt, ceb, bundle, algorithm, verification),
|
|
1127
|
+
algorithm,
|
|
1128
|
+
verification
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function verifyReceiptProofMethodInner(receipt, ceb, bundle, algorithm, verification) {
|
|
1133
|
+
if (!algorithm) {
|
|
1134
|
+
return { valid: false, reason: "verification_method_missing" };
|
|
1135
|
+
}
|
|
1136
|
+
if (algorithm === VERIFICATION_METHOD_NONE) {
|
|
1137
|
+
return { valid: false, reason: "verification_method_none" };
|
|
1138
|
+
}
|
|
1139
|
+
if (algorithm === XOONYA_NATIVE_HYBRID_METHOD) {
|
|
1140
|
+
const hybridVerification = verifyNativeHybridReceiptProof(receipt, ceb, verification);
|
|
1141
|
+
if (!hybridVerification.valid) {
|
|
1142
|
+
return { valid: false, reason: hybridVerification.reason };
|
|
1143
|
+
}
|
|
1144
|
+
return {
|
|
1145
|
+
valid: true,
|
|
1146
|
+
method: { ...NATIVE_HYBRID_METHOD_DESCRIPTOR },
|
|
1147
|
+
verification_state: hybridVerification.verification_state,
|
|
1148
|
+
signature_suite: hybridVerification.signature_suite,
|
|
1149
|
+
public_key_ref: hybridVerification.public_key_ref,
|
|
1150
|
+
classical_public_key_ref: hybridVerification.classical_public_key_ref,
|
|
1151
|
+
pq_public_key_ref: hybridVerification.pq_public_key_ref
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const method = PROOF_VERIFICATION_METHODS[algorithm];
|
|
1156
|
+
if (!method) {
|
|
1157
|
+
return { valid: false, reason: "verification_method_unsupported" };
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const legacyReceipt = receipt.compatibility && receipt.compatibility.legacy_receipt;
|
|
1161
|
+
if (legacyReceipt && algorithm !== LEGACY_OPRETURN_METHOD) {
|
|
1162
|
+
return { valid: false, reason: "legacy_receipt_method_mismatch" };
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (algorithm === LEGACY_OPRETURN_METHOD) {
|
|
1166
|
+
if (!legacyReceipt) {
|
|
1167
|
+
return { valid: false, reason: "legacy_receipt_missing" };
|
|
1168
|
+
}
|
|
1169
|
+
const legacyVerification = verifyReceipt(
|
|
1170
|
+
legacyReceipt.opreturn_hex,
|
|
1171
|
+
Buffer.from(canonicalize(ceb), "utf8")
|
|
1172
|
+
);
|
|
1173
|
+
if (!legacyVerification.valid) {
|
|
1174
|
+
return { valid: false, reason: `legacy_receipt_${legacyVerification.reason}` };
|
|
1175
|
+
}
|
|
1176
|
+
if (normalizeString(legacyReceipt.agent_id).toLowerCase() !== normalizeString(legacyVerification.agent_id).toLowerCase()) {
|
|
1177
|
+
return { valid: false, reason: "legacy_agent_id_mismatch" };
|
|
1178
|
+
}
|
|
1179
|
+
if (normalizeString(receipt.signature).toLowerCase() !== normalizeString(legacyReceipt.opreturn_hex).toLowerCase()) {
|
|
1180
|
+
return { valid: false, reason: "signature_legacy_mismatch" };
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
valid: true,
|
|
1184
|
+
method,
|
|
1185
|
+
verification_state: "verified_legacy_opreturn"
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
if (algorithm === XOONYA_NATIVE_SHA256_METHOD) {
|
|
1190
|
+
const expectedSignature = expectedNativeDigestSignature(receipt, ceb);
|
|
1191
|
+
if (normalizeString(receipt.signature) !== expectedSignature) {
|
|
1192
|
+
return { valid: false, reason: "signature_native_mismatch" };
|
|
1193
|
+
}
|
|
1194
|
+
return {
|
|
1195
|
+
valid: true,
|
|
1196
|
+
method,
|
|
1197
|
+
verification_state: "verified_native_digest"
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (algorithm === XOONYA_NATIVE_SIGNED_METHOD) {
|
|
1202
|
+
const signedVerification = verifyNativeSignedReceiptProof(receipt, ceb, verification);
|
|
1203
|
+
if (!signedVerification.valid) {
|
|
1204
|
+
return { valid: false, reason: signedVerification.reason };
|
|
1205
|
+
}
|
|
1206
|
+
return {
|
|
1207
|
+
valid: true,
|
|
1208
|
+
method,
|
|
1209
|
+
verification_state: signedVerification.verification_state,
|
|
1210
|
+
signature_suite: signedVerification.signature_suite,
|
|
1211
|
+
public_key_ref: signedVerification.public_key_ref
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (algorithm === XOONYA_NATIVE_ANCHORED_METHOD) {
|
|
1216
|
+
const anchoredVerification = verifyNativeAnchoredReceiptProof(receipt, ceb, bundle);
|
|
1217
|
+
if (!anchoredVerification.valid) {
|
|
1218
|
+
return { valid: false, reason: anchoredVerification.reason };
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
valid: true,
|
|
1222
|
+
method,
|
|
1223
|
+
verification_state: anchoredVerification.verification_state,
|
|
1224
|
+
anchor_count: anchoredVerification.anchor_count,
|
|
1225
|
+
anchor_root_sha256: anchoredVerification.anchor_root_sha256
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if (isExternalAssertionMethod(algorithm)) {
|
|
1230
|
+
const signature = normalizeString(receipt.signature);
|
|
1231
|
+
if (
|
|
1232
|
+
signature.startsWith(`${XOONYA_NATIVE_SHA256_METHOD}:`) ||
|
|
1233
|
+
signature.startsWith(`${XOONYA_NATIVE_SIGNED_METHOD}:`) ||
|
|
1234
|
+
signature.startsWith(`${XOONYA_NATIVE_ANCHORED_METHOD}:`) ||
|
|
1235
|
+
signature.startsWith(`${XOONYA_NATIVE_HYBRID_METHOD}:`)
|
|
1236
|
+
) {
|
|
1237
|
+
return { valid: false, reason: "signature_method_mismatch" };
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
valid: true,
|
|
1241
|
+
method,
|
|
1242
|
+
verification_state: algorithm === DETACHED_ASSERTION_METHOD
|
|
1243
|
+
? "accepted_detached_assertion"
|
|
1244
|
+
: "accepted_external_proof_envelope"
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return { valid: false, reason: "verification_method_unsupported" };
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function verifyProofBundle(bundle, options = {}) {
|
|
1252
|
+
const verification = resolveProofVerificationOptions(options);
|
|
1253
|
+
const result = verifyProofBundleInner(bundle, verification);
|
|
1254
|
+
const algorithm = bundle && bundle.receipt && typeof bundle.receipt === "object" && !Array.isArray(bundle.receipt)
|
|
1255
|
+
? bundle.receipt.algorithm
|
|
1256
|
+
: null;
|
|
1257
|
+
return {
|
|
1258
|
+
...result,
|
|
1259
|
+
cryptographically_verified: result.cryptographically_verified === true,
|
|
1260
|
+
verification_class: result.verification_class !== undefined ? result.verification_class : verificationClassForMethod(algorithm),
|
|
1261
|
+
strict: verification.strict,
|
|
1262
|
+
trust_root_applied: verification.trust_root_applied
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function verifyProofBundleInner(bundle, verification) {
|
|
1267
|
+
try {
|
|
1268
|
+
if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
|
|
1269
|
+
return { valid: false, reason: "bundle_not_object" };
|
|
1270
|
+
}
|
|
1271
|
+
if (normalizeString(bundle.version) !== PROOF_BUNDLE_VERSION) {
|
|
1272
|
+
return { valid: false, reason: "bad_bundle_version" };
|
|
1273
|
+
}
|
|
1274
|
+
const ceb = canonicalizeEvent(bundle.ceb);
|
|
1275
|
+
const receipt = bundle.receipt;
|
|
1276
|
+
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
|
|
1277
|
+
return { valid: false, reason: "receipt_not_object" };
|
|
1278
|
+
}
|
|
1279
|
+
if (normalizeString(receipt.version) !== CANONICAL_RECEIPT_VERSION) {
|
|
1280
|
+
return { valid: false, reason: "bad_receipt_version" };
|
|
1281
|
+
}
|
|
1282
|
+
if (normalizeString(receipt.domain_id) !== ceb.domain_id) {
|
|
1283
|
+
return { valid: false, reason: "receipt_domain_mismatch" };
|
|
1284
|
+
}
|
|
1285
|
+
if (normalizeString(receipt.sequence) !== ceb.sequence) {
|
|
1286
|
+
return { valid: false, reason: "receipt_sequence_mismatch" };
|
|
1287
|
+
}
|
|
1288
|
+
const expectedCommitment = hashEvent(ceb);
|
|
1289
|
+
if (normalizeString(receipt.event_commitment).toLowerCase() !== expectedCommitment) {
|
|
1290
|
+
return { valid: false, reason: "event_commitment_mismatch" };
|
|
1291
|
+
}
|
|
1292
|
+
const expectedReceiptId = sha256Hex(Buffer.from(
|
|
1293
|
+
canonicalize({
|
|
1294
|
+
agent_identity: normalizeString(receipt.agent_identity),
|
|
1295
|
+
domain_id: ceb.domain_id,
|
|
1296
|
+
event_commitment: expectedCommitment,
|
|
1297
|
+
sequence: ceb.sequence,
|
|
1298
|
+
version: CANONICAL_RECEIPT_VERSION
|
|
1299
|
+
}),
|
|
1300
|
+
"utf8"
|
|
1301
|
+
));
|
|
1302
|
+
if (normalizeString(receipt.receipt_id).toLowerCase() !== expectedReceiptId) {
|
|
1303
|
+
return { valid: false, reason: "receipt_id_mismatch" };
|
|
1304
|
+
}
|
|
1305
|
+
if (!normalizeString(receipt.agent_identity)) {
|
|
1306
|
+
return { valid: false, reason: "agent_identity_missing" };
|
|
1307
|
+
}
|
|
1308
|
+
if (!normalizeString(receipt.signature)) {
|
|
1309
|
+
return { valid: false, reason: "signature_missing" };
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const methodVerification = verifyReceiptProofMethod(receipt, ceb, bundle, verification);
|
|
1313
|
+
if (!methodVerification.valid) {
|
|
1314
|
+
return {
|
|
1315
|
+
valid: false,
|
|
1316
|
+
reason: methodVerification.reason,
|
|
1317
|
+
verification_class: methodVerification.verification_class,
|
|
1318
|
+
cryptographically_verified: false
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
return {
|
|
1323
|
+
valid: true,
|
|
1324
|
+
reason: null,
|
|
1325
|
+
version: PROOF_BUNDLE_VERSION,
|
|
1326
|
+
event_commitment: expectedCommitment,
|
|
1327
|
+
receipt_id: expectedReceiptId,
|
|
1328
|
+
verification_method: methodVerification.method.id,
|
|
1329
|
+
verification_method_family: methodVerification.method.family,
|
|
1330
|
+
verification_state: methodVerification.verification_state,
|
|
1331
|
+
...(methodVerification.anchor_count != null ? { anchor_count: methodVerification.anchor_count } : {}),
|
|
1332
|
+
...(methodVerification.anchor_root_sha256 ? { anchor_root_sha256: methodVerification.anchor_root_sha256 } : {}),
|
|
1333
|
+
...(methodVerification.signature_suite ? { signature_suite: methodVerification.signature_suite } : {}),
|
|
1334
|
+
...(methodVerification.public_key_ref ? { public_key_ref: methodVerification.public_key_ref } : {}),
|
|
1335
|
+
...(methodVerification.classical_public_key_ref ? { classical_public_key_ref: methodVerification.classical_public_key_ref } : {}),
|
|
1336
|
+
...(methodVerification.pq_public_key_ref ? { pq_public_key_ref: methodVerification.pq_public_key_ref } : {}),
|
|
1337
|
+
verification_class: methodVerification.verification_class,
|
|
1338
|
+
cryptographically_verified: methodVerification.cryptographically_verified === true
|
|
1339
|
+
};
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
return { valid: false, reason: error.message };
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// Verify a receipt plus the canonical event it commits to, without a bundle
|
|
1346
|
+
// wrapper. `anchors` is required for the anchored method.
|
|
1347
|
+
function verifyNativeProof(input, options = {}) {
|
|
1348
|
+
const source = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
1349
|
+
const anchors = Array.isArray(source.anchors) ? source.anchors : undefined;
|
|
1350
|
+
return verifyProofBundle({
|
|
1351
|
+
version: PROOF_BUNDLE_VERSION,
|
|
1352
|
+
ceb: source.ceb || source.event,
|
|
1353
|
+
receipt: source.receipt,
|
|
1354
|
+
anchors
|
|
1355
|
+
}, options);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// ---------------------------------------------------------------------------
|
|
1359
|
+
// Compatibility OP_RETURN receipts (PRFT) and proof envelopes
|
|
1360
|
+
// ---------------------------------------------------------------------------
|
|
1361
|
+
|
|
1362
|
+
function parseReceipt(opreturnHex) {
|
|
1363
|
+
const hex = String(opreturnHex || "").trim().toLowerCase();
|
|
1364
|
+
if (!/^[a-f0-9]+$/.test(hex)) {
|
|
1365
|
+
throw new Error("opreturnHex must be hex");
|
|
1366
|
+
}
|
|
1367
|
+
if (hex.length !== 116 && hex.length !== 140) {
|
|
1368
|
+
throw new Error("opreturnHex must be a supported v1 or v2 receipt");
|
|
1369
|
+
}
|
|
1370
|
+
const magic = hex.slice(0, 8);
|
|
1371
|
+
const version = hex.slice(8, 10);
|
|
1372
|
+
const recordType = hex.slice(10, 12);
|
|
1373
|
+
const agentId = hex.slice(12, 52);
|
|
1374
|
+
const payloadHash = hex.slice(52, 116);
|
|
1375
|
+
if (magic !== MAGIC_HEX) {
|
|
1376
|
+
throw new Error("opreturnHex has bad receipt magic");
|
|
1377
|
+
}
|
|
1378
|
+
if (!VALID_TYPES.has(recordType)) {
|
|
1379
|
+
throw new Error("opreturnHex has unsupported record type");
|
|
1380
|
+
}
|
|
1381
|
+
if (version === VERSION_V1_HEX && hex.length === 116) {
|
|
1382
|
+
return {
|
|
1383
|
+
spec_version: "v1",
|
|
1384
|
+
record_type_hex: recordType,
|
|
1385
|
+
agent_id: agentId,
|
|
1386
|
+
payload_hash_sha256: payloadHash,
|
|
1387
|
+
receipt_hash_sha256: sha256Hex(Buffer.from(hex, "hex")),
|
|
1388
|
+
opreturn_hex: hex
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
if (version === VERSION_V2_HEX && hex.length === 140) {
|
|
1392
|
+
return {
|
|
1393
|
+
spec_version: "v2",
|
|
1394
|
+
record_type_hex: recordType,
|
|
1395
|
+
agent_id: agentId,
|
|
1396
|
+
payload_hash_sha256: payloadHash,
|
|
1397
|
+
context_hash12: hex.slice(116, 140),
|
|
1398
|
+
receipt_hash_sha256: sha256Hex(Buffer.from(hex, "hex")),
|
|
1399
|
+
opreturn_hex: hex
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
throw new Error("opreturnHex/version length mismatch");
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function verifyReceipt(opreturnHex, payloadBytes) {
|
|
1406
|
+
const hex = String(opreturnHex || "").trim().toLowerCase();
|
|
1407
|
+
if (!/^[a-f0-9]+$/.test(hex)) {
|
|
1408
|
+
return { valid: false, reason: "non_hex", spec_version: "v1", compliance_hash: null };
|
|
1409
|
+
}
|
|
1410
|
+
if (hex.length !== 116) {
|
|
1411
|
+
return { valid: false, reason: "invalid_length", spec_version: "v1", compliance_hash: null };
|
|
1412
|
+
}
|
|
1413
|
+
const magic = hex.slice(0, 8);
|
|
1414
|
+
const version = hex.slice(8, 10);
|
|
1415
|
+
const type = hex.slice(10, 12);
|
|
1416
|
+
const agentId = hex.slice(12, 52);
|
|
1417
|
+
const payloadHash = hex.slice(52, 116);
|
|
1418
|
+
if (magic !== MAGIC_HEX) return { valid: false, reason: "bad_magic", spec_version: "v1", compliance_hash: null };
|
|
1419
|
+
if (version !== VERSION_V1_HEX) return { valid: false, reason: "bad_version", spec_version: "v1", compliance_hash: null };
|
|
1420
|
+
if (!VALID_TYPES.has(type)) return { valid: false, reason: "bad_type", spec_version: "v1", compliance_hash: null };
|
|
1421
|
+
const expectedPayload = payloadBytes && payloadBytes.length
|
|
1422
|
+
? sha256Hex(Buffer.from(payloadBytes))
|
|
1423
|
+
: ZERO_HASH32;
|
|
1424
|
+
if (expectedPayload !== payloadHash) {
|
|
1425
|
+
return { valid: false, reason: "payload_mismatch", spec_version: "v1", compliance_hash: null };
|
|
1426
|
+
}
|
|
1427
|
+
return {
|
|
1428
|
+
valid: true,
|
|
1429
|
+
reason: null,
|
|
1430
|
+
spec_version: "v1",
|
|
1431
|
+
compliance_hash: sha256Hex(Buffer.from(hex, "hex")),
|
|
1432
|
+
agent_id: agentId
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function verifyReceiptV2(opreturnHex, payloadBytes) {
|
|
1437
|
+
const hex = String(opreturnHex || "").trim().toLowerCase();
|
|
1438
|
+
if (!/^[a-f0-9]+$/.test(hex)) {
|
|
1439
|
+
return { valid: false, reason: "non_hex", spec_version: "v2", compliance_hash: null };
|
|
1440
|
+
}
|
|
1441
|
+
if (hex.length !== 140) {
|
|
1442
|
+
return { valid: false, reason: "invalid_length", spec_version: "v2", compliance_hash: null };
|
|
1443
|
+
}
|
|
1444
|
+
const magic = hex.slice(0, 8);
|
|
1445
|
+
const version = hex.slice(8, 10);
|
|
1446
|
+
const type = hex.slice(10, 12);
|
|
1447
|
+
const agentId = hex.slice(12, 52);
|
|
1448
|
+
const payloadHash = hex.slice(52, 116);
|
|
1449
|
+
const contextHash12 = hex.slice(116, 140);
|
|
1450
|
+
if (magic !== MAGIC_HEX) return { valid: false, reason: "bad_magic", spec_version: "v2", compliance_hash: null };
|
|
1451
|
+
if (version !== VERSION_V2_HEX) return { valid: false, reason: "bad_version", spec_version: "v2", compliance_hash: null };
|
|
1452
|
+
if (!VALID_TYPES.has(type)) return { valid: false, reason: "bad_type", spec_version: "v2", compliance_hash: null };
|
|
1453
|
+
if (contextHash12 === ZERO_CONTEXT12) return { valid: false, reason: "context_zero_forbidden", spec_version: "v2", compliance_hash: null };
|
|
1454
|
+
const expectedPayload = payloadBytes && payloadBytes.length
|
|
1455
|
+
? sha256Hex(Buffer.from(payloadBytes))
|
|
1456
|
+
: ZERO_HASH32;
|
|
1457
|
+
if (expectedPayload !== payloadHash) {
|
|
1458
|
+
return { valid: false, reason: "payload_mismatch", spec_version: "v2", compliance_hash: null };
|
|
1459
|
+
}
|
|
1460
|
+
return {
|
|
1461
|
+
valid: true,
|
|
1462
|
+
reason: null,
|
|
1463
|
+
spec_version: "v2",
|
|
1464
|
+
compliance_hash: sha256Hex(Buffer.from(hex, "hex")),
|
|
1465
|
+
agent_id: agentId,
|
|
1466
|
+
context_hash12: contextHash12
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function normalizeProofEntry(proof, index) {
|
|
1471
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)) {
|
|
1472
|
+
throw new Error(`proofs[${index}] must be an object`);
|
|
1473
|
+
}
|
|
1474
|
+
const normalized = {
|
|
1475
|
+
proof_id: String(proof.proof_id || "").trim(),
|
|
1476
|
+
proof_type: String(proof.proof_type || "").trim(),
|
|
1477
|
+
role: String(proof.role || "").trim(),
|
|
1478
|
+
media_type: String(proof.media_type || "").trim(),
|
|
1479
|
+
proof_hash_sha256: assertHex(proof.proof_hash_sha256, 64, `proofs[${index}].proof_hash_sha256`),
|
|
1480
|
+
locator: String(proof.locator || "").trim()
|
|
1481
|
+
};
|
|
1482
|
+
if (!normalized.proof_id) throw new Error(`proofs[${index}].proof_id required`);
|
|
1483
|
+
if (!normalized.proof_type) throw new Error(`proofs[${index}].proof_type required`);
|
|
1484
|
+
if (!normalized.role) throw new Error(`proofs[${index}].role required`);
|
|
1485
|
+
if (!normalized.media_type) throw new Error(`proofs[${index}].media_type required`);
|
|
1486
|
+
if (!normalized.locator) throw new Error(`proofs[${index}].locator required`);
|
|
1487
|
+
return normalized;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
function makeProofEnvelopeBindingMaterial(receiptRef, proofs) {
|
|
1491
|
+
return {
|
|
1492
|
+
envelope_version: PROOF_ENVELOPE_VERSION,
|
|
1493
|
+
payload_hash_sha256: receiptRef.payload_hash_sha256,
|
|
1494
|
+
proofs: proofs.map((proof) => ({
|
|
1495
|
+
locator: proof.locator,
|
|
1496
|
+
media_type: proof.media_type,
|
|
1497
|
+
proof_hash_sha256: proof.proof_hash_sha256,
|
|
1498
|
+
proof_id: proof.proof_id,
|
|
1499
|
+
proof_type: proof.proof_type,
|
|
1500
|
+
role: proof.role
|
|
1501
|
+
})),
|
|
1502
|
+
receipt_hash_sha256: receiptRef.receipt_hash_sha256
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
function verifyProofEnvelopeV1(envelope) {
|
|
1507
|
+
try {
|
|
1508
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
1509
|
+
return { valid: false, reason: "envelope_not_object" };
|
|
1510
|
+
}
|
|
1511
|
+
if (envelope.envelope_version !== PROOF_ENVELOPE_VERSION) {
|
|
1512
|
+
return { valid: false, reason: "bad_envelope_version" };
|
|
1513
|
+
}
|
|
1514
|
+
const receiptRef = parseReceipt(envelope.receipt_ref && envelope.receipt_ref.opreturn_hex);
|
|
1515
|
+
if (String((envelope.receipt_ref && envelope.receipt_ref.spec_version) || "") !== receiptRef.spec_version) {
|
|
1516
|
+
return { valid: false, reason: "receipt_spec_version_mismatch" };
|
|
1517
|
+
}
|
|
1518
|
+
if (String((envelope.receipt_ref && envelope.receipt_ref.receipt_hash_sha256) || "").toLowerCase() !== receiptRef.receipt_hash_sha256) {
|
|
1519
|
+
return { valid: false, reason: "receipt_hash_mismatch" };
|
|
1520
|
+
}
|
|
1521
|
+
if (String((envelope.receipt_ref && envelope.receipt_ref.payload_hash_sha256) || "").toLowerCase() !== receiptRef.payload_hash_sha256) {
|
|
1522
|
+
return { valid: false, reason: "payload_hash_mismatch" };
|
|
1523
|
+
}
|
|
1524
|
+
const proofs = Array.isArray(envelope.proofs) ? envelope.proofs.map(normalizeProofEntry) : [];
|
|
1525
|
+
if (proofs.length === 0) return { valid: false, reason: "proofs_empty" };
|
|
1526
|
+
const proofIds = new Set();
|
|
1527
|
+
for (const proof of proofs) {
|
|
1528
|
+
if (proofIds.has(proof.proof_id)) return { valid: false, reason: "proof_duplicate_id" };
|
|
1529
|
+
proofIds.add(proof.proof_id);
|
|
1530
|
+
}
|
|
1531
|
+
const expectedBindingHash = canonicalJsonSha256Hex(makeProofEnvelopeBindingMaterial(receiptRef, proofs));
|
|
1532
|
+
if (String(envelope.binding_hash_sha256 || "").toLowerCase() !== expectedBindingHash) {
|
|
1533
|
+
return { valid: false, reason: "binding_hash_mismatch" };
|
|
1534
|
+
}
|
|
1535
|
+
return {
|
|
1536
|
+
valid: true,
|
|
1537
|
+
reason: null,
|
|
1538
|
+
envelope_version: PROOF_ENVELOPE_VERSION,
|
|
1539
|
+
binding_hash_sha256: expectedBindingHash
|
|
1540
|
+
};
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
return { valid: false, reason: error.message };
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// ---------------------------------------------------------------------------
|
|
1547
|
+
// XNYA native atoms
|
|
1548
|
+
// ---------------------------------------------------------------------------
|
|
1549
|
+
|
|
1550
|
+
function atomValidationError(code, message) {
|
|
1551
|
+
const error = new Error(message);
|
|
1552
|
+
error.code = code;
|
|
1553
|
+
return error;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
function normalizexoonyaNativeIdentityMaterial(identityMaterialHex) {
|
|
1557
|
+
const materialHex = assertVariableHex(identityMaterialHex, "identityMaterialHex");
|
|
1558
|
+
const typePrefix = materialHex.slice(0, 2);
|
|
1559
|
+
if (!XOONYA_NATIVE_IDENTITY_TYPES[typePrefix]) {
|
|
1560
|
+
throw atomValidationError(
|
|
1561
|
+
XNYA_NATIVE_ATOM_ERROR_CODES.IDENTITY_UNSUPPORTED_TYPE_PREFIX,
|
|
1562
|
+
"identityMaterialHex has unsupported identity type prefix"
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
if (materialHex.length === 2) {
|
|
1566
|
+
throw atomValidationError(
|
|
1567
|
+
XNYA_NATIVE_ATOM_ERROR_CODES.IDENTITY_MISSING_RAW_BYTES,
|
|
1568
|
+
"identityMaterialHex must include raw identity bytes"
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
return materialHex;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function hashxoonyaNativePayload(payloadBytes, domainSeparator) {
|
|
1575
|
+
if (!payloadBytes || payloadBytes.length === 0) {
|
|
1576
|
+
return ZERO_HASH32;
|
|
1577
|
+
}
|
|
1578
|
+
const digest = sha256Hex(Buffer.concat([
|
|
1579
|
+
Buffer.from(domainSeparator, "utf8"),
|
|
1580
|
+
Buffer.from(payloadBytes)
|
|
1581
|
+
]));
|
|
1582
|
+
if (digest === ZERO_HASH32) {
|
|
1583
|
+
throw new Error("payload hash cannot equal the absent-payload sentinel");
|
|
1584
|
+
}
|
|
1585
|
+
return digest;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function parsexoonyaNativeAtom(atomHex) {
|
|
1589
|
+
const hex = String(atomHex || "").trim().toLowerCase();
|
|
1590
|
+
if (!/^[a-f0-9]+$/.test(hex)) {
|
|
1591
|
+
throw atomValidationError(XNYA_NATIVE_ATOM_ERROR_CODES.ATOM_HEX_NOT_HEX, "atomHex must be hex");
|
|
1592
|
+
}
|
|
1593
|
+
if (hex.length !== 116 && hex.length !== 140) {
|
|
1594
|
+
throw atomValidationError(
|
|
1595
|
+
XNYA_NATIVE_ATOM_ERROR_CODES.ATOM_UNSUPPORTED_LENGTH,
|
|
1596
|
+
"atomHex must be a supported xoonya native atom"
|
|
1597
|
+
);
|
|
1598
|
+
}
|
|
1599
|
+
const magic = hex.slice(0, 8);
|
|
1600
|
+
const version = hex.slice(8, 10);
|
|
1601
|
+
const recordType = hex.slice(10, 12);
|
|
1602
|
+
if (magic !== XOONYA_NATIVE_ATOM_MAGIC_HEX.toLowerCase()) {
|
|
1603
|
+
throw atomValidationError(
|
|
1604
|
+
XNYA_NATIVE_ATOM_ERROR_CODES.ATOM_BAD_MAGIC,
|
|
1605
|
+
"atomHex has bad xoonya native atom magic"
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
if (!VALID_TYPES.has(recordType)) {
|
|
1609
|
+
throw atomValidationError(
|
|
1610
|
+
XNYA_NATIVE_ATOM_ERROR_CODES.ATOM_UNSUPPORTED_RECORD_TYPE,
|
|
1611
|
+
"atomHex has unsupported record type"
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
if (version === VERSION_V1_HEX && hex.length === 116) {
|
|
1615
|
+
const agentId = hex.slice(12, 52);
|
|
1616
|
+
const payloadHash = hex.slice(52, 116);
|
|
1617
|
+
return {
|
|
1618
|
+
atom_version: XOONYA_NATIVE_ATOM_V1,
|
|
1619
|
+
spec_version: "v1",
|
|
1620
|
+
byte_length: 58,
|
|
1621
|
+
record_type_hex: recordType,
|
|
1622
|
+
agent_id: agentId,
|
|
1623
|
+
payload_hash_sha256: payloadHash,
|
|
1624
|
+
atom_hash_sha256: sha256Hex(Buffer.from(hex, "hex")),
|
|
1625
|
+
atom_hex: hex
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
if (version === VERSION_V2_HEX && hex.length === 140) {
|
|
1629
|
+
const agentId = hex.slice(12, 76);
|
|
1630
|
+
const payloadHash = hex.slice(76, 140);
|
|
1631
|
+
return {
|
|
1632
|
+
atom_version: XOONYA_NATIVE_ATOM_V2,
|
|
1633
|
+
spec_version: "v2",
|
|
1634
|
+
byte_length: 70,
|
|
1635
|
+
record_type_hex: recordType,
|
|
1636
|
+
agent_id: agentId,
|
|
1637
|
+
payload_hash_sha256: payloadHash,
|
|
1638
|
+
atom_hash_sha256: sha256Hex(Buffer.from(hex, "hex")),
|
|
1639
|
+
atom_hex: hex
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
throw atomValidationError(
|
|
1643
|
+
XNYA_NATIVE_ATOM_ERROR_CODES.ATOM_VERSION_LENGTH_MISMATCH,
|
|
1644
|
+
"atomHex/version length mismatch"
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
function verifyxoonyaNativeAtom(atomHex, options = {}) {
|
|
1649
|
+
try {
|
|
1650
|
+
const parsed = parsexoonyaNativeAtom(atomHex);
|
|
1651
|
+
const hasIdentityMaterial = options.identity_material_hex != null;
|
|
1652
|
+
if (hasIdentityMaterial) {
|
|
1653
|
+
const materialHex = normalizexoonyaNativeIdentityMaterial(options.identity_material_hex);
|
|
1654
|
+
const expectedAgentId = parsed.spec_version === "v1"
|
|
1655
|
+
? hash160Hex(Buffer.concat([Buffer.from(XNYA_DS_AGENT_V1, "utf8"), Buffer.from(materialHex, "hex")]))
|
|
1656
|
+
: sha256Hex(Buffer.concat([Buffer.from(XNYA_DS_AGENT_V2, "utf8"), Buffer.from(materialHex, "hex")]));
|
|
1657
|
+
if (parsed.agent_id !== expectedAgentId) {
|
|
1658
|
+
return {
|
|
1659
|
+
valid: false,
|
|
1660
|
+
reason: "agent_id_mismatch",
|
|
1661
|
+
error_code: XNYA_NATIVE_ATOM_ERROR_CODES.AGENT_ID_MISMATCH,
|
|
1662
|
+
...parsed
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const hasPayload = options.payload_bytes != null;
|
|
1668
|
+
if (hasPayload) {
|
|
1669
|
+
const payloadHash = parsed.spec_version === "v1"
|
|
1670
|
+
? hashxoonyaNativePayload(options.payload_bytes, XNYA_DS_PAYLOAD_V1)
|
|
1671
|
+
: hashxoonyaNativePayload(options.payload_bytes, XNYA_DS_PAYLOAD_V2);
|
|
1672
|
+
if (payloadHash !== parsed.payload_hash_sha256) {
|
|
1673
|
+
return {
|
|
1674
|
+
valid: false,
|
|
1675
|
+
reason: "payload_hash_mismatch",
|
|
1676
|
+
error_code: XNYA_NATIVE_ATOM_ERROR_CODES.PAYLOAD_HASH_MISMATCH,
|
|
1677
|
+
...parsed
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
} else if (parsed.payload_hash_sha256 === ZERO_HASH32) {
|
|
1681
|
+
return { valid: true, reason: null, error_code: null, payload_state: "absent", ...parsed };
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
return {
|
|
1685
|
+
valid: true,
|
|
1686
|
+
reason: null,
|
|
1687
|
+
error_code: null,
|
|
1688
|
+
payload_state: parsed.payload_hash_sha256 === ZERO_HASH32 ? "absent" : "committed",
|
|
1689
|
+
...parsed
|
|
1690
|
+
};
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
return { valid: false, reason: error.message, error_code: error.code || "native_atom_validation_error" };
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// ---------------------------------------------------------------------------
|
|
1697
|
+
// XNYP payload profile (parse/verify) and XNYA line-rate frame
|
|
1698
|
+
// ---------------------------------------------------------------------------
|
|
1699
|
+
|
|
1700
|
+
const XNYA_PAYLOAD_FIELD_TYPES = Object.freeze({
|
|
1701
|
+
bytes32: Object.freeze({ id: 1, fixed_length: 32 }),
|
|
1702
|
+
bytes20: Object.freeze({ id: 2, fixed_length: 20 }),
|
|
1703
|
+
uint64: Object.freeze({ id: 3, fixed_length: 8 }),
|
|
1704
|
+
utf8: Object.freeze({ id: 4, max_length: 1024 }),
|
|
1705
|
+
bool: Object.freeze({ id: 5, fixed_length: 1 })
|
|
1706
|
+
});
|
|
1707
|
+
const XNYA_PAYLOAD_FIELD_TYPE_BY_ID = Object.freeze(
|
|
1708
|
+
Object.fromEntries(Object.entries(XNYA_PAYLOAD_FIELD_TYPES).map(([name, spec]) => [spec.id, { name, ...spec }]))
|
|
1709
|
+
);
|
|
1710
|
+
|
|
1711
|
+
function parsexoonyaNativePayloadProfileV1(canonicalBytesHex) {
|
|
1712
|
+
const hex = assertVariableHex(canonicalBytesHex, "payload profile canonical bytes");
|
|
1713
|
+
const buf = Buffer.from(hex, "hex");
|
|
1714
|
+
if (buf.length < 38) {
|
|
1715
|
+
throw new Error("payload profile bytes too short");
|
|
1716
|
+
}
|
|
1717
|
+
if (buf.subarray(0, 4).toString("ascii") !== "XNYP") {
|
|
1718
|
+
throw new Error("payload profile magic mismatch");
|
|
1719
|
+
}
|
|
1720
|
+
if (buf[4] !== 1) {
|
|
1721
|
+
throw new Error("payload profile version unsupported");
|
|
1722
|
+
}
|
|
1723
|
+
const schemaHash = buf.subarray(5, 37).toString("hex");
|
|
1724
|
+
const fieldCount = buf[37];
|
|
1725
|
+
if (fieldCount === 0 || fieldCount > 64) {
|
|
1726
|
+
throw new Error("payload profile field count unsupported");
|
|
1727
|
+
}
|
|
1728
|
+
let offset = 38;
|
|
1729
|
+
const fields = [];
|
|
1730
|
+
const seen = new Set();
|
|
1731
|
+
for (let i = 0; i < fieldCount; i += 1) {
|
|
1732
|
+
if (offset + 5 > buf.length) {
|
|
1733
|
+
throw new Error("payload profile truncated field header");
|
|
1734
|
+
}
|
|
1735
|
+
const fieldId = buf.readUInt16BE(offset);
|
|
1736
|
+
const fieldTypeId = buf[offset + 2];
|
|
1737
|
+
const length = buf.readUInt16BE(offset + 3);
|
|
1738
|
+
offset += 5;
|
|
1739
|
+
if (offset + length > buf.length) {
|
|
1740
|
+
throw new Error("payload profile truncated field value");
|
|
1741
|
+
}
|
|
1742
|
+
const typeSpec = XNYA_PAYLOAD_FIELD_TYPE_BY_ID[fieldTypeId];
|
|
1743
|
+
if (!typeSpec) {
|
|
1744
|
+
throw new Error("payload profile field type unsupported");
|
|
1745
|
+
}
|
|
1746
|
+
const value = buf.subarray(offset, offset + length);
|
|
1747
|
+
offset += length;
|
|
1748
|
+
if (typeSpec.fixed_length && value.length !== typeSpec.fixed_length) {
|
|
1749
|
+
throw new Error("payload profile fixed field length mismatch");
|
|
1750
|
+
}
|
|
1751
|
+
if (typeSpec.max_length && (value.length === 0 || value.length > typeSpec.max_length)) {
|
|
1752
|
+
throw new Error("payload profile variable field length mismatch");
|
|
1753
|
+
}
|
|
1754
|
+
if (typeSpec.name === "bool" && value[0] !== 0 && value[0] !== 1) {
|
|
1755
|
+
throw new Error("payload profile bool value invalid");
|
|
1756
|
+
}
|
|
1757
|
+
const key = `${fieldId}:${fieldTypeId}`;
|
|
1758
|
+
if (seen.has(key)) {
|
|
1759
|
+
throw new Error("payload profile duplicate field id/type");
|
|
1760
|
+
}
|
|
1761
|
+
seen.add(key);
|
|
1762
|
+
fields.push({
|
|
1763
|
+
field_id: fieldId,
|
|
1764
|
+
field_type: typeSpec.name,
|
|
1765
|
+
field_type_id: fieldTypeId,
|
|
1766
|
+
value_hex: value.toString("hex"),
|
|
1767
|
+
...(typeSpec.name === "uint64" ? { value: readUInt64String(value) } : {}),
|
|
1768
|
+
...(typeSpec.name === "bool" ? { value: value[0] === 1 } : {})
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
if (offset !== buf.length) {
|
|
1772
|
+
throw new Error("payload profile trailing bytes");
|
|
1773
|
+
}
|
|
1774
|
+
const sorted = fields.slice().sort((a, b) => a.field_id - b.field_id || a.field_type_id - b.field_type_id);
|
|
1775
|
+
if (canonicalize(fields) !== canonicalize(sorted)) {
|
|
1776
|
+
throw new Error("payload profile fields not canonical order");
|
|
1777
|
+
}
|
|
1778
|
+
return {
|
|
1779
|
+
profile: XOONYA_NATIVE_PAYLOAD_PROFILE_V1,
|
|
1780
|
+
version: 1,
|
|
1781
|
+
schema_hash_sha256: schemaHash,
|
|
1782
|
+
field_count: fieldCount,
|
|
1783
|
+
fields,
|
|
1784
|
+
canonical_bytes_hex: hex,
|
|
1785
|
+
payload_hash_sha256: sha256Hex(Buffer.concat([
|
|
1786
|
+
Buffer.from(XNYA_DS_PAYLOAD_PROFILE_V1, "utf8"),
|
|
1787
|
+
buf
|
|
1788
|
+
]))
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
function verifyxoonyaNativePayloadProfileV1(canonicalBytesHex, options = {}) {
|
|
1793
|
+
try {
|
|
1794
|
+
const parsed = parsexoonyaNativePayloadProfileV1(canonicalBytesHex);
|
|
1795
|
+
const expectedHash = normalizeString(options.payload_hash_sha256 || options.expected_payload_hash_sha256).toLowerCase();
|
|
1796
|
+
if (expectedHash && expectedHash !== parsed.payload_hash_sha256) {
|
|
1797
|
+
return { valid: false, reason: "payload_profile_hash_mismatch", parsed };
|
|
1798
|
+
}
|
|
1799
|
+
const schemaRef = normalizeString(options.schema_ref || options.schemaRef);
|
|
1800
|
+
if (schemaRef) {
|
|
1801
|
+
const expectedSchemaHash = sha256Hex(Buffer.from(`xoonya:payload-schema:v1:${schemaRef}`, "utf8"));
|
|
1802
|
+
if (expectedSchemaHash !== parsed.schema_hash_sha256) {
|
|
1803
|
+
return { valid: false, reason: "payload_profile_schema_mismatch", parsed };
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
const allowedFieldIds = Array.isArray(options.allowed_field_ids)
|
|
1807
|
+
? options.allowed_field_ids.map((entry) => Number(entry))
|
|
1808
|
+
: null;
|
|
1809
|
+
if (allowedFieldIds) {
|
|
1810
|
+
const allowed = new Set(allowedFieldIds);
|
|
1811
|
+
if (parsed.fields.some((field) => !allowed.has(field.field_id))) {
|
|
1812
|
+
return { valid: false, reason: "payload_profile_unknown_field", parsed };
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
return { valid: true, parsed };
|
|
1816
|
+
} catch (error) {
|
|
1817
|
+
return { valid: false, reason: error.message };
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function lineRateFrameChecksum(atomBytes) {
|
|
1822
|
+
return sha256Hex(Buffer.concat([
|
|
1823
|
+
Buffer.from(XNYA_DS_LINE_RATE_FRAME_V1, "utf8"),
|
|
1824
|
+
atomBytes
|
|
1825
|
+
])).slice(0, 4);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
function parsexoonyaNativeLineRateFrameV1(frameHex) {
|
|
1829
|
+
const hex = assertVariableHex(frameHex, "line-rate frame");
|
|
1830
|
+
const byteLength = hex.length / 2;
|
|
1831
|
+
if (![60, 72].includes(byteLength)) {
|
|
1832
|
+
throw new Error("line-rate frame unsupported length");
|
|
1833
|
+
}
|
|
1834
|
+
const atomHex = hex.slice(0, -4);
|
|
1835
|
+
const checksumHex = hex.slice(-4);
|
|
1836
|
+
const parsedAtom = parsexoonyaNativeAtom(atomHex);
|
|
1837
|
+
const expectedChecksum = lineRateFrameChecksum(Buffer.from(parsedAtom.atom_hex, "hex"));
|
|
1838
|
+
return {
|
|
1839
|
+
profile: XOONYA_NATIVE_LINE_RATE_FRAME_V1,
|
|
1840
|
+
frame_hex: hex,
|
|
1841
|
+
atom_hex: parsedAtom.atom_hex,
|
|
1842
|
+
atom_version: parsedAtom.atom_version,
|
|
1843
|
+
atom_byte_length: parsedAtom.byte_length,
|
|
1844
|
+
frame_byte_length: byteLength,
|
|
1845
|
+
checksum_hex: checksumHex,
|
|
1846
|
+
expected_checksum_hex: expectedChecksum,
|
|
1847
|
+
checksum_valid: checksumHex === expectedChecksum
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function verifyxoonyaNativeLineRateFrameV1(frameHex, options = {}) {
|
|
1852
|
+
try {
|
|
1853
|
+
const parsedFrame = parsexoonyaNativeLineRateFrameV1(frameHex);
|
|
1854
|
+
if (!parsedFrame.checksum_valid) {
|
|
1855
|
+
return { valid: false, reason: "line_rate_frame_checksum_mismatch", parsed_frame: parsedFrame };
|
|
1856
|
+
}
|
|
1857
|
+
const atomVerification = verifyxoonyaNativeAtom(parsedFrame.atom_hex, options);
|
|
1858
|
+
if (!atomVerification.valid) {
|
|
1859
|
+
return { valid: false, reason: `line_rate_frame_atom_${atomVerification.reason}`, parsed_frame: parsedFrame, atom_verification: atomVerification };
|
|
1860
|
+
}
|
|
1861
|
+
return {
|
|
1862
|
+
valid: true,
|
|
1863
|
+
parsed_frame: parsedFrame,
|
|
1864
|
+
atom_verification: atomVerification
|
|
1865
|
+
};
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
return { valid: false, reason: error.message };
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
module.exports = {
|
|
1872
|
+
// constants
|
|
1873
|
+
CANONICAL_EVENT_BUNDLE_VERSION,
|
|
1874
|
+
CANONICAL_RECEIPT_VERSION,
|
|
1875
|
+
PROOF_BUNDLE_VERSION,
|
|
1876
|
+
XOONYA_NATIVE_PROOF_FORMAT_VERSION,
|
|
1877
|
+
XOONYA_NATIVE_SHA256_METHOD,
|
|
1878
|
+
XOONYA_NATIVE_SIGNED_METHOD,
|
|
1879
|
+
XOONYA_NATIVE_ANCHORED_METHOD,
|
|
1880
|
+
XOONYA_NATIVE_HYBRID_METHOD,
|
|
1881
|
+
XNYA_NATIVE_ATOM_ERROR_CODES,
|
|
1882
|
+
PROOF_VERIFICATION_STRICT_REJECTION_CODES,
|
|
1883
|
+
PROOF_VERIFICATION_TRUST_ROOT_REJECTION_CODES,
|
|
1884
|
+
// canonicalization
|
|
1885
|
+
canonicalize,
|
|
1886
|
+
canonicalJsonSha256Hex,
|
|
1887
|
+
canonicalizeEvent,
|
|
1888
|
+
hashEvent,
|
|
1889
|
+
// registries
|
|
1890
|
+
listProofVerificationMethods,
|
|
1891
|
+
getProofVerificationMethod,
|
|
1892
|
+
listProofVerificationClasses,
|
|
1893
|
+
listNativeSignatureSuites,
|
|
1894
|
+
getNativeSignatureSuite,
|
|
1895
|
+
listImplementedNativeHybridSuites,
|
|
1896
|
+
listxoonyaNativeIdentityTypes,
|
|
1897
|
+
getxoonyaNativeIdentityType,
|
|
1898
|
+
runtimeSupportsNativeHybridSuite,
|
|
1899
|
+
nativePublicKeyRefFromPem,
|
|
1900
|
+
nativeHybridPublicKeyRefFromPems,
|
|
1901
|
+
// proof bundle verification
|
|
1902
|
+
verifyProofBundle,
|
|
1903
|
+
verifyNativeBundle: verifyProofBundle,
|
|
1904
|
+
verifyNativeProof,
|
|
1905
|
+
verifyReceiptProofMethod,
|
|
1906
|
+
// compatibility receipts and envelopes
|
|
1907
|
+
parseReceipt,
|
|
1908
|
+
verifyReceipt,
|
|
1909
|
+
verifyReceiptV2,
|
|
1910
|
+
verifyProofEnvelopeV1,
|
|
1911
|
+
// XNYA atoms, payload profiles, line-rate frames
|
|
1912
|
+
parsexoonyaNativeAtom,
|
|
1913
|
+
verifyxoonyaNativeAtom,
|
|
1914
|
+
parsexoonyaNativePayloadProfileV1,
|
|
1915
|
+
verifyxoonyaNativePayloadProfileV1,
|
|
1916
|
+
parsexoonyaNativeLineRateFrameV1,
|
|
1917
|
+
verifyxoonyaNativeLineRateFrameV1
|
|
1918
|
+
};
|