Search by

k2gl / signed-note

k2gl

Parse, verify and sign signed notes — the transparency-log / Go sumdb format used by Sigstore Rekor checkpoints — in PHP

Package info

github.com/k2gl/signed-note

pkg:composer/k2gl/signed-note

Statistics

Installs: 390

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

1.1.0 2026-09-17 16:51 UTC

This package is auto-updated.

Last update: 2026-09-17 16:55:59 UTC


README

CI Latest Stable Version Total Downloads PHPStan Level License

Parse, verify and sign signed notes in PHP — the format Go's sumdb and transparency-log checkpoints (Sigstore Rekor) use. A note is some text, a blank line, then one or more signature lines:

If you think cryptography is the answer to your problem,
then you don't know what your problem is.

— PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=

Each signature line is — <name> <base64>, where the base64 decodes to a 4-byte key hash and the raw signature over the text. The cryptography is delegated to [k2gl/dsse].

Install

composer require k2gl/signed-note

Requires PHP 8.1+ with ext-sodium (Ed25519); ext-openssl is needed only for ECDSA-signed notes such as Rekor v1 checkpoints.

Verify

Give a NoteVerifier the keys you trust; it returns the signatures that verify and throws if none do.

use K2gl\SignedNote\Note;
use K2gl\SignedNote\NoteVerifier;
use K2gl\SignedNote\VerifierKey;

$key = VerifierKey::fromString('PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW');

$verified = (new NoteVerifier($key))->verify(Note::parse($envelope));
// $verified is the list of NoteSignature that checked out; SignatureVerificationFailed otherwise.

A Rekor checkpoint is a note signed by the log's key. Load that key from its PEM — the key hash is derived the Sigstore way (first four bytes of SHA-256 of the DER key), and any RSA/ECDSA/Ed25519 key works:

$log = VerifierKey::fromPem('rekor.sigstore.dev - 1193050959916656506', $logPublicKeyPem);
(new NoteVerifier($log))->verify(Note::parse($checkpoint));

Checkpoints

A transparency log publishes its head as a checkpoint: a note whose text is the log's origin, the tree size and the base64 root hash, one per line (further lines are extensions). Checkpoint reads those three lines; the signatures stay the note's, so it is verified with the log's key like any other note.

use K2gl\SignedNote\Checkpoint;
use K2gl\SignedNote\NoteVerifier;
use K2gl\SignedNote\VerifierKey;

$checkpoint = Checkpoint::parse($envelope);

$checkpoint->origin;     // "log2025-1.rekor.sigstore.dev"
$checkpoint->treeSize;   // 114068855
$checkpoint->rootHash;   // raw 32 bytes
$checkpoint->extensions; // any lines after the first three

$log = VerifierKey::ed25519($checkpoint->origin, $rawEd25519PublicKey); // a Rekor v2 log
$checkpoint->verify(new NoteVerifier($log)); // SignatureVerificationFailed otherwise

A Rekor v2 log signs with an Ed25519 key whose hash is the note one (origin plus key), hence VerifierKey::ed25519(); a Rekor v1 log's ECDSA key is fromPem() as above. The test suite reads a real log2025-1.rekor.sigstore.dev checkpoint and verifies it against the key from Sigstore's trusted root.

Sign

use K2gl\SignedNote\SignerKey;

$signer = SignerKey::fromString('PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz');

$note = $signer->sign("hello\n"); // text must end with a newline
echo $note;                       // renders the note with its signature line

The output is byte-for-byte what Go's note.Sign produces (checked against the golang.org/x/mod/sumdb/note test vectors).

Design

  • Two layers. Note is the generic note and knows nothing about logs; Checkpoint adds the meaning of the first three lines and nothing else.
  • Fail-closed. A verified result means a trusted key signed the exact text.
  • Ed25519 is the standard. VerifierKey::fromString() / SignerKey handle the Ed25519 key strings from Go's note package; fromPem() covers ECDSA/RSA logs.

License

MIT.