Skip to content

About

Tamper-proof state machine & multi-step flow verification for Kotlin Multiplatform (Android, iOS, Desktop, Server). Prevents screen skipping, MITM tampering, and unauthorized state mutations in checkouts, KYC, and wizards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Maven Central Kotlin License

TrustChain

Tamper-Proof State Machine & Verifiable Multi-Step Flow Ledger for Kotlin Multiplatform (KMP).

Protect checkout flows, onboarding wizards, KYC funnels, and server-driven navigation from step skipping, parameter tampering, and replay attacks — across Android, iOS, Desktop, and Server.


The Problem: Why HTTPS & Backend Checks Are Not Enough

HTTPS encrypts traffic between client and server, but it does not protect your business logic from a compromised client. On rooted devices, modified APKs, or via simple deep-link manipulation, attackers can:

  1. Skip mandatory steps (e.g., jumping straight from Cart to Payment without selecting/paying for delivery).
  2. Tamper with intermediate state (e.g., modifying client-side discount fields or KYC verification flags).
  3. Replay stale screens or expired states out of order.

TrustChain solves this at the architectural level. It models your navigation and state transitions as a cryptographically verifiable chain (similar to a lightweight blockchain ledger). Each step is strictly tied to the cryptographic digest of the previous step, and all state mutations are digitally signed by the server. If a step is skipped or tampered with, the client-side state machine fails fast before any action is executed.


When Should You Use TrustChain?

  • E-Commerce & Checkouts: Ensure delivery calculation and coupon verification cannot be bypassed before payment authorization.
  • Fintech & Banking: Prevent out-of-order execution in multi-step transfers, limit increases, or loan applications.
  • KYC & Onboarding: Guarantee sequential document capture and liveness checks with an immutable audit trail.
  • Server-Driven UI (SDUI): Ensure screens sent by the backend are rendered strictly in the intended sequence.
  • Zero-Trust Client Architectures: Validate every transition locally without trusting client memory or intent extras.

Security Comparison

Security Concern Standard REST + HTTPS JWT Tokens TrustChain
Data In-Transit Encryption Yes (TLS) Yes (TLS) Yes (TLS)
Prevent Step Skipping (e.g. Deep Links) No (Requires complex backend logic) No Yes (Automatic Parent Hash Check)
Detect Tampered State Deltas No Partially Yes (Asymmetric Signature Verification)
Replay Attack Prevention Manual nonces required No Yes (Strict Hash Lineage)
Zero Runtime Dependencies N/A Often heavy Yes (Pure Kotlin Multiplatform)

Table of Contents


Architecture Overview

graph LR
    subgraph Server [SERVER]
        ServerLogic[Business Logic<br>Workflow Step] --> HashCalc[Compute State Hash<br>Link previousHash]
        ServerLogic --> Signer[Sign Delta Updates<br>Private Key]
        HashCalc --> ResponsePayload[Blocks / Deltas<br>JSON / ProtoBuf]
        Signer --> ResponsePayload
    end

    subgraph Client [CLIENT]
        Transport[Transport Layer] --> EventDispatcher[Chain Event<br>Push / Pop / Patch]
        EventDispatcher --> Reducer[TrustChain Reducer<br>Pure State Machine]

        subgraph Verification [Zero-Trust Engine]
            Reducer --> HashVerify{Parent Hash<br>Matches?}
            HashVerify -- Yes --> SigVerify{Signature<br>Valid?}
            SigVerify -- Yes --> Patcher[Block Patcher<br>Apply & Re-hash]
        end

        Patcher --> NewChain[New Immutable<br>TrustChain Stack]
    end

    ResponsePayload -->|Network Transport| Transport
    style Server fill: #e8daef, stroke: #6c3483, color: #1a1a1a
    style Client fill: #d6eaf8, stroke: #2471a3, color: #1a1a1a
    style Verification fill: #d5f5e3, stroke: #1e8449, color: #1a1a1a
    style ResponsePayload fill: #f9e79f, stroke: #b7950b, color: #1a1a1a
    style Transport fill: #aed6f1, stroke: #1f618d, color: #1a1a1a
    linkStyle default stroke: #333, stroke-width: 2px
    linkStyle 3 stroke: #c0392b, stroke-width: 3px
Loading

The server establishes the chain of authority by referencing the client's current hash in subsequent blocks and signing state mutations. The client maintains an immutable TrustChain stack that validates all hashes and cryptographic signatures before committing state changes.


