PHPackages                             condorcet-vote/elephstamp - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. condorcet-vote/elephstamp

ActiveLibrary

condorcet-vote/elephstamp
=========================

Modern, object-oriented PHP library for OpenTimestamps: register timestamp proofs with calendar servers and collect the completed .ots certification.

v1.0.0(1mo ago)1280↓75%1[2 PRs](https://github.com/CondorcetVote/Elephstamp/pulls)MITPHPPHP ^8.5CI passing

Since Jul 15Pushed 2w ago1 watchersCompare

[ Source](https://github.com/CondorcetVote/Elephstamp)[ Packagist](https://packagist.org/packages/condorcet-vote/elephstamp)[ RSS](/packages/condorcet-vote-elephstamp/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (5)Versions (2)Used By (0)

ElephStamp
==========

[](#elephstamp)

[![Packagist Version](https://camo.githubusercontent.com/9179592a06d2bd0c8e5d5bd94beb28ed52b6ed9141d13a42ae355b98c2517d67/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f636f6e646f726365742d766f74652f656c6570687374616d70)](https://packagist.org/packages/condorcet-vote/elephstamp)[![Packagist Downloads](https://camo.githubusercontent.com/5c517df1e2773a7b200e309fc6b33d6f887a156d081f725a08b1aa12fd866881/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f636f6e646f726365742d766f74652f656c6570687374616d70)](https://packagist.org/packages/condorcet-vote/elephstamp)[![CI](https://github.com/CondorcetVote/Elephstamp/actions/workflows/ci.yml/badge.svg)](https://github.com/CondorcetVote/Elephstamp/actions/workflows/ci.yml)[![License](https://camo.githubusercontent.com/55dc86c8be07e4ab0a765ed009bc945819ff8b60f7e32a94eed6afbbe60fe461/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f636f6e646f726365742d766f74652f656c6570687374616d70)](LICENSE)

A modern, object-oriented PHP library for [OpenTimestamps](https://opentimestamps.org/). It lets you **register a timestamp proof** for a file with the public calendar servers and **track/collect the completed proof** once it has been anchored in the Bitcoin blockchain.

ElephStamp is a focused, partial port of the Python [`opentimestamps-client`](https://github.com/opentimestamps/opentimestamps-client): it is not a line-by-line translation but an expressive PHP API. The `.ots`proof files it produces and reads are byte-for-byte interoperable with the reference tooling.

Scope
-----

[](#scope)

**In scope**

- Creating timestamp requests and submitting them to calendar servers.
- Following a proof's status and fetching the completed `.ots` once the calendars can provide a blockchain attestation.
- A built-in **fake mode** for tests and local/integration environments.

**Out of scope (by design)**

- No command-line tool — this is a library only.
- No on-chain verification. ElephStamp reports what a proof *claims* (e.g. the Bitcoin block height) but never talks to a Bitcoin node to verify it. Use a dedicated verifier for that.

Requirements
------------

[](#requirements)

- PHP **8.5+** with the `mbstring` extension.
- [`symfony/http-client`](https://symfony.com/doc/current/http_client.html)(pulled in automatically) for talking to calendar servers.

Installation
------------

[](#installation)

```
composer require condorcet-vote/elephstamp
```

Quick start
-----------

[](#quick-start)

### Stamp a file

[](#stamp-a-file)

```
use CondorcetVote\ElephStamp\ElephStamp;
use CondorcetVote\ElephStamp\FileToStamp;

$client = new ElephStamp();

$receipt = $client->stamp(FileToStamp::fromPath('contract.pdf'));

// Persist the proof next to your file.
$receipt->saveToPath('contract.pdf.ots');

echo $receipt->status()->name;        // "Pending"
echo $receipt->fileDigestHex();       // sha256 of contract.pdf
```

A freshly created timestamp is **pending**: the calendars have recorded your commitment but Bitcoin has not confirmed it yet (this usually takes a few hours).

All calendars are contacted **concurrently**, and a calendar that is unreachable or misbehaves is tolerated: a stamp succeeds as long as at least `requiredCalendars` of them accept it (see [Configuration](#configuration)).

### Follow up and collect the completed proof

[](#follow-up-and-collect-the-completed-proof)

```
use CondorcetVote\ElephStamp\ElephStamp;
use CondorcetVote\ElephStamp\Receipt;

$client = new ElephStamp();

$receipt = Receipt::fromPath('contract.pdf.ots');

if ($client->upgrade($receipt)) {
    // Something changed: persist the richer proof.
    $receipt->saveToPath('contract.pdf.ots');
}

if ($receipt->isComplete()) {
    echo 'Anchored in Bitcoin block ' . $receipt->bitcoinBlockHeight();
}
```

`upgrade()` performs a single polling pass and returns whether the proof changed. Call it again later while the receipt is still pending.

Describing what to stamp
------------------------

[](#describing-what-to-stamp)

`FileToStamp` has one explicit, typed constructor per source. Files are always hashed as a stream — their content is never loaded into memory in full.

```
use CondorcetVote\ElephStamp\FileToStamp;

FileToStamp::fromPath('invoice.pdf');                       // a file on disk
FileToStamp::fromSplFileObject(new SplFileObject('a.bin')); // an open handle
FileToStamp::fromContent('some in-memory string');          // raw bytes
FileToStamp::fromDigest($sha256);                           // a digest you already computed
```

`fromDigest()` uses the digest verbatim; its length must match the client's hash operation (SHA-256 by default).

### Privacy nonce

[](#privacy-nonce)

By default a random nonce is mixed into the digest before it reaches a calendar, so the calendar never learns the real file hash. When linkability is acceptable — or desirable, e.g. so the commitment equals the file's plain SHA-256 and can be recomputed without the `.ots` — disable it:

```
FileToStamp::fromPath('public-release.zip')->withoutNonce();
```

Note that inside a `stampMany()` batch, a file stamped without nonce also exposes its plain digest in the **sibling receipts** of the batch (the merkle tree embeds each leaf's message into its neighbours' proofs) — not only to the calendars.

### Stamping several files at once

[](#stamping-several-files-at-once)

`stampMany()` binds all files into a single merkle tree, so one calendar submission covers them, while each file still gets its own independent receipt.

```
$receipts = $client->stampMany(
    FileToStamp::fromPath('a.pdf'),
    FileToStamp::fromPath('b.pdf'),
    FileToStamp::fromPath('c.pdf'),
);
```

Reading a receipt
-----------------

[](#reading-a-receipt)

```
$receipt->status();               // Status::Pending | Status::Complete
$receipt->isComplete();
$receipt->isPending();
$receipt->fileDigest();           // raw digest bytes
$receipt->fileDigestHex();        // lower-case hex
$receipt->pendingCalendarUris();  // list of calendars still to poll
$receipt->bitcoinBlockHeight();   // int|null (claimed, not verified)
$receipt->toBytes();              // the raw .ots content
$receipt->describe();             // human-readable proof tree (for inspection)
```

`describe()` renders the proof as an indented tree, handy for debugging:

```
file sha256 digest: da7badf6…f655
append d6c877a9…
sha256
 -> append 014870d0…
    sha256
    prepend f23482df…
    sha256
    pending attestation → https://finney.calendar.eternitywall.com
 -> …

```

Testing: fake mode
------------------

[](#testing-fake-mode)

`ElephStamp::fake()` returns a fully offline client. Proofs are deterministic and the fake calendar lets you simulate Bitcoin confirmation, so you can test both the pending and complete states without any network.

```
use CondorcetVote\ElephStamp\ElephStamp;
use CondorcetVote\ElephStamp\FileToStamp;

$client = ElephStamp::fake();

$receipt = $client->stamp(FileToStamp::fromContent('hello world'));
$receipt->isPending();   // true

// Simulate Bitcoin confirming the commitment.
$client->fakeCalendar()->confirmAll(blockHeight: 812_345);

$client->upgrade($receipt);
$receipt->isComplete();          // true
$receipt->bitcoinBlockHeight();  // 812345
```

Confirm a specific receipt, or everything submitted so far:

```
use CondorcetVote\ElephStamp\Calendar\FakeCalendarClient;

$calendar = new FakeCalendarClient();
$client = ElephStamp::fake($calendar);

$receipt = $client->stamp(FileToStamp::fromContent('data'));

$calendar->confirm($receipt);  // confirm just this receipt
// or
$calendar->confirmAll();       // confirm every commitment submitted so far
```

Configuration
-------------

[](#configuration)

```
use CondorcetVote\ElephStamp\ElephStamp;

$client = new ElephStamp(
    calendarUrls: ['https://a.pool.opentimestamps.org', 'https://b.pool.opentimestamps.org'],
    requiredCalendars: 2, // the "m" of an m-of-n policy
);
```

Calendar URLs must be unique and use **https** (a plaintext connection would let a network attacker inject forged responses); the same goes for whitelist patterns. When `requiredCalendars` is omitted it defaults to **2** — like the reference client — or to 1 when a single calendar is configured.

### Upgrade whitelist (security)

[](#upgrade-whitelist-security)

An `.ots` proof embeds the calendar URIs to poll when upgrading. Because a proof may come from an untrusted source, `upgrade()` only contacts hosts on an allowlist — otherwise a hostile proof could point the process at arbitrary hosts (an SSRF risk). The default covers the known public operators (`*.calendar.opentimestamps.org`, `*.calendar.eternitywall.com`, `*.calendar.catallaxy.com`). Override it when you use private calendars:

```
$client = new ElephStamp(
    calendarUrls: ['https://ots.internal.example'],
    upgradeWhitelist: ['https://*.internal.example'],
);
```

Host patterns accept shell-style globs; URLs with a query, fragment or credentials are always rejected.

### Customising the HTTP client

[](#customising-the-http-client)

The default transport is the Symfony HTTP client, hardened for calendar traffic: redirects are never followed, responses are capped at 10 kB, and requests time out after 10 s of silence (30 s in total). Tune the timeouts, or inject your own configured instance (proxy, retries, ...) when needed:

```
use CondorcetVote\ElephStamp\Calendar\HttpCalendarClient;
use CondorcetVote\ElephStamp\ElephStamp;
use Symfony\Component\HttpClient\HttpClient;

$calendarClient = new HttpCalendarClient(
    HttpClient::create(['proxy' => 'http://proxy.internal:3128']),
    timeout: 5.0,       // idle timeout, seconds
    maxDuration: 15.0,  // hard cap per request, seconds
);

$client = new ElephStamp(calendarClient: $calendarClient);
```

Exceptions
----------

[](#exceptions)

Every exception implements `CondorcetVote\ElephStamp\Exception\ElephStampException`:

- `InvalidInputException` — bad caller input (unreadable file, invalid options).
- `SerializationException` — malformed or unsupported `.ots` data.
- `CalendarException` — a calendar was unreachable or misbehaved.
- `StampingException` — too few calendars accepted a stamp (m-of-n not met).

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance94

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 85.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4020317?v=4)[Julien Boudry](/maintainers/julien-boudry)[@julien-boudry](https://github.com/julien-boudry)

---

Top Contributors

[![julien-boudry](https://avatars.githubusercontent.com/u/4020317?v=4)](https://github.com/julien-boudry "julien-boudry (6 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

open-time-serveropen-timestampsopentimestampopentimestampsbitcointimestampcertificationproofotsopentimestampsnotary

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/condorcet-vote-elephstamp/health.svg)

```
[![Health](https://phpackages.com/badges/condorcet-vote-elephstamp/health.svg)](https://phpackages.com/packages/condorcet-vote-elephstamp)
```

###  Alternatives

[craftcms/cms

Craft CMS

3.6k3.7M3.5k](/packages/craftcms-cms)[symfony/asset-mapper

Maps directories of assets &amp; makes them available in a public directory with versioned filenames.

16710.3M302](/packages/symfony-asset-mapper)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
