How to check a record
A Registration Decision Record and its export package can be checked by a person who does not use EUDAMate. This page lists the checks in order. It also publishes how the hashes of the audit trail are made, so that a hash can be recomputed from the audit trail file.
1. Check the audit chain head
From format version 1.1 a record prints the hash of the newest event in the project's audit trail under "Audit chain head", where the audit trail verified when the record was made. Records of format version 1.0 do not print it.
Copy the 64 characters into the form at /verify. The answer says whether the hash is an event in an audit trail EUDAMate holds, when that event was recorded, how many events the trail has and whether the trail verifies now. Compare the number of events and the time with the record.
A match shows that the hash is in an audit trail EUDAMate holds. It does not show who holds a file. It does not show that the copy of the record you hold was made from that trail. It does not show that a value is right. It says nothing about what EUDAMED does with any file.
2. Check the files of an export package
Unpack the ZIP file into a folder. It holds the clean CSV, the record, the folder xml, the file checksums.sha256 and a README.txt. The record lists the SHA-256 of the CSV and of each XML file. The file checksums.sha256 lists the same values and the SHA-256 of the record itself.
macOS and Linux
Open a terminal in the folder with the unpacked files and run the line below. Each file shows OK when it matches.
shasum -a 256 -c checksums.sha256
Windows
Run one of the lines below in a terminal in the folder. Put the name of the file in place of the part in angle brackets.
certutil -hashfile <file> SHA256 Get-FileHash <file> -Algorithm SHA256
The first line is for the command prompt and the second for PowerShell. Compare the result with the line for that file in checksums.sha256 or in the record. Get-FileHash writes capital letters, so compare without regard to case.
A match shows that the file is byte for byte the file the export made. The hash of an XML file cannot be rebuilt from the audit trail. The export package section of the specification says why.
The page at /verify also takes the hash of a file listed in checksums.sha256. A match shows that an export made the file and when. It does not show who holds it or that EUDAMED accepted it. To tie a file to a record, compare its hash with the list in the record.
3. Recompute the approval fingerprint
The audit trail never holds the approver's name. It holds a fingerprint of the name, the role and a salt. The steps to recompute it, with the sample record as an example, are in the section Checking the approval fingerprint of the specification.
4. Check the anchor of the chain head
A record's producer should anchor the chain head when the record is made, with any RFC 3161 timestamp authority or a public anchor such as EUDAMate's. An anchor shows that the head existed at that time. The format does not depend on any one anchor. EUDAMate's anchor is one option and never a requirement.
An RFC 3161 timestamp
The producer hands over the timestamp token with the record. Check the token with the certificate of the authority that issued it. The hash inside the token must equal the chain head. Where the chain head itself was stamped, this line checks both with OpenSSL.
openssl ts -verify -digest <chain head> -in <token>.tsr -CAfile <authority>.pem
EUDAMate's anchor
Anyone can register a SHA-256 at /verify. Only the fingerprint is sent and EUDAMate keeps it with the time it arrived. Enter the chain head at /verify to learn when it was first registered. EUDAMate does not see the record and does not check it.
An anchor dates the chain head. It does not show who made the record or that its content is right.
A record made by another tool
The format of the record is published, so any tool can make one. EUDAMate does not hold the audit trail of such a record and cannot check it. Check it like this.
- Check the files of its package against the hashes the record lists, as in step 2.
- Recompute the approval fingerprint where the record prints a name, a role and a salt, as in step 3.
- Ask the producer for the audit trail file. Where the tool writes its audit trail as described below, walk the chain with the same formulas and find the chain head the record prints. Where it uses other formulas, ask the producer to publish them.
- Check the anchor of the chain head, as in step 4. A chain head with no anchor shows only what the producer's own database shows.
At /verify such a record is answered only through a fingerprint someone registered. EUDAMate did not make the record and has not seen it.
The audit trail file
The audit trail file lists every event of one project in chain order. The customer can ask EUDAMate for it in writing. With it, a hash from the record can be recomputed without EUDAMate.
Line format
The file holds one JSON object per line. Each line ends with a line feed. The keys of every object are in ascending order and there are no spaces. Keys inside the payload are in ascending order too. A missing value is written as null. The fields are below. Three of them appear on the lines of v2 events only.
| Field | Content |
|---|---|
| actor | Who or what made the event. |
| basic_udi | The Basic UDI-DI of the event, or null. |
| chain_version | Present on v2 lines only. The number 2. |
| created_at | The time of the event as stored. It can carry an offset such as +00:00 and a fraction of fewer than six digits. |
| device_di | The UDI-DI of the event, or null. |
| device_id | Present on v2 lines only. The identifier of the device, or null. |
| event_hash | The hash of this event. |
| event_type | The stored name of the event. |
| id | The identifier of the event. It is not hashed. |
| payload | The content of the event, as stored. |
| prev_hash | The event_hash of the event before this one, or null for the first event. |
| project_id | The project the event belongs to. |
| seq | Present on v2 lines only. The position of the event in the chain, starting at 1. |
A line of a v1 event looks like this. It comes from the illustration further down.
{"actor":"system","basic_udi":null,"created_at":"2026-10-01T09:00:00.000+00:00","device_di":null,"event_hash":"caa9c798126b9fd73fe64292fe3a05eb5d7c2d5f240461a0b5583075f0ef60c6","event_type":"FILE_UPLOAD","id":"00000000-0000-4000-8000-0000000000a1","payload":{"fileType":"csv","filename":"devices.csv","rowCount":2},"prev_hash":null,"project_id":"00000000-0000-4000-8000-000000000001"}Order of the chain
Events written before chain version 2 are v1 events. They come first and follow one another by their prev_hash. Events written after that are v2 events and follow by seq. The first v2 event has a seq of the number of v1 events plus one and its prev_hash is the hash of the last v1 event. The file is already in this order.
How an event hash is made
Every hash is the SHA-256 of the exact bytes of a text, written as 64 lowercase hexadecimal characters. The text is encoded as UTF-8. The hash of an event takes in the hash of the event before it. That is how the previous hash enters.
Version 1
event_hash = lowercase hex of SHA-256 over the UTF-8 bytes of project_id | event_type | actor | device_di | basic_udi | payload | prev_hash the seven parts joined with the character | and nothing else
project_idis the project identifier as text.event_typeis the stored event name as text.actoris the actor as text. An empty actor is written assystem.device_diandbasic_udiare written as text. An empty value is written as nothing.payloadis the payload as JSON text with no spaces. The keys of every object are in ascending order by their UTF-16 code units, which is the default sort of JavaScript. A key whose value is undefined is left out. Strings and numbers are written as JavaScript's JSON.stringify writes them. A missing payload is written as{}.prev_hashis theevent_hashof the event before this one. The first event has nothing there, so the text ends with the character |.
A v1 hash does not cover the time of the event, a sequence number or a device identifier. Moving a v1 event in time without changing the order is not found by the hashes.
Version 2
event_hash = lowercase hex of SHA-256 over the UTF-8 bytes of the text of this array
[ "eudamate.audit/v2", project_id, seq, event_type, device_id,
device_di, basic_udi, actor, created_at, payload, prev_hash ]"eudamate.audit/v2"is the literal text, quotes included.project_idis the project identifier as a string.seqis a number.event_typeandactorare strings.device_id,device_diandbasic_udiare strings or null.created_atis a string in UTC with six digits of fraction and the letter Z, for example2026-10-01T09:10:00.123456Z. The line of the file can show the same time with an offset and fewer digits. Convert it to UTC and pad the fraction with zeros to six digits.payloadis the payload as an object, or{}when missing.prev_hashis a string, or null for the very first event of the chain.
The text of the array is written the way Postgres writes the text of a jsonb value. It is not what JSON.stringify writes, so a hash made with JSON.stringify will not match. The rules are these.
- A comma and one space stand between elements of an array and between members of an object.
- One space follows the colon between a key and its value.
- The members of an object are ordered by the length of the key in UTF-8 bytes. Keys of the same length are ordered by their bytes. This is not alphabetical order.
- A string is in double quotes. A double quote and a backslash are escaped with a backslash. The controls backspace, form feed, line feed, carriage return and tab are written as \b, \f, \n, \r and \t. Any other character below U+0020 is written as \u followed by four lowercase hexadecimal digits. Every other character is written as itself.
- A number is written in plain decimal and never with an exponent.
- The values true, false and null are written as those words. A member whose value is undefined is left out.
A v2 hash covers the time, the sequence number and the device identifier. Because the fields stand in an array, one field cannot run into the next one.
Walking the chain
- Recompute the hash of every line and compare it with its
event_hash. - The
prev_hashof a line must equal theevent_hashof the line before it. The first line has null. - The
seqof v2 lines must rise by one from line to line. - Find the line whose
event_hashequals the audit chain head the record prints. It is the last event the record covers. A file made later can hold newer events after it.
The checks find an event that was changed, moved or removed after the record was made, as long as you hold the head from the record. They do not stop a person who controls the database from rewriting an event and every hash after it. The head printed on a record you hold is the copy kept outside the database.
A worked example
This is an illustration. The project, the actor and the values are made up. The three events were written into a chain with EUDAMate's own audit trail code, which made every hash below. Each hash was then checked again with a plain SHA-256 over the text shown, and the chain verified as intact. It is not a record of a real project.
The first event is a v1 event and the first in the chain. Its text ends with the character | because there is no previous hash. The keys of the payload are in ascending order.
00000000-0000-4000-8000-000000000001|FILE_UPLOAD|system|||{"fileType":"csv","filename":"devices.csv","rowCount":2}|Its SHA-256 is caa9c798126b9fd73fe64292fe3a05eb5d7c2d5f240461a0b5583075f0ef60c6. The second event is also v1. Its text ends with the hash of the first event. The double quote in the value is escaped.
00000000-0000-4000-8000-000000000001|OVERRIDE|user-1|04012345678901||{"field":"deviceName","reason":"typo","value":"Syringe \"S\""}|caa9c798126b9fd73fe64292fe3a05eb5d7c2d5f240461a0b5583075f0ef60c6Its SHA-256 is f890733157df83fc16c30436ec91bd7bd34e89d90742f682c3ac86ff21508293. The third event is a v2 event with a seq of 3, which is the two v1 events plus one. Its previous hash is the hash of the second event. In the payload, sha256 stands first because it is the shortest key. fileType, filename and rowCount have eight characters each and follow in byte order.
["eudamate.audit/v2", "00000000-0000-4000-8000-000000000001", 3, "FILE_UPLOAD", null, null, null, "system", "2026-10-01T09:10:00.123456Z", {"sha256": "abababababababababababababababababababababababababababababababab", "fileType": "csv", "filename": "devices2.csv", "rowCount": 2}, "f890733157df83fc16c30436ec91bd7bd34e89d90742f682c3ac86ff21508293"]Its SHA-256 is eb15c505b86a929d02b4118e19c92d480013038ec2d46c654b8af66a36bc925c. That is the head of this example chain. The audit trail file of the example holds these three lines.
{"actor":"system","basic_udi":null,"created_at":"2026-10-01T09:00:00.000+00:00","device_di":null,"event_hash":"caa9c798126b9fd73fe64292fe3a05eb5d7c2d5f240461a0b5583075f0ef60c6","event_type":"FILE_UPLOAD","id":"00000000-0000-4000-8000-0000000000a1","payload":{"fileType":"csv","filename":"devices.csv","rowCount":2},"prev_hash":null,"project_id":"00000000-0000-4000-8000-000000000001"}
{"actor":"user-1","basic_udi":null,"created_at":"2026-10-01T09:05:00.000+00:00","device_di":"04012345678901","event_hash":"f890733157df83fc16c30436ec91bd7bd34e89d90742f682c3ac86ff21508293","event_type":"OVERRIDE","id":"00000000-0000-4000-8000-0000000000a2","payload":{"field":"deviceName","reason":"typo","value":"Syringe \"S\""},"prev_hash":"caa9c798126b9fd73fe64292fe3a05eb5d7c2d5f240461a0b5583075f0ef60c6","project_id":"00000000-0000-4000-8000-000000000001"}
{"actor":"system","basic_udi":null,"chain_version":2,"created_at":"2026-10-01T09:10:00.123456+00:00","device_di":null,"device_id":null,"event_hash":"eb15c505b86a929d02b4118e19c92d480013038ec2d46c654b8af66a36bc925c","event_type":"FILE_UPLOAD","id":"00000000-0000-4000-8000-0000000000a3","payload":{"fileType":"csv","filename":"devices2.csv","rowCount":2,"sha256":"abababababababababababababababababababababababababababababababab"},"prev_hash":"f890733157df83fc16c30436ec91bd7bd34e89d90742f682c3ac86ff21508293","project_id":"00000000-0000-4000-8000-000000000001","seq":3}More
The specification of the record describes what the record holds. The same specification is available as the machine-readable specification.