This project implements a blind signature over an MSS (Merkle Signature Scheme) using WOTS as the one-time signature (OTS) and a ZK proof in the ZKBoo / MPC-in-the-head style to prove knowledge of a valid signature without revealing secret material.
⚠️ This code is for research/education. Do not use in production.
This work was carried out during my final internship for the Master's degree in Cryptology and Computer Security at the University of Bordeaux. The internship took place in the first half of 2025 at UPC (Barcelona), supervised by Javier Herranz Sotoca.
Requirements:
- A C compiler (GCC/Clang) with OpenMP support
- OpenSSL libcrypto
make
Build everything:
makeClean:
make cleanBinaries produced:
CLIENT_blinding_message(client)SIGNER_MSS_keygen(signer)SIGNER_MSS_sign(signer)CLIENT_blind_sign(client)VERIFIER_verify(verifier / anyone)
From shared.c:
H = 10(Merkle tree height →2^10 = 1024leaves)N = 32(byte length of hashes / words)WOTS_len = 512(number of WOTS chain elements)
All hex in files is UPPERCASE without spaces.
-
MSS_secret_key.txt(created bySIGNER_MSS_keygen)- Line 1:
sk_seed— 32 bytes as 64 hex chars - Line 2:
leaf_index— decimal (initially0)
- Line 1:
-
MSS_public_key.txt(created bySIGNER_MSS_keygen)- Merkle-tree root — 32 bytes as 64 hex chars, newline-terminated
-
blinding_key.txt(created byCLIENT_blinding_message)- Blinding key r - 32 bytes (64 hex chars)
-
blinded_message.txt(created byCLIENT_blinding_message)- Blinded message - 64 bytes (128 hex chars) defined as blinded = commitment || ~commitment with commitment = SHA256( SHA256(m) || r )
-
MSS_signature.txt(created bySIGNER_MSS_sign)- Line 1:
leaf_index— decimal - Line 2: empty line
- Lines 3 .. 3+WOTS_len-1: WOTS signature; each line is 32 bytes (64 hex chars)
- Next line: empty line
- Next
Hlines: authentication path; each line is 32 bytes (64 hex chars)
- Line 1:
-
signature_proof.bin(created byCLIENT_blind_sign)- Binary ZK proof (ZKBoo/MPC-in-the-head) that a valid MSS signature exists for the committed message.
-
Signer generates keys
./SIGNER_MSS_keygen
Produces
MSS_secret_key.txtandMSS_public_key.txt. Prints the public key and the secret seed to stdout as a convenience. -
Client blinds a message
./CLIENT_blinding_message
-
Prompts: plaintext message
m(one line from stdin). -
Produces:
-
blinding_key.txtwith Blinding keyr(32 bytes, 64 hex chars) -
blinded_messge.txtwith Blinded message (64 bytes, 128 hex chars) defined ascommitment || ~commitment, wherecommitment = SHA256( SHA256(m) || r ).
Client keeps
rsecret and sends the blinded message to the signer. -
-
Signer signs the blinded message
./SIGNER_MSS_sign
-
Reads
MSS_secret_key.txt,blinded_messge.txt -
Produces
MSS_signature.txtwith:leaf_index, the WOTS signature (512 × 32-byte lines), and the Merkle authentication path (10 × 32-byte lines).
-
-
Client produces a zero-knowledge signature proof
./CLIENT_blind_sign
-
Prompts for:
- plaintext message
m(stdin)
- plaintext message
-
Reads:
blinding_key.txt,MSS_signature.txt,MSS_public_key.txt -
Writes
signature_proof.bin.
If anything is inconsistent (message orrdoesn’t match, signature invalid, etc.), it prints an error and exits.
-
-
Verifier checks the proof against the public key and message
./VERIFIER_verify
- Prompts for the signed message
m(stdin). - Reads:
MSS_public_key.txtandsignature_proof.bin. - Prints success/failure.
- Prompts for the signed message
-
CLIENT_blinding_message
- Input: message
mfrom stdin - Output file:
blinding_key.txt,blinded_message.txt
- Input: message
-
SIGNER_MSS_keygen
- Output files:
MSS_secret_key.txt,MSS_public_key.txt
- Output files:
-
SIGNER_MSS_sign
- Reads:
blinded_message.txt,MSS_secret_key.txt - Output file:
MSS_signature.txt
- Reads:
-
CLIENT_blind_sign
- Inputs: message
m(stdin) - Reads:
blinding_key.txt,MSS_signature.txt,MSS_public_key.txt - Output file:
signature_proof.bin
- Inputs: message
-
VERIFIER_verify
- Input: message
m(stdin) - Reads:
MSS_public_key.txt,signature_proof.bin - Output: success/failure (stdout)
- Input: message