You sign your commits and tags. Someone asks you to show that a release existed on a given date, and you reach for git log --show-signature. The signature checks out, the date is right there in the commit, and it still does not settle the question. A signed commit answers who produced this content. A Polygon anchor answers when a file with this exact hash existed. They are different questions, and you usually need both answers.

What a signed commit binds

Git has signed commits and tags with GPG for years and with SSH keys since Git 2.34. git commit -S or git tag -s computes a signature over the commit or tag object with your private key. git verify-commit, git verify-tag or git log --show-signature checks it against the public key.

The signature covers the object's content: the tree hash, the parent hashes, the author and committer lines, the message, and the dates inside those lines. Change any byte after signing and the check fails. So a valid signature tells a reader two things. Whoever holds the private key produced this exact object, and nobody has altered it since.

That is authorship and integrity. Both are real, solved problems, and nothing in this post replaces them.

The committer date is a field you set

The committer date sits inside the signed content, which looks like it should settle timing too. It does not. The date is a value the committer chooses, not a value an outside system observed. It comes from your clock by default, and any of these put something else there before the signature is applied:

git commit -S --date="2025-01-10T09:00:00" -m "release notes"
GIT_AUTHOR_DATE="2025-01-10T09:00:00" GIT_COMMITTER_DATE="2025-01-10T09:00:00" git commit -S -m "release notes"
git rebase --committer-date-is-author-date HEAD~3

Sign a commit with a backdated timestamp and git verify-commit reports it as good. The signature was never checking the date against anything outside the repository. A hosting platform's "verified" badge checks the same thing: that the signature matches a key uploaded to the account. The date is still whatever the committer typed.

This is not a flaw in commit signing. Signing was built to answer "did this key holder produce this content", and it does. Wrapping a number in a signature does not change who chose the number.

What a Polygon anchor adds

ProofLedger is proof of existence as an API. You send the SHA-256 hash of a file, and the hash is written into its own transaction on Polygon, usually within a minute. The anchor time comes from block inclusion on a public chain, not from a field either party filled in. Only the hash leaves your machine; the repository and the build artifact stay where they are.

The claim is deliberately narrower than a signature's. It does not say who made the file. It says a file with this exact hash existed by the anchor time and has not changed since. Combine the two and you have who (the signature) and when (the anchor), each from a source built for that question.

Anchoring a release artifact

Hash the artifact you ship, not the repository. A tarball, a wheel, an installer, a container image manifest: whatever a user would download.

sha256sum dist/app-1.4.0.tar.gz
# 3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b  dist/app-1.4.0.tar.gz

Submit that hash. The tag name makes a good external_id: if the release job reruns, the same request returns the proof that already exists instead of creating a second one.

curl -X POST https://proofledger.io/api/v1/proof \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sha256": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b",
    "filename": "app-1.4.0.tar.gz",
    "external_id": "release-v1.4.0"
  }'

A new proof comes back as 201 with id, polygon_status (PENDING until the transaction lands, then ANCHORED), certificate_url and verification_url. A rerun with the same external_id comes back as 200 with already_existed: true. Store the id with the release.

When the anchor lands, GET /api/v1/proof/{id} carries chain_tx, polygon_anchored_at and explorer_url. Then save the bundle next to the artifact:

curl -H "Authorization: Bearer sk_YOUR_KEY" \
  "https://proofledger.io/api/v1/proof/42/bundle?download=1" \
  -o dist/app-1.4.0.tar.gz.proof.json

The bundle is one JSON document with the hash, the chain, the transaction id and the anchor time. Commit it or publish it beside the release. From then on the check needs nothing from ProofLedger. If you would rather not poll, register a webhook with POST /api/v1/webhooks and the proof.polygon.anchored event arrives with the bundle inside it.

Verifying a proof yourself

Install the open-source tool and check the artifact against its bundle. Neither command touches the network.

pip install verify-proof
verify-proof hash dist/app-1.4.0.tar.gz
verify-proof verify dist/app-1.4.0.tar.gz --proof dist/app-1.4.0.tar.gz.proof.json

verify recomputes the SHA-256, compares it with the hash in the bundle, and prints the transaction to look up. It does not read the chain; that last step is yours, on any block explorer. The same check exists for Node (npm install verify-proof).

Anyone can also ask the public endpoint, with no key:

GET https://proofledger.io/api/v1/verify?hash=3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b

It returns found and, when true, the proof record and an explorer_url. Treat it as a convenience. The record is the Polygon transaction and the bundle.

Which fits your situation

If the question is whether a specific key holder produced a commit, and whether its content has changed since, commit signing answers it. Keep signing commits and tags.

If the question is whether a build, a document or a file existed by a certain date, a signed committer date does not get you there. It is a field you control, checked by a signature that only confirms you controlled it. Anchor the hash of the artifact and the date comes from a chain neither party operates.

Use both. Sign commits for who. Anchor artifacts for when. Keep the bundle next to the thing it describes.

To check a proof without ProofLedger, run verify-proof verify against the file and its bundle, then look the transaction id up on any Polygon block explorer. API reference: https://proofledger.io/api.html