UPI_Without_Internet: Why the Payment Isn’t the Packet, and the Packet Isn’t the Trust Boundary
A mesh-network payment demo that encrypts money before it hops phones, dedupes by ciphertext hash, and settles only when a bridge node reaches the server.

AIR 546, GATE 21 CS Computer Science Undergrad.
- The project treats every intermediate phone as hostile and makes the ciphertext, not the user-controlled packet metadata, carry the real identity of a payment.
- Bridge nodes do the first trustworthy server handoff, which lets a delay-tolerant mesh behave like a payment system instead of just a store-and-forward chat demo.
- Hybrid encryption, ciphertext hashing, and freshness checks combine to stop tampering, duplicates, and stale replayed packets at different layers.
- The code is still prototype-sized, but the crypto and concurrency choices are the kind you would expect in a serious financial pipeline.
The internet is optional. Trust is not.
This repo is not trying to make connectivity pretty. It assumes the ugly case: a payment instruction has to survive a path of offline devices, and none of those devices should be trusted with the payload. That changes the whole design. The system behaves less like a normal app flow and more like a delay-tolerant network for money.
The key move is the bridge node. Most devices in the mesh only relay packets. Only one device, eventually, needs a route back to the server. That is the whole mental shift: the network does not need to be online everywhere. It only needs one honest path to appear later.
The real identity of a payment lives in the ciphertext
The security stack is straightforward in the best way. A random AES key encrypts the payment instruction, and that AES key is wrapped with the server’s RSA public key. The payload then rides through the mesh as an opaque blob, protected by AES-GCM so any tampering breaks authentication at the bridge.
The unusual part is not the encryption. It is the identity rule. Instead of trusting a user-supplied packet ID, the system hashes the encrypted blob itself and uses that SHA-256 value as the idempotency key. That means the thing being deduped is exactly the thing that was actually relayed, not a label attached to it by an untrusted device.
Why the mesh does not need to know the payment
The mesh simulator is the easiest part to misunderstand. It is not the product. It is a proof that the trust boundary is where the packet is decrypted, not where it hops. Devices gossip packets forward, TTL counts down, and the packet can survive multiple relays before a bridge ever sees the wire format.
That separation matters because relay devices do not need the private key. They can move the packet without learning the payment, and they can do it without becoming part of the trust chain. In other words, the mesh is only a transport layer, not a witness.
The bridge node is where offline turns into ledger state
BridgeIngestionService is where the demo stops being a networking exercise and starts looking like a payment system. It claims the ciphertext hash first, before decryption, so duplicate uploads do not race into the same settlement path. Then it decrypts, checks freshness against the instruction timestamp, and rejects stale packets that have been sitting around too long.
That order is deliberate. Claim first. Decrypt second. Decide third. It keeps the idempotency boundary outside the payload, which is exactly what you want when multiple bridge nodes can flush uploads in parallel.
// Bridge ingestion pattern
String hash = sha256(ciphertext);
if (!idempotencyService.claim(hash)) {
return duplicate();
}
PaymentInstruction pi = crypto.decrypt(ciphertext);
if (isExpired(pi.getSignedAt(), maxAgeSeconds)) {
return stale();
}
return settlementService.settle(pi);
| Axis | UPI_Without_Internet mesh | USSD automation / *99# | Official offline rails like UPI Lite X or UPI 123PAY | Manual deferred sync systems |
|---|---|---|---|---|
| Connectivity requirement | No continuous network path; only one eventual bridge needs internet | Needs GSM signaling and an active session | Needs the supported offline rail and compatible devices or channels | Usually assumes a later manual sync or upload path |
| Payload visibility | Relays see opaque ciphertext and limited metadata | Menu steps and session state are exposed to the automation layer | Depends on the rail, but routing is not the core problem | Often visible inside local app state until sync |
| Offline duration | Designed for multi-hop delay tolerant delivery | Short, fragile sessions can time out | Short to moderate offline use | Depends on how long the local store can buffer |
| Primary trust boundary | At the bridge node, after claim and decrypt | At the telecom session and app automation layer | At the official payment rail | At the eventual sync or reconciliation step |
| Failure mode | Tampering, duplicate upload, or stale packet rejection | Session timeout or automation failure | Device and rail compatibility limits | Merge conflicts and inconsistent local state |
| Best use case | Places with no reliable path to the network at all | Users who still have a GSM link but need a simpler flow | Mainstream offline taps or low-friction feature-phone access | Applications that can tolerate manual reconciliation |
The implementation also goes one step further on the ledger side. The Account entity uses optimistic locking, which means the system is prepared for parallel bridge uploads instead of pretending they will never happen. That is the right kind of paranoia for money.
The ledger still has to survive race conditions
This is where the prototype earns credibility. A toy mesh demo would stop at packet forwarding. This repo keeps going into settlement behavior, which is where concurrency bugs become financial bugs. Optimistic locking gives the account balance a versioned guardrail when two bridge nodes try to write at once.
The result is not a full production rail. It is something more useful for a repo explainer: a compact example of how you design for replay, duplication, and simultaneous ingestion without pretending the offline world will behave politely.
What this project is actually competing with
The cleanest comparison is not against a normal online payment app. It is against other ways to survive weak or missing connectivity. Some solutions lean on GSM and session automation. Others lean on official offline rails. This project leans on store-and-forward mesh delivery, which is a different answer to a different failure mode.
| Approach | Strength | Weakness | Where it fits |
|---|---|---|---|
| Mesh relay with bridge upload | Works even when no direct network path exists | Needs cryptography, dedupe, and a bridge later | Basements, remote sites, emergency connectivity |
| USSD automation | Uses the existing telecom signaling path | Short sessions and brittle flows | Users who still have GSM access |
| Official offline rails | Lowest friction for supported devices | Requires platform support and policy constraints | Mainstream consumer payments |
| Manual deferred sync | Simple to build | Easy to drift into inconsistent state | Internal tools and non-financial workflows |
That comparison is the real story. This repo is not trying to win every offline payments scenario. It is showing what you build when you cannot assume a stable network path, cannot trust relay nodes, and still need the eventual settlement to be consistent.
The prototype is simple. The idea is durable.
The stack is intentionally lightweight. Spring Boot, H2, JPA, and a server-rendered dashboard keep the demo easy to run. But the ideas underneath it are durable: authenticated encryption, ciphertext-based idempotency, freshness checks, and optimistic locking are not demo-only tricks.
That is why the project is interesting. It treats every untrusted hop as a fact of life, not a special case. The payment is not the packet, and the packet is not the trust boundary. The trust boundary is where the server can finally verify what the mesh only carried.