C

Chainhunters

10 min
Bitcoin
Bitcoin
utxo
crypto
Hoe koppel je UTXOs (Bitcoin adressen) aan een persoon?
Unspent Transaction Outputs (UTXOs) zijn de basis van Bitcoin. In dit artikel lees je hoe je UTXOs kunt gebruiken om Bitcoin adressen aan een persoon te koppelen.

Hoe kun je UTXO's aan elkaar koppelen?

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.

Methoden om UTXO's te koppelen

1. Multi-input 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:

  • Bob wil 1.2 BTC betalen
  • Zijn wallet kiest twee UTXO's: 0.7 BTC en 0.5 BTC
  • Dit betekent dat deze twee UTXO's bij Bob's wallet horen input.png
  • Bob’s wallet ondertekent beide inputs.
  • Dit betekent dat hij de private keys bezit van beide adressen.
  • Als meerdere inputs in één transactie verschijnen, zijn ze vrijwel altijd van dezelfde eigenaar.

2. Change Address Detection (wisselgeldadressen)

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:

  • Bob heeft een UTXO van 0.5 BTC
  • Hij wil 0.3 BTC naar Alice sturen
  • De transactie ziet er zo uit:
    • 0.3 BTC → Alice
    • 0.1999 BTC → Bob's nieuwe wisselgeldadres
    • 0.0001 BTC → Miners (fee)

Detectietechnieken:

  • Het wisselgeldadres is vaak een nieuw / schoon adres
  • Adresvorming kan patronen vertonen (bijv. alle nieuwe adressen starten met bc1q...)
  • De ontvanger krijgt meestal een afgerond bedrag
  • Meestal is het wisselgeldadres het onderste adres in de transactie

3. Co-spend Detection

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:

  • Bob's wisselgeldadres bc1qdef...456 wordt later gebruikt in een nieuwe transactie, samen met een ander adres dat eerder al werd herkend als van Bob
  • Dit betekent dat de eigenaar dezelfde private keyset heeft en dus de adressen waarschijnlijk tot één persoon behoren

4. Heuristieken en patroonherkenning

Onderzoekers kunnen aanvullende patronen ontdekken door:

  • Time analysis: Kijken hoe snel een output wordt uitgegeven (hot wallets vs. cold wallets)
  • Address reuse: Hergebruik van adressen bij bijvoorbeeld donaties
  • Transaction graph analysis: Visuele weergave van geldstromen

Cryptografische basis

Bitcoin gebruikt ECDSA (Elliptic Curve Digital Signature Algorithm) voor handtekeningen. Een handtekening in Bitcoin werkt als volgt:

  1. Bereken een hash van de transactie (message hash, "𝑚")

    • Dit is een SHA-256(SHA-256(transaction data)), ook wel double SHA-256 hash genoemd
    • De handtekening garandeert dat de transactie niet veranderd kan worden zonder de geldige sleutel
  2. Gebruik de private key (𝑘) om de transactie te ondertekenen

    • De handtekening bestaat uit twee waarden, (𝑟,𝑠):
      • 𝑟 = (𝑔ᵏ mod p) mod 𝑛 (random elliptische curve punt)
      • 𝑠 = k⁻¹ * (m + r𝑑) mod 𝑛 (waarbij 𝑑 de private key is)
    • Dit betekent dat elke handtekening is gebonden aan de private key van het adres
  3. De node en miners controleren of de handtekening correct is

    • Ze gebruiken de publieke sleutel om te verifiëren of de handtekening geldig is voor de input-UTXO
  • Om een transactie met meerdere inputs te ondertekenen, moet de afzender de private keys van alle gebruikte adressen bezitten
  • Dit betekent dat alle inputs uit dezelfde wallet of dezelfde eigenaar komen

Waarom kan dit niet van meerdere eigenaren zijn?

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:

  1. Private keys zitten in verschillende wallets

    • Een Bitcoin-wallet kan alleen UTXO's ondertekenen waarvoor hij de private keys bezit
    • Een gebruiker kan niet zomaar een UTXO van een ander uitgeven zonder toegang tot zijn private key
  2. Softwarematige beperkingen

    • Wallets ondersteunen geen "gedeelde transacties" tenzij het via een multisig-contract of CoinJoin-methode gaat
  3. CoinJoin is een uitzondering, maar herkenbaar

    • CoinJoin-transacties gebruiken speciale technieken zodat inputs van verschillende eigenaren worden gemengd
    • Onderzoekers kunnen dit detecteren door naar outputpatronen te kijken

Impact van nieuwe technologie

Bitcoin's overstap naar Taproot (BIP-340) en Schnorr-handtekeningen heeft gevolgen voor tracking:

  • Minder zichtbare multi-input transacties door gecombineerde handtekeningen
  • Betere privacy voor multisig-transacties
  • Adressen zijn te herkennen als bc1p...

Maar: De meeste mensen gebruiken deze technieken niet actief, waardoor de meeste UTXO's nog steeds gemakkelijk te koppelen zijn.

Zelf proberen in Python

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:

  • Unieke ondertekening: Elke transactie-input (UTXO) moet worden ondertekend met de private key van de eigenaar. De ECDSA-handtekening (bestaande uit de waarden (r, s)) is cryptografisch gekoppeld aan zowel de transactiegegevens als de specifieke private key.
  • Onvervalsbaarheid: Het is door de sterke wiskundige eigenschappen van de elliptische curve en de gebruikte hashing (double SHA-256) praktisch onmogelijk om een handtekening te forgeren zonder de juiste private key. Zelfs als iemand de transactiegegevens zou manipuleren, dan verandert de hash en wordt de handtekening ongeldig.
  • Koppeling van inputs: Omdat iedere input in een transactie (UTXO) slechts kan worden uitgegeven als deze correct is ondertekend door de eigenaar, kunnen alle transacties waarbij dezelfde public key (afgeleid van de private key) wordt gebruikt, aan elkaar worden gekoppeld. Dit betekent dat als één persoon meerdere UTXO’s beheert, deze via hun handtekeningen als behorend tot dezelfde entiteit kunnen worden herkend.

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()

privatekey.png

Toelichting

  • 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.