ocidigest

package
v0.0.13 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

ocidigest

ocidigest calculates and verifies OCI-compatible content digests:

<algorithm>:<encoded-hash>

The built-in algorithms are sha256 and sha512. sha256 is the canonical default.

Parse a Digest

dgst, err := ocidigest.Parse("sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")
if err != nil {
    return err
}

fmt.Println(dgst.Algorithm()) // sha256
fmt.Println(dgst.Encoded())   // b94d27...
fmt.Println(dgst.String())    // sha256:b94d27...

Digest values are comparable and can be used as map keys:

seen := map[ocidigest.Digest]bool{
    dgst: true,
}

Digest is a string-backed type. Use Parse when accepting a digest string that should be valid immediately:

dgst, err := ocidigest.Parse(input)
if err != nil {
    return err
}

Use Validate when a digest may have arrived through a plain string path, such as JSON decoding into a struct:

if err := desc.Digest.Validate(); err != nil {
    return fmt.Errorf("invalid descriptor digest: %w", err)
}

Empty digests represent absent optional values. Parse("") and Validate both reject an empty digest, so callers should check optional strings before parsing.

Digest Bytes

Use FromBytes when content is already buffered, such as manifest JSON or test data:

payload := []byte(`{"schemaVersion":2}`)
dgst := ocidigest.FromBytes(payload)

To choose a non-canonical algorithm:

dgst := ocidigest.SHA512.FromBytes(payload)

Digest a Reader

Use FromReader when content should be consumed and discarded after hashing:

f, err := os.Open("layer.tar")
if err != nil {
    return err
}
defer f.Close()

dgst, err := ocidigest.FromReader(f)
if err != nil {
    return err
}

For a specific algorithm:

dgst, err := ocidigest.SHA512.FromReader(f)

Digest While Copying

Create a Digester and write bytes through it directly:

d, err := ocidigest.SHA256.New()
if err != nil {
    return err
}

if _, err := io.Copy(d, src); err != nil {
    return err
}

dgst, err := d.Digest()
if err != nil {
    return err
}

Use io.TeeReader when bytes should be copied somewhere else while hashing:

d, err := ocidigest.SHA256.New()
if err != nil {
    return err
}

tee := io.TeeReader(src, d)
if _, err := io.Copy(dst, tee); err != nil {
    return err
}

dgst, err := d.Digest()

Digesting Reader Wrapper

NewReader passes reads through to an underlying reader and records the digest. When no algorithm is provided, it uses Canonical:

r, err := ocidigest.NewReader(src)
if err != nil {
    return err
}

if _, err := io.Copy(dst, r); err != nil {
    return err
}

dgst, err := r.Digest()

Pass one or more algorithms to choose what is calculated:

r, err := ocidigest.NewReader(src, ocidigest.SHA256, ocidigest.SHA512)
if err != nil {
    return err
}

if _, err := io.Copy(dst, r); err != nil {
    return err
}

sha256Digest, err := r.DigestFor(ocidigest.SHA256)
digests, err := r.Digests()

The wrapper also exposes how many bytes were digested:

fmt.Println(r.Size())

Digesting Writer Wrapper

NewWriter passes writes through to an underlying writer and records the digest. When no algorithm is provided, it uses Canonical:

w, err := ocidigest.NewWriter(dst)
if err != nil {
    return err
}

if _, err := io.Copy(w, src); err != nil {
    return err
}

dgst, err := w.Digest()

Pass nil as the underlying writer to only calculate the digest:

w, err := ocidigest.NewWriter(nil, ocidigest.SHA256, ocidigest.SHA512)
if err != nil {
    return err
}
_, err = w.Write(payload)

Verify While Reading

VerifierReader reports a digest mismatch as a read error at EOF:

vr, err := ocidigest.NewVerifierReader(src, expected)
if err != nil {
    return err
}

if _, err := io.Copy(dst, vr); err != nil {
    if errors.Is(err, ocidigest.ErrDigestMismatch) {
        return fmt.Errorf("content digest did not match: %w", err)
    }
    return err
}

if !vr.Verified() {
    return ocidigest.ErrDigestMismatch
}

If the final read returns bytes and EOF together, VerifierReader returns the final bytes first. The EOF or mismatch is returned by the next read. This preserves normal io.Reader byte-delivery behavior.

Resume Digest State

Digest state can be exported and restored for short-lived resumable workflows, such as chunked uploads. State is only intended for the same library version and same algorithm implementation.

w, err := ocidigest.NewWriter(uploadWriter, ocidigest.SHA256, ocidigest.SHA512)
if err != nil {
    return err
}

if _, err := w.Write(firstChunk); err != nil {
    return err
}

state, err := w.State()
if err != nil {
    return err
}

// Persist state alongside the upload ID and offset.
save(state)

Later:

state := load()

w, err := ocidigest.NewWriterFromState(uploadWriter, state)
if err != nil {
    return err
}

if w.Size() != expectedUploadOffset {
    return fmt.Errorf("digest state offset mismatch")
}

if _, err := w.Write(nextChunk); err != nil {
    return err
}

