Secure Linkage Service is a Rust web service for multi-party privacy-preserving record linkage1. It lets two organizations coordinate linkage results without sending each other plaintext identifiers.
The service exposes HTTP endpoints for health checks, fresh encrypted record IDs, and paired linkage-result submission. It uses ChaCha20-Poly1305 for authenticated encryption and XOR-based match-bit combination for the linkage step.
- ChaCha20-Poly1305 AEAD encryption with random nonces
- Paired client/server linkage requests between named parties
- Fresh encrypted record ID generation
- Async HTTP server built on Axum and Tokio
- Health endpoint, structured tracing, and input validation
- Unit, integration, and end-to-end test coverage
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REST API Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Health β βFresh Record β β Linkage β β
β β Endpoints β β IDs β β Endpoints β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Services Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Party β β Linkage β β Fresh Record IDs β β
β β Service β β Service β β Service β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Core Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Cryptographyβ β Linkage β β Domain β β
β β Module β β Logic β β Models β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Rust 1.89 or newer
- Git
- just
git clone https://github.com/your-org/secure-linkage-service.git
cd secure-linkage-service
cargo build --release
cargo testjust startjust start uses linkage-parties.toml. Pass an empty config argument to use the built-in test parties:
just start ""Stop it with:
just stopThe server listens on http://localhost:5000 by default.
curl http://localhost:5000/healthExpected response:
{
"status": "healthy",
"service": "linkage-service",
"timestamp": "2025-01-30T10:30:00Z",
"version": "0.1.0"
}Base URL:
http://localhost:5000
GET /healthReturns service health and version metadata.
POST /freshRecordIDs/{party}?n={count}Parameters:
party: party name, such asdkfzortudan: number of IDs to generate (positiveu32), defaults to1. Large requests consume memory and produce large responses; clients should set appropriate timeouts.
Example:
curl -X POST "http://localhost:5000/freshRecordIDs/dkfz?n=5"Response:
{
"status": "success",
"party": "dkfz",
"count": 5,
"fresh_record_ids": [
"Y3J5cHRvZ3JhcGhpY2FsbHlfZW5jcnlwdGVkX2RhdGE=",
"c2VjdXJlX2xpbmthZ2VfZGF0YV93aXRoX3Byb3Blcg=="
],
"message": null
}POST /linkageResult/{source_party}/{destination_party}The service pairs a server request with the matching client request for the opposite party direction.
Server request:
{
"role": "server",
"result": [
{
"match": 0,
"tentativeMatch": 1,
"bestIndex": 0
}
],
"ids": ["base64_encoded_encrypted_id"]
}Client request:
{
"role": "client",
"result": [
{
"match": 1,
"tentativeMatch": 0,
"bestIndex": 0
}
]
}Example workflow:
curl -X POST "http://localhost:5000/linkageResult/dkfz/tuda" \
-H "Content-Type: application/json" \
-d '{
"role": "server",
"result": [{"match": 0, "tentativeMatch": 1, "bestIndex": 0}],
"ids": ["Y3J5cHRvZ3JhcGhpY2FsbHlfZW5jcnlwdGVkX2RhdGE="]
}' &
curl -X POST "http://localhost:5000/linkageResult/tuda/dkfz" \
-H "Content-Type: application/json" \
-d '{
"role": "client",
"result": [{"match": 1, "tentativeMatch": 0, "bestIndex": 0}]
}'Response:
{
"status": "completed",
"linkage_ids": [
"ZW5jcnlwdGVkX2xpbmthZ2VfaWRfcmVzdWx0XzE="
],
"message": "Linkage computation completed"
}For each linkage round:
- Each party uses a 256-bit ChaCha20 key.
- The server-side party sends encrypted record IDs.
- The client and server submit match bits.
- The service XORs the submitted bits.
- For a match, the service derives the linkage ID from the server ID.
- For a non-match, the service generates a random linkage ID.
Match-bit table:
Client Bits Server Bits XOR Result Outcome
match=1 match=0 1 MATCH FOUND
tentative=0 tentative=1 1 MATCH FOUND
match=0 match=0 0 NO MATCH
tentative=0 tentative=0 0 NO MATCH
Security properties:
- Parties do not receive each other's plaintext identifiers.
- ChaCha20-Poly1305 detects ciphertext tampering.
- Random nonces protect repeated encryption operations.
- Rust memory safety prevents buffer overflows and use-after-free bugs in safe code.
Run unit and integration tests:
cargo testRun the end-to-end suite:
just e2eRun a single end-to-end scenario:
./tests/e2e/run-single-test.sh 01 # basic client-server flow
./tests/e2e/run-single-test.sh 05 # multiple matches with real crypto
./tests/e2e/run-single-test.sh 14 # performance testThe test suite covers fresh ID generation, paired request coordination, concurrent requests, large inputs, malformed requests, and error paths.
Local test runs exercise these workloads:
| Metric | Current test coverage |
|---|---|
| Concurrent requests | 100 or more |
| Typical request latency | Under 1 second for tested datasets |
| Dataset size | 1000 or more records |
| Scaling behavior | Linear in tested cases |
Example checks:
time curl -X POST "http://localhost:5000/freshRecordIDs/dkfz?n=1000"# Submit paired linkage requests with 100 records to measure linkage latency.just start
just stopFROM rust:1.85-slim AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/secure-linkage-service /usr/local/bin/
EXPOSE 5000
CMD ["secure-linkage-service"]docker build -t secure-linkage-service .
docker run -p 5000:5000 secure-linkage-serviceProduction deployments should add TLS termination, request limits, log collection, and a non-root container user.
Environment variables:
HOST=127.0.0.1 # bind address
PORT=5000 # server port
PARTIES_CONFIG_FILE=./linkage-parties.toml # known linkage parties
RUST_LOG=info # tracing level
RUST_BACKTRACE=1 # backtraces during developmentProject layout:
secure-linkage-service/
βββ src/
β βββ core/
β β βββ crypto.rs
β β βββ linkage.rs
β βββ domain.rs
β βββ services/
β β βββ party_service.rs
β β βββ linkage_service.rs
β β βββ fresh_record_ids_service.rs
β βββ rest_api/
β β βββ health/
β β βββ linkage/
β β βββ fresh_record_ids/
β β βββ party/
β βββ server.rs
β βββ main.rs
βββ tests/
β βββ e2e/
β βββ *.rs
βββ Cargo.toml
βββ README.md
Common commands:
just fmt
just lint
just test
just e2eAdd new code in the layer that owns the behavior:
src/core/for cryptography and linkage algorithmssrc/services/for request coordination and application rulessrc/rest_api/for HTTP handlers and routingtests/for unit, integration, and end-to-end coverage
The service addresses these risks in code:
- Plaintext identifier exposure between parties
- Ciphertext tampering
- Replay through nonce reuse
- Malformed input
- Memory corruption in safe Rust code
Deployers still need to provide transport security, authentication, authorization, rate limits, and operational monitoring.
Report security vulnerabilities by email to Tobias Kussel. Do not file public issues for security reports.
Pull requests are welcome.
- Fork the repository.
- Create a feature branch:
git checkout -b feature/my-change. - Add code and tests.
- Run
just test && just e2e. - Commit your changes.
- Push your branch.
- Open a pull request.
This project follows the Free Software Camp Code of Conduct.
This program is free software. You can redistribute it and modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 or any later version.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see http://www.gnu.org/licenses/.
AGPL points for service deployers:
- You may use, study, modify, and distribute the software.
- You must disclose source code for modifications.
- Network use triggers the source-distribution requirement.
- Derivative works must use a compatible open-source license.
See LICENSE for the full license text.
Copyright:
- Rust reimplementation: Copyright Β© 2025 Leandro Doctors(*)
- Python core functional overhaul: Copyright Β© 2025 Leandro Doctors(*)
- Original Python imperative implementation: Copyright Β© 2018 Tobias Kussel(*)
(*) affiliated with the Federated Information Systems Department, German Cancer Research Center (DKFZ) / University of Mannheim (UMM), Germany
Leandro Doctors thanks Tobias Kussel for problem-domain guidance, the initial Rust AEAD implementation, and review.
Footnotes
-
Stammler S, Kussel T, Schoppmann P, Stampe F, Tremper G, Katzenbeisser S, Hamacher K, Lablans M. Mainzelliste SecureEpiLinker (MainSEL): privacy-preserving record linkage using secure multi-party computation. Bioinformatics. 2022 Mar 4;38(6):1657-1668. doi: 10.1093/bioinformatics/btaa764. PMID: 32871006; PMCID: PMC8896632. β©