This is a part of my Bachelor's thesis. Nguyen Huu Thuan, University of Science - VNUHCM (2026).
- tss-node
- tss-orchestrator (current)
- tss-node-p2p
-
Verifiability: Any falsified/corrupted information a node receives from the Orchestrator server or other peer nodes can be independently verified. However, this comes at the cost of not guaranteeing the availability of the service if the Orchestrator server intentionally delays or provides falsified/corrupted information.
-
Byzantine fault tolerance: The network is fault-tolerant up to a threshold of
$t$ such that$n/2 < t \leq n$ . In the most extreme case where an attacker simultaneously controls both the Orchestrator server and$t - 1$ nodes, the system must still ensure cryptographic safety, meaning it is impossible to generate a valid signature pair for any state that is fraudulent or affects the state of the remaining$t$ honest nodes. -
Commitment of the signature: A transaction once signed must be atomic and committed. Even when
$\geq t$ nodes go offline simultaneously, any transaction that has been signed and confirmed in the past can still be unilaterally settled on-chain by anyone without causing a conflict or exposing a vulnerability that would freeze latest valid state by any kind of attacks.
-
Optimizing input throughput: Instead of signing directly on each individual transaction, the system signs indirectly on the cumulative state of the entire fund. This strategy allows an unlimited number of micro-transactions to be aggregated off-chain into a single settlement message, maximizing throughput and reducing transaction fees to near-zero.
-
Optimizing output throughput: Instead of performing direct transfers at each settlement, the Smart Contract adopts a proactive pull-model.
-
Optimizing data structure and blockchain costs: Instead of having Smart Contract to update the cumulative state for each user at each settlement, the system uses a Sparse Merkle Tree approach. This strategy allows the Smart Contract to perform only a fixed
$O(1)$ complexity of Merkle Root reassignment (settlement) and proof verification (fund withdrawal). Additionally, the Merkle root is optimized to just a fixed-size32-bytesvalue by precomputing the values of empty branches for all$2^{256}$ states. -
Optimizing consensus process and native multi-chain support: Instead of having on-chain threshold consensus, the system moves the consensus process to an off-chain network by utilizing mathematical properties to constrain the ability to generate threshold signatures. Additionally, the off-chain consensus process naturally enables native multi-chain support.
-
Optimizing cryptographic security: Instead of using only a single Feldman Verifiable Secret Sharing phase for the Distributed Key Generation, the system adds Pedersen Verifiable Secret Sharing to address the waiting attack vulnerability and employs Threshold Signature Scheme to ensure that the fund's private key exists only mathematically but is never reconstructed anywhere in memory.
-
Optimizing network complexity: Instead of using a fully distributed architecture with communication complexity of
$O(n^2)$ and$O(t^2)$ , the system employs a central Orchestrator server to aggregate messages, reducing complexity to linear$O(n)$ and$O(t)$ without compromising cryptographic security. -
Optimizing transaction finality: Instead of having a lock-time to handle the latest state dispute, the system relies on cumulative state and one-way transaction properties to eliminate the possibility of forking or state dispute.
This repository contains the implementation of the off-chain matchmaker
The Orchestrator acts as a trustless, verifiable router and matchmaker for the participant nodes
The Orchestrator coordinates a secp256k1 curve. It centralizes peer-to-peer message transmission to simplify routing.
-
Initialization (
POST /dkg/initialize)Initiates the process by calling
POST /dkg/broadcaston all nodes. It collects their Paillier public keys, vectors of Pedersen Commitments$C_{i,k} = f_{i,k}G + g_{i,k}H$ , and the encrypted secret shares$(s_{i,j}, t_{i,j})$ . -
Encrypted segment routing
The Orchestrator routes the encrypted shares to their respective target nodes
$j$ via a single batch request (POST /dkg/receive), along with the sender's validation commitments. Each node$i$ is responsible for decrypting and verifying all$n - 1$ secret and noise shares intended for it by the corresponding Pedersen Commitments. Then returns the Feldman Commitments$A_{i,k} = a_{i,k}G$ . -
Master key reconstruction
Calls
POST /dkg/compute-public-keyon all nodes. It distributes all Feldman Commitments to all nodes. By performing Elliptic Curve point addition on the primary coefficients ($A_{i,0}$ ), the nodes reconstruct and verify the uncompressed Global Public Key:$Y = \sum_{i=1}^n A_{i,0}$
The Orchestrator coordinates the 4-phase interactive GG18 signing pipeline for a selected subset
-
Asynchronous Proposal Pooling (
POST /tss/propose)Nodes autonomously submit state update proposals containing the transaction payload (
chainId,userId,amountHex) and a deterministic message digest:$$\text{messageHash} = \text{sha256}(\text{chainId} \parallel \text{newNonce} \parallel \text{newRoot})$$ The Orchestrator groups these proposals bymessageHashin a database polling map, handling potential node overrides. -
Threshold Triggering & Execution
Once the voting subset reaches the threshold
$t$ , the Orchestrator locks the execution state for thatmessageHashand clears the proposal. It discards subsequent duplicate votes and starts an isolated TSS signing session for the matched$t$ -node subset. -
Phase Coordination & Mathematical Extraction
-
Phase 1: Start
Sends concurrent requests to
/tss/startacross the subset. It accumulates the public ephemeral curve points$\Gamma_i = \gamma_i G$ and the homomorphically encrypted random scalars$E_i(k_i)$ and$E_i(w_i)$ . -
Phase 2: Multiplicative-to-Additive
For every node
$i$ , it bundles the Paillier ciphertexts of all other subset members$j \neq i$ and routes them to node$i$ 's/tss/mtaendpoint. It intercepts the returning ciphertext matrix containing homomorphically blinded shares ($\alpha, \nu$ ). -
Phase 3: Delta & Sigma
Routes the blinded shares (
$\alpha_{ji}, \nu_{ji}$ ) back to the recipient node$i$ via/tss/deltaand collects the local decrypted scalar values$\delta_i$ . The Orchestrator computes the global scalar$\delta = \sum_{i \in S} \delta_i \pmod q$ and performs an EC scalar multiplication to resolve the global signature point:$$R = \delta^{-1} \sum_{i \in S} \Gamma_i = \left( \sum_{i \in S} k_i \right)^{-1} G$$ The$x$ -coordinate of point$R$ is extracted modulo$q$ to derive the ECDSA$r$ value. -
Phase 4: Signature Aggregation
Dispatches the scalar
$r$ to/tss/signfor all nodes in the subset, collects the local signature components$s_i = m \cdot k_i + r \cdot \sigma_i \pmod q$ , and computes the global signature scalar$s = \sum_{i \in S} s_i \pmod q$ .
-
The Orchestrator maintains the off-chain balance state using a fixed-depth (
-
Binary Operations
To ensure gas-efficient on-chain verification, the SMT algorithm uses raw binary block operations instead of string-based hex conversions. Leaves are generated by padding the 20-byte
userIdand 32-bytebalanceinto a unified 52-byte buffer. Intermediate parent hashes are resolved using standard binary buffer concatenation:$$\text{parentHash} = \text{keccak256}(\text{Buffer.concat}([\text{leftBuf}, \text{rightBuf}]))$$ -
Parallel Multi-Chain State Isolation
The system supports multi-chain isolation. Database persistence uses atomic sub-document updates rather than document-wide overwrites to prevent race conditions during concurrent multi-chain traffic.
-
Ledger Commitments
Upon receiving a successful threshold signature from the TSS session, the Orchestrator updates the user's mapping in the
Funddocument. It mutates the SMT, increments thenoncecounter, and binds the${r, s}$ signature to the current state root. -
Disbursement Proof Generation
Generates an
$O(\log n)$ path vector containing 256 structural sibling hashes. This inclusion proof is packaged with the cumulative token balance, allowing users to submit an on-chainclaim()execution autonomously.
Represents the cumulative executed state for specific blockchain networks.
-
chainId: The globally unique blockchain identifier (e.g.,mainnet_1). -
nonce: Sequential integer counter tracking successful settlement occurrences on this chain. -
root: 64-character hex string representing the absolute root of the Sparse Merkle Tree. -
signature: The active${ r, s }$ ECDSA threshold signature backing the current root. -
balances: Granular mapping ofuserId(40-char hex) to cumulative token balances in hexadecimal.
Ephemeral storage acting as a concurrent voting mempool for incoming multi-node proposals.
-
messageHash: The cryptographic identifier of the proposed state. -
chainId: The target blockchain for this proposal. -
payload: The transaction details{ chainId, userId, amount }. -
proposers: An array ofnodeIds that have submitted an identical proposal for thismessageHash. When its size reaches$t$ , a TSS session is triggered.
-
Spared Merkle Tree
- Basic Operations
-
should return default root for empty tree(13 ms) -
should change root after update(4 ms) -
should produce different roots for different values(11 ms) -
should produce same root for same insertions regardless of order(10 ms)
-
- Proof Generation & Verification
-
should generate proof of length 256(4 ms) -
should verify valid inclusion proof(3 ms) -
should reject proof with wrong value(6 ms) -
should reject proof with wrong root(4 ms) -
should handle multiple entries and prove each correctly(8 ms)
-
- Basic Operations
-
Orchestrator FundService (Matchmaker Architecture)
-
Phase 1.1: getLatestState returns empty and valid fallback state for a new chain(12 ms) -
Phase 1.2: getSettlementData returns null gracefully if chain does not exist(1 ms) -
Phase 2.1: commitTransaction processes successful TSS Match for User A(6 ms) -
Phase 2.2: getSettlementData formats EVM-ready signature correctly(2 ms) -
Phase 3.1: commitTransaction processes sequential TSS Match for a NEW User B(7 ms) -
Phase 3.2: getMerkleProof returns valid proofs for all active users(13 ms) -
Phase 4.1: getMerkleProof handles unregistered user without throwing errors(3 ms)
-
These are end-to-end benchmark results ran locally with their corresponding numbers of ports and Mongo DBs.
Macbook M1 Pro 2021, 16GB RAM, 10-core CPU.
This is the result when running benchmark on @SnowyField1906/tss-node (proposed architecture).
This is the result when running benchmark on @SnowyField1906/tss-node-p2p (alternative architecture).
HOST=127.0.0.1
PORT=4000
MONGO_URI=mongodb://127.0.0.1:27017/orchestrator
SIZE=3
THRESHOLD=2
NODE_1_URL=http://127.0.0.1:3001
NODE_2_URL=http://127.0.0.1:3002
NODE_3_URL=http://127.0.0.1:3003
yarnyarn test # smt.spec.ts fund.spec.tsyarn start:dev


