Chainhunters
Bitcoin wordt vaak gezien als een anonieme valuta, maar in werkelijkheid is het pseudoniem. Dit betekent dat, hoewel er geen namen aan Bitcoin-adressen gekoppeld zijn, transacties wél volledig transparant zijn. Dit maakt het mogelijk om UTXO's aan elkaar te koppelen en patronen te ontdekken. Dit proces heet transaction linking of clustering.
Als een transactie meerdere inputs gebruikt (meerdere UTXO's als bron), betekent dit dat al deze UTXO's hoogstwaarschijnlijk bij dezelfde eigenaar horen. Dit komt doordat elke Bitcoin-transactie cryptografisch ondertekend moet worden met een private key die bij het juiste adres hoort. Als een transactie meerdere inputs gebruikt, betekent dit dat één wallet toegang heeft tot alle gebruikte private keys.
Voorbeeld:
Wanneer een transactie wordt uitgevoerd, krijgt de ontvanger meestal precies het bedrag dat bedoeld is. De afzender krijgt vaak wisselgeld terug naar een nieuw gegenereerd adres.
Voorbeeld:
Detectietechnieken:
bc1q...
)Als een wisselgeldadres in een latere transactie samen met een ander adres wordt gebruikt als input, dan betekent dit dat die adressen waarschijnlijk dezelfde eigenaar hebben.
Voorbeeld:
bc1qdef...456
wordt later gebruikt in een nieuwe transactie, samen met een ander adres dat eerder al werd herkend als van BobOnderzoekers kunnen aanvullende patronen ontdekken door:
Bitcoin gebruikt ECDSA (Elliptic Curve Digital Signature Algorithm) voor handtekeningen. Een handtekening in Bitcoin werkt als volgt:
Bereken een hash van de transactie (message hash, "𝑚")
Gebruik de private key (𝑘) om de transactie te ondertekenen
De node en miners controleren of de handtekening correct is
Het enige scenario waarin meerdere mensen hun inputs in één transactie kunnen gebruiken, is als er sprake is van coöperatieve samenwerking. Dit gebeurt normaal gesproken niet behalve in CoinJoin-transacties.
Redenen:
Private keys zitten in verschillende wallets
Softwarematige beperkingen
CoinJoin is een uitzondering, maar herkenbaar
Bitcoin's overstap naar Taproot (BIP-340) en Schnorr-handtekeningen heeft gevolgen voor tracking:
Maar: De meeste mensen gebruiken deze technieken niet actief, waardoor de meeste UTXO's nog steeds gemakkelijk te koppelen zijn.
Hieronder vind je het uitgebreide script met extra uitleg, inclusief een toelichting waarom het cryptografisch onmogelijk is om dit systeem te 'foppen'. Hierdoor kunnen we in het UTXO-model van Bitcoin met zekerheid inputs aan dezelfde persoon koppelen, omdat:
Hieronder volgt het complete Python-script met zowel de geldige scenario's als voorbeelden van pogingen tot vervalsing, zodat je kunt zien dat elke afwijking (andere private key of gewijzigde data) leidt tot een ongeldig resultaat:
import hashlib from ecdsa import SigningKey, SECP256k1, BadSignatureError, util def double_sha256(data: bytes) -> bytes: """ Bereken de double SHA-256 hash (SHA256(SHA256(data))). Dit komt overeen met de manier waarop Bitcoin transacties worden gehasht. Eerst wordt de SHA-256 hash van de data berekend, daarna wordt daar nogmaals SHA-256 op toegepast. """ return hashlib.sha256(hashlib.sha256(data).digest()).digest() def main(): # Voorbeeld van een ruwe Bitcoin-transactie (in hexadecimaal) # Dit is een vereenvoudigd voorbeeld waarin de structuur van een Bitcoin-transactie wordt nagebootst. tx_data = bytes.fromhex( "0100000001" + # Version "c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704" + # Previous TX hash "00000000" + # Output index "6b" + # Script length (107 bytes) "483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5e" + # ScriptSig "ffffffff" + # Sequence "02" + # Aantal outputs "00ca9a3b00000000" + # Amount (10 BTC) "19" + # Script length (25 bytes) "76a91496b538e853519e726a6727282ef6904ba7fa1d9388ac" + # ScriptPubKey "20a10700000000" + # Amount (0.5 BTC) "19" + # Script length (25 bytes) "76a9146d23156cbbdcc82a5a47eee4c2c7c583c18b6bf488ac" + # ScriptPubKey "00000000" # Locktime ) # ----------------------------------------- # Stap 1: Bereken de message hash m # ----------------------------------------- # Bitcoin gebruikt een double SHA-256 hash op de geserialiseerde transactiegegevens. m = double_sha256(tx_data) print("Message hash (m):", m.hex()) # ----------------------------------------- # Stap 2: Genereer een private key en onderteken de message hash # ----------------------------------------- # We genereren een willekeurige private key op de secp256k1 curve (dezelfde curve als Bitcoin) sk = SigningKey.generate(curve=SECP256k1) vk = sk.get_verifying_key() # Verkrijg de bijbehorende public key # Haal de private key op als een integer (dit is de sleutel d) d = sk.privkey.secret_multiplier print("Private key (d):", hex(d)) # Onderteken de message hash met expliciete DER-encoding. # Hierbij wordt intern een willekeurige nonce (k) gekozen en worden de handtekeningcomponenten (r, s) berekend. signature_der = sk.sign_digest(m, sigencode=util.sigencode_der) # Decodeer de DER-gecodeerde handtekening naar de twee componenten (r, s) order = SECP256k1.order r, s = util.sigdecode_der(signature_der, order) print("Handtekening componenten:") print("r =", hex(r)) print("s =", hex(s)) # ----------------------------------------- # Stap 3: Verifieer de handtekening met de public key # ----------------------------------------- try: vk.verify_digest(signature_der, m, sigdecode=util.sigdecode_der) print("De handtekening is geldig!") except BadSignatureError: print("Ongeldige handtekening!") # ---------------------------------------------------------- # Scenario 1: Iemand anders probeert de transactie te ondertekenen # ---------------------------------------------------------- # Een "attacker" genereert een eigen private key en ondertekent dezelfde message hash. attacker_sk = SigningKey.generate(curve=SECP256k1) attacker_vk = attacker_sk.get_verifying_key() # De attacker ondertekent de message hash. attacker_signature_der = attacker_sk.sign_digest(m, sigencode=util.sigencode_der) # We proberen nu de handtekening van de attacker te verifiëren met de originele public key (vk). try: vk.verify_digest(attacker_signature_der, m, sigdecode=util.sigdecode_der) print("Fout: Een handtekening van iemand anders werd toch geaccepteerd!") except BadSignatureError: print("Correct: De handtekening van een ander (attacker) is ongeldig!") # ---------------------------------------------------------- # Scenario 2: De transactiegegevens worden gewijzigd na ondertekening # ---------------------------------------------------------- # Dit simuleert een situatie waarin iemand de transactie manipuleert nadat deze is ondertekend. # We wijzigen hier de transactie (bijv. door de laatste byte te flippen) en berekenen een nieuwe hash. modified_tx_data = tx_data[:-1] + bytes([tx_data[-1] ^ 0xff]) modified_m = double_sha256(modified_tx_data) # De originele handtekening hoort niet meer bij de gewijzigde transactie. try: vk.verify_digest(signature_der, modified_m, sigdecode=util.sigdecode_der) print("Fout: De handtekening werd geaccepteerd voor gewijzigde transactiegegevens!") except BadSignatureError: print("Correct: De handtekening is ongeldig voor gewijzigde transactiegegevens!") # ---------------------------------------------------------- # Belangrijk inzicht: # ---------------------------------------------------------- # Omdat de handtekening (r, s) cryptografisch is gebonden aan zowel de transactiegegevens als de private key, # is het onmogelijk voor iemand anders om deze te vervalsen. Dit betekent: # - Alleen de eigenaar van de juiste private key kan een geldige handtekening genereren voor een gegeven transactie. # - Als dezelfde private key wordt gebruikt voor meerdere inputs (UTXO's), kunnen deze aan elkaar worden gekoppeld. # Dit is een cruciaal kenmerk van het UTXO-model in Bitcoin: inputs kunnen aan dezelfde persoon worden gekoppeld, # omdat de geldige ondertekening aantoont dat de eigenaar de controle heeft over die UTXO's. # # Hierdoor zorgt de cryptografie ervoor dat je nooit een handtekening kunt forceren of vervalsen zonder de juiste private key, # waardoor de integriteit en traceerbaarheid van transacties gegarandeerd is. if __name__ == "__main__": main()
Cryptografische Zekerheid: Door de sterke eigenschappen van ECDSA in combinatie met de dubbele SHA-256 hashing is het praktisch onmogelijk om een geldige handtekening te forgeren zonder in het bezit te zijn van de juiste private key. Dit betekent dat als iemand anders probeert de transactie te ondertekenen of als de gegevens worden aangepast, de handtekening ongeldig wordt verklaard.
Linken van Inputs in het UTXO-model: Omdat elke UTXO alleen kan worden uitgegeven met een geldige handtekening van de eigenaar (die blijkt uit de public key), kunnen alle inputs die met dezelfde private key worden ondertekend, aan dezelfde persoon worden gekoppeld. Dit is essentieel voor de veiligheid en traceerbaarheid in Bitcoin: alle transacties die ondertekend worden met dezelfde sleutel, wijzen erop dat dezelfde entiteit de controle heeft over de betreffende fondsen.
Kortom, de combinatie van ECDSA en het UTXO-model zorgt ervoor dat het cryptografisch onmogelijk is om dit systeem te misleiden. Dit biedt niet alleen beveiliging tegen vervalsingen, maar maakt het ook mogelijk om transacties te linken aan dezelfde persoon, omdat de ondertekening een uniek bewijs is dat de eigenaar van de private key de transactie heeft bekrachtigd.