VeriFactu hash chain

The chained SHA-256 hashes VeriFactu requires, computed in Python with not a single dependency.

Open-source libraries · Python · Code published

The problem

Every billing record has to carry the hash of the one before it, so that altering an old invoice breaks every record that came after. It is a short requirement, and whether an entire system is compliant hangs on it: if the field order or the format of an amount does not match what the tax agency expects, the hash comes out different and nothing anywhere raises an error.

What it does

  • Computes the SHA-256 hash of a billing record and maintains the chain that links it to the previous one.
  • Validates the record against the Spanish tax agency specification, version 0.1.2.
  • Reproduces the official test vector published by the agency itself.
  • Ships a command-line tool to chain records and to verify a chain that already exists.

What was decided while building it

The official vector outranks our own judgement
The agency publishes a sample record with its hash already computed, and the library reproduces it as a test. That is the difference between «we read the spec and think it is right» and «it returns exactly what the source returns». A hash implementation cannot be validated by reading it: either it matches the vector, or it is wrong and looks right.
The scope stops at the hash, on purpose
It does not sign, build XML, transmit or store anything. A package promising all five would demand trust in all five, and this is the one you genuinely cannot afford to get wrong. Left on its own it can be read end to end in an afternoon, and swapped out without touching the rest of the system.
Zero dependencies is not minimalism: it is surface area
This ends up inside somebody's billing system, which is exactly where a chain of transitive dependencies becomes a problem that is not theirs. The standard library is enough, so only the standard library is used, and type hints ship with it so mistakes surface while writing rather than in production.

How far it goes

  • It does not sign electronically. The XAdES signature submission requires has to come from another component.
  • It does not build the billing XML and does not transmit anything to the agency: a record goes in, a hash comes out.
  • It does not store records. Retention, which is a separate obligation, is on whoever uses it.
  • It does not generate the invoice QR code.
  • 0 dependencies
  • MIT licence
  • 3.9+ Python

Built with

  • Python
  • SHA-256
  • CLI

Backs this service

Bespoke software and application development

Read the code