Skip to content

HTLC claim/reclaim deadline is validated against local time.Now(), making validation non-deterministic #1750

Description

@SuyashAlphaC

Summary

The HTLC claim-vs-reclaim decision is evaluated during transaction validation using the validating node's local wall clock (time.Now()), instead of the deterministic ledger/block timestamp. The same transaction can therefore be judged a Claim on one validation and a Reclaim on another, depending on when and where validation runs.

Where

Both validators compute now from the wall clock and feed it into the HTLC deadline check:

  • token/core/fabtoken/v1/validator/validator_transfer.go:143now := time.Now()
  • token/core/zkatdlog/nogh/v1/validator/validator_transfer.go:151now := time.Now()

That now flows into htlc.VerifyOwner(...) (token/services/identity/interop/htlc/validator.go), which decides the operation from now.Before(script.Deadline):

  • before the deadline → must be a Claim (output owner must be the recipient),
  • at/after the deadline → must be a Reclaim (output owner must be the sender).

It also reaches script.Validate(now) in the same files.

Why this matters

Token-request validation runs on multiple peers and at more than one point in a transaction's life (endorsement, then commit-time re-validation). Using time.Now() makes the Claim/Reclaim verdict depend on which node and what moment the check runs:

  • Cross-peer clock skew: for a spend submitted near the deadline, one endorser (clock just before) validates it as a Claim and accepts, while another (clock just after) expects a Reclaim and rejects. Endorsement becomes inconsistent.
  • Endorse vs commit on the same peer: a spend valid as a Claim at endorsement time can flip to "should have been a Reclaim" by commit-time re-validation, or vice versa.

For HTLC-based atomic swaps this is a safety problem, not just a robustness one: the deadline is the hinge of the swap, and a fuzzy/non-deterministic deadline can let a claim through past the intended cutoff or let a claim and a reclaim race.

Note: the fix hook already exists but is unused

htlc.Verifier has a ClockFunc func() time.Time field (token/services/interop/htlc/signer.go:96-100) whose own doc says callers with the Fabric block timestamp should inject it "to ensure deterministic, clock-skew-safe deadline enforcement." ClockFunc is currently never set anywhere, and the validators bypass it by calling time.Now() directly.

Metadata

Metadata

Assignees

Labels

Fields

No fields configured for Feature.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions