Skip to content

RT01010011/Ghost-Protocol-V1

Repository files navigation

Ghost Protocol

Anonymous Dual-Key Payment Protocol — Built on Base Mainnet

Contract Verified Network Solidity License Status Slither


Overview

Ghost Protocol is a privacy-first ERC-20 payment protocol that enables secure, consent-based token transfers between a public wallet (Hash1) and a fully private off-chain wallet (Hash2) — without exposing the private address on-chain in V1 (partial anonymity: sig2 visible in calldata, to = Hash2 visible on Basescan), fully solved in V2 via keccak256 key2Proof + stealth addresses.

  • V1 = The vault — triple-validation secured (Pseudo2 + Key2 + Key1), anti-spam Guardian System, mathematically inviolable escrow
  • V2 = The bank — full Hash2 anonymization via keccak256 key2Proof + stealth addresses, simplified UX

Built and deployed solo by Rayane Hila — RayTech R&D (Belgium)


Contract — Mainnet Deployment

Field Value
Contract Address 0x747bb06a40F3f895F7d7BB8764C9B23a4bC11929
Network Base Mainnet — Chain ID: 8453
Deploy Block 41974756
Compiler Solidity v0.8.20+commit.a1b79de6
Optimization 200 runs — Paris EVM
Verification ✓ Exact Match — 15 files — Standard Json-Input
Static Analysis Slither — 0 High / 0 Medium
Author (NatSpec) @author Rayane Hila - RayTech R&D

Architecture — Dual Identity System

┌─────────────────────────────────────────────────────────────┐
│                     GHOST PROTOCOL V1                       │
│                                                             │
│  SENDER ──── sendToPseudo1() ────► ESCROW (contract)        │
│                                        │                    │
│                                   Guardian System           │
│                              senderBlocked[h1][s][t]=true   │
│                                        │                    │
│  Hash1 ◄──── approveReceive() ─── Pseudo2 + sig1 Key1       │
│  (public)    rejectReceive()      (line 282 + line 292)     │
│                   ▲                                         │
│                   │                                         │
│  Hash2 ────── sendFromHash1() ─── Pseudo2 + sig2 Key2       │
│  (private)    Triple Validation       + sig1 Key1           │
│               L.475 / L.484 / L.496   │                     │
│                                       ▼                     │
│                                 RECIPIENT                   │
└─────────────────────────────────────────────────────────────┘

Component Table

Component Role Visibility
Pseudo1 Public username (e.g. alice) — 1-32 bytes On-chain — public
Hash1 Public wallet address — pays gas On-chain — public by design
Hash2 Cold private wallet — signs only, never on-chain Off-chain — recoverable via ecrecover in V1 / unreachable in V2
Key1 ECDSA sig1 — required for approve / reject / send Off-chain — signature required
Key2 (V1) ECDSA sig2 — required for send only Off-chain — address recoverable via ecrecover (V1 limitation)
key2Secret (V2) keccak256 proof — replaces sig2 Off-chain — Hash2 unreachable (V2 fix)
Pseudo2 Shared secret — required for approve / reject / send (NOT for receiving) Off-chain — keccak256(Pseudo2) stored on-chain only
Escrow Smart contract vault On-chain — hash1TokenBalances[hash1][token] private mapping

Core Flows

Flow 1 — Receiving

External wallet → sendToPseudo1('alice', token, amount)
  — CEI Pattern: Effects before Interactions —
  → senderBlocked[hash1][msg.sender][token] = true      (Guardian — line 231)
  → pendingByFingerprint[hash2Fingerprint].push(...)    (state — line 235)
  → safeTransferFrom(msg.sender, address(this), amount) (interaction LAST — line 246)
  → emit TransferPending(hash2Fingerprint, from, token, amount, index, timestamp)

  Notes:
  - Hash2 NEVER emitted — only fingerprint = keccak256(Hash2)
  - TIMEOUT = 7 days (line 81) — transfer expires if not approved/rejected
  - Pseudo2 NOT required at this stage

Flow 2 — Approval / Rejection (Key1 + Pseudo2)

Hash1 → approveReceive(hash1, transferIndex, pseudo2Hash, sig1)
  → require(acc.pseudo2Hash == pseudo2Hash)             (line 282)
  → message = keccak256("approve" + hash1 + index + nonce + address(this) + chainId)
  → verifySignature(message, sig1, acc.key1Hash)        (line 292)
  → nonces[hash1]++                                     (line 297)
  → require(!transfer.processed)                        (line 311)
  → require(block.timestamp <= transfer.timestamp + TIMEOUT) (line 312)
  → transfer.approved = true; transfer.processed = true (lines 317-318)
  → hash1TokenBalances[hash1][token] += amount          (line 325)
  → senderBlocked[hash1][from][token] = false           (Guardian reset — line 328)

Hash1 → rejectReceive(hash1, transferIndex, pseudo2Hash, sig1)
  → Same validations with message = keccak256("reject" + ...)
  → nonces[hash1]++                                     (line 366)
  → transfer.processed = true                           (line 386 — CEI)
  → senderBlocked reset                                 (line 389)
  → safeTransfer(transfer.from, transfer.amount)        (line 396 — funds returned)

Anyone → autoRejectExpired(hash1, transferIndex)
  → No signature required — callable by anyone
  → require(block.timestamp > transfer.timestamp + TIMEOUT) (line 421)
  → transfer.processed = true                           (line 426 — CEI)
  → senderBlocked reset                                 (line 429)
  → safeTransfer(transfer.from, transfer.amount)        (line 436 — funds returned)

Flow 3 — Sending — Triple Validation (Pseudo2 + Key2 + Key1)

Hash2 + Hash1 → sendFromHash1(hash1, to, token, amount, deadline, pseudo2Hash, sig2, sig1)
  → require(block.timestamp <= deadline)                (line 465)
  → (1) require(acc.pseudo2Hash == pseudo2Hash)         (line 475)
  → (2) message2 = keccak256("send" + hash1 + to + token + amount + nonce + deadline + address(this) + chainId)
        verifySignature(message2, sig2, acc.key2Hash)   (lines 484-491)
  → (3) message1 = keccak256("send_unlock" + same params)
        verifySignature(message1, sig1, acc.key1Hash)   (lines 496-503)
  → nonces[hash1] = currentNonce + 1                   (line 506 — CEI)
  → require(hash1TokenBalances[hash1][token] >= amount) (line 516)
  → hash1TokenBalances[hash1][token] -= amount          (line 520 — CEI)
  → safeTransfer(to, amount)                            (line 523)
  → emit SentFromHash1(hash1, to, token, amount)
    Hash2 NEVER emitted

Security Architecture

Protection Implementation Coverage
ReentrancyGuard nonReentrant on 5 state functions: sendToPseudo1 (L.214), approveReceive (L.276), rejectReceive (L.350), autoRejectExpired (L.407), sendFromHash1 (L.463) Reentrancy attacks
CEI Pattern Effects before Interactions on all state changes — senderBlocked before safeTransferFrom, transfer.processed before safeTransfer Flash loan / reentrancy
Anti-Replay ×3 nonce + address(this) + block.chainid in every state-changing message (approve, reject, send, send_unlock) — address(this) + block.chainid in view message (get_pending, no nonce needed) Cross-contract / cross-chain replay
Deadline require(block.timestamp ≤ deadline) in sendFromHash1 only (line 465) Delayed transaction attacks
TIMEOUT TIMEOUT = 7 days (line 81) — transfers expire in approveReceive (L.312) and rejectReceive (L.381) Locked-funds attacks
SafeERC20 safeTransferFrom (L.246) + safeTransfer (L.396, L.436, L.523) Non-standard tokens (USDT etc.)
Guardian System senderBlocked[hash1][sender][token] — reset on approve (L.328), reject (L.389), autoRejectExpired (L.429) Spam / flood attacks
Triple Validation Pseudo2 (L.475) + sig2 ECDSA Key2 (L.484) + sig1 ECDSA Key1 (L.496) — all 3 required simultaneously Single-key or single-secret compromise insufficient
Anti Gas DoS maxReturn = 100 in getPendingTransfers — count unprocessed → allocate exact → fill Unbounded loop attacks
EIP-712 DOMAIN_SEPARATOR computed in constructor (line 95) — PROTOCOL_NAME + PROTOCOL_VERSION + chainId + address(this) Signature domain isolation

Interface — Electron App (V1)

Single-file Electron interface (index.html) using ethers.js with Ledger USB hardware wallet support.

4 Tabs

Tab 1 — Configuration

  • Hash1, Hash2, Pseudo2, contract address, RPC URL
  • Config persisted in localStorage
  • Connects via JsonRpcProvider to Base mainnet (https://mainnet.base.org)
  • Computes myFingerprint = keccak256(solidityPacked(['address'], [hash2])) for event filtering
  • Escrow balance check via getHash1TokenBalance

Tab 2 — Create Account (createAccount)

  • Pseudo1 input (1–32 bytes)
  • Key1: private key or Ledger USB
  • Key2: private key, address only (cold wallet mode), or Ledger USB
  • Real-time derivation preview (Hash1, Hash2, pseudo2Hash, key1Hash, key2Hash, hash2Fingerprint)
  • Hash derivations matching contract exactly:
    key1Hash         = keccak256(solidityPacked(['address'], [hash1]))
    key2Hash         = keccak256(solidityPacked(['address'], [hash2]))
    hash2Fingerprint = keccak256(solidityPacked(['address'], [hash2]))
    pseudo2Hash      = keccak256(toUtf8Bytes(pseudo2))
  • Pre-flight checks: pseudo1 availability, hash1 uniqueness, ETH balance
  • Config auto-saved on success — private keys cleared from form immediately

Tab 3 — Incoming Transfers (approve / reject)

  • Real-time listener: contract.on('TransferPending') filtered by myFingerprint
  • Past event scan: chunks of 5000 blocks from deploy block 41974756
  • Manual scan button for force-searching past pending transfers
  • Auto-switches to Transfers tab on incoming event
  • Approve: approveReceive(hash1, index, pseudo2Hash, sig1) — Key1 + Pseudo2
  • Reject: rejectReceive(hash1, index, pseudo2Hash, sig1) — Key1 + Pseudo2
  • Key1: private key or Ledger USB

Tab 4 — Send from Escrow (sendFromHash1)

  • Key1 + Key2 + Pseudo2 required simultaneously
  • Escrow balance check before any signing
  • Deadline: Math.floor(Date.now() / 1000) + 3600 (1 hour window)
  • Signing order matching contract exactly:
    // sig2 — Key2 signs (lines 484-485):
    keccak256("send" + hash1 + to + token + amount + nonce + deadline + contract + chainId)
    
    // sig1 — Key1 signs (lines 496-497):
    keccak256("send_unlock" + hash1 + to + token + amount + nonce + deadline + contract + chainId)
  • Key1: private key or Ledger USB
  • Key2: private key or Ledger USB
  • Private keys cleared from form after transaction

V1 — Live on Mainnet

Ghost Protocol V1 is fully deployed, verified, and operational on Base mainnet. This is not a testnet prototype — it is a production smart contract with real transactions.

All transactions are publicly verifiable on Basescan: https://basescan.org/address/0x747bb06a40F3f895F7d7BB8764C9B23a4bC11929


V2 Roadmap — 7 Smart Contract Modifications

Change V1 (current) V2 (to build) Impact
Key2 proof — sendFromHash1 sig2 ECDSA → ecrecover → Hash2 visible key2Proof = keccak256(key2Secret + params) Hash2 100% invisible
Recipient address to = Hash2 direct → visible Basescan to = stealth address (ephemeral per tx) No address linkability
key2Hash storage keccak256(Key2_wallet_address) — L.666 keccak256(key2Secret + "GhostProtocol-v2" + contract + chainId) Domain salt — no cross-chain reuse
Key2 proof — getPendingTransfers ECDSA sig → ecrecover → Hash2 exposed on read key2Proof keccak256 — ecrecover impossible Full read privacy
getHash1TokenBalance Public — anyone can read balances require(msg.sender == hash1) Financial surveillance blocked
createAccount() No msg.sender restriction require(msg.sender == hash1) Access control enforced in V2
pendingByFingerprint Array grows unbounded Processed entries purged Gas optimization

Repository Structure

ghost-protocol/
│
├── README.md
├── LICENSE
├── SECURITY.md          # Responsible disclosure policy + known V1 limitations
│
└── contracts/
    └── GhostProtocol.sol    # V1 — deployed & verified on Base mainnet

Interface status — V1 operational, not yet publicly distributed. The V1 Electron interface is functional and used for internal testing. It requires manual configuration (RPC URL, contract address, private key or Ledger USB) and targets technical users only at this stage.

The interface repository is private during this phase — the app handles private key input and Ledger USB integration directly, and publishing the source would enable visually identical phishing clones before a stable public release.

A retail-ready V2 interface is planned alongside the V2 smart contract.


License

Business Source License 1.1 — see LICENSE

  • Non-commercial use (personal, research, educational, open-source) → free, no permission needed
  • Commercial use → written permission requiredrayane@ia-optimisation.org
  • On March 7, 2030 → automatically converts to GPL-3.0 (all forks must remain open source)

Author

Rayane Hila — RayTech R&D — Independent R&D Lab — Belgium


Ghost Protocol V1 — March 2026 — Base Mainnet — Chain ID: 8453

About

Privacy-preserving ERC20 transfer protocol with dual-key authentication. Deployed on Base Mainnet.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors