Skip to content

Commit 6b6ae4d

Browse files
committed
Bind BTC SPV adapter to live evidence
1 parent ccc7706 commit 6b6ae4d

4 files changed

Lines changed: 276 additions & 48 deletions

File tree

‎proposals/novaseal/v0-mvp-skeleton/proofs/public_btc_spv_evidence.template.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
{
2020
"profile": "btc-transaction-commitment-profile-v0",
2121
"scenario": "btc-transaction-commitment-transition",
22+
"ckb_live_tx_hash": "0xREPLACE_WITH_64_HEX_CURRENT_CKB_LIVE_TX_HASH",
23+
"live_report_hash": "0xREPLACE_WITH_64_HEX_CURRENT_LIVE_REPORT_HASH",
24+
"service_builder_case_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_CASE_HASH",
25+
"service_builder_tx_skeleton_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_TX_SKELETON_HASH",
26+
"service_builder_receipt_binding_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_RECEIPT_BINDING_HASH",
2227
"btc_txid": "0xREPLACE_WITH_64_HEX_BTC_TXID",
2328
"btc_block_hash": "0xREPLACE_WITH_64_HEX_BTC_BLOCK_HASH",
2429
"minimum_confirmations": 6,
@@ -39,6 +44,11 @@
3944
{
4045
"profile": "btc-utxo-seal-profile-v0",
4146
"scenario": "btc-utxo-seal-closure",
47+
"ckb_live_tx_hash": "0xREPLACE_WITH_64_HEX_CURRENT_CKB_LIVE_TX_HASH",
48+
"live_report_hash": "0xREPLACE_WITH_64_HEX_CURRENT_LIVE_REPORT_HASH",
49+
"service_builder_case_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_CASE_HASH",
50+
"service_builder_tx_skeleton_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_TX_SKELETON_HASH",
51+
"service_builder_receipt_binding_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_RECEIPT_BINDING_HASH",
4252
"btc_txid": "0xREPLACE_WITH_64_HEX_BTC_SPEND_TXID",
4353
"btc_block_hash": "0xREPLACE_WITH_64_HEX_BTC_BLOCK_HASH",
4454
"minimum_confirmations": 6,
@@ -59,6 +69,11 @@
5969
{
6070
"profile": "dual-seal-profile-v0",
6171
"scenario": "dual-seal-finality",
72+
"ckb_live_tx_hash": "0xREPLACE_WITH_64_HEX_CURRENT_CKB_LIVE_TX_HASH",
73+
"live_report_hash": "0xREPLACE_WITH_64_HEX_CURRENT_LIVE_REPORT_HASH",
74+
"service_builder_case_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_CASE_HASH",
75+
"service_builder_tx_skeleton_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_TX_SKELETON_HASH",
76+
"service_builder_receipt_binding_hash": "0xREPLACE_WITH_64_HEX_SERVICE_BUILDER_RECEIPT_BINDING_HASH",
6277
"btc_txid": "0xREPLACE_WITH_64_HEX_BTC_CLOSURE_TXID",
6378
"btc_block_hash": "0xREPLACE_WITH_64_HEX_BTC_BLOCK_HASH",
6479
"minimum_confirmations": 6,
@@ -82,6 +97,7 @@
8297
"A real report must be saved as public_btc_spv_evidence.json and must not use local-devnet or placeholder fields.",
8398
"The request contract generated at target/novaseal-btc-spv-evidence-adapter.json lists the exact BTC-facing profile/scenario cases and service-builder hashes this public report must satisfy.",
8499
"The request_handoff block must reference the current target/novaseal-external-evidence-handoff-bundle.json hash, blake2b-256(person=NovaExtHandoff) hash algorithm, and public_btc_spv_evidence group.",
100+
"Each case must also echo the current CKB live tx hash, live report hash, and service-builder binding hashes from the handoff bundle; unrelated BTC SPV proofs are rejected.",
85101
"The production gate requires all three BTC-facing profiles to have public BTC SPV cases with at least six confirmations."
86102
]
87103
}

‎scripts/novaseal_btc_spv_evidence_adapter.py‎

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,61 @@
3232
"btc-utxo-seal-profile-v0": "btc-utxo-seal-closure",
3333
"dual-seal-profile-v0": "dual-seal-finality",
3434
}
35+
REQUIRED_PUBLIC_FIELDS = [
36+
"network",
37+
"generated_at",
38+
"evidence_provider",
39+
"required_profiles",
40+
"profile",
41+
"scenario",
42+
"ckb_live_tx_hash",
43+
"live_report_hash",
44+
"service_builder_case_hash",
45+
"service_builder_tx_skeleton_hash",
46+
"service_builder_receipt_binding_hash",
47+
"btc_txid",
48+
"btc_block_hash",
49+
"spv_proof_hash",
50+
"minimum_confirmations",
51+
"confirmations",
52+
"spv_client_cell_dep.out_point",
53+
"spv_client_cell_dep.data_hash",
54+
"spv_client_cell_dep.dep_type",
55+
"spv_client_cell_dep.hash_type",
56+
"source_service.name",
57+
"source_service.commit",
58+
"source_service.report_hash",
59+
"request_handoff.bundle",
60+
"request_handoff.bundle_hash",
61+
"request_handoff.bundle_hash_algorithm",
62+
"request_handoff.group",
63+
]
64+
FIELD_CONSTRAINTS = {
65+
"network": "explicit public mainnet/testnet name; placeholders and local/devnet/regtest/simnet/private/fake labels are rejected",
66+
"generated_at": "UTC timestamp in YYYY-MM-DDTHH:MM:SSZ form; future timestamps are rejected",
67+
"evidence_provider": "real external provider identity; placeholder, example, and unknown tokens are rejected",
68+
"ckb_live_tx_hash": "0x-prefixed 32-byte CKB live transaction hash matching the current NovaSeal service-builder case",
69+
"live_report_hash": "0x-prefixed 32-byte hash of the current NovaSeal live devnet report for this profile",
70+
"service_builder_case_hash": "0x-prefixed 32-byte hash of the current NovaSeal service-builder case for this profile",
71+
"service_builder_tx_skeleton_hash": "0x-prefixed 32-byte service-builder transaction skeleton hash for this profile",
72+
"service_builder_receipt_binding_hash": "0x-prefixed 32-byte service-builder receipt binding hash for this profile",
73+
"btc_txid": "0x-prefixed 32-byte non-placeholder Bitcoin transaction id",
74+
"btc_block_hash": "0x-prefixed 32-byte non-placeholder Bitcoin block hash anchoring the SPV proof",
75+
"spv_proof_hash": "0x-prefixed 32-byte non-placeholder hash of the SPV proof material",
76+
"minimum_confirmations": "integer confirmation floor; at least 6",
77+
"confirmations": "integer observed confirmations meeting minimum_confirmations",
78+
"spv_client_cell_dep.out_point": "0x-prefixed 32-byte CKB transaction hash plus numeric output index",
79+
"spv_client_cell_dep.data_hash": "0x-prefixed 32-byte non-placeholder SPV client data hash",
80+
"spv_client_cell_dep.dep_type": "code",
81+
"spv_client_cell_dep.hash_type": "data, data1, or type CKB script hash type",
82+
"source_service.name": "real external SPV service identity; placeholder, example, and unknown tokens are rejected",
83+
"source_service.commit": "40-character hex service source commit",
84+
"source_service.report_hash": "0x-prefixed 32-byte non-placeholder SPV service report hash",
85+
"request_handoff.bundle": "target/novaseal-external-evidence-handoff-bundle.json",
86+
"request_handoff.bundle_hash": "0x-prefixed 32-byte hash of the NovaSeal external evidence handoff bundle",
87+
"request_handoff.bundle_hash_algorithm": "blake2b-256(person=NovaExtHandoff)",
88+
"request_handoff.group": "public_btc_spv_evidence",
89+
}
3590

3691

3792
def hex0x(data: bytes) -> str:
@@ -58,56 +113,16 @@ def profile_cases(service_builder: dict[str, Any], template: dict[str, Any]) ->
58113
builder_case = next((case for case in builder_cases if case.get("profile") == profile), None)
59114
template_case = next((case for case in template_cases if case.get("profile") == profile), None)
60115
external_inputs = builder_case.get("request", {}).get("production_external_inputs", []) if builder_case else []
116+
required_live_inputs = builder_case.get("request", {}).get("required_live_inputs", {}) if builder_case else {}
61117
request = {
62118
"profile": profile,
63119
"scenario": template_case.get("scenario") if template_case else None,
64120
"minimum_confirmations": template_case.get("minimum_confirmations") if template_case else 6,
65-
"required_public_fields": [
66-
"network",
67-
"generated_at",
68-
"evidence_provider",
69-
"required_profiles",
70-
"profile",
71-
"scenario",
72-
"btc_txid",
73-
"btc_block_hash",
74-
"spv_proof_hash",
75-
"minimum_confirmations",
76-
"confirmations",
77-
"spv_client_cell_dep.out_point",
78-
"spv_client_cell_dep.data_hash",
79-
"spv_client_cell_dep.dep_type",
80-
"spv_client_cell_dep.hash_type",
81-
"source_service.name",
82-
"source_service.commit",
83-
"source_service.report_hash",
84-
"request_handoff.bundle",
85-
"request_handoff.bundle_hash",
86-
"request_handoff.bundle_hash_algorithm",
87-
"request_handoff.group",
88-
],
89-
"field_constraints": {
90-
"network": "explicit public mainnet/testnet name; placeholders and local/devnet/regtest/simnet/private/fake labels are rejected",
91-
"generated_at": "UTC timestamp in YYYY-MM-DDTHH:MM:SSZ form; future timestamps are rejected",
92-
"evidence_provider": "real external provider identity; placeholder, example, and unknown tokens are rejected",
93-
"btc_txid": "0x-prefixed 32-byte non-placeholder Bitcoin transaction id",
94-
"btc_block_hash": "0x-prefixed 32-byte non-placeholder Bitcoin block hash anchoring the SPV proof",
95-
"spv_proof_hash": "0x-prefixed 32-byte non-placeholder hash of the SPV proof material",
96-
"minimum_confirmations": "integer confirmation floor; at least 6",
97-
"confirmations": "integer observed confirmations meeting minimum_confirmations",
98-
"spv_client_cell_dep.out_point": "0x-prefixed 32-byte CKB transaction hash plus numeric output index",
99-
"spv_client_cell_dep.data_hash": "0x-prefixed 32-byte non-placeholder SPV client data hash",
100-
"spv_client_cell_dep.dep_type": "code",
101-
"spv_client_cell_dep.hash_type": "data, data1, or type CKB script hash type",
102-
"source_service.name": "real external SPV service identity; placeholder, example, and unknown tokens are rejected",
103-
"source_service.commit": "40-character hex service source commit",
104-
"source_service.report_hash": "0x-prefixed 32-byte non-placeholder SPV service report hash",
105-
"request_handoff.bundle": "target/novaseal-external-evidence-handoff-bundle.json",
106-
"request_handoff.bundle_hash": "0x-prefixed 32-byte hash of the NovaSeal external evidence handoff bundle",
107-
"request_handoff.bundle_hash_algorithm": "blake2b-256(person=NovaExtHandoff)",
108-
"request_handoff.group": "public_btc_spv_evidence",
109-
},
121+
"required_public_fields": REQUIRED_PUBLIC_FIELDS,
122+
"field_constraints": FIELD_CONSTRAINTS,
110123
"required_external_inputs": external_inputs,
124+
"ckb_live_tx_hash": required_live_inputs.get("live_devnet_tx_hash"),
125+
"live_report_hash": required_live_inputs.get("live_report_hash"),
111126
"service_builder_case_hash": report_hash("service_builder_case", builder_case),
112127
"service_builder_tx_skeleton_hash": builder_case.get("response", {}).get("tx_skeleton_hash") if builder_case else None,
113128
"service_builder_receipt_binding_hash": builder_case.get("response", {}).get("receipt_binding_hash") if builder_case else None,
@@ -119,9 +134,10 @@ def profile_cases(service_builder: dict[str, Any], template: dict[str, Any]) ->
119134
"scenario_matches_required_profile": request["scenario"] == REQUIRED_SCENARIOS[profile],
120135
"public_btc_spv_external_input_named": "public_btc_spv_evidence" in external_inputs,
121136
"minimum_confirmations_at_least_six": request["minimum_confirmations"] >= 6,
137+
"live_binding_hashes_present": bool(request["ckb_live_tx_hash"]) and bool(request["live_report_hash"]),
122138
"service_builder_hashes_present": bool(request["service_builder_tx_skeleton_hash"])
123139
and bool(request["service_builder_receipt_binding_hash"]),
124-
"required_public_fields_complete": len(request["required_public_fields"]) == 22,
140+
"required_public_fields_complete": len(request["required_public_fields"]) == len(REQUIRED_PUBLIC_FIELDS),
125141
}
126142
cases.append(
127143
{

‎scripts/novaseal_external_evidence_handoff_bundle.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,24 @@ def btc_spv_handoff_case(adapter: dict[str, Any]) -> dict[str, Any]:
171171
for case in cases
172172
if isinstance(case.get("profile"), str) and isinstance(case.get("request", {}).get("scenario"), str)
173173
}
174+
expected_case_bindings = {
175+
case.get("profile"): {
176+
"ckb_live_tx_hash": case.get("request", {}).get("ckb_live_tx_hash"),
177+
"live_report_hash": case.get("request", {}).get("live_report_hash"),
178+
"service_builder_case_hash": case.get("request", {}).get("service_builder_case_hash"),
179+
"service_builder_tx_skeleton_hash": case.get("request", {}).get("service_builder_tx_skeleton_hash"),
180+
"service_builder_receipt_binding_hash": case.get("request", {}).get("service_builder_receipt_binding_hash"),
181+
}
182+
for case in cases
183+
if isinstance(case.get("profile"), str)
184+
}
185+
binding_fields = {
186+
"ckb_live_tx_hash",
187+
"live_report_hash",
188+
"service_builder_case_hash",
189+
"service_builder_tx_skeleton_hash",
190+
"service_builder_receipt_binding_hash",
191+
}
174192
checks = {
175193
"source_adapter_passed": adapter.get("status") == "passed",
176194
"source_adapter_status_request_ready": adapter.get("adapter_status") == "request_ready_external_evidence_required",
@@ -180,6 +198,11 @@ def btc_spv_handoff_case(adapter: dict[str, Any]) -> dict[str, Any]:
180198
"required_profiles_complete": profiles == set(REQUIRED_BTC_SPV_PROFILES),
181199
"expected_scenarios_complete": set(expected_scenarios) == set(REQUIRED_BTC_SPV_PROFILES)
182200
and all(expected_scenarios.values()),
201+
"expected_case_bindings_complete": set(expected_case_bindings) == set(REQUIRED_BTC_SPV_PROFILES)
202+
and all(
203+
set(binding.keys()) == binding_fields and all(isinstance(value, str) and value.startswith("0x") for value in binding.values())
204+
for binding in expected_case_bindings.values()
205+
),
183206
"source_cases_passed": all(case.get("status") == "passed" for case in cases),
184207
}
185208
return {
@@ -191,13 +214,19 @@ def btc_spv_handoff_case(adapter: dict[str, Any]) -> dict[str, Any]:
191214
"production_output": PUBLIC_BTC_SPV_EVIDENCE,
192215
"required_profiles": REQUIRED_BTC_SPV_PROFILES,
193216
"expected_scenarios": expected_scenarios,
217+
"expected_case_bindings": expected_case_bindings,
194218
"required_external_fields": [
195219
"network",
196220
"generated_at",
197221
"evidence_provider",
198222
"required_profiles",
199223
"profile",
200224
"scenario",
225+
"ckb_live_tx_hash",
226+
"live_report_hash",
227+
"service_builder_case_hash",
228+
"service_builder_tx_skeleton_hash",
229+
"service_builder_receipt_binding_hash",
201230
"btc_txid",
202231
"btc_block_hash",
203232
"spv_proof_hash",
@@ -219,6 +248,11 @@ def btc_spv_handoff_case(adapter: dict[str, Any]) -> dict[str, Any]:
219248
"network": "explicit public mainnet/testnet name; placeholders and local/devnet/regtest/simnet/private/fake labels are rejected",
220249
"generated_at": "UTC timestamp in YYYY-MM-DDTHH:MM:SSZ form; future timestamps are rejected",
221250
"evidence_provider": "real external provider identity; placeholder, example, and unknown tokens are rejected",
251+
"ckb_live_tx_hash": "0x-prefixed 32-byte CKB live transaction hash matching the current NovaSeal service-builder case",
252+
"live_report_hash": "0x-prefixed 32-byte hash of the current NovaSeal live devnet report for this profile",
253+
"service_builder_case_hash": "0x-prefixed 32-byte hash of the current NovaSeal service-builder case for this profile",
254+
"service_builder_tx_skeleton_hash": "0x-prefixed 32-byte service-builder transaction skeleton hash for this profile",
255+
"service_builder_receipt_binding_hash": "0x-prefixed 32-byte service-builder receipt binding hash for this profile",
222256
"btc_txid": "0x-prefixed 32-byte non-placeholder Bitcoin transaction id",
223257
"btc_block_hash": "0x-prefixed 32-byte non-placeholder Bitcoin block hash anchoring the SPV proof",
224258
"spv_proof_hash": "0x-prefixed 32-byte non-placeholder hash of the SPV proof material",

0 commit comments

Comments
 (0)