diff --git a/provenance.go b/provenance.go index 513a006..ba46b51 100644 --- a/provenance.go +++ b/provenance.go @@ -573,3 +573,91 @@ func InspectPredicateResult(result map[string]any, capsuleInclusion *bool) (*Pre NotClaimed: notClaimed, }, nil } + +// ZKRangeWidths are the proof widths ATTESTO-ZK-RANGE-001 permits. Pinned rather +// than derived: a client offering a width outside this set would build +// statements the proving library refuses after the transcript is already bound. +var ZKRangeWidths = [4]uint{8, 16, 32, 64} + +// zkRangeStatementFields is exactly what a range statement carries. Every field +// is folded into the proof transcript, so an extra one would bind to nothing and +// a missing one would change the challenges. +var zkRangeStatementFields = map[string]struct{}{ + "capsule_root": {}, "claim_id": {}, "claim_descriptor_version": {}, + "provider_id": {}, "provider_version": {}, "commitment_c": {}, + "predicate_type": {}, "lower_bound": {}, "upper_bound": {}, + "encoding_version": {}, "proof_scheme_version": {}, "verifier_nonce": {}, +} + +// ZKRangeWidth returns the smallest permitted width covering the whole interval. +// +// Both proved differences are bounded by upper-lower, so one width serves both. +// It is derived from the public bounds and never chosen by the prover: a prover +// who picked it could prove a wider range than the statement says. +func ZKRangeWidth(lowerBound, upperBound uint64) (uint, error) { + if upperBound < lowerBound { + return 0, fmt.Errorf("upper bound is below its lower bound") + } + span := upperBound - lowerBound + for _, width := range ZKRangeWidths { + if width == 64 || span < (uint64(1)<