exportv1

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package exportv1 writes the behalf.sh/export/v1 run export file per docs/export-format-v1.md: one header line with embedded JWK keys, one leaf line per receipt with the plaintext payload spliced byte-exactly, and a signed head line.

The span rule governs everything: the payload bytes handed to Append are the bytes that were signed, and they are copied into the emitted line verbatim — no re-serialization, no re-indentation. Leaf and head lines are therefore assembled by direct byte concatenation, never by re-marshaling a structure that contains the payload.

Index

Constants

View Source
const (
	// Format is the export format string (header and head lines).
	Format = "behalf.sh/export/v1"
	// PayloadTypeReceipt is the DSSE payloadType for receipt leaves.
	PayloadTypeReceipt = "application/vnd.behalf.receipt+json"
	// PayloadTypeChainHead is the DSSE payloadType for the head line.
	PayloadTypeChainHead = "application/vnd.behalf.chain-head+json"
)

Variables

View Source
var ErrNotExport = errors.New("exportv1: not a behalf export file")

ErrNotExport marks a file that is not a behalf export at all, so a caller can say so plainly rather than reporting a parse error from line one.

Functions

func ChainNext

func ChainNext(prev, leaf [32]byte) [32]byte

ChainNext folds one leaf hash into the chain: chain_i = SHA-256(chain_{i-1} || leaf_hash_raw_32_bytes).

func ChainStart

func ChainStart(logOrigin string) [32]byte

ChainStart returns SHA-256("behalf.sh/chain/v1\n" + logOrigin).

func TokenRef

func TokenRef(jws string) string

TokenRef is the key form used by the header's `tokens` section: exactly the string a hop's `verification.evidence_ref` carries, `sha256:<hex>`.

Keying by the whole reference rather than by the bare digest is deliberate. A reader looks a hop's token up by the value the receipt already holds, with no string surgery in between, so there is no opportunity to reconstruct the key slightly differently on one side and miss.

Types

type Export

type Export struct {
	LogOrigin string
	Keys      []HeaderKey
	Leaves    []Leaf
	Head      *Head
	// Tokens is the header's delegation hop tokens, keyed by the
	// `evidence_ref` the receipts carry (ENG-38). Nil when the export
	// predates the section or carries no signed hops.
	//
	// Every entry has already been checked to digest to the key it sits
	// under, so a caller may look a hop's token up by its `evidence_ref` and
	// use the bytes without re-hashing. A file whose token does not match its
	// address does not parse at all: substituting the evidence for a
	// verification claim is the one thing this section must not permit
	// silently.
	Tokens map[string]string
}

Export is a parsed export file.

func Read

func Read(r io.Reader) (*Export, error)

Read parses an export file.

type Head struct {
	// Bytes is the exact span the head signature covers.
	Bytes     []byte
	LogOrigin string
	Count     int
	Chain     string // hex
	KeyID     string
	Sig       []byte
}

Head is the export's trailing signed head.

type HeaderKey

type HeaderKey struct {
	JKT string   `json:"jkt"`
	JWK dsse.JWK `json:"jwk"`
}

HeaderKey is one entry in the header's keys array: an Ed25519 public key in JWK form, keyed by its RFC 7638 thumbprint.

type Leaf

type Leaf struct {
	Index int
	// PayloadType and Payload are what the signature covers. Payload aliases
	// the line's own bytes: the exact span, never re-serialized.
	PayloadType string
	Payload     []byte
	KeyID       string
	Sig         []byte
	// LeafHash is the value the line carries, already checked against the
	// payload span.
	LeafHash [32]byte
}

Leaf is one receipt as it appears in an export.

type Signer

type Signer struct {
	Private ed25519.PrivateKey
	KeyID   string
}

Signer signs leaves or the head. KeyID must be the RFC 7638 thumbprint of the corresponding public key, and that key must appear in the header.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer emits one export file. Use NewWriter (writes the header), Append for each receipt in order, then Close (writes the head).

func NewWriter

func NewWriter(w io.Writer, logOrigin string, keys []HeaderKey) (*Writer, error)

NewWriter writes the header line and returns a Writer whose chain is initialized to ChainStart(logOrigin). keys must contain every public key referenced by any signature in the file.

func NewWriterWithTokens

func NewWriterWithTokens(w io.Writer, logOrigin string, keys []HeaderKey, tokens map[string]string) (*Writer, error)

NewWriterWithTokens is NewWriter carrying the delegation hop tokens the receipts reference (ENG-38).

tokens maps a hop's `verification.evidence_ref` — `sha256:<hex>`, see TokenRef — to that hop's compact JWS. Without it an export states that a chain was verified and carries nothing to re-verify it against: the hop signatures were checked once, at capture, by behalf's own code, and a receipt is not evidence of its own verification.

It goes in the header rather than in a new line kind or a sidecar for three reasons. The export stays one self-contained artefact, which the browser verifier and `behalf export --html` both rest on. Hops deduplicate — a run's 47 receipts typically share three hops, so the section grows with distinct hops rather than with receipts. And §2 of the format already requires unknown members to be ignored, so a verifier that predates this section reads the file exactly as before and the format string does not move.

A nil or empty map writes no `tokens` member at all, which is what keeps every existing vector byte-identical.

func (*Writer) Append

func (wr *Writer) Append(payload []byte, s Signer) error

Append signs payload (the exact sealed receipt bytes), splices it verbatim into a leaf line, writes the line, and folds the leaf hash into the chain.

func (*Writer) AppendSigned

func (wr *Writer) AppendSigned(payload []byte, keyid string, sig []byte) error

AppendSigned is Append for a payload that already carries its signature (the Week-2 log bridge: the emitter signed the payload at capture time and the stored envelope carries that signature — the bridge must splice the original bytes and the original signature, never re-sign). sig must be the Ed25519 signature over PAE(PayloadTypeReceipt, payload) by the header key with thumbprint keyid.

func (*Writer) Chain

func (wr *Writer) Chain() [32]byte

Chain returns the current chain value (after the last appended leaf).

func (*Writer) Close

func (wr *Writer) Close(s Signer) error

Close writes the signed head line. The head value's exact byte span is what gets signed (PAE with the chain-head payloadType) and it is spliced verbatim into the line, same rule as leaves.

func (*Writer) Count

func (wr *Writer) Count() int

Count returns the number of leaves appended so far.

func (*Writer) LeafHashes

func (wr *Writer) LeafHashes() [][32]byte

LeafHashes returns the leaf hashes appended so far, in order.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL