Documentation
¶
Overview ¶
errors.go — sentinel errors for the P1.3 ZK opt-in flags.
Each Error() returns the upstream stable code from paygate-zk/docs/prds/konareef-integration-contract-v1.md § 6 (PRD 4 error taxonomy) so the CLI surface, structured logs, and downstream consumers all see the same identifier.
Package publish orchestrates the local half of `konareef pod publish` — turning a pod directory into a signed bundle ready to POST to reef-core's `/api/pods` endpoint.
Prepare composes the three existing primitives:
- internal/pod — parse + schema validation
- internal/canon — canonicalization + pod_hash (SHA-256)
- internal/identity — secp256k1 ECDSA over the canonical bytes
It is deliberately offline-only: anything that needs the network (the actual POST, the install URL, the published-pod row) lives in the Submit function, so the local steps can be `--dry-run`-tested without a running reef-core.
zk_session.go — ZK-session orchestration for `konareef pod publish` per PRD 4 § 4.3.
The four publish flags (--circuit-id, --pin-circuit-vkey, --disclosure-policy, --zk) carry distinct semantics with a fail-closed combination matrix. ValidateZKFlags is the single parse-time gate; downstream code can assume any ZKFlags value passing through it is structurally valid.
PreflightPinCheck is the pre-session vkey pin check per PRD 4 § 4.3.1. It is invoked AFTER ValidateZKFlags and BEFORE any network submission. A pin failure halts the session with vkeystore.ErrCircuitPinMismatch — no fold request reaches PayGate.
SealDisclosurePolicy is the witness-time enforcement point per PRD 4 § 4.3.3. Once called, the witness's disclosure policy is irrevocable; a second call returns ErrDisclosurePolicySealed.
EnsureSaltForTypeD provisions a 32-byte salt for the pod's lineage_id and persists it via the supplied saltstore backend for Type-D publishes (PRD 4 § B.1). No-op for Type-C and non-ZK.
Index ¶
- Variables
- func EnsureSaltForTypeD(flags ZKFlags, spec *pod.Spec, backend saltstore.Backend) ([16]byte, [32]byte, error)
- func PreflightPinCheck(ctx context.Context, resolver Resolver, flags ZKFlags, ...) error
- func PublisherBaseDomain(zkServiceHost string) (string, error)
- func SealDisclosurePolicy(flags ZKFlags, w *Witness) error
- func ValidateZKFlags(flags ZKFlags) error
- type AnchorState
- type DiscoveryManifest
- type PreparedPod
- type Resolver
- type SubmitOpts
- type SubmitResponse
- type Witness
- type ZKFlags
Constants ¶
This section is empty.
Variables ¶
var ErrAnchorBroadcastUnsupported = errors.New("ERR_ANCHOR_BROADCAST_UNSUPPORTED")
ErrAnchorBroadcastUnsupported is returned by EnsureVkeyAnchor when no on-chain anchor exists yet for (circuit_id, vkey_sha256) AND the BSV wallet integration has not landed. The publisher must wait for P1.8 (BSV wallet wiring) before --pin-circuit-vkey can initiate a fresh anchor transaction.
var ErrCircuitIDRequiredForPinCircuitVkey = errors.New("ERR_CIRCUIT_ID_REQUIRED_FOR_PIN_CIRCUIT_VKEY")
ErrCircuitIDRequiredForPinCircuitVkey is returned at flag-parse time when --pin-circuit-vkey is set without --circuit-id. The runtime path fetches the discovery manifest and looks up the vkey sha256 for flags.CircuitID before anchoring, so an empty CircuitID is internally inconsistent — there is no manifest entry to anchor against. Terminal; the publisher must supply --circuit-id before re-invoking. (Round-6 B1.)
var ErrDisclosurePolicySealed = errors.New("ERR_DISCLOSURE_POLICY_SEALED")
ErrDisclosurePolicySealed is returned when SealDisclosurePolicy is called twice on the same witness — once sealed, the witness's disclosure policy is irrevocable per PRD 4 § 4.3.3.
var ErrDisclosurePolicyViolation = errors.New("ERR_DISCLOSURE_POLICY_VIOLATION")
ErrDisclosurePolicyViolation is returned by konareef verify when the --disclosure-policy assertion does not match the bundle's embedded `disclosure` field. PRD 4 § 6 stable code.
var ErrInvalidDisclosurePolicy = errors.New("ERR_INVALID_DISCLOSURE_POLICY")
ErrInvalidDisclosurePolicy is returned when --disclosure-policy is supplied with a value other than the literal upper-case strings "C" or "D". Terminal.
var ErrPaygateZKDomainMissingPrefix = errors.New("ERR_PAYGATE_ZK_DOMAIN_MISSING_PREFIX")
ErrPaygateZKDomainMissingPrefix is returned by PublisherBaseDomain when the discovery manifest's `paygate_zk_domain` is not the expected service host (must start with `paygate-zk.`). The contract is documented at PRD 2 § 5.3 and internal/publish/discovery.go: the value MUST be the bare service host (e.g. `paygate-zk.example.com`), not a publisher base domain.
var ErrZkRequiresCircuitID = errors.New("ERR_ZK_REQUIRES_CIRCUIT_ID")
ErrZkRequiresCircuitID is returned at flag-parse time when --zk is set without --circuit-id. Terminal; the publisher must supply --circuit-id before re-invoking.
var ErrZkRequiresDisclosurePolicy = errors.New("ERR_ZK_REQUIRES_DISCLOSURE_POLICY")
ErrZkRequiresDisclosurePolicy is returned at flag-parse time when --zk is set without --disclosure-policy. Terminal; the publisher must supply --disclosure-policy {C|D}.
Functions ¶
func EnsureSaltForTypeD ¶
func EnsureSaltForTypeD(flags ZKFlags, spec *pod.Spec, backend saltstore.Backend) ([16]byte, [32]byte, error)
EnsureSaltForTypeD provisions a 32-byte salt for the pod's lineage_id and persists it via the supplied saltstore backend. Idempotent: if a salt is already stored for the lineage_id, the stored value is returned unchanged.
No-op for Type C (the salt rides in the public bundle and is not stored separately) and for non-ZK publishes.
When backend is nil (the publisher's environment has no usable salt-storage backend), returns saltstore.ErrSaltStorageUnavailable so the caller can halt the publish before any submission.
Spec: paygate-zk @ konareef-integration-contract-v1.md § 4.3.3 + P1.7.1 plan (saltstore.Resolve).
func PreflightPinCheck ¶
func PreflightPinCheck(ctx context.Context, resolver Resolver, flags ZKFlags, manifest DiscoveryManifest) error
PreflightPinCheck is the pre-session vkey pin check per PRD 4 § 4.3.1. When flags.ZK is true:
- Look up the manifest's vkey_sha256 for flags.CircuitID; absent → return a wrapped error (the manifest does not advertise the requested circuit).
- Resolve the vkey through the resolver (cache → Tier 1 → Tier 2); the resolver itself enforces the SHA-256(vkey) == vkey_sha256 contract via vkeystore.VerifyPin.
- On vkeystore.ErrCircuitPinMismatch: propagate; the caller MUST halt with no submission.
- On vkeystore.ErrVkeyUnavailable: propagate; the caller MUST halt — verification cannot proceed without the vkey.
When flags.ZK is false this function is a no-op (legacy publish does not pin to a circuit).
func PublisherBaseDomain ¶
PublisherBaseDomain converts a discovery `paygate_zk_domain` service host into the publisher base domain expected by vkeystore.ResolveRequest.PublisherDomain. The conversion is a pure case-insensitive `paygate-zk.` prefix strip; the remainder MUST be a non-empty base domain (the caller passes it to vkeystore.ValidatePublisherDomain, which applies the strict DNS-hostname grammar).
Returns ErrPaygateZKDomainMissingPrefix when zkServiceHost is empty or does not start with `paygate-zk.` (case-insensitive). The prefix-stripped remainder is also checked for non-emptiness so a pathological input like `paygate-zk.` alone is rejected.
MR !21 round-2 B2 (note 680): fixes the live ZK publish + standalone pin flows when reef-core returns `paygate_zk_domain= "paygate-zk.example.com"`. Before this helper the value was passed raw to the resolver, which rejected it with ErrDomainAlreadyPrefixed.
func SealDisclosurePolicy ¶
SealDisclosurePolicy is the witness-time enforcement point per PRD 4 § 4.3.3. It is the single permitted place to set witness.DisclosurePolicy. Once sealed, the witness's policy is irrevocable for the session — a downstream serialiser cannot subvert the publisher's choice.
When flags.ZK is false this function is a no-op (legacy publish has no witness).
func ValidateZKFlags ¶
ValidateZKFlags enforces the parse-time fail-closed matrix per PRD 4 § 4.3.4. Returns one of:
- nil — the combination is valid; the caller may proceed.
- ErrZkRequiresCircuitID — --zk without --circuit-id.
- ErrZkRequiresDisclosurePolicy — --zk without --disclosure-policy.
- ErrInvalidDisclosurePolicy — --disclosure-policy with a value other than the literal upper-case strings "C" or "D".
- ErrCircuitIDRequiredForPinCircuitVkey — --pin-circuit-vkey without --circuit-id (round-6 B1). The runtime path fetches the discovery manifest and looks up VkeySha256For(flags.CircuitID) before anchoring; an empty CircuitID is internally inconsistent.
Non-ZK invocations are accepted unchanged: --circuit-id is usable standalone. --pin-circuit-vkey is NOT usable standalone — it requires an explicit --circuit-id (round-6 B1).
Types ¶
type AnchorState ¶
type AnchorState int
AnchorState reports the result of EnsureVkeyAnchor.
const ( // AnchorAlreadyExists means the on-chain anchor for // (circuit_id, SHA-256(vkey)) is already recorded; no new // broadcast is needed. AnchorAlreadyExists AnchorState = iota // AnchorBroadcasted means a new anchor transaction was // constructed, signed, and broadcast. Not reachable in v1; the // BSV wallet integration is owed by P1.8. AnchorBroadcasted )
func EnsureVkeyAnchor ¶
func EnsureVkeyAnchor(ctx context.Context, anchorBackend vkeystore.Tier2AnchorBackend, circuitID string, vkeyBytes []byte) (AnchorState, error)
EnsureVkeyAnchor implements the --pin-circuit-vkey behaviour:
- Look up the existing on-chain anchor hash for circuitID via the Tier-2 backend.
- If an anchor exists AND SHA-256(vkeyBytes) == anchor hash: return AnchorAlreadyExists (the flag is a no-op).
- If an anchor exists but the hash MISMATCHES: return vkeystore.ErrCircuitPinMismatch — the publisher must re-anchor or upgrade the circuit; do not proceed.
- If no anchor exists (ErrAnchorNotFound): return ErrAnchorBroadcastUnsupported — the broadcast path is owed by a future PRD.
- Any other Tier-2 error: propagate verbatim.
The function never has side effects on the chain in v1.
func (AnchorState) String ¶
func (s AnchorState) String() string
String returns a stable lowercase identifier for logging.
type DiscoveryManifest ¶
type DiscoveryManifest interface {
// PaygateZKDomainHost returns the host portion (no scheme) of the
// PayGate ZK service domain.
PaygateZKDomainHost() string
// VkeySha256For returns the vkey_sha256 hex string committed in
// the manifest for circuitID, or ("", false) if the circuit is
// absent.
VkeySha256For(circuitID string) (string, bool)
}
DiscoveryManifest is the slice of the PRD 2 § 5.3 manifest this package consumes. The caller fetches and decodes the full manifest; this package only needs the paygate-zk domain + the circuits list.
func FetchDiscoveryManifest ¶
func FetchDiscoveryManifest(ctx context.Context, serverURL string) (DiscoveryManifest, error)
FetchDiscoveryManifest GETs <serverURL>/api/discovery and decodes the slice consumed by PreflightPinCheck.
type PreparedPod ¶
type PreparedPod struct {
Handle string
PodName string
PodVersion string
PodHash [32]byte
CanonicalBytes []byte
Signature []byte
PublicKeyHex string
// Spec is the parsed pod.toml. Carried through so the CLI can
// derive the lineage_id for Type-D salt provisioning without
// re-parsing the manifest. Set by Prepare.
Spec *pod.Spec
}
PreparedPod is the local result of `Prepare`: the canonical manifest bytes, the SHA-256 pod_hash they produced, and the publisher's signature over those bytes — plus the metadata fields (`handle`, `pod_name`, `pod_version`, `publisher_pubkey`) that reef-core needs to register the pod.
Submit consumes one of these directly; the CLI's `--dry-run` mode just renders it.
func Prepare ¶
func Prepare(podDir string, id *identity.Identity) (*PreparedPod, error)
Prepare runs the offline half of `konareef pod publish`:
- Read `<podDir>/pod.toml`.
- Parse + validate against the v0.1 schema. Any validation issue is fatal — `pod publish` is not the place to discover schema bugs.
- Canonicalize the manifest + the pod-directory contents (the synthetic `[_files]` section binds every other file in podDir into pod_hash, defeating post-sign file-swap attacks).
- `pod_hash = SHA-256(canonical_bytes)`.
- Sign the canonical bytes with `id`. The Sign primitive hashes internally, so the signature is over pod_hash (matches the publisher-signing-design v0 §"Stage 2" algorithm).
The returned PreparedPod is ready for Submit or `--dry-run` rendering; no network call is performed here.
type Resolver ¶
type Resolver interface {
Resolve(ctx context.Context, req vkeystore.ResolveRequest) (*vkeystore.Vkey, error)
}
Resolver is the narrow interface PreflightPinCheck consumes from internal/vkeystore. Declaring it inline here lets the publish package depend on the abstraction (and the test wire a fake without a full vkeystore.Resolver).
type SubmitOpts ¶
type SubmitOpts struct {
PublicBundle bool // WI-P0-012
CircuitID string // PRD 4 § 4.3.1
ZkEnabled bool // PRD 4 § 4.3.4
DisclosurePolicy string // PRD 4 § 4.3.3 — "C" or "D"
Witness *Witness // PRD 4 § B.1 — sealed witness for v2 / Type-D path; nil for v1 / non-ZK
}
SubmitOpts groups the post-P1.0 publish-time flags. The zero value represents a legacy publish (no opt-ins). Each non-zero field maps 1-1 to a `submitRequest` field of the same name.
Round-4 B1: SubmitOpts gains a Witness *Witness field. For ZK publishes the caller sets Witness to the sealed witness after SealDisclosurePolicy has succeeded, and Submit threads the witness's lineage commitment into the wire body (Type-D) and into the v2 packer (PRD 4 § B.1). Witness is nil for v1 / non-ZK publishes; in that case Submit emits the byte-identical pre-P1.3 wire body.
Data-path discipline (PRD 4 § B.2): Witness.LineageID flows into the publish wire body; Witness.Salt stays LOCAL on the publisher — consumed only by the v2 bundle packer for index / value_hash derivation, then discarded; NEVER serialised into the public bundle output or the submit wire body.
type SubmitResponse ¶
type SubmitResponse struct {
InstallURL string `json:"install_url"`
RegisteredAt time.Time `json:"registered_at"`
}
SubmitResponse is the 201 body from `POST /api/pods`.
func Submit ¶
func Submit(serverURL string, p *PreparedPod, opts SubmitOpts) (*SubmitResponse, error)
Submit POSTs a PreparedPod to `<serverURL>/api/pods`. On 2xx it returns the parsed install URL + registration timestamp. Any non-2xx status, network failure, or unparseable response is a fatal error — there is no retry policy here; the CLI is the human-facing wrapper that interprets failures.
SubmitOpts carries the opt-in flags; the zero value preserves pre-P1.3 wire shape exactly.
type Witness ¶
type Witness struct {
// DisclosurePolicy is the per-session policy sealed by
// SealDisclosurePolicy. After Sealed is true, it MUST NOT change.
DisclosurePolicy string
// Sealed is true once SealDisclosurePolicy has been called. A
// second seal call returns ErrDisclosurePolicySealed.
Sealed bool
// LineageID is the 16-byte pod lineage identifier provisioned by
// EnsureSaltForTypeD on Type-D publishes. Zero for Type-C and for
// non-ZK publishes.
LineageID [16]byte
// Salt is the 32-byte CSPRNG-derived salt persisted by the
// saltstore backend for Type-D publishes. Zero for Type-C and
// non-ZK. Consumed LOCALLY by the v2 packer per PRD 4 § B.1 then
// discarded; NEVER serialised over the wire (§ B.2).
Salt [32]byte
}
Witness is the publisher's intent envelope passed into PayGate ZK proof construction. Reduced to the fields this PRD touches; the full payload (P, R, M_in, M_out, T_log_records) is the V2 packer's concern (P1.2).
type ZKFlags ¶
type ZKFlags struct {
// CircuitID is the value of --circuit-id. Empty when absent.
// Mandatory when ZK is true (PRD 4 § 4.3.4 + § 4.3.1).
CircuitID string
// PinCircuitVkey is the value of --pin-circuit-vkey. True
// triggers the on-chain anchor flow per PRD 4 § 4.3.2.
PinCircuitVkey bool
// DisclosurePolicy is the value of --disclosure-policy,
// normalised to upper-case at parse time by the caller. Valid
// values: "C", "D". Empty when absent. Mandatory when ZK is true
// (PRD 4 § 4.3.4 + § 4.3.3).
DisclosurePolicy string
// ZK is the value of --zk. True activates the full ZK path:
// witness construction, Spartan proof generation, v2 bundle
// emission. Sets published_pods.zk_enabled = true on reef-core.
ZK bool
}
ZKFlags captures the four ZK-related publish flags. The zero value represents a legacy publish (no ZK opt-in).