digests, err := w.Digests()

Multiple Digests from One Stream

Pass multiple algorithms when one pass should produce several digests:

r, err := ocidigest.NewReader(src, ocidigest.SHA256, ocidigest.SHA512)
if err != nil {
    return err
}

if _, err := io.Copy(dst, r); err != nil {
    return err
}

digests, err := r.Digests()
if err != nil {
    return err
}

sha256Digest := digests[0]
sha512Digest := digests[1]

Use NewWriter(nil, ...) to calculate several digests without forwarding bytes:

w, err := ocidigest.NewWriter(nil, ocidigest.SHA256, ocidigest.SHA512)
if err != nil {
    return err
}

if _, err := w.Write(payload); err != nil {
    return err
}

digests, err := w.Digests()

Register a Custom Algorithm

Algorithms are registered globally by name:

alg, err := ocidigest.RegisterAlgorithm(ocidigest.RegisterOptions{
    Name:    "mysha256",
    Size:    sha256.Size,
    NewHash: sha256.New,
})
if err != nil {
    return err
}

dgst := alg.FromBytes(payload)
fmt.Println(dgst.String()) // mysha256:<hex>

Custom algorithms that need resumable state can provide MarshalState and NewState. Most callers should not need this unless the hash implementation does not support Go's standard binary marshaling interfaces.

JSON Encoding

Digest is encoded as a JSON string because it is a string-backed type:

data, err := json.Marshal(dgst)
// "sha256:b94d27..."

JSON decoding stores the string value as-is. It does not validate the digest:

var dgst ocidigest.Digest
if err := json.Unmarshal(data, &dgst); err != nil {
    return err
}

if err := dgst.Validate(); err != nil {
    return err
}

This keeps unmarshaling cheap and lets higher-level validation decide whether a digest is required in that context.

Documentation

Overview

Package ocidigest calculates and verifies OCI-compatible content digests.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrAlgorithmExists indicates that an algorithm is already registered.
	ErrAlgorithmExists = errors.New("ocidigest: algorithm is already registered")
	// ErrAlgorithmInvalidName indicates that an algorithm name is syntactically invalid.
	ErrAlgorithmInvalidName = errors.New("ocidigest: invalid algorithm name")
	// ErrAlgorithmUnknown indicates that an algorithm has not been registered.
	ErrAlgorithmUnknown = errors.New("ocidigest: algorithm is not registered")
	// ErrAlgorithmDuplicate indicates that an algorithm was specified more than once.
	ErrAlgorithmDuplicate = errors.New("ocidigest: duplicate algorithm")
	// ErrDigestInvalid indicates that a digest is invalid.
	ErrDigestInvalid = errors.New("ocidigest: digest is invalid")
	// ErrDigestMismatch indicates that calculated content did not match the expected digest.
	ErrDigestMismatch = errors.New("ocidigest: digest mismatch")
	// ErrEncodingInvalid indicates that a digest encoding is invalid.
	ErrEncodingInvalid = errors.New("ocidigest: encoded digest is invalid")
	// ErrHashInvalid indicates that a hash is invalid.
	ErrHashInvalid = errors.New("ocidigest: invalid hash")
	// ErrReaderInvalid indicates that a reader is invalid.
	ErrReaderInvalid = errors.New("ocidigest: invalid reader")
	// ErrStateInvalid indicates that digest state is invalid.
	ErrStateInvalid = errors.New("ocidigest: state is invalid")
	// ErrStateUnsupported indicates that digest state cannot be restored.
	ErrStateUnsupported = errors.New("ocidigest: state is not supported")
	// ErrWriterInvalid indicates that a writer is invalid.
	ErrWriterInvalid = errors.New("ocidigest: invalid writer")
)

Functions

This section is empty.

Types

type Algorithm

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

Algorithm identifies a digest algorithm.

var (
	// SHA256 is the OCI sha256 digest algorithm.
	SHA256 Algorithm
	// SHA512 is the OCI sha512 digest algorithm.
	SHA512 Algorithm
	// Canonical is the default digest algorithm.
	Canonical Algorithm
)

func LookupAlgorithm

func LookupAlgorithm(name string) (Algorithm, error)

LookupAlgorithm returns a previously registered algorithm by name.

func RegisterAlgorithm

func RegisterAlgorithm(opts RegisterOptions) (Algorithm, error)

RegisterAlgorithm registers a digest algorithm.

func (Algorithm) FromBytes

func (a Algorithm) FromBytes(p []byte) Digest

FromBytes returns the digest of p.

func (Algorithm) FromReader

func (a Algorithm) FromReader(r io.Reader) (Digest, error)

FromReader reads r to EOF and returns its digest.

func (Algorithm) New

func (a Algorithm) New() (Digester, error)

New returns a new digester for the algorithm.

func (Algorithm) Size

func (a Algorithm) Size() int

Size returns the digest size in bytes.

func (Algorithm) String

func (a Algorithm) String() string

String returns the algorithm name.

type Digest

type Digest string

Digest is an OCI-compatible digest in the form "<algorithm>:<encoded-hash>".

func FromBytes

func FromBytes(p []byte) Digest

FromBytes returns the canonical digest of p.

func FromReader

func FromReader(r io.Reader) (Digest, error)

FromReader reads r to EOF and returns its canonical digest.

func Parse

func Parse(s string) (Digest, error)

Parse parses and validates a digest string.

func (Digest) Algorithm

func (d Digest) Algorithm() Algorithm

Algorithm returns the digest algorithm.

func (Digest) Encoded

func (d Digest) Encoded() string

Encoded returns the encoded hash portion of the digest.

func (Digest) Equal

func (d Digest) Equal(cmp Digest) bool

Equal reports whether d and cmp are identical.

func (Digest) String

func (d Digest) String() string

String returns the string form of the digest.

func (Digest) Validate

func (d Digest) Validate() error

Validate checks whether d is a valid non-zero digest.

type DigestState

type DigestState struct {
	Algorithm string `json:"algorithm"`
	Encoding  string `json:"encoding"`
	Offset    int64  `json:"offset"`
	Payload   []byte `json:"payload"`
}

DigestState is a serialized digest state for one algorithm.

type Digester

type Digester interface {
	io.Writer
	Algorithm() Algorithm
	Digest() (Digest, error)
	Reset()
	Size() int64
	State() (DigestState, error)
}

Digester calculates a digest incrementally.

type Reader

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

Reader wraps an io.Reader and records the digest of bytes read through it.

func NewReader

func NewReader(r io.Reader, algs ...Algorithm) (*Reader, error)

NewReader returns a digesting reader for r.

func NewReaderFromState

func NewReaderFromState(r io.Reader, st State) (*Reader, error)

NewReaderFromState returns a digesting reader restored from state.

func (*Reader) Algorithms

func (r *Reader) Algorithms() []Algorithm

Algorithms returns the algorithms in digest output order.

func (*Reader) Digest

func (r *Reader) Digest() (Digest, error)

Digest returns the default digest of bytes read so far.

func (*Reader) DigestFor

func (r *Reader) DigestFor(alg Algorithm) (Digest, error)

DigestFor returns the current digest for alg.

func (*Reader) Digests

func (r *Reader) Digests() ([]Digest, error)

Digests returns all current digests in algorithm order.

func (*Reader) Read

func (r *Reader) Read(p []byte) (int, error)

Read reads from the underlying reader and updates the digest.

func (*Reader) Size

func (r *Reader) Size() int64

Size returns the number of bytes read and digested.

func (*Reader) State

func (r *Reader) State() (State, error)

State returns the current digest state.

type RegisterOptions

type RegisterOptions struct {
	Name         string
	Size         int
	NewHash      func() hash.Hash
	MarshalState func(hash.Hash) (encoding string, payload []byte, err error)
	NewState     func(DigestState) (hash.Hash, error)
}

RegisterOptions defines a digest algorithm implementation.

type State

type State struct {
	States []DigestState `json:"states"`
}

State is serialized digest state for short-term resume.

type VerifierReader

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

VerifierReader wraps an io.Reader and verifies the stream at EOF.

func NewVerifierReader

func NewVerifierReader(r io.Reader, expected Digest) (*VerifierReader, error)

NewVerifierReader returns a reader that verifies bytes read from r.

func (*VerifierReader) Calculated

func (r *VerifierReader) Calculated() (Digest, error)

Calculated returns the digest calculated from bytes read so far.

func (*VerifierReader) Digest

func (r *VerifierReader) Digest() Digest

Digest returns the expected digest.

func (*VerifierReader) Read

func (r *VerifierReader) Read(p []byte) (int, error)

Read reads from the underlying reader and updates the verifier.

func (*VerifierReader) Verified

func (r *VerifierReader) Verified() bool

Verified reports whether EOF has been reached and the digest matched.

type Writer

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

Writer wraps an io.Writer and records the digest of bytes written through it.

func NewWriter

func NewWriter(w io.Writer, algs ...Algorithm) (*Writer, error)

NewWriter returns a digesting writer. If w is nil, writes are only digested.

func NewWriterFromState

func NewWriterFromState(w io.Writer, st State) (*Writer, error)

NewWriterFromState returns a digesting writer restored from state.

func (*Writer) Algorithms

func (w *Writer) Algorithms() []Algorithm

Algorithms returns the algorithms in digest output order.

func (*Writer) Digest

func (w *Writer) Digest() (Digest, error)

Digest returns the default digest of bytes written so far.

func (*Writer) DigestFor

func (w *Writer) DigestFor(alg Algorithm) (Digest, error)

DigestFor returns the current digest for alg.

func (*Writer) Digests

func (w *Writer) Digests() ([]Digest, error)

Digests returns all current digests in algorithm order.

func (*Writer) Size

func (w *Writer) Size() int64

Size returns the number of bytes written and digested.

func (*Writer) State

func (w *Writer) State() (State, error)

State returns the current digest state.

func (*Writer) Write

func (w *Writer) Write(p []byte) (int, error)

Write writes p to the underlying writer, if any, and updates the digest.

Jump to

Keyboard shortcuts

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