proofbundle 3.2.3__tar.gz → 3.6.0__tar.gz
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.
- {proofbundle-3.2.3/src/proofbundle.egg-info → proofbundle-3.6.0}/PKG-INFO +11 -6
- {proofbundle-3.2.3 → proofbundle-3.6.0}/README.md +6 -4
- {proofbundle-3.2.3 → proofbundle-3.6.0}/pyproject.toml +13 -5
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/__init__.py +1 -1
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/anchors_chia.py +4 -0
- proofbundle-3.6.0/src/proofbundle/anchors_ots.py +273 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/automation_verdict.py +13 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/cli.py +625 -4
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/decision.py +86 -4
- proofbundle-3.6.0/src/proofbundle/evidence_pack.py +162 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/outcome.py +67 -4
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/policy.py +99 -1
- proofbundle-3.6.0/src/proofbundle/relation.py +452 -0
- proofbundle-3.6.0/src/proofbundle/relation_statement.py +319 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/trust_pack.py +18 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0/src/proofbundle.egg-info}/PKG-INFO +11 -6
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle.egg-info/SOURCES.txt +12 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle.egg-info/requires.txt +5 -1
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchors_markovian.py +11 -3
- proofbundle-3.6.0/tests/test_anchors_ots.py +275 -0
- proofbundle-3.6.0/tests/test_audit_candidate_360.py +364 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_claims_hygiene.py +106 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_evidence_pack.py +71 -22
- proofbundle-3.6.0/tests/test_fuzz_soak_regression.py +114 -0
- proofbundle-3.6.0/tests/test_ots_calendar_hardening.py +548 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_policy_explain_lint.py +13 -0
- proofbundle-3.6.0/tests/test_relation_profile.py +736 -0
- proofbundle-3.6.0/tests/test_relation_property.py +110 -0
- proofbundle-3.6.0/tests/test_relation_signer_target_340.py +357 -0
- proofbundle-3.6.0/tests/test_relation_statement.py +327 -0
- proofbundle-3.6.0/tests/test_relation_statement_rust_parity.py +53 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_renewal_external_token_glue.py +12 -4
- proofbundle-3.6.0/tests/test_roadmap_frontload_foundations.py +159 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_schema_parity.py +61 -0
- proofbundle-3.6.0/tests/test_trust_pack_payloadtype_negatives.py +96 -0
- proofbundle-3.2.3/src/proofbundle/anchors_ots.py +0 -115
- proofbundle-3.2.3/src/proofbundle/evidence_pack.py +0 -98
- proofbundle-3.2.3/tests/test_anchors_ots.py +0 -132
- {proofbundle-3.2.3 → proofbundle-3.6.0}/LICENSE +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/setup.cfg +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/_inspect_registry.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/_integration.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/_strict_json.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/adapters/__init__.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/adapters/_provenance.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/adapters/eee.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/adapters/inspect_ai.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/adapters/lm_eval.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/adapters/promptfoo.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/adapters/samples.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/anchors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/anchors_chia_add.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/anchors_markovian.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/anchors_rfc3161.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/assurance.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/beacon.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/budget.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/bundle.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/canonical.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/checkpoint.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/demo.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/dsse.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/eee_eval_schema.json +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/emit.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/errors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/evalcard.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/evalclaim.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/experimental/__init__.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/experimental/enclave.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/hashalg.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/hf_evals.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/inspect_hook.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/intoto.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/kbjwt.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/merkle.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/persample.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/policies/decision-receipt-template-v1.json +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/policies/research-preview-v1.json +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/policies/strict-eval-authenticated-root-template-v1.json +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/policies/strict-eval-template-v1.json +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/policies/strict-prereg-template-v1.json +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/policy_profiles.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/pqsig.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/prereg.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/public_transparency.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/py.typed +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/pytest_plugin.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/renewal.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/run_ledger.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/sdjwt.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/sdjwt_issue.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/sdjwt_vc.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/signature.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/statuslist.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/subject_binding.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/tlogproof.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle/verification_summary.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle.egg-info/dependency_links.txt +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle.egg-info/entry_points.txt +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/src/proofbundle.egg-info/top_level.txt +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_adapters.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_adversarial.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchor_hardening_321.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchor_longevity_property.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchor_target_trustedtime.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchors_chia.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchors_chia_add.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchors_chia_claims.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchors_generic.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_anchors_rfc3161.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_assurance.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_automation_verdict.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_beacon.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_branch_base_check.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_budget.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_bundle.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_bundle_robustness.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_canonical.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_checkpoint.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_checkpoint_external_vectors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_checkpoint_quorum_property.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_cli.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_cli_eval.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_cli_require_anchor.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_conformance.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_content_root_property.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_cosignature.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_cosignature_mldsa.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_anchors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_cli.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_evidence.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_fuzz.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_hardening.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_policy.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_schema.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_validator_api.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_decision_verify.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_demo.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_docs_truth.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_dsse_adversarial.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_dup_key_reject.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_ed25519_semantics.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_eee.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_emit.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_enclave_assurance.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_eval_claim_schema.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_eval_evidence_class.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_evalcard.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_evalclaim.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_examples.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_experimental_enclave.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_fork_pr_secret_isolation.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_fuzz_parsers.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_hashalg.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_hf_entry_verify_eee_digest.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_hf_evals.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_inspect_hook.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto_claims_hygiene.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto_content_root_migration.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto_dsse.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto_eval_result.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto_examples.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto_spec_diff.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_intoto_svr.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_kbjwt.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_lens_review_fixes_3_1_3.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_merkle.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_merkle_consistency_property.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_merkle_property.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_mldsa_acvp_vectors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_official_vectors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_ok_semantics.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_ots_external_vectors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_outcome_receiver_corroboration.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_outcome_verify.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_oversized_int_guard.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_persample.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_pinned_key_validation.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_policy_lifecycle_purpose.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_policy_profiles.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_policy_templates.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_pqsig.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_predicate_type_enforcement.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_prereg.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_promptfoo.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_provenance.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_public_transparency.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_pytest_plugin.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_rekor_interop.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_release_integrity_gate.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_renewal.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_renewal_policy.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_renewal_signed.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_rfc6962_external_vectors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_root_authenticity.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_run_ledger.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_rust_parity_gate.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_schema.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwt_adversarial.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwt_duplicate_cnf.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwt_issue.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwt_reference.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwt_resource_budget.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwt_vc.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwt_verify_binding.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_sdjwtvc_external_vectors.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_signature.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_statuslist.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_subject_binding.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_tlogproof.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_tree_context_authenticity.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_trust_pack.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_trust_pack_crypto_agility.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_trust_pack_outcome_receivers_role.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_trust_policy.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_verification_summary.py +0 -0
- {proofbundle-3.2.3 → proofbundle-3.6.0}/tests/test_verify_matrix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proofbundle
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.0
|
|
4
4
|
Summary: Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT.
|
|
5
5
|
Author: Konrad Gruszka
|
|
6
6
|
License: MIT
|
|
@@ -29,13 +29,15 @@ Provides-Extra: eval
|
|
|
29
29
|
Requires-Dist: rfc8785>=0.1.4; extra == "eval"
|
|
30
30
|
Provides-Extra: anchors
|
|
31
31
|
Requires-Dist: rfc3161-client>=1.0.6; extra == "anchors"
|
|
32
|
-
Requires-Dist: opentimestamps
|
|
32
|
+
Requires-Dist: opentimestamps<0.5,>=0.4.5; extra == "anchors"
|
|
33
33
|
Requires-Dist: rfc8785>=0.1.4; extra == "anchors"
|
|
34
34
|
Provides-Extra: adapters
|
|
35
35
|
Provides-Extra: chia
|
|
36
36
|
Provides-Extra: experimental
|
|
37
37
|
Provides-Extra: pq
|
|
38
38
|
Requires-Dist: cryptography>=48; extra == "pq"
|
|
39
|
+
Provides-Extra: formal
|
|
40
|
+
Requires-Dist: z3-solver>=4.12; extra == "formal"
|
|
39
41
|
Provides-Extra: pytest
|
|
40
42
|
Requires-Dist: pytest>=7; extra == "pytest"
|
|
41
43
|
Provides-Extra: inspect
|
|
@@ -49,6 +51,7 @@ Requires-Dist: build>=1; extra == "dev"
|
|
|
49
51
|
Requires-Dist: hypothesis>=6; extra == "dev"
|
|
50
52
|
Requires-Dist: rfc8785>=0.1.4; extra == "dev"
|
|
51
53
|
Requires-Dist: sd-jwt>=0.10; extra == "dev"
|
|
54
|
+
Requires-Dist: PyYAML>=6; extra == "dev"
|
|
52
55
|
Requires-Dist: inspect_ai<0.4,>=0.3.112; extra == "dev"
|
|
53
56
|
Dynamic: license-file
|
|
54
57
|
|
|
@@ -72,7 +75,7 @@ Merkle, one file, no server, no network.
|
|
|
72
75
|
[](https://pypi.org/project/proofbundle/)
|
|
73
76
|
[](https://pypi.org/project/proofbundle/)
|
|
74
77
|
[](https://github.com/b7n0de/proofbundle/blob/main/LICENSE)
|
|
75
|
-
[](https://doi.org/10.5281/zenodo.21110642)
|
|
76
79
|
[](https://github.com/astral-sh/ruff)
|
|
77
80
|
[](https://github.com/b7n0de/proofbundle/blob/main/scripts/mutation_check.py)
|
|
78
81
|
[](https://pypi.org/project/proofbundle/#files)
|
|
@@ -211,9 +214,11 @@ a preprint.
|
|
|
211
214
|
## Cite this work
|
|
212
215
|
|
|
213
216
|
If proofbundle helped your evaluation pipeline, please cite it. Machine-readable metadata is in
|
|
214
|
-
[`CITATION.cff`](https://github.com/b7n0de/proofbundle/blob/main/CITATION.cff)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
[`CITATION.cff`](https://github.com/b7n0de/proofbundle/blob/main/CITATION.cff). The archival software record
|
|
218
|
+
is on Zenodo under concept DOI [10.5281/zenodo.21110642](https://doi.org/10.5281/zenodo.21110642); the
|
|
219
|
+
Technical Note (design write-up) under concept DOI
|
|
220
|
+
[10.5281/zenodo.21230466](https://doi.org/10.5281/zenodo.21230466), also linked from
|
|
221
|
+
[b7n0de.com/proofbundle](https://b7n0de.com/proofbundle).
|
|
217
222
|
|
|
218
223
|
## Post-quantum posture (honest, two layers)
|
|
219
224
|
|
|
@@ -18,7 +18,7 @@ Merkle, one file, no server, no network.
|
|
|
18
18
|
[](https://pypi.org/project/proofbundle/)
|
|
19
19
|
[](https://pypi.org/project/proofbundle/)
|
|
20
20
|
[](https://github.com/b7n0de/proofbundle/blob/main/LICENSE)
|
|
21
|
-
[](https://doi.org/10.5281/zenodo.21110642)
|
|
22
22
|
[](https://github.com/astral-sh/ruff)
|
|
23
23
|
[](https://github.com/b7n0de/proofbundle/blob/main/scripts/mutation_check.py)
|
|
24
24
|
[](https://pypi.org/project/proofbundle/#files)
|
|
@@ -157,9 +157,11 @@ a preprint.
|
|
|
157
157
|
## Cite this work
|
|
158
158
|
|
|
159
159
|
If proofbundle helped your evaluation pipeline, please cite it. Machine-readable metadata is in
|
|
160
|
-
[`CITATION.cff`](https://github.com/b7n0de/proofbundle/blob/main/CITATION.cff)
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
[`CITATION.cff`](https://github.com/b7n0de/proofbundle/blob/main/CITATION.cff). The archival software record
|
|
161
|
+
is on Zenodo under concept DOI [10.5281/zenodo.21110642](https://doi.org/10.5281/zenodo.21110642); the
|
|
162
|
+
Technical Note (design write-up) under concept DOI
|
|
163
|
+
[10.5281/zenodo.21230466](https://doi.org/10.5281/zenodo.21230466), also linked from
|
|
164
|
+
[b7n0de.com/proofbundle](https://b7n0de.com/proofbundle).
|
|
163
165
|
|
|
164
166
|
## Post-quantum posture (honest, two layers)
|
|
165
167
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proofbundle"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.6.0"
|
|
8
8
|
description = "Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -50,9 +50,12 @@ eval = ["rfc8785>=0.1.4"]
|
|
|
50
50
|
# EXPERIMENTAL: external time anchors (proofbundle.anchors) — RFC 3161 TSA + OpenTimestamps. Opt-in and
|
|
51
51
|
# clearly experimental (API/format may change). rfc3161-client (Trail of Bits) is deliberately offline —
|
|
52
52
|
# it verifies an RFC 3161 token against bundled roots, no network in the verify path. opentimestamps is
|
|
53
|
-
# the OTS proof
|
|
54
|
-
#
|
|
55
|
-
|
|
53
|
+
# the OTS proof LIBRARY (python-opentimestamps, the consensus-critical opentimestamps.core modules), NOT
|
|
54
|
+
# the `opentimestamps-client` CLI tool (a SEPARATE PyPI package on the 0.7.x line; do not confuse the
|
|
55
|
+
# two). The library line is 0.4.x; it is upper-bounded so a future 0.5 wire/API change is a deliberate
|
|
56
|
+
# opt-in, not a silent break (WP-D2 reproducibility). rfc8785 gives the receipt its canonical (JCS) root.
|
|
57
|
+
# The base install stays anchor-free (only cryptography); a receipt with no anchors verifies unchanged.
|
|
58
|
+
anchors = ["rfc3161-client>=1.0.6", "opentimestamps>=0.4.5,<0.5", "rfc8785>=0.1.4"]
|
|
56
59
|
# The lm-eval adapter reads exported results.json (no import) → pure stdlib.
|
|
57
60
|
adapters = []
|
|
58
61
|
# chia-datalayer/v1 anchor WRITER (anchors_chia_add). Needs no extra Python package — it drives the
|
|
@@ -69,6 +72,11 @@ experimental = []
|
|
|
69
72
|
# cryptography build with OpenSSL 3.5+ (default PyPI wheels since 48.0.0). Verify-only by default;
|
|
70
73
|
# the emit helper needs the same. Ed25519 paths never require this.
|
|
71
74
|
pq = ["cryptography>=48"]
|
|
75
|
+
# EXPERIMENTAL: the versioned formal model of the relation-lineage ladder (Fundament F3, formal/model.py).
|
|
76
|
+
# z3-solver enables the z3-EXTENDED symbolic proof (higher symbolic arity than bounded enumeration can
|
|
77
|
+
# reach). Without it the model still runs a complete bounded-enumeration proof — the extra only adds the
|
|
78
|
+
# symbolic layer, never gates the model. Verify/emit paths never touch z3.
|
|
79
|
+
formal = ["z3-solver>=4.12"]
|
|
72
80
|
# The pytest plugin (opt-in test-run receipt via the pytest11 entry-point) — pytest is an optional extra,
|
|
73
81
|
# never a core dependency. Floor 7.0: the hooks/stats API is signature-stable across pytest 7/8/9.
|
|
74
82
|
pytest = ["pytest>=7"]
|
|
@@ -78,7 +86,7 @@ pytest = ["pytest>=7"]
|
|
|
78
86
|
# marker is needed here (inspect_ai's own >=3.10 requirement is always satisfiable on a supported base).
|
|
79
87
|
inspect = ['inspect_ai>=0.3.112,<0.4']
|
|
80
88
|
dev = ["pytest>=7", "ruff>=0.5", "jsonschema>=4", "mypy>=1.8", "build>=1", "hypothesis>=6",
|
|
81
|
-
"rfc8785>=0.1.4", "sd-jwt>=0.10", 'inspect_ai>=0.3.112,<0.4']
|
|
89
|
+
"rfc8785>=0.1.4", "sd-jwt>=0.10", "PyYAML>=6", 'inspect_ai>=0.3.112,<0.4']
|
|
82
90
|
|
|
83
91
|
[project.urls]
|
|
84
92
|
Homepage = "https://b7n0de.com"
|
|
@@ -13,7 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
from typing import TYPE_CHECKING
|
|
15
15
|
|
|
16
|
-
__version__ = "3.
|
|
16
|
+
__version__ = "3.6.0"
|
|
17
17
|
|
|
18
18
|
# The `proofbundle/v0.1` normative spec revision this build implements — kept in sync with the
|
|
19
19
|
# `Revision:` line at the top of SPEC.md by tests/test_docs_truth.py (WP-B1, closes #28). Bump
|
|
@@ -122,6 +122,10 @@ def verify_offline_merkle(proof_obj: dict, canonical_root: bytes) -> dict:
|
|
|
122
122
|
Proves ONLY that ``canonical_root`` is a key included under ``published_root``; NOT that ``published_root``
|
|
123
123
|
is on-chain (that is level ii/iii, needs Chia software — see docs/ANCHORS.md).
|
|
124
124
|
"""
|
|
125
|
+
# Fail-closed type guard (F4 type-confusion): a non-object proof is malformed input, reported in
|
|
126
|
+
# the function's own {ok, detail} contract — never a raw AttributeError on `.get`.
|
|
127
|
+
if not isinstance(proof_obj, dict):
|
|
128
|
+
return {"ok": False, "detail": "malformed chia-datalayer proof: expected a JSON object"}
|
|
125
129
|
try:
|
|
126
130
|
key_clvm = _hexbytes(proof_obj.get("key_clvm_hash"), "key_clvm_hash")
|
|
127
131
|
value_clvm = _hexbytes(proof_obj.get("value_clvm_hash"), "value_clvm_hash")
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""OpenTimestamps anchor (EXPERIMENTAL; the `[anchors]` extra).
|
|
2
|
+
|
|
3
|
+
Honest lifecycle (this is where OTS is easy to over-claim):
|
|
4
|
+
|
|
5
|
+
* A fresh stamp is submitted to public calendars and is **PENDING** — it commits your root but is not
|
|
6
|
+
yet anchored in Bitcoin. A pending proof is a WARN, never a full-strength anchor.
|
|
7
|
+
* ``ots upgrade`` embeds the Bitcoin block-header path; only then is the proof **upgraded** and
|
|
8
|
+
self-contained (no calendar needed to verify).
|
|
9
|
+
* Verifying an upgraded proof still needs the Bitcoin **block header** for the attested height — per the
|
|
10
|
+
documented client path, a local (pruned) Bitcoin node. There is no documented "header file instead of
|
|
11
|
+
a node" mode, and we do not claim one. If the caller supplies the block header (its Merkle root) in the
|
|
12
|
+
anchor's ``frozen`` block, we verify against it offline; otherwise we report, honestly, that the proof
|
|
13
|
+
is upgraded but Bitcoin verification needs a node/header — we never silently PASS it.
|
|
14
|
+
|
|
15
|
+
``proof`` is the serialized detached OTS proof; ``canonicalRoot`` is the exact bytes that were stamped.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _classify(timestamp):
|
|
23
|
+
"""Return (has_bitcoin, bitcoin_heights, has_pending) over all attestations in the proof."""
|
|
24
|
+
from opentimestamps.core.notary import ( # noqa: PLC0415
|
|
25
|
+
BitcoinBlockHeaderAttestation, PendingAttestation,
|
|
26
|
+
)
|
|
27
|
+
heights, has_pending = [], False
|
|
28
|
+
for _msg, att in timestamp.all_attestations():
|
|
29
|
+
if isinstance(att, BitcoinBlockHeaderAttestation):
|
|
30
|
+
heights.append(att.height)
|
|
31
|
+
elif isinstance(att, PendingAttestation):
|
|
32
|
+
has_pending = True
|
|
33
|
+
return (bool(heights), heights, has_pending)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _bitcoin_confirmations(timestamp):
|
|
37
|
+
"""Yield ``(attested_msg, height, has_hash_op)`` for every ``BitcoinBlockHeaderAttestation`` in the
|
|
38
|
+
proof tree — ONLY a Bitcoin attestation (a Litecoin or any other chain's attestation with a colliding
|
|
39
|
+
integer height is NOT a Bitcoin confirmation) — where ``has_hash_op`` is True iff at least one
|
|
40
|
+
CRYPTOGRAPHIC hash operation (``CryptOp``, e.g. ``OpSHA256``) lies on the op path from the root
|
|
41
|
+
``file_digest`` down to the attestation.
|
|
42
|
+
|
|
43
|
+
WP-A1.c (Null-Op hardening, 2026-07-17). A genuine Bitcoin timestamp always descends through the
|
|
44
|
+
block's SHA-256 merkle path, so ``has_hash_op`` is always True for it. A SELF-FABRICATED pack can plant
|
|
45
|
+
a ``BitcoinBlockHeaderAttestation`` DIRECTLY on the file digest (leaf == root, zero ops) or under a
|
|
46
|
+
hash-free append/prepend-only chain — then the producer freely set ``file_digest == canonicalRoot ==
|
|
47
|
+
the attested block merkle root`` with NO hashing at all, which is not a timestamp. Such a branch yields
|
|
48
|
+
``has_hash_op=False`` and the caller MUST NOT treat it as a confirmation (fail-closed). ``getattr`` on
|
|
49
|
+
the height is deliberately avoided (the WP-A1.b confirm loop used to read ``getattr(att, 'height')``,
|
|
50
|
+
which a Litecoin attestation also carries — that colliding-height branch is closed here by the
|
|
51
|
+
``isinstance`` filter)."""
|
|
52
|
+
from opentimestamps.core.notary import BitcoinBlockHeaderAttestation # noqa: PLC0415
|
|
53
|
+
from opentimestamps.core.op import CryptOp # noqa: PLC0415
|
|
54
|
+
stack = [(timestamp, False)]
|
|
55
|
+
while stack:
|
|
56
|
+
ts, seen_hash = stack.pop()
|
|
57
|
+
for att in ts.attestations:
|
|
58
|
+
if isinstance(att, BitcoinBlockHeaderAttestation):
|
|
59
|
+
yield (ts.msg, att.height, seen_hash)
|
|
60
|
+
for op, child in ts.ops.items():
|
|
61
|
+
stack.append((child, seen_hash or isinstance(op, CryptOp)))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def verify_opentimestamps(proof: bytes, canonical_root: bytes, *, frozen: dict,
|
|
65
|
+
now: Optional[int] = None, rp_trust: Optional[dict] = None) -> dict:
|
|
66
|
+
"""Fail-closed OTS verify. Returns {ok, detail, warn, status}. A pending proof is warn (status
|
|
67
|
+
'pending'); an upgraded proof with no RELYING-PARTY block header is not-ok-but-not-warn honest report
|
|
68
|
+
(status 'needs_rp_trust'); an upgraded proof verified against an RP-supplied header is ok (status
|
|
69
|
+
'confirmed').
|
|
70
|
+
|
|
71
|
+
WP-A1 (Owner-GO, trust from the relying party): the Bitcoin block header that turns an upgraded proof
|
|
72
|
+
into a CONFIRMED anchor is trust material and MUST come from the relying party (``rp_trust`` — CLI
|
|
73
|
+
``--bitcoin-header`` / policy ``anchors.bitcoin_block_headers``), NEVER from the bundle's own ``frozen``
|
|
74
|
+
block (which the producer controls and could backdate with a self-committed header). A frozen header is
|
|
75
|
+
reported as EVIDENCE (``frozenEvidence``) but is never trusted. Without RP trust material an upgraded
|
|
76
|
+
proof is honestly ``needs_rp_trust`` (ok=False), so ``--require-anchor`` is unmet → exit 3."""
|
|
77
|
+
try:
|
|
78
|
+
from opentimestamps.core.serialize import BytesDeserializationContext # noqa: PLC0415
|
|
79
|
+
from opentimestamps.core.timestamp import DetachedTimestampFile # noqa: PLC0415
|
|
80
|
+
except ImportError:
|
|
81
|
+
return {"ok": False, "warn": False, "status": "no_lib",
|
|
82
|
+
"detail": "opentimestamps anchor needs proofbundle[anchors] (opentimestamps)"}
|
|
83
|
+
try:
|
|
84
|
+
dtf = DetachedTimestampFile.deserialize(BytesDeserializationContext(proof))
|
|
85
|
+
except Exception as exc: # any malformed proof → FAIL (fail-closed)
|
|
86
|
+
return {"ok": False, "warn": False, "status": "malformed",
|
|
87
|
+
"detail": f"OTS proof did not deserialize: {exc}"}
|
|
88
|
+
# structural binding: the proof must commit to EXACTLY the canonical root
|
|
89
|
+
if dtf.file_digest != canonical_root:
|
|
90
|
+
return {"ok": False, "warn": False, "status": "unbound",
|
|
91
|
+
"detail": "OTS proof does not commit to the target canonical root"}
|
|
92
|
+
has_bitcoin, heights, has_pending = _classify(dtf.timestamp)
|
|
93
|
+
if not has_bitcoin:
|
|
94
|
+
if has_pending:
|
|
95
|
+
return {"ok": False, "warn": True, "status": "pending",
|
|
96
|
+
"detail": "OTS proof is PENDING (submitted to calendars, not yet on Bitcoin) — "
|
|
97
|
+
"run `ots upgrade`; not a full anchor yet"}
|
|
98
|
+
return {"ok": False, "warn": False, "status": "empty",
|
|
99
|
+
"detail": "OTS proof has no Bitcoin or pending attestation"}
|
|
100
|
+
# upgraded: to verify offline we need the block's Merkle root for the attested height. WP-A1: that root
|
|
101
|
+
# is TRUST material — it must come from the RELYING PARTY (rp_trust.bitcoin_block_headers, i.e. their
|
|
102
|
+
# own trusted/pruned Bitcoin node), NEVER from the producer-controlled `frozen` block. A frozen header
|
|
103
|
+
# is surfaced as evidence only. BitcoinBlockHeaderAttestation's own check is exactly
|
|
104
|
+
# `attestation_message == block_header.hashMerkleRoot`; we do that comparison directly against the
|
|
105
|
+
# RP-supplied root (equivalent, and avoids reconstructing a full CBlockHeader).
|
|
106
|
+
rp_headers = (rp_trust or {}).get("bitcoin_block_headers") or {}
|
|
107
|
+
frozen_headers = frozen.get("bitcoinBlockHeaderMerkleRootsByHeight") or {}
|
|
108
|
+
if not rp_headers:
|
|
109
|
+
# no RP trust material → cannot confirm. Frozen alone is NOT trust (it could be a self-committed
|
|
110
|
+
# backdated header). Honest not-ok report; --require-anchor stays unmet (exit 3).
|
|
111
|
+
return {"ok": False, "warn": False, "status": "needs_rp_trust", "needs_rp_trust": True,
|
|
112
|
+
"frozenEvidence": bool(frozen_headers),
|
|
113
|
+
"detail": f"OTS proof is upgraded (Bitcoin height {heights}) but confirming it needs a "
|
|
114
|
+
"relying-party-supplied Bitcoin block header (--bitcoin-header / policy "
|
|
115
|
+
"anchors.bitcoin_block_headers). The bundle's own frozen header is producer-"
|
|
116
|
+
"controlled evidence, not trust; not claiming a pass"}
|
|
117
|
+
# WP-A1.b (Berkeley audit, 2026-07-16): a proof can carry SEVERAL Bitcoin attestations (independent
|
|
118
|
+
# calendar branches, a reorg-era re-anchor, a bad/tampered branch alongside a good one). Because the
|
|
119
|
+
# structural binding above pins EVERY attestation's message to the same canonical root (the walk starts
|
|
120
|
+
# at `file_digest`, which must equal `canonical_root`), confirming on ANY branch whose attested block
|
|
121
|
+
# matches the relying-party header is sound — the branch cannot commit an unrelated root. So we scan ALL
|
|
122
|
+
# RP-covered heights and confirm as soon as one matches; a single wrong/tampered branch must NEVER
|
|
123
|
+
# short-circuit and mask a genuinely confirmable one (that would be a False-REJECT / DoS).
|
|
124
|
+
#
|
|
125
|
+
# WP-A1.c (Null-Op hardening, 2026-07-17): a confirmable Bitcoin branch must also sit at the END of a
|
|
126
|
+
# REAL op chain (>=1 cryptographic hash op below the file digest). `_bitcoin_confirmations` filters to
|
|
127
|
+
# BitcoinBlockHeaderAttestation only (a Litecoin attestation with a colliding height is not a Bitcoin
|
|
128
|
+
# confirmation) and reports `has_hash_op`; a branch attested DIRECTLY on the file digest (leaf==root, no
|
|
129
|
+
# ops) is a self-fabricated Null-Op pack, not a timestamp, and is refused (fail-closed) even if its
|
|
130
|
+
# attested value equals the relying-party header. This is defense-in-depth only: the canonical
|
|
131
|
+
# `verify --require-anchor` path is unaffected — it independently binds the anchor to the receipt's
|
|
132
|
+
# recomputed root, so it never trusts a self-declared canonicalRoot.
|
|
133
|
+
#
|
|
134
|
+
# Per-branch diagnostics are retained: if no branch confirms we surface whether a covered branch was a
|
|
135
|
+
# fabricated Null-Op (null_op), present-and-wrong (block_mismatch, a tamper signal), carried bad
|
|
136
|
+
# relying-party hex (bad_header), or was simply uncovered (upgraded_unverified).
|
|
137
|
+
mismatch_heights: list = []
|
|
138
|
+
bad_header_heights: list = []
|
|
139
|
+
null_op_heights: list = []
|
|
140
|
+
for att_msg, height, has_hash_op in _bitcoin_confirmations(dtf.timestamp):
|
|
141
|
+
merkle_root_hex = rp_headers.get(str(height))
|
|
142
|
+
if not merkle_root_hex:
|
|
143
|
+
continue
|
|
144
|
+
try:
|
|
145
|
+
expected = bytes.fromhex(merkle_root_hex)
|
|
146
|
+
except ValueError:
|
|
147
|
+
# a malformed RP header for THIS height must not short-circuit — another branch may confirm.
|
|
148
|
+
bad_header_heights.append(height)
|
|
149
|
+
continue
|
|
150
|
+
if att_msg != expected:
|
|
151
|
+
mismatch_heights.append(height)
|
|
152
|
+
continue
|
|
153
|
+
if not has_hash_op:
|
|
154
|
+
# leaf==root / hash-free chain: a self-fabricated Null-Op pack, never a confirmation. Record it
|
|
155
|
+
# but KEEP scanning — a genuine branch (if any) must still be able to confirm (WP-A1.b).
|
|
156
|
+
null_op_heights.append(height)
|
|
157
|
+
continue
|
|
158
|
+
return {"ok": True, "warn": False, "status": "confirmed", "rp_trusted": True,
|
|
159
|
+
# WP-A2: a Bitcoin HEIGHT is the proof's native trusted-time unit — reported
|
|
160
|
+
# structured, never converted to a wall-clock guess (the header time is not
|
|
161
|
+
# part of the supplied material).
|
|
162
|
+
"trustedTime": {"source": "bitcoin_block", "height": height},
|
|
163
|
+
"detail": f"OTS proof confirmed: committed in the Bitcoin block at height {height} "
|
|
164
|
+
"(merkle root supplied by the relying party)"}
|
|
165
|
+
# No genuine RP-covered branch matched. Fall through with an honest, tamper-visible diagnostic; the
|
|
166
|
+
# fabricated Null-Op case is the most severe (it matched but was never a real timestamp) — surface it
|
|
167
|
+
# first, but keep the other per-branch signals for the relying party.
|
|
168
|
+
if null_op_heights:
|
|
169
|
+
return {"ok": False, "warn": False, "status": "null_op", "rp_trusted": True,
|
|
170
|
+
"nullOpHeights": sorted(null_op_heights),
|
|
171
|
+
"mismatchHeights": sorted(mismatch_heights),
|
|
172
|
+
"badHeaderHeights": sorted(bad_header_heights),
|
|
173
|
+
"detail": f"OTS proof's Bitcoin attestation(s) at height(s) {sorted(null_op_heights)} sit "
|
|
174
|
+
"directly on the canonical root with no cryptographic op chain (leaf==root) — a "
|
|
175
|
+
"self-fabricated Null-Op pack, not a real Bitcoin timestamp; not confirmed. The "
|
|
176
|
+
"relying party must bind the anchor independently (verify --require-anchor)"}
|
|
177
|
+
if mismatch_heights:
|
|
178
|
+
return {"ok": False, "warn": False, "status": "block_mismatch", "rp_trusted": True,
|
|
179
|
+
"mismatchHeights": sorted(mismatch_heights),
|
|
180
|
+
"badHeaderHeights": sorted(bad_header_heights),
|
|
181
|
+
"detail": f"no OTS Bitcoin attestation matched the relying-party block merkle root: "
|
|
182
|
+
f"present-and-wrong at height(s) {sorted(mismatch_heights)}"
|
|
183
|
+
+ (f", invalid relying-party hex at height(s) {sorted(bad_header_heights)}"
|
|
184
|
+
if bad_header_heights else "")}
|
|
185
|
+
if bad_header_heights:
|
|
186
|
+
return {"ok": False, "warn": False, "status": "bad_header", "rp_trusted": True,
|
|
187
|
+
"badHeaderHeights": sorted(bad_header_heights),
|
|
188
|
+
"detail": f"relying-party Bitcoin block merkle root is not valid hex for height(s) "
|
|
189
|
+
f"{sorted(bad_header_heights)}, and no other covered branch confirmed"}
|
|
190
|
+
return {"ok": False, "warn": False, "status": "upgraded_unverified", "needs_rp_trust": True,
|
|
191
|
+
"frozenEvidence": bool(frozen_headers),
|
|
192
|
+
"detail": f"OTS proof is upgraded (Bitcoin height {heights}) but the relying party supplied no "
|
|
193
|
+
"block header for any attested height; not claiming a pass"}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ── Calendar transparency (WP-B1) ──────────────────────────────────────────────────────────────────
|
|
197
|
+
# A stamp is created with the aid of MULTIPLE remote calendars — the OpenTimestamps client submits to
|
|
198
|
+
# three default endpoints across at least two independent operators (a/b.pool.opentimestamps.org run by
|
|
199
|
+
# OpenTimestamps, a.pool.eternitywall.com run by Eternity Wall), and requires at least two to reply.
|
|
200
|
+
# Surfacing WHICH calendars carry a proof is an embedded-but-UNVERIFIED transparency hint (a
|
|
201
|
+
# PendingAttestation URI is unauthenticated and offline-constructible), NOT cryptographic redundancy
|
|
202
|
+
# evidence: an outage or defunding of any one calendar removes none of the others' PendingAttestations,
|
|
203
|
+
# and verifying an UPGRADED proof needs no calendar at all. These helpers are pure transparency: they
|
|
204
|
+
# read a proof, never trust it, never raise.
|
|
205
|
+
|
|
206
|
+
_KNOWN_CALENDAR_OPERATORS = (
|
|
207
|
+
("opentimestamps.org", "opentimestamps"),
|
|
208
|
+
("eternitywall.com", "eternitywall"),
|
|
209
|
+
("catallaxy.com", "catallaxy"),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def calendar_operator(uri: str) -> str:
|
|
214
|
+
"""Best-effort operator label for a calendar URI. Operator redundancy (distinct OPERATORS), not URL
|
|
215
|
+
count, is what tolerates an outage or a defunding — two URLs on one operator are one point of
|
|
216
|
+
failure. Maps a known calendar host to its operator; an unknown host falls back to its last two
|
|
217
|
+
host labels so a distinct third-party or self-hosted calendar still counts as a distinct operator.
|
|
218
|
+
Never raises (a transparency helper must not break a verify path).
|
|
219
|
+
|
|
220
|
+
BLIND SPOT (documented, not hidden — Berkeley audit 2026-07-16): this is a bare-hostname heuristic,
|
|
221
|
+
NOT a verified-independent-entity claim. The last-two-labels fallback does not know the public-suffix
|
|
222
|
+
boundary, so a ccSLD host like ``cal.example.co.uk`` collapses to ``co.uk`` (and ``example.com.au`` to
|
|
223
|
+
``com.au``): two genuinely independent operators under the same ccSLD would be counted as one, and the
|
|
224
|
+
label itself is a registrable-domain guess, not an attestation of who runs the calendar. It is a
|
|
225
|
+
transparency hint only; for a real independence claim, pin the operators you trust. An optional
|
|
226
|
+
``tldextract`` dependency would resolve the public-suffix boundary; it is deliberately not added here,
|
|
227
|
+
so this stays a heuristic."""
|
|
228
|
+
if not isinstance(uri, str) or not uri:
|
|
229
|
+
return "unknown"
|
|
230
|
+
from urllib.parse import urlparse # noqa: PLC0415
|
|
231
|
+
host = (urlparse(uri if "://" in uri else "https://" + uri).hostname or "").lower()
|
|
232
|
+
if not host:
|
|
233
|
+
return "unknown"
|
|
234
|
+
for needle, label in _KNOWN_CALENDAR_OPERATORS:
|
|
235
|
+
if host == needle or host.endswith("." + needle):
|
|
236
|
+
return label
|
|
237
|
+
parts = [p for p in host.split(".") if p]
|
|
238
|
+
return ".".join(parts[-2:]) if len(parts) >= 2 else host
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def calendar_uris(proof: bytes) -> list[str]:
|
|
242
|
+
"""The distinct calendar URIs whose PendingAttestations carry ``proof`` (WP-B1 transparency).
|
|
243
|
+
Fail-closed: without the ``[anchors]`` extra, or on a malformed proof, returns ``[]`` (never raises).
|
|
244
|
+
An UPGRADED proof that no longer retains pending attestations honestly returns ``[]`` — its calendar
|
|
245
|
+
dependency is already discharged, which is precisely the calendar-independence being surfaced."""
|
|
246
|
+
try:
|
|
247
|
+
from opentimestamps.core.notary import PendingAttestation # noqa: PLC0415
|
|
248
|
+
from opentimestamps.core.serialize import BytesDeserializationContext # noqa: PLC0415
|
|
249
|
+
from opentimestamps.core.timestamp import DetachedTimestampFile # noqa: PLC0415
|
|
250
|
+
except ImportError:
|
|
251
|
+
return []
|
|
252
|
+
try:
|
|
253
|
+
dtf = DetachedTimestampFile.deserialize(BytesDeserializationContext(proof))
|
|
254
|
+
except Exception: # malformed → no calendars, never raise (fail-closed transparency)
|
|
255
|
+
return []
|
|
256
|
+
uris: set[str] = set()
|
|
257
|
+
for _msg, att in dtf.timestamp.all_attestations():
|
|
258
|
+
if isinstance(att, PendingAttestation):
|
|
259
|
+
uri = getattr(att, "uri", None)
|
|
260
|
+
if isinstance(uri, bytes):
|
|
261
|
+
try:
|
|
262
|
+
uri = uri.decode("utf-8")
|
|
263
|
+
except Exception:
|
|
264
|
+
continue
|
|
265
|
+
if isinstance(uri, str) and uri:
|
|
266
|
+
uris.add(uri)
|
|
267
|
+
return sorted(uris)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def calendar_operators(uris) -> list[str]:
|
|
271
|
+
"""The distinct, sorted operator labels behind a list of calendar URIs (WP-B1). ``len(...)`` is the
|
|
272
|
+
OPERATOR redundancy — the number that survives an outage, unlike a raw URL count."""
|
|
273
|
+
return sorted({calendar_operator(u) for u in (uris or [])})
|
|
@@ -33,6 +33,19 @@ AUTOMATION_BLOCKER_REASONS = {
|
|
|
33
33
|
"POLICY_FAILED": "The supplied trust policy / authorization gate was not satisfied",
|
|
34
34
|
"REFERENCES_NOT_RESOLVED": "One or more referenced/bound artifacts did not resolve (see the "
|
|
35
35
|
"predicate's own *_ok / *_bound / *_intact fields for which)",
|
|
36
|
+
# relation/v0.1 (EXPERIMENTAL): raised by the DECISION verify path when the trust
|
|
37
|
+
# policy's relations section is violated (the outcome-path policy gate is a documented
|
|
38
|
+
# follow-up — verify_outcome_receipt has no policy parameter today) (a named relation did not resolve, or an attached,
|
|
39
|
+
# verified successor supersedes the receipt under rejectSuperseded). LIVE, not dormant.
|
|
40
|
+
"LINEAGE_REQUIREMENT_FAILED": "The trust policy's lineage requirement was not met (relations "
|
|
41
|
+
"section: unresolved required relation or superseded receipt)",
|
|
42
|
+
# relation/v0.1 3.4.0 (WP-A / WP-A2): raised on BOTH the decision and outcome verify paths.
|
|
43
|
+
"RELATION_SIGNER_UNAUTHORIZED": "The successor's issuer key is not authorized by the trust "
|
|
44
|
+
"policy's relation_signer pin (WHO may replace — set membership "
|
|
45
|
+
"under the verifier's pins, not a proof of authority)",
|
|
46
|
+
"RELATION_TARGET_MISMATCH": "A supersedes-like edge resolves to a parent NOT in the trust "
|
|
47
|
+
"policy's require_relation_target pin (WHICH parent — a valid but "
|
|
48
|
+
"wrong/decoy parent, rejected only in the policy verdict)",
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
|