Python 3, a terminal, and the file tonaddr.py from the TON
introduction tutorial. If you do not have it, open
Introduction to TON and do
its step 1 — two minutes, one file. Check Python with
python3 --version; nothing else needs installing.
Comparison articles usually end in opinion. This one ends in numbers you generated: two address formats measured against the same corruption test, two transaction models implemented side by side, and the arithmetic behind “wait six confirmations” against “final immediately”. Nothing here connects to a network.
Why Compare?
Understanding how TON differs from other major blockchains helps you make informed decisions about which network to use for different purposes. Each blockchain makes different tradeoffs between speed, security, decentralization, and features.
Architecture Comparison
Speed and Throughput
- Bitcoin: ~7 TPS, 10-minute blocks, ~60-minute finality (6 confirmations)
- Ethereum: ~15-30 TPS (L1), 12-second blocks, ~12-15 minute finality. Layer 2s (Arbitrum, Optimism) add 2,000-4,000+ TPS
- Solana: ~4,000 TPS (actual, not theoretical), 400ms blocks, ~12-second finality
- TON: ~10,000+ TPS (current), 2-5 second blocks, under 6 seconds to finality. Theoretical limit: millions of TPS with full sharding
Many blockchains advertise theoretical maximums. What matters is sustained real-world throughput under load. TON's current ~10,000 TPS is actual on-chain capacity, with the sharding design allowing much more as demand grows.
Transaction Fees
- Bitcoin: $1-50+ depending on network congestion
- Ethereum (L1): $1-100+ for simple transfers, more for smart contract interactions. Gas spikes during high demand
- Ethereum (L2): $0.01-0.50 on Arbitrum/Optimism, under $0.01 on newer L2s
- Solana: $0.001-0.01 per transaction. Very cheap but priority fees can spike during congestion
- TON: $0.01-0.05 per transaction. Stable and predictable due to the sharding model distributing load
Smart Contract Model
- Bitcoin: Limited scripting (Bitcoin Script). Not Turing-complete. Designed for value transfer, not complex logic
- Ethereum: Solidity/Vyper on EVM. Synchronous execution — all contracts share global state. Atomic transactions (everything succeeds or everything reverts)
- Solana: Rust/C on Sealevel VM. Parallel execution but requires explicit account declarations. Programs are stateless; state lives in separate accounts
- TON: FunC/Tact on TVM. Asynchronous message-passing — contracts communicate like microservices. No atomic cross-contract calls. Each contract manages its own state
Security Model
- Bitcoin: Proof-of-Work. 15+ years of battle testing. The most secure blockchain by hash rate. 51% attack is economically impractical
- Ethereum: Proof-of-Stake since 2022. 900,000+ validators. Slashing for malicious behavior. Large validator set makes attacks expensive
- Solana: Proof-of-Stake with Proof-of-History ordering. ~1,900 validators. Has experienced multiple network outages under load (7+ in 2022-2023)
- TON: BFT Proof-of-Stake. ~300+ validators. High minimum stake (~300,000 TON). Fewer validators means faster consensus but higher concentration risk
TON's ~300 validators is significantly fewer than Ethereum's 900,000+. While the BFT model only needs honest majority (2/3), a smaller set means each validator has more power and the cost to corrupt the network is lower in absolute terms.
Ecosystem Maturity
- Bitcoin (2009): Most mature. Primarily used as a store of value. Limited smart contract ecosystem (Ordinals, Lightning Network)
- Ethereum (2015): Largest smart contract ecosystem. Thousands of dApps. Most tooling, auditors, and developer resources
- Solana (2020): Fast-growing ecosystem. Strong in DeFi and NFTs. Good developer tooling.
- TON (2021): Youngest ecosystem. Growing rapidly due to Telegram. Fewer dApps and auditors. Unique advantage: 900M+ potential users via Telegram
When to Use Which
- Long-term store of value: Bitcoin — simplest, most secure, most decentralized, longest track record
- Complex DeFi and smart contracts: Ethereum (or its L2s) — largest ecosystem, most auditors, most battle-tested contracts
- High-frequency trading and performance: Solana — fastest single-chain execution, lowest latency
- Payments and Telegram integration: TON — sub-second finality, lowest barrier to entry via Telegram, ideal for micropayments and social payments
Now Measure the Differences Yourself, in Five Steps
Networks are usually compared with adjectives. In the next twenty-five minutes you will compare four design decisions with measurements instead: how two address formats behave when the address is damaged, what a UTXO chain has to do that an account chain does not, what a confirmation is actually worth, and what happens to your fee when a block is full. Every figure below came from running these files.
Go: open a terminal in the folder holding tonaddr.py.
Do: save this as btcaddr.py. It prints nothing; it builds an
older-style address — a hash of the public key, a network byte, and a four-byte
checksum, written in base58.
import hashlib
ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
def b58(data: bytes) -> str:
n = int.from_bytes(data, "big")
out = ""
while n:
n, rem = divmod(n, 58)
out = ALPHABET[rem] + out
return "1" * (len(data) - len(data.lstrip(b"\x00"))) + out
def b58_decode(s: str) -> bytes:
n = 0
for ch in s:
n = n * 58 + ALPHABET.index(ch)
body = n.to_bytes((n.bit_length() + 7) // 8, "big")
return b"\x00" * (len(s) - len(s.lstrip("1"))) + body
def address(public_key: bytes) -> str:
h = hashlib.new("ripemd160", hashlib.sha256(public_key).digest()).digest()
payload = b"\x00" + h # 0x00 = the main Bitcoin network
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
return b58(payload + checksum)
def valid(addr: str) -> bool:
raw = b58_decode(addr)
payload, given = raw[:-4], raw[-4:]
return hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] == given
Now save this as both.py and run python3 both.py:
import hashlib
from btcaddr import address
from tonaddr import encode
pubkey = hashlib.sha256(b"one key, two networks").digest()
btc = address(pubkey)
ton = encode(pubkey)
print("the same public key, on two networks:")
print(f" bitcoin-style: {btc} ({len(btc)} characters)")
print(f" ton : {ton} ({len(ton)} characters)")
You should see: two addresses with the right shapes:
the same public key, on two networks:
bitcoin-style: 1Ej1P6CQRGsxbiCBfMDiLBeN4TsL5gzu4N (34 characters)
ton : EQD0jnavzuRcCT5EU143Qn5QpURR3ZassbNTPZdQ5VAo_6qK (48 characters)
Both results check themselves against something you already know: a Bitcoin address of
this type begins with 1 and runs to about 34 characters, and a TON address
begins with EQ and is 48. Neither convention was written into the code —
they fall out of the network byte and the flags byte.
If not: ValueError: unsupported hash type ripemd160 means
your Python was built against an OpenSSL that disables it; the rest of this page still works,
so skip to step 3. ModuleNotFoundError: No module named 'tonaddr' means the file
from the introduction tutorial is not in this folder.
Go: same folder. A checksum is a safety net, and nets have gauges.
Do: save this as corrupt.py and run it. It takes about a
minute.
import hashlib, random
from btcaddr import address, valid, ALPHABET
from tonaddr import encode, decode
pubkey = hashlib.sha256(b"one key, two networks").digest()
btc, ton = address(pubkey), encode(pubkey)
TON_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
def missed(good, alphabet, check, changes, trials=40_000, seed=5):
random.seed(seed)
slipped = 0
for _ in range(trials):
chars = list(good)
for _ in range(changes):
i = random.randrange(len(good) - 1)
chars[i] = random.choice([c for c in alphabet if c != good[i]])
try:
slipped += bool(check("".join(chars)))
except Exception:
pass
return slipped
for changes in (1, 3):
print(f"{changes} character(s) corrupted, 40,000 attempts each:")
print(f" bitcoin-style (4-byte checksum, 32 bits): {missed(btc, ALPHABET, valid, changes)} slipped through")
print(f" ton (2-byte checksum, 16 bits): "
f"{missed(ton, TON_ALPHABET, lambda a: decode(a)['checksum_ok'], changes)} slipped through")
You should see: both designs perfect against a single typo, and the margin showing only under heavier damage:
1 character(s) corrupted, 40,000 attempts each:
bitcoin-style (4-byte checksum, 32 bits): 0 slipped through
ton (2-byte checksum, 16 bits): 0 slipped through
3 character(s) corrupted, 40,000 attempts each:
bitcoin-style (4-byte checksum, 32 bits): 0 slipped through
ton (2-byte checksum, 16 bits): 2 slipped through
Two in forty thousand is a small number and a real one, and it is what a shorter checksum buys: a shorter address. Neither result should change how you behave, because both are defences against your fingers and neither is a defence against an attacker substituting a different valid address — which is the failure that actually costs people money.
If not: if the bitcoin-style rows raise an exception rather than printing,
ripemd160 is unavailable in your build (see step 1). If both rows read 0 at three
characters, your trial count is lower than 40,000 — the effect is rare by design.
Go: same folder. This is the difference people feel without being able to name it.
Do: save this as models.py and run it.
# UTXO model: you spend whole coins and get change back.
coins = [{"id": "c1", "value": 3}, {"id": "c2", "value": 5}, {"id": "c3", "value": 1}]
pay = 6
chosen, total = [], 0
for coin in sorted(coins, key=lambda c: -c["value"]):
chosen.append(coin)
total += coin["value"]
if total >= pay:
break
print("UTXO model")
print(f" you must pay {pay}; you hold coins of", [c["value"] for c in coins])
print(f" spent whole coins {[c['id'] for c in chosen]} worth {total}")
print(f" change returned to a NEW address of yours: {total - pay}")
print(f" coins left over: {[c['id'] for c in coins if c not in chosen]}")
print()
print("account model")
balances = {"you": 9, "shop": 0}
balances["you"] -= pay
balances["shop"] += pay
print(f" one subtraction, one addition: {balances}")
print(" no change, no coin selection, but every payment needs a sequence number")
You should see: two ways to move six coins:
UTXO model
you must pay 6; you hold coins of [3, 5, 1]
spent whole coins ['c2', 'c1'] worth 8
change returned to a NEW address of yours: 2
coins left over: ['c3']
account model
one subtraction, one addition: {'you': 3, 'shop': 6}
no change, no coin selection, but every payment needs a sequence number
The UTXO side explains several things at once: why a wallet’s fee depends on how
many small coins it must combine, why a change address appears in your history that you never
created, and why the clustering attack works — spending c1 and
c2 together proves one person holds both. The account side has none of that, and
pays for it with the sequence number, which is what stops a signed payment being replayed
forever.
If not: if the change comes out negative, the coin-selection loop is
breaking before it has enough — the break belongs after the running total
reaches the amount.
Go: same folder. “Wait for six confirmations” is repeated everywhere and justified almost nowhere.
Do: save this as finality.py and run it. The model is the
simple race: an attacker rebuilding the chain from behind must out-produce everyone else.
print("probabilistic finality: an attacker with some share of the mining power")
print("must out-run the honest chain to reverse a payment\n")
for share in (0.10, 0.25, 0.40):
row = []
for depth in (1, 3, 6):
chance = (share / (1 - share)) ** depth
row.append(f"{depth} block(s): {chance:>8.4%}")
print(f" attacker holds {share:>4.0%} of the power -> " + " ".join(row))
print()
print("BFT finality (TON, and other proof-of-stake designs):")
print(" a block signed by the required majority of validators is final immediately;")
print(" reversing it requires those validators to sign a conflicting block,")
print(" which is publicly provable and costs them their stake")
You should see: where the number six comes from:
probabilistic finality: an attacker with some share of the mining power
must out-run the honest chain to reverse a payment
attacker holds 10% of the power -> 1 block(s): 11.1111% 3 block(s): 0.1372% 6 block(s): 0.0002%
attacker holds 25% of the power -> 1 block(s): 33.3333% 3 block(s): 3.7037% 6 block(s): 0.1372%
attacker holds 40% of the power -> 1 block(s): 66.6667% 3 block(s): 29.6296% 6 block(s): 8.7791%
BFT finality (TON, and other proof-of-stake designs):
a block signed by the required majority of validators is final immediately;
reversing it requires those validators to sign a conflicting block,
which is publicly provable and costs them their stake
Six confirmations is not a magic number — it is where the risk becomes negligible against a modest attacker. Read the bottom row: against one holding 40% of the power, six confirmations still leaves nearly a 9% chance, which is why large transfers wait longer. The two designs make different promises: one gives you a probability that improves with patience, the other gives certainty conditional on validators not destroying their own stake.
If not: if the percentages exceed 100%, the attacker’s share was set above 0.5, where the formula no longer applies — a majority attacker succeeds eventually, at any depth.
Go: same folder. Fees are quoted as prices. They behave like bids.
Do: save this as fees.py and run it.
import random
random.seed(2)
BLOCK_CAPACITY = 20
waiting = [{"id": i, "fee": round(random.uniform(0.1, 20), 2)} for i in range(100)]
included = sorted(waiting, key=lambda t: -t["fee"])[:BLOCK_CAPACITY]
cheapest_in = min(t["fee"] for t in included)
print(f"{len(waiting)} transactions waiting, {BLOCK_CAPACITY} fit in the next block")
print(f"the cheapest fee that got in: {cheapest_in}")
print(f"transactions left waiting : {len(waiting) - BLOCK_CAPACITY}")
print()
for offer in (0.5, 15.0, 19.0):
print(f" you offer {offer:>5}: "
f"{'included' if offer >= cheapest_in else 'still waiting -- and the price rises with demand'}")
You should see: an auction, not a price list:
100 transactions waiting, 20 fit in the next block
the cheapest fee that got in: 17.62
transactions left waiting : 80
you offer 0.5: still waiting -- and the price rises with demand
you offer 15.0: still waiting -- and the price rises with demand
you offer 19.0: included
This is the mechanism behind every “fees are high today” complaint, and it is also the honest form of TON’s scalability claim: a network that can add shards raises capacity so the auction rarely binds, while a fixed-capacity chain has no answer to demand except price. The trade — asynchronous cross-shard transfers, which you can measure in the TON architecture tutorial — is what buys that headroom.
If not: if every offer is included, BLOCK_CAPACITY is at or
above 100, so nothing is competing. If the clearing fee differs from 17.62, the
random.seed(2) line is missing.
Without scrolling up: you are choosing a network for a shop that accepts payments of about fifty dollars, dozens of times a day. Which two of the five measurements matter most, and which one is nearly irrelevant? Answer: step 4 and step 5 matter most. Finality decides how long a customer stands at your counter before you can hand over the goods, and the fee auction decides whether a fifty-dollar payment costs you cents or a meaningful share of the sale when the network is busy. Step 2 is nearly irrelevant — both address formats catch typing mistakes reliably, and the real address risk is substitution rather than corruption, which no checksum addresses. Step 3 matters mainly for privacy and fee predictability rather than for the shop’s day-to-day operation.
Now do it without the page: change fees.py so capacity grows
with demand — say one block slot for every five waiting transactions — and watch
the clearing fee collapse. Then ask what that model leaves out. You will have found the real
argument between these designs: not whether capacity can scale, but what is given up to scale
it.
Summary
- TON is the fastest by throughput and finality, with unique multi-blockchain sharding
- Ethereum has the most mature ecosystem and security tooling
- Bitcoin is the most battle-tested and decentralized
- Solana offers the best single-chain performance
- TON's unique advantage is Telegram integration and mass-market accessibility
- TON's unique risk is fewer validators and a younger, less-audited ecosystem
- No blockchain is "best" — each optimizes for different tradeoffs
With this knowledge, you can evaluate which platform is best suited for your specific use case and risk tolerance.