Cross-chain köprüler ağlar arasında doğrudan kurulan gizli bir tünel değil, iki izole akıllı sözleşme ve bir off-chain röleden (relay node) oluşan sıradan bir finansal aracıdır. Blokzincirler birbirleriyle doğrudan haberleşmeyi bilmezler: Ethereum, Solana’nın içinde ne dönüp bittiğinden habersizdir; Bitcoin ise Arbitrum’un varlığından bile bihaberdir.
Bir kullanıcı Ethereum ağından Arbitrum’a 10 ETH aktardığında, köprü aslında bu token’ları fiziken bir yerden bir yere taşımaz. Kaynak ağdaki akıllı sözleşmede orijinal 10 ETH’yi kilitler (Lock) ve eş zamanlı olarak hedef ağda 10 sentetik "wrapped" (wETH) muadilini basar (Mint).

Cross-chain mimarisinin en kritik zafiyeti ara katmanda, yani doğrulama (proof verification) işlemini üstlenen relayer ve validator'larda saklıdır. Bu katman, hedef ağdaki sözleşmeye token’ların dürüstçe kilitlendiğini kanıtladığı anda sözleşme arkasına bakmadan yeni varlıkları basmaya başlar. Bu kanıtın sahtelenmesi ise likidite havuzlarının saniyeler içinde sıfırlanmasına yol açar.
Hack'ler Neden Yaşanıyor: Kanıt Sahteciliği ve Anahtar Sızıntıları
Köprülerdeki kritik zafiyetlerin çoğu temel olarak iki ana kategoride toplanır: kriptografik/mantıksal doğrulama hataları ve off-chain altyapının ele geçirilmesi (multisig özel anahtarlarının sızdırılması).
Haziran 2026’da Syscoin Bridge, kanıt doğrulama mantığındaki mimari bir çöküş nedeniyle 10 milyon dolarlık bir darbe yedi. Köprü, EVM tarafında (NEVM) varlık basmadan önce Syscoin’in UTXO tarafındaki token yakma/kilitleme işlemlerini doğrulamak adına SPV (Simplified Payment Verification) mekanizmasını kullanıyordu.
Saldırı kriptografinin kırılmasıyla değil, röle parser’ındaki saçma bir hatadan kaynaklandı. Saldırgan, bozuk bayt yapısına sahip özel bir veri paketi hazırladı. Girdinin bayt dizisi uzunluğu ve formatı sıkı bir şekilde doğrulanmadığı için parser, sahte bir hash’i gerçek bir yakma işleminin geçerli SPV kanıtı olarak kabul etti. Köprü, NEVM üzerinde tek bir coin dahi kilitlenmeden UTXO zincirinde 5 milyar SYS token basılmasına onay verdi.
İkinci temel zafiyet vektörü ise imzanın yeniden kullanılmasıdır (Signature Reuse). Temmuz 2026’da Cardano ve BNB Chain arasındaki Wanchain Bridge bu yüzden yaklaşık 13 milyon dolar kaybetti. Validator mantığı, daha önce kullanılmış geçerli kriptografik imzaları sözleşmenin global storage’ında `spent` (kullanıldı) olarak işaretlemiyordu. Saldırgan, önceden gerçekleşmiş meşru bir işlemi alıp alıcı adresini değiştirerek tekrar çağırdı. Sözleşme validator imzasını kontrol etti — imza önceden üretildiği için matematiksel olarak doğruydu — ve fonları hiç sorgulamadan tekrar serbest bıraktı.
2026 Köprü Hack Günlüğü
Yalnızca 2026’nın ilk birkaç ayında, köprülerdeki ve cross-chain altyapısındaki açıklar nedeniyle DeFi ekosisteminden yüz milyonlarca dolar buharlaştı.
| Proje / Köprü | Tarih | Zarar Miktarı | Saldırı Vektörü / Teknik Neden |
|---|---|---|---|
| Aethir Bridge (OFT Adapter) | Nisan 2026 | ~$5.0M | BNB Chain ve Tron arasında ATH token transferi sırasında cross-chain mesajlaşma mantık hatası |
| Syscoin Bridge | Haziran 2026 | ~$10.0M | SPV kanıtı parse etme hatası. Karşılıksız 5 milyar SYS basımı |
| Wanchain Bridge | Temmuz 2026 | ~$13.0M | Validator tarafında imza tekrarı zafiyeti (Signature-reuse flaw) |
| AFX Trade Bridge | Temmuz 2026 | $24.15M | Arbitrum üzerindeki köprü validator’larının özel anahtarlarının sızdırılması |
| Verus Ethereum Bridge | Temmuz 2026 | $7.54M | Rezerv kontrolünün olmaması (teminat doğrulanmadan çekim çağrısına izin verilmesi) |
Geliştiricilerin buradaki hataları sistematik bir hal almış durumda. En büyük etken, amansız TVL (Total Value Locked) yarışı ve rakip projelerden önce piyasaya ürün çıkarma hırsı. Geliştiriciler durum makinesi denetimlerini (state machine audit) es geçiyor; karmaşık kontrolleri doğrulanabilir ZK şemalarına (Zero-Knowledge Proofs) taşımak yerine Solidity veya Go üzerinde hantal veritabanı/parse mantıkları yazmaya çalışıyorlar.
Güvenli Bir Köprünün Prodüksiyon Seviye Kodu (Solidity 0.8.24)
Aşağıda kaynak ağ için hazırlanan `SecureBridgeVault` sözleşme örneğini görebilirsiniz. İçerisinde Replay Attack koruması, relayer imzalarını doğrulamak için güvenli EIP-712 mekanizması, Reentrancy Guard ve sıkı nonce durum kaydı (logging) yer almaktadır.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
/**
* @title SecureBridgeVault v3.0
* @author EXMON Engineering Team (https://exmon.pro)
* @dev Lead Architect & Security Audit: EXMON Core Team
* @notice Production-grade cross-chain bridge vault contract.
*/
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/**
* @title SecureBridgeVault v3.0
* @author EXMON Engineering Team
* @notice Tekil depositHash kimliğine, EnumerableSet ile katı M-of-N relayer kontrolüne
* ve konfigürasyon hatalarına karşı korumaya sahip prodüksiyon kasa sözleşmesi.
*/
contract SecureBridgeVault is EIP712, ReentrancyGuard, AccessControl, Pausable {
using SafeERC20 for IERC20;
using ECDSA for bytes32;
using EnumerableSet for EnumerableSet.AddressSet;
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
bytes32 public constant EMERGENCY_ADMIN_ROLE = keccak256("EMERGENCY_ADMIN_ROLE");
// lock ile yapısal olarak özdeş olan unlock imzası için tekil Typehash
bytes32 private constant UNLOCK_TYPEHASH = keccak256(
"Unlock(bytes32 depositHash,address token,address sender,address recipient,uint256 amount,uint256 sourceChainId,uint256 targetChainId,uint256 depositId)"
);
// Sözleşme depolama alanındaki relayer kayıt defteri
EnumerableSet.AddressSet private _relayers;
uint256 public requiredSignatures;
uint256 public totalDepositCount;
mapping(uint256 => bool) public supportedChains;
mapping(address => bool) public supportedTokens;
mapping(bytes32 => bool) public executedHashes;
mapping(bytes32 => bool) public knownDeposits; // Yerel olarak oluşturulan yatırma işlemlerinin takibi
event Locked(
bytes32 indexed depositHash,
uint256 indexed depositId,
address indexed token,
address sender,
address recipient,
uint256 amount,
uint256 targetChainId
);
event Unlocked(
bytes32 indexed depositHash,
address indexed token,
address recipient,
uint256 amount,
uint256 sourceChainId
);
event RelayerAdded(address indexed relayer);
event RelayerRemoved(address indexed relayer);
event ChainStatusUpdated(uint256 indexed chainId, bool supported);
event TokenStatusUpdated(address indexed token, bool supported);
event RequiredSignaturesUpdated(uint256 newThreshold);
error ZeroAddress();
error ZeroAmount();
error UnsupportedChain();
error UnsupportedToken();
error TransactionAlreadyExecuted();
error InvalidSignatureThreshold();
error InvalidSignaturesLength();
error DuplicateOrUnsortedSignature();
error InvalidSigner();
error RelayerAlreadyExists();
error RelayerDoesNotExist();
constructor(
address admin,
address emergencyAdmin,
uint256 _requiredSignatures,
address[] memory initialRelayers
) EIP712("EXMON_Bridge_Vault", "3.0.0") {
if (admin == address(0) || emergencyAdmin == address(0)) revert ZeroAddress();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(EMERGENCY_ADMIN_ROLE, emergencyAdmin);
uint256 relayerLength = initialRelayers.length;
for (uint256 i = 0; i < relayerLength; ) {
address relayer = initialRelayers[i];
if (relayer == address(0)) revert ZeroAddress();
if (_relayers.add(relayer)) {
_grantRole(RELAYER_ROLE, relayer);
emit RelayerAdded(relayer);
}
unchecked { ++i; }
}
if (_requiredSignatures == 0 || _requiredSignatures > _relayers.length()) {
revert InvalidSignatureThreshold();
}
requiredSignatures = _requiredSignatures;
}
// --- RELAYER VE YÖNETİŞİM YÖNETİMİ ---
function addRelayer(address relayer) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (relayer == address(0)) revert ZeroAddress();
if (!_relayers.add(relayer)) revert RelayerAlreadyExists();
_grantRole(RELAYER_ROLE, relayer);
emit RelayerAdded(relayer);
}
function removeRelayer(address relayer) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (!_relayers.remove(relayer)) revert RelayerDoesNotExist();
if (_relayers.length() < requiredSignatures) revert InvalidSignatureThreshold();
_revokeRole(RELAYER_ROLE, relayer);
emit RelayerRemoved(relayer);
}
function setRequiredSignatures(uint256 _required) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (_required == 0 || _required > _relayers.length()) {
revert InvalidSignatureThreshold();
}
requiredSignatures = _required;
emit RequiredSignaturesUpdated(_required);
}
function getRelayers() external view returns (address[] memory) {
return _relayers.values();
}
function getRelayerCount() external view returns (uint256) {
return _relayers.length();
}
// --- İDARİ AĞ VE TOKEN AYARLARI ---
function setChainSupport(uint256 chainId, bool supported) external onlyRole(DEFAULT_ADMIN_ROLE) {
supportedChains[chainId] = supported;
emit ChainStatusUpdated(chainId, supported);
}
function setTokenSupport(address token, bool supported) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (token == address(0)) revert ZeroAddress();
supportedTokens[token] = supported;
emit TokenStatusUpdated(token, supported);
}
function pause() external onlyRole(EMERGENCY_ADMIN_ROLE) {
_pause();
}
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
function emergencyWithdraw(
address token,
address to,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) whenPaused {
if (to == address(0)) revert ZeroAddress();
IERC20(token).safeTransfer(to, amount);
}
// --- KİLİTLEME VE KİLİT AÇMA MANTIĞI ---
/**
* @notice Deterministik depositHash üretimi ve kilitleme kaydı
*/
function lock(
address token,
uint256 amount,
address recipient,
uint256 targetChainId
) external nonReentrant whenNotPaused returns (bytes32 depositHash) {
if (amount == 0) revert ZeroAmount();
if (recipient == address(0)) revert ZeroAddress();
if (!supportedTokens[token]) revert UnsupportedToken();
if (!supportedChains[targetChainId]) revert UnsupportedChain();
uint256 depositId;
unchecked {
depositId = ++totalDepositCount;
}
// abi.encode ile tekil depositHash'in güvenli hesaplanması
depositHash = keccak256(
abi.encode(
block.chainid,
targetChainId,
token,
msg.sender,
recipient,
amount,
depositId
)
);
knownDeposits[depositHash] = true;
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
emit Locked(depositHash, depositId, token, msg.sender, recipient, amount, targetChainId);
}
/**
* @notice M-of-N imza doğrulaması ile tekil depositHash üzerinden fon çekimi
* @dev İmzalar ZORUNLU OLARAK imzalayan adreslerin küçükten büyüğe sıralamasına göre Off-chain sıralanmalıdır.
*/
function unlock(
bytes32 depositHash,
address token,
address sender,
address recipient,
uint256 amount,
uint256 sourceChainId,
uint256 depositId,
bytes[] calldata signatures
) external nonReentrant whenNotPaused {
if (signatures.length != requiredSignatures) revert InvalidSignaturesLength();
if (amount == 0) revert ZeroAmount();
if (recipient == address(0) || sender == address(0)) revert ZeroAddress();
if (!supportedTokens[token]) revert UnsupportedToken();
if (!supportedChains[sourceChainId]) revert UnsupportedChain();
// Parametre hash'inin beyan edilen depositHash ile eşleşip eşleşmediğinin kontrolü
bytes32 expectedDepositHash = keccak256(
abi.encode(
sourceChainId,
block.chainid,
token,
sender,
recipient,
amount,
depositId
)
);
if (expectedDepositHash != depositHash) revert InvalidSigner();
bytes32 structHash = keccak256(
abi.encode(
UNLOCK_TYPEHASH,
depositHash,
token,
sender,
recipient,
amount,
sourceChainId,
block.chainid,
depositId
)
);
bytes32 txHash = _hashTypedDataV4(structHash);
if (executedHashes[txHash]) revert TransactionAlreadyExecuted();
// Adres sıralaması ve imza eşiği kontrolü
address lastSigner = address(0);
for (uint256 i = 0; i < requiredSignatures; ) {
address signer = txHash.recover(signatures[i]);
if (!_relayers.contains(signer)) revert InvalidSigner();
if (signer <= lastSigner) revert DuplicateOrUnsortedSignature();
lastSigner = signer;
unchecked { ++i; }
}
executedHashes[txHash] = true;
IERC20(token).safeTransfer(recipient, amount);
emit Unlocked(depositHash, token, recipient, amount, sourceChainId);
}
}Köprüler, iki tamamen farklı sistemin konsensüsünü zincir dışı kırılgan bir kod katmanıyla bağlamaya çalıştıkları için Web3 altyapısındaki en zayıf halka olmaya devam ediyor. Bayt formatı doğrulamasındaki ufacık bir hata, EIP-712 yapı kontrolündeki bir gözden kaçırma ya da validator sunucularından basit bir anahtar sızıntısı, yüz milyonlarca dolarlık akıllı sözleşmeyi bir anda hacker'lar için açık bir büfeye dönüştürebilir.