Background
ForeignChainSignatureVerifier derives borsh serialization so consumer contracts can persist it between submitting verify_foreign_transaction and verifying the response in the callback (added deliberately for the bridge team in #2266, closing #2263).
However, the verifier does not record which payload version it was built against. build() pins payload_version (from DEFAULT_PAYLOAD_VERSION) into the on-chain request, while verify_signature() re-derives the expected payload hash from the crate-global DEFAULT_PAYLOAD_VERSION at verify time.
Failure scenario
- A consumer contract builds a request with SDK version X (
DEFAULT_PAYLOAD_VERSION = V1) and persists the verifier in its state.
- The consumer upgrades to SDK version Y where the default moved to V2.
- The persisted verifier now recomputes a V2 hash for a request that was bound to V1, and rejects a valid response.
Surfaced by review on #4032 (which added ForeignTxSignPayload::new(version, ...), making the fix straightforward).
Proposed fix
Store the payload version on the verifier at build time and derive the expected hash from self.payload_version in verify_signature().
Timing constraint
Adding a field changes the verifier's borsh layout, which breaks deserialization of any already-persisted verifier (loudly: consumer state reads panic). Today no end-to-end foreign-tx flow is live, so nothing real can be persisted yet — the change is free now and becomes a consumer-breaking change once integrations ship. This should land before the foreign-tx feature/SDK gets real consumers.
Additional consideration
While changing the layout anyway, consider wrapping the persisted verifier in a versioned enum (e.g. V1 { ... }) so future field additions are non-breaking instead of hitting this same cliff.
Background
ForeignChainSignatureVerifierderives borsh serialization so consumer contracts can persist it between submittingverify_foreign_transactionand verifying the response in the callback (added deliberately for the bridge team in #2266, closing #2263).However, the verifier does not record which payload version it was built against.
build()pinspayload_version(fromDEFAULT_PAYLOAD_VERSION) into the on-chain request, whileverify_signature()re-derives the expected payload hash from the crate-globalDEFAULT_PAYLOAD_VERSIONat verify time.Failure scenario
DEFAULT_PAYLOAD_VERSION = V1) and persists the verifier in its state.Surfaced by review on #4032 (which added
ForeignTxSignPayload::new(version, ...), making the fix straightforward).Proposed fix
Store the payload version on the verifier at build time and derive the expected hash from
self.payload_versioninverify_signature().Timing constraint
Adding a field changes the verifier's borsh layout, which breaks deserialization of any already-persisted verifier (loudly: consumer state reads panic). Today no end-to-end foreign-tx flow is live, so nothing real can be persisted yet — the change is free now and becomes a consumer-breaking change once integrations ship. This should land before the foreign-tx feature/SDK gets real consumers.
Additional consideration
While changing the layout anyway, consider wrapping the persisted verifier in a versioned enum (e.g.
V1 { ... }) so future field additions are non-breaking instead of hitting this same cliff.