or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

dirhash.mdgosumcheck.mdindex.mdmodfile.mdmodule.mdnote.mdsemver.mdstorage.mdsumdb.mdtlog.mdzip.md
tile.json

note.mddocs/

note - Signed Notes for Go Module Database

Package note defines signed notes used by the Go module database server.

Import

import "golang.org/x/mod/sumdb/note"

Overview

The note package provides functionality for creating and verifying cryptographically signed notes. A note is text signed by one or more server keys. The text should be ignored unless the note is signed by a trusted server key and the signature has been verified using the server's public key.

A server's public key is identified by a name, typically the "host[/path]" giving the base URL of the server's transparency log. The name must be non-empty, well-formed UTF-8 containing neither Unicode spaces nor plus (U+002B).

Signed Note Format

A signed note consists of:

  1. Text ending in newline (U+000A)
  2. A blank line (only a newline)
  3. One or more signature lines: — server-name base64-signature\n

Signed notes must be valid UTF-8 and must not contain any ASCII control characters (those below U+0020) other than newline.

Example:

go.sum database tree
1234567
abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVW=

— sum.golang.org Az3grubJCmM...

Signature Format

A signature is a base64 encoding of 4+n bytes:

  • First 4 bytes: uint32 key hash (big-endian)
  • Remaining n bytes: signature of note text

Types

Note

type Note struct {
    Text           string      // text of note
    Sigs           []Signature // verified signatures
    UnverifiedSigs []Signature // unverified signatures
}

A Note is text with signatures.

Signature

type Signature struct {
    // Name and Hash give the name and key hash
    // for the key that generated the signature.
    Name string
    Hash uint32

    // Base64 records the base64-encoded signature bytes.
    Base64 string
}

A Signature is a single signature found in a note.

Signer

type Signer interface {
    // Name returns the server name associated with the key.
    Name() string

    // KeyHash returns the key hash.
    KeyHash() uint32

    // Sign returns a signature for the given message.
    Sign(msg []byte) ([]byte, error)
}

A Signer signs messages using a specific key.

Verifier

type Verifier interface {
    // Name returns the server name associated with the key.
    Name() string

    // KeyHash returns the key hash.
    KeyHash() uint32

    // Verify reports whether sig is a valid signature of msg.
    Verify(msg, sig []byte) bool
}

A Verifier verifies messages signed with a specific key.

Verifiers

type Verifiers interface {
    // Verifier returns the Verifier associated with the key
    // identified by the name and hash.
    // If the name, hash pair is unknown, Verifier should return
    // an UnknownVerifierError.
    Verifier(name string, hash uint32) (Verifier, error)
}

A Verifiers is a collection of known verifier keys.

Error Types

type InvalidSignatureError struct {
    Name string
    Hash uint32
}

An InvalidSignatureError indicates that the given key was known and the associated Verifier rejected the signature.

func (e *InvalidSignatureError) Error() string
type UnknownVerifierError struct {
    Name    string
    KeyHash uint32
}

An UnknownVerifierError indicates that the given key is not known.

func (e *UnknownVerifierError) Error() string
type UnverifiedNoteError struct {
    Note *Note
}

An UnverifiedNoteError indicates that the note successfully parsed but had no verifiable signatures.

func (e *UnverifiedNoteError) Error() string

Functions

Key Generation

func GenerateKey(rand io.Reader, name string) (skey, vkey string, err error)

Generates a signer and verifier key pair for a named server. The signer key skey is private and must be kept secret.

Example:

import "crypto/rand"

signerKey, verifierKey, err := note.GenerateKey(rand.Reader, "example.com")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Signer key (keep secret):\n%s\n\n", signerKey)
fmt.Printf("Verifier key (publish):\n%s\n", verifierKey)
func NewEd25519VerifierKey(name string, key ed25519.PublicKey) (string, error)

Returns an encoded verifier key using the given name and Ed25519 public key.

Example:

import "crypto/ed25519"

pub, _, _ := ed25519.GenerateKey(rand.Reader)
vkey, err := note.NewEd25519VerifierKey("example.com", pub)
if err != nil {
    log.Fatal(err)
}
fmt.Println(vkey)

Creating Signers and Verifiers

func NewSigner(skey string) (Signer, error)

Constructs a new Signer from an encoded signer key.

Example:

signer, err := note.NewSigner(signerKey)
if err != nil {
    log.Fatal(err)
}
func NewVerifier(vkey string) (Verifier, error)

Constructs a new Verifier from an encoded verifier key.

Example:

verifier, err := note.NewVerifier(verifierKey)
if err != nil {
    log.Fatal(err)
}
func VerifierList(list ...Verifier) Verifiers

Returns a Verifiers implementation that uses the given list of verifiers.

Example:

v1, _ := note.NewVerifier(vkey1)
v2, _ := note.NewVerifier(vkey2)
verifiers := note.VerifierList(v1, v2)

Signing and Verification

func Sign(n *Note, signers ...Signer) ([]byte, error)

Signs the note with the given signers and returns the encoded message. The new signatures from signers are listed in the encoded message after existing signatures. If any signer uses the same key as an existing signature, the existing signature is elided from the output.

Example:

n := &note.Note{Text: "This is a message\n"}
signed, err := note.Sign(n, signer)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(signed))
func Open(msg []byte, known Verifiers) (*Note, error)

Opens and parses the message msg, checking signatures from the known verifiers. For each signature in the message, Open calls known.Verifier to find a verifier. Returns an error if no known verifier has signed an otherwise valid note.

Example:

n, err := note.Open(signed, verifiers)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Text: %s", n.Text)
fmt.Printf("Verified signatures: %d\n", len(n.Sigs))

Usage Examples

Complete Example: Signing and Verifying

package main

import (
    "crypto/rand"
    "fmt"
    "log"

    "golang.org/x/mod/sumdb/note"
)

func main() {
    // Generate keys
    signerKey, verifierKey, err := note.GenerateKey(rand.Reader, "example.com")
    if err != nil {
        log.Fatal(err)
    }

    // Create signer and verifier
    signer, err := note.NewSigner(signerKey)
    if err != nil {
        log.Fatal(err)
    }

    verifier, err := note.NewVerifier(verifierKey)
    if err != nil {
        log.Fatal(err)
    }

    // Create and sign a note
    n := &note.Note{
        Text: "go.sum database tree\n12345\nhash...\n",
    }

    signed, err := note.Sign(n, signer)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Signed note:")
    fmt.Println(string(signed))

    // Verify the note
    verifiers := note.VerifierList(verifier)
    verified, err := note.Open(signed, verifiers)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("\nVerified! Text: %s", verified.Text)
    fmt.Printf("Signatures: %d\n", len(verified.Sigs))
}

Multiple Signers

package main

import (
    "crypto/rand"
    "log"

    "golang.org/x/mod/sumdb/note"
)

func main() {
    // Generate two key pairs
    skey1, vkey1, _ := note.GenerateKey(rand.Reader, "server1.com")
    skey2, vkey2, _ := note.GenerateKey(rand.Reader, "server2.com")

    signer1, _ := note.NewSigner(skey1)
    signer2, _ := note.NewSigner(skey2)

    verifier1, _ := note.NewVerifier(vkey1)
    verifier2, _ := note.NewVerifier(vkey2)

    // Sign with multiple signers
    n := &note.Note{Text: "Multi-signed message\n"}
    signed, err := note.Sign(n, signer1, signer2)
    if err != nil {
        log.Fatal(err)
    }

    // Verify with known verifiers
    verifiers := note.VerifierList(verifier1, verifier2)
    verified, err := note.Open(signed, verifiers)
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Verified signatures: %d", len(verified.Sigs))
}

Handling Unknown Signers

package main

import (
    "fmt"
    "log"

    "golang.org/x/mod/sumdb/note"
)

func main() {
    // Verifier only knows about server1
    verifier1, _ := note.NewVerifier(vkey1)
    verifiers := note.VerifierList(verifier1)

    // Note signed by server1 and server2
    n, err := note.Open(signedByBoth, verifiers)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Verified: %d\n", len(n.Sigs))
    fmt.Printf("Unverified: %d\n", len(n.UnverifiedSigs))

    for _, sig := range n.UnverifiedSigs {
        fmt.Printf("Unknown signer: %s (hash: %x)\n", sig.Name, sig.Hash)
    }
}

Custom Verifiers Implementation

package main

import (
    "fmt"

    "golang.org/x/mod/sumdb/note"
)

type KeyStore struct {
    keys map[string]note.Verifier
}

func (ks *KeyStore) Verifier(name string, hash uint32) (note.Verifier, error) {
    v, ok := ks.keys[name]
    if !ok {
        return nil, &note.UnknownVerifierError{Name: name, KeyHash: hash}
    }

    if v.KeyHash() != hash {
        return nil, &note.UnknownVerifierError{Name: name, KeyHash: hash}
    }

    return v, nil
}

func main() {
    ks := &KeyStore{keys: make(map[string]note.Verifier)}

    v, _ := note.NewVerifier(vkey)
    ks.keys[v.Name()] = v

    n, err := note.Open(signed, ks)
    if err != nil {
        panic(err)
    }

    fmt.Printf("Verified note: %s", n.Text)
}

Security Considerations

Key Management

  • Private Keys: Store signer keys securely, never transmit over network
  • Public Keys: Distribute verifier keys through secure channels
  • Key Rotation: Plan for key rotation and revocation

Signature Verification

Always verify signatures before trusting note content:

n, err := note.Open(msg, verifiers)
if err != nil {
    // Do not trust content
    return err
}
if len(n.Sigs) == 0 {
    // No verified signatures
    return errors.New("no valid signatures")
}
// Content is verified

Multi-Signature Notes

Require multiple signatures for critical operations:

if len(n.Sigs) < requiredSignatures {
    return errors.New("insufficient signatures")
}

Note Format Details

Text Section

The text section contains the actual message:

  • Must end with newline (U+000A)
  • Must be valid UTF-8
  • No ASCII control characters except newline

Signature Section

After a blank line, signatures appear as:

— server-name base64-signature

The em dash (U+2014) marks each signature line.

See Also

  • sumdb - Checksum database using signed notes
  • tlog - Transparent log with signed trees
  • Ed25519 Signature Scheme