Module Structure

Module Target Description
:library:core Common / KMP Pure functional state machine, interfaces, events, and validation logic without third-party runtime dependencies.
:example JVM / CLI Standalone runnable demonstration featuring a full multi-step checkout workflow and simulated MITM/skip-step attack.

Key Concepts

VerifiableBlock

Any step, screen, or transaction in your domain implements VerifiableBlock:

interface VerifiableBlock {
    val id: String
    val hash: String
    val previousHash: String?
}
  • hash — Deterministic digest of the block's content and state.
  • previousHash — Hash of the preceding block in the stack (null for the root/genesis block).

DeltaBlock (Signed State Mutations)

Instead of resending full screen models or state trees for minor updates, the server produces a DeltaBlock:

@Serializable
data class DeltaBlock<P>(
    val patches: P,
    val previousHash: String,
    val newHash: String,
    val signature: String = ""
)
  • patches — Arbitrary patch payload (e.g., Map<String, String>, JSON object, or typed delta).
  • previousHash — The exact block hash this patch must be applied to.
  • newHash — The expected resulting block hash after the patch is applied.
  • signature — Base64-encoded digital signature (e.g., RSA, Ed25519) of newHash.

ChainEvents & Reducer

Mutations to the state machine are dispatched as pure algebraic data types:

sealed interface ChainEvent<out B : VerifiableBlock, out P> {
    data class Push<B : VerifiableBlock>(val block: B) : ChainEvent<B, Nothing>
    data object Pop : ChainEvent<Nothing, Nothing>
    data class Replace<B : VerifiableBlock>(val block: B) : ChainEvent<B, Nothing>
    data class ApplyDeltas<P>(val blocks: List<DeltaBlock<P>>) : ChainEvent<Nothing, P>
}

The TrustChain container evaluates events immutably via reduce():

val result: Result<TrustChain<MyBlock, MyPatch>> = chain.reduce(ChainEvent.Push(nextBlock))

ChainActions & Errors

Every successful transition records its action type:

enum class ChainAction {
    PUSH, POP, REPLACE, PATCH, IDLE
}

When validation fails in strictMode, reduction halts immediately with an explicit typed error:

sealed class ChainError(message: String) : RuntimeException(message) {
    data class HashMismatch(val expected: String?, val actual: String?) : ChainError(...)
    data class SignatureVerificationFailed(val blockHash: String) : ChainError(...)
    class CannotPopEmptyChain : ChainError(...)
    data class UnsupportedDeltaOperation(val detail: String) : ChainError(...)
}

Signature Verification (SPI)

TrustChain delegates asymmetric verification to a pluggable interface:

fun interface SignatureVerifier {
    fun verify(payload: String, signatureBase64: String): Boolean
}

State Patching Strategy

When applying deltas, a BlockPatcher defines how the current block incorporates the patch:

fun interface BlockPatcher<B : P VerifiableBlock, in > {
    fun patch(block: B, patches: P, newHash: String): B
}

Server-Side Usage

Constructing Chained Steps

When advancing the user flow, the server references the user's active session hash:

fun proceedToShipping(sessionId: String, address: String): CheckoutStep {
    val currentHash = sessionStore.getHash(sessionId) ?: error("Session expired")
    val content = "prev=$currentHash;step=shipping;addr=$address"
    val newHash = Sha256.digest(content)

    sessionStore.updateHash(sessionId, newHash)

    return CheckoutStep(
        id = "step_shipping",
        title = "Shipping & Address",
        hash = newHash,
        previousHash = currentHash
    )
}

Generating Signed Delta Blocks

For partial updates (e.g., applying a promotional discount or updating live status):

fun applyPromo(sessionId: String, promoCode: String): DeltaBlock<Map<String, String>> {
    val currentHash = sessionStore.getHash(sessionId)
    val patches = mapOf("discount" to "10%", "total" to "$180")
    val newHash = Sha256.digest("$currentHash;promo=$promoCode")
    val signature = ServerKeyStore.sign(newHash) // RSA or Ed25519

    sessionStore.updateHash(sessionId, newHash)

    return DeltaBlock(
        patches = patches,
        previousHash = currentHash,
        newHash = newHash,
        signature = signature
    )
}

Client-Side Usage

Initializing TrustChain

Configure the client chain container with strict verification rules:

var chain = TrustChain<CheckoutStep, Map<String, String>>(
    strictMode = true,
    verifier = SignatureVerifier { payload, signatureBase64 ->
        // Verify with server's public key (RSA / Ed25519)
        CryptoEngine.verifySignature(publicKey, payload, signatureBase64)
    },
    patcher = { currentStep, patches, newHash ->
        currentStep.copy(
            state = currentStep.state + patches,
            hash = newHash
        )
    }
)

Applying Transitions & Patches

// 1. Push next screen
chain = chain.reduce(ChainEvent.Push(step1)).getOrThrow()

// 2. Apply signed deltas received from WebSocket / HTTP
val delta = receiveDeltaFromServer()
chain = chain.reduce(ChainEvent.ApplyDeltas(listOf(delta))).getOrThrow()

// 3. Pop back
if (chain.canPop) {
    chain = chain.reduce(ChainEvent.Pop).getOrThrow()
}

Handling Verification Failures

Atomic reductions ensure that failures do not corrupt the active state stack:

chain.reduce(ChainEvent.Push(untrustedBlock)).fold(
    onSuccess = { updatedChain ->
        chain = updatedChain
    },
    onFailure = { error ->
        when (error) {
            is ChainError.HashMismatch -> {
                logger.error("Tamper Alert: Hash link broken! Expected ${error.expected}, got ${error.actual}")
            }
            is ChainError.SignatureVerificationFailed -> {
                logger.error("Security Alert: Server signature invalid for hash ${error.blockHash}")
            }
            else -> logger.error("Chain error: ${error.message}")
        }
    }
)

Real-World Example (Checkout Flow)

A complete executable workflow simulation is included in the :example module:

example/
└── src/main/kotlin/io/github/numq/trustchain/example/
    ├── CheckoutStep.kt      # VerifiableBlock domain implementation
    ├── CheckoutServer.kt    # In-memory server tracking session hashes
    ├── SimpleCrypto.kt      # Digest & signature verification logic
    └── Application.kt       # CLI runner demonstrating valid & MITM attack flows

Running the Example

Run directly via Gradle:

./gradlew :example:run

Output:

======================================================
          TrustChain Zero-Trust Flow Demo             
======================================================

[+] 1. Cart loaded:
    Step: Shopping Cart | Hash: h_c2c1a28598b8ad69

[+] 2. Shipping confirmed:
    Step: Shipping & Address | Hash: h_50bda3ba68cced0d | Prev: h_c2c1a28598b8ad69

[+] 3. Signed promo patch applied:
    Discount: 10% | New Total: 1890$
    New Hash: h_6471c5f0c53a644b

[+] 4. Payment reached:
    Step: Payment Authorization | Hash: h_6a6fd155e392f7ef

Legitimate flow completed. Stack size: 3

------------------------------------------------------
  Simulating Attack: Attacker tries to skip shipping  
------------------------------------------------------
Attacker has Cart loaded.
Attacker attempts to directly apply Payment step (bypassing Shipping)...
[!] BLOCKED BY TRUSTCHAIN: State machine integrity preserved.
    Error: Hash mismatch: expected 'h_c2c1a28598b8ad69', actual 'h_6471c5f0c53a644b'

Getting Started

Add the dependency to your Kotlin Multiplatform build.gradle.kts:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("io.github.numq.trustchain:core:1.0.0")
        }
    }
}

Minimal Example

data class Step(
    override val id: String,
    override val hash: String,
    override val previousHash: String? = null
) : VerifiableBlock

// Setup chain
val root = Step(id = "1", hash = "root_hash", previousHash = null)
val next = Step(id = "2", hash = "step_2_hash", previousHash = "root_hash")

var chain = TrustChain<Step, Unit>(strictMode = true)
chain = chain.reduce(ChainEvent.Push(root)).getOrThrow()
chain = chain.reduce(ChainEvent.Push(next)).getOrThrow()

println("Current active step: ${chain.current?.id}") // Prints: 2

License

Apache-2.0 License - see LICENSE file for details.


Support QR code
Support Development: numq.github.io/support


Support QR code
Support Development: numq.github.io/support

About

Tamper-proof state machine & multi-step flow verification for Kotlin Multiplatform (Android, iOS, Desktop, Server). Prevents screen skipping, MITM tampering, and unauthorized state mutations in checkouts, KYC, and wizards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages