This project implements Shamir Secret Sharing for splitting a text secret into multiple shares and reconstructing it from a threshold number of shares.
The backend uses FastAPI, RabbitMQ, and a worker process. The API does not call the Shamir core directly. It sends a task to RabbitMQ, waits for the worker response through a temporary reply queue, and returns the result to the client.
- Split a UTF-8 text secret into
total_sharesserialized shares. - Recover the original secret from at least
thresholdvalid shares. - Use SHA-256 to verify that the reconstructed secret matches the original.
- Log generation and reconstruction metadata for auditability.
- Use RabbitMQ to route work from the API process to the worker process.
flowchart LR
Client([Frontend / Client])
API([FastAPI API])
Queue[(RabbitMQ<br/>shamir_queue)]
Worker([Worker])
Core[[Shamir Core]]
Reply[(RabbitMQ<br/>temporary reply queue)]
Client -->|HTTP request| API
API -->|task message| Queue
Queue -->|consume| Worker
Worker -->|split / recover| Core
Core -->|result| Worker
Worker -->|response message| Reply
Reply -->|correlation_id| API
API -->|HTTP response| Client
This architecture is intentionally more complex than a simple synchronous API that calls the Shamir functions directly. A simpler design would be enough for small local demos. We chose the RabbitMQ worker flow because the project is also about practicing distributed-system design: queue-based communication, worker separation, request correlation, and failure boundaries.
Secrets and generated shares are not stored in files or a database on the server. During request processing, the input message and response may exist for a short time in RabbitMQ queues and in API/worker memory. After the response is returned, the backend does not keep a saved copy.
sequenceDiagram
participant C as Frontend
participant A as FastAPI
participant Q as RabbitMQ task queue
participant W as Worker
participant S as Shamir core
participant R as RabbitMQ reply queue
C->>A: POST /split or /recover
A->>Q: publish task with request_id
Q->>W: deliver task
W->>S: run split_secret or recover_secret
S-->>W: return result
W->>R: publish response with correlation_id
R-->>A: deliver matching response
alt success
A-->>C: shares or secret
else validation or processing error
A-->>C: INVALID_REQUEST
end
src/
├── api/ # FastAPI app and endpoints
├── broker/ # RabbitMQ client and worker
├── shamir/ # Core Shamir algorithm
└── frontend/ # UI work area
tests/ # Core/API tests
Each generated share is a serialized Share object. The integrity hash is stored
inside every share, so the API does not expose a separate hash field.
classDiagram
class Share {
<<dataclass>>
int threshold : minimum shares required
int prime : finite field modulus
int byte_length : original secret byte size
int x : point index
int y : polynomial value
str secret_hash : SHA-256 integrity check
serialize() str
parse(raw) Share
}
Serialized shares use : as a separator:
threshold:prime:byte_length:x:y:hash
The separator is needed because fields such as prime, y, and hash can have
different lengths. Without a separator, the parser would not know where one
field ends and the next one starts.
Serialized share example:
3:1f4a9c:5:1:19ad3f:2cf24dba5fb0a30e26e83b2ac5b9e29e...
POST /api/v1/secrets/split
Request:
{
"secret": "hello",
"threshold": 3,
"total_shares": 5
}Response:
{
"shares": ["..."],
"share_count": 5,
"request_id": "..."
}The integrity hash is embedded into every serialized share. It is not returned as a separate API field.
POST /api/v1/secrets/recover
Request:
{
"shares": ["share1", "share2", "share3"]
}Response:
{
"secret": "hello",
"request_id": "..."
}Errors use this shape:
{
"code": "INVALID_REQUEST",
"message": "insufficient shares",
"request_id": "..."
}Install dependencies:
pip install -r requirements.txtStart RabbitMQ:
brew services start rabbitmqRun the worker:
cd src
python -m broker.workerRun the API in another terminal:
cd src
python -m uvicorn api.main:app --reloadExample split request:
curl -X POST http://127.0.0.1:8000/api/v1/secrets/split \
-H "Content-Type: application/json" \
-d '{
"secret": "hello",
"threshold": 3,
"total_shares": 5
}'Example recover request:
curl -X POST http://127.0.0.1:8000/api/v1/secrets/recover \
-H "Content-Type: application/json" \
-d '{
"shares": ["share1", "share2", "share3"]
}'Run tests:
pytestRun the frontend in another terminal:
cd src/frontend
npm install
npm run devThe Vite app usually starts at:
http://localhost:5173
- Hash integrity:
splitembeds a SHA-256 hash into every share, andrecoververifies the reconstructed secret against that embedded hash. - Insufficient shares: reconstruction with fewer than
thresholdshares fails. - Audit logs: split and recover operations log request metadata without logging the original secret.
- Shares are serialized as
threshold:prime:byte_length:x:y:hash. - The code uses finite field arithmetic over
GF(p), cryptographically secure randomness fromsecrets, and Lagrange interpolation for reconstruction. - Do not log the original secret or the full list of shares.
- Demo API: docs/demo.mov
- Demo Front: docs/demo-front.mp4
- PDF report: docs/report.pdf
- Original paper: https://web.mit.edu/6.857/OldStuff/Fall03/ref/Shamir-HowToShareASecret.pdf
- Visualization: https://iancoleman.io/shamir/