Press enter or click to view image in full size
By Mohamadisha Shaikh (HackerMD)| Security Researcher | Bug Bounty Hunter
TL;DR: I discovered a critical signature replay vulnerability in a blockchain bridge SDK that allowed the same cryptographic signature to validate across multiple network checksum formats. This led to a $1,000 bounty reward after 90 days of persistence, rejections, and technical research.
Bug bounty hunting in blockchain security is not easy. Unlike traditional web vulnerabilities (XSS, SQLi, CSRF), blockchain security requires understanding cryptographic primitives, consensus mechanisms, and cross-chain protocols.
This is the story of how I found my first blockchain bug bounty — a signature replay vulnerability in a production Bridge SDK — and what I learned along the way.
When I started this research, I specifically focused on blockchain bridge SDKs because:
The target was a blockchain bridge SDK — software that allows users to move assets between two different blockchain networks (Bitcoin ↔ EVM chain).
Key files I focused on:
src/
├── blockchain/blockchain.ts ← Wallet connections
├── client/httpClient.ts ← HTTP/RPC communication
├── utils/validation.ts ← Input validation logic
└── initialization.ts ← SDK initializationBefore finding bugs, I spent 2–3 days just understanding how the SDK works:
User (Bitcoin) → Bridge SDK → Smart Contract → User (EVM Chain)
└── Signs transaction └── Verifies signatureThe SDK:
Inside src/utils/validation.ts, I found the isValidSignature() function — the core of all signature verification in the SDK.
// Simplified version of what I found
export function isValidSignature(
address: string,
message: string,
signature: string,
isHex: boolean = true
): boolean {
// Recovers address from signature
// Then compares with provided address
// BUG: No network-specific validation!
}While reading the official test file (validation.test.ts), I noticed something interesting on lines 330-344:
test('return true if the signature is valid regardless
of the address checksum', () => { const signature = 'b00dcad964ab97d965ac...1b'
const address = '0x26f40996671e622A0a6408B24C0e678D93a9eFEA'
const quoteHash = '767aa241ab418dfca0d...'
const rskChecksumAddress = rskChecksum(address, 31) // Testnet
const ethChecksumAddress = ethers.utils.getAddress(address)
// ALL of these pass ✓
expect(isValidSignature(
address.toLowerCase(), quoteHash, signature)
).toBe(true)
expect(isValidSignature(
rskChecksumAddress, quoteHash, signature)
).toBe(true)
expect(isValidSignature(
ethChecksumAddress, quoteHash, signature)
).toBe(true)
})
My initial thought: “Wait… same signature works for ALL checksum formats? That means Testnet and Mainnet signatures are interchangeable!”
To understand the vulnerability, you need to understand EIP-1191 (RSK’s checksum standard):
0x26f40996671e622A0a6408B24C0e678D93a9eFEA0x26f40996671E622A0a6408B24C0e678D93a9eFEA0x26F40996671e622A0a6408b24c0E678d93A9EfEASame address, different capitalization patterns. The checksum encodes the network in the letter casing.
The bug: isValidSignature() was normalizing addresses to lowercase BEFORE comparison, effectively stripping the network information from the validation process.
git clone https://github.com/[target]/bridges-core-sdk
cd bridges-core-sdk
npm install
npm run build
# Output: ./lib/index.js// real_exploit.js
const { isValidSignature, rskChecksum } = require('./lib/index.js');
const ethers = require('ethers');// Real signature from official test suite
const signature = 'b00dcad964ab97d965ac473fc8bb8ceb21ce13608cdc44d7b65e9d2d2443d0535a094ec449a18ef1f1a5d91cbee5302cfa9b99556a7de3414c190a1d3e811a5b1b';
const quoteHash = '767aa241ab418dfca0d418fef395d85c398a4c70a6ac4ea81429cf18ef4d6038';
const address = '0x26f40996671e622A0a6408B24C0e678D93a9eFEA';
// Test 1: Lowercase (no network info)
const result1 = isValidSignature(address.toLowerCase(), quoteHash, signature);
// Test 2: RSK Mainnet checksum (chainId 30)
const rskMain = rskChecksum(address, 30);
const result2 = isValidSignature(rskMain, quoteHash, signature);
// Test 3: RSK Testnet checksum (chainId 31)
const rskTest = rskChecksum(address, 31);
const result3 = isValidSignature(rskTest, quoteHash, signature);
// Test 4: Ethereum checksum
const ethCheck = ethers.utils.getAddress(address);
const result4 = isValidSignature(ethCheck, quoteHash, signature);
console.log(`Lowercase: ${result1}`); // true ✓
console.log(`RSK Mainnet: ${result2}`); // true ✓
console.log(`RSK Testnet: ${result3}`); // true ✓
console.log(`ETH Checksum: ${result4}`); // true ✓
node real_exploit.js# Output:
Lowercase: true ✓
RSK Mainnet: true ✓
RSK Testnet: true ✓
ETH Checksum: true ✓
[!] VULNERABILITY CONFIRMED!
The vulnerability was real and reproducible!
After researching top bug bounty reports, I structured mine as:
Step 1: User signs transaction on TESTNET
Uses RSK Testnet checksum address
Signature: 0xb00dcad964...Step 2: Attacker captures signature
Source: Network monitoring, transaction history
Step 3: Attacker replays on MAINNET
Uses lowercase address (strips network info)
Same signature → VALIDATES ✓
Step 4: Unintended transaction executes
No additional user signature required
"The issues you pointed out are based on best practices
but they do not affect directly the security of the platform"My reaction: Disappointed but not defeated.
Join Medium for free to get updates from this writer.
My analysis of rejection:
"Regarding the first one, we are running some extra
validations. We will reopen this issue."What changed?
Key lesson: Don’t give up after first rejection!
90 days after submission:
Status: TRIAGED ✅
Severity: Critical → Low (downgraded)
Bounty: $1,000 AWARDED 💰Reason for downgrade:
"Deployment addresses are not the same in testnet
and mainnet, so there is no practical attack
under those circumstances"
They downgraded from Critical to Low because:
My takeaway: The vulnerability is real, but practical exploitation requires additional conditions. Always consider deployment context when assessing impact!
1. Understand EIP Standards
EIP-55 → Ethereum address checksum
EIP-191 → Signed data standard
EIP-155 → Replay attack protection (chain ID)
EIP-1191 → RSK network checksum2. Chain ID Matters
Always check if signatures include chain ID binding:
// BAD - No chain binding
const sig = await signer.signMessage(hash)// GOOD - Chain bound (EIP-155)
const sig = await signer.signTransaction({ chainId: 30, ...txData })
3. Cross-Chain Security is Hard
Bridge protocols are among the most attacked in DeFi:
1. One Bug Per Report
Programs explicitly request this. Multiple bugs = multiple reports = more bounties!
2. PoC Must Be Production-Like
Programs reject theoretical vulnerabilities. Show real impact with working code.
3. Rejection ≠ Invalid
First rejection is often from junior triagers. Politely provide more evidence.
4. Patience is Key
My timeline: 90 days for $1,000. Worth every day!
5. Communicate Professionally
✅ "Could you please provide an update on the validation status?"
❌ "Why are you ignoring my report???"For the SDK developers reading this:
// Current (Vulnerable):
function isValidSignature(address: string, message: string, sig: string): boolean {
const recovered = ethers.utils.recoverAddress(hash, sig)
return recovered.toLowerCase() === address.toLowerCase() // ← Problem here
}
// Fixed (Secure):
function isValidSignature(
address: string,
message: string,
sig: string,
chainId: number // ← Add chain ID
): boolean {
// Validate address checksum matches expected network
const expectedChecksum = rskChecksum(address, chainId) const recovered = ethers.utils.recoverAddress(hash, sig)
// Compare with network-specific checksum (not lowercase)
return rskChecksum(recovered, chainId) === expectedChecksum
}
Additional recommendations:
Press enter or click to view image in full size
Time Spent on Research: ~2 weeks
Days to First Response: 12 days
Days to Triage: 72 days
Days to Bounty: 90 days
Severity: Low (downgraded from Critical)
Bounty Received: $1,000 USD
Lines of Code Reviewed: ~500 lines
Files Analyzed: 8 core filesFinding my first blockchain bug bounty taught me more than any course or tutorial could. The combination of:
…led to a real security finding and a real bounty.
If you’re starting out in blockchain security:
The best bugs are found by those who look deepest. 🔍
I’m a security researcher and bug bounty hunter from India, specializing in:
Thanks for reading! If this helped you, give it a clap 👏 and follow for more security research writeups.
#BlockchainSecurity #BugBounty #EthicalHacking #Web3Security #CryptographicVulnerability #SecurityResearch #DeFiSecurity #PenTesting