Blockchain BlockchainSupply ChainTraceabilityMCPTutorial

Blockchain Two-Party Supply Chain Traceability — Step by Step

T
TechnoPKG
2026-06-30 📖 5 min read 👁 9 views

1. The problem: single-party traceability isn't traceability

TechnoPKG already had a blockchain traceability page — a real SHA-256 hash chain with proof-of-work, persisted server-side and tamper-evident. But it had a conceptual hole: one logged-in account mined all eight supply-chain stages itself.

That means the supplier never actually attested “yes, I shipped and QC’d these components,” and the customer never attested “yes, I received and accept them.” One party narrated the entire story. For real traceability, each step needs to be authorized by the party responsible for it, and nobody should be able to complete a passport alone.

2. The design

Two parties, each with their own key. The supplier signs the upstream stages (Supplier Receipt, Component Lot, Quality Check); the customer signs the downstream stages and the final acceptance.

The mechanism. Each party gets an Ed25519 keypair — public key stored on the user record, private key encrypted at rest. Every block’s SHA-256 hash (which also covers its role and signer identity) is signed with that party’s key. Validation re-checks the hash chain, that the correct role signed each stage, and that every signature verifies against the stored public key.

customer initiates → AWAITING_SUPPLIER supplier signs 3 stages → AWAITING_CUSTOMER customer signs 5 + accepts → SEALED

A passport is only SEALED and valid when both parties have signed. That single rule closes the hole.

3. Deploying safely to a live server

The portal is live, so every change went through a four-step, reversible flow: backup → deploy → verify → healthcheck, with a one-line rollback at every stage.

./1_backup.sh      # full project + data + mongodump
./2_deploy.sh      # install files, ensure cryptography in venv
./3_verify.sh      # files present, app boots, routes registered
systemctl restart technopkg.service
./4_healthcheck.sh # service up, page wired, self-test passes
A gotcha worth sharing: the first healthcheck failed — not in the feature, but in the test fixture. It created throwaway users without an email, and the live Mongo has a unique index on email, so the second collided on a null email. Lesson: when testing against a real database, fixtures need unique keys and must clean up after themselves.

4. Setting up the two parties

I registered a supplier and a customer account, then granted each a role and minted its keypair with a one-line admin script:

python scripts/grant_sc_role.py supply@precisionparts.com supplier
python scripts/grant_sc_role.py orders@dpsound.com        customer
python scripts/grant_sc_role.py --list

5. The live walkthrough

I opened two sessions — the customer in a normal window, the supplier in a private window. The cleanest tell of which account you’re on is the UI itself: the Initiate form only appears for the customer.

The supplier's Two-Party Passport page — no Initiate form, only the Passports list. The UI enforces customer-only initiation.

Move 1 — Customer initiates

As the customer: Finished Good ITM-001, blank serial, supplier username → Initiate.

Move 1 — Customer initiates. Status AWAITING SUPPLIER, signers: none, 1 block (genesis only). The request exists; nothing is signed yet.

Move 2 — Supplier signs

Switching to the supplier window, the same passport appears with a Sign as Supplier button. One click signs the three upstream blocks with the supplier’s key.

The supplier sees the passport with a Sign as Supplier button — available only to the designated supplier on an AWAITING_SUPPLIER passport.
Move 2 — Supplier signs. Status AWAITING CUSTOMER, signers: supplier, 4 blocks. The supplier has done their half and cannot seal it alone.

Move 3 — Customer accepts & seals

Back as the customer, the passport shows Accept & Seal. One click signs the five downstream blocks and seals it.

Back as the customer: an Accept & Seal button appears. The supplier's stages are signed and waiting for the customer's counter-signature.
Move 3 — Customer accepts & seals. Status SEALED, both parties in signers, 9 blocks, chain valid. Sealed only when both parties have signed.

6. Proving it's real (from the backend)

Screens are nice; cryptography is the proof. I pulled the sealed passport straight from MongoDB and re-validated it:

status : SEALED
signers: ['supply@precisionparts.com', 'orders@dpsound.com']
blocks : 9
valid  : True - Passport valid — all stages correctly signed by their parties.
roles  : [(0,'system'),(1,'supplier'),(2,'supplier'),(3,'supplier'),
          (4,'customer'),(5,'customer'),(6,'customer'),(7,'customer'),(8,'customer')]

Tamper & forgery tests

ORIGINAL : (True,  -1, 'Passport valid — all stages correctly signed by their parties.')
TAMPERED : (False,  2, 'Block #2: hash mismatch (tampered).')
FORGED   : (False,  5, 'Block #5: signature invalid or forged.')

Two distinct guarantees, both working: change any block’s contents and the recomputed SHA-256 no longer matches; sign a block with any key other than the real signer’s and verification fails.

7. Where MCP fits in

Alongside the web UI, the portal exposes its ledger over an MCP server (Model Context Protocol) — a small JSON-RPC service that lets an external agent or auditor talk to the chain through a standard tool interface, with no UI and no Flask. For the two-party feature I added three tools: list_two_party_passports, get_two_party_passport, and verify_two_party_passport.

Why it helped: it gave me a third, independent way to verify the same chain. During testing I could confirm a passport in the browser, read it straight from MongoDB, and verify it again over MCP — three layers agreeing on one cryptographic result. And in a real supply chain it’s the integration point: a customer’s system, an auditor, or an AI assistant can call verify_two_party_passport and get a pass/fail verdict programmatically, without logging into the portal.

verify_two_party_passport(serial="SN001-2026-4261")
-> { "valid": true, "status": "SEALED",
     "signers": ["supply@precisionparts.com","orders@dpsound.com"],
     "message": "Passport valid — all stages correctly signed by their parties." }

8. What I learned

  • “Real” lives in the validation, not the UI — the chain is only as trustworthy as what validate() re-checks.
  • Testing against a live DB teaches you things; a unique index turned a sloppy fixture into a good failure.
  • Enforce roles in the UI and the API — the supplier never sees an Initiate button, and the server returns 403 anyway.
  • Custodial keys are a deliberate simplification: the server holds each key (encrypted) and signs on the authenticated user’s behalf. The signatures are real Ed25519; production would push keys client-side or to an HSM/KMS.

Built on TechnoPKG, a personal learning portal. All data shown is fictional demo data (DP Sound Systems & PrecisionParts Inc). This is a single-node educational chain — not a public distributed network — but the cryptography, per-party signing, and consent lifecycle are genuine.

Tags: BlockchainSupply ChainTraceabilityMCPTutorial

Comments (0)

💬
No comments yet. Be the first to share your thoughts!
Sign in to leave a comment.
Table of Contents
Generating...
Share