Transactions & adresses

Transaction

Une transaction est, pour simplifier, un enregistrement disant que “Alice donne x bitcoins à Bob”.
On distingue deux types de transactions:

Adresse

Pour aller plus loin:
Address reuse

Clé publique / clé privée


Formats d’adresse

Pour aller plus loin:
List of address prefixes
Bech32 adoption

Adresse Pay-to-Pubkey-Hash (P2PKH)

Format WIF

Les clés privées sont stockées dans le portefeuille au format WIF (Wallet Import Format), qui est simplement la clé privée encodée en chaîne ASCII via Base58Check — on peut facilement inverser l’encodage pour retrouver la clé privée.

Pour aller plus loin:
Wallet import format

Transation input/output

Un utilisateur ne peut pas dépenser des bitcoins qu’il n’a pas: il ne peut dépenser que des bitcoins qu’il a reçu via une (ou des) transaction(s) — coinbase ou tx. Pour s’en assurer, chaque transaction contient

Chaque transaction dépense la sortie d’une ou plusieurs transactions antérieures. Une transaction peut créer plusieurs sorties, mais chaque sortie d’une transaction donnée ne peut être utilisée qu’une seule fois. Toute référence ultérieure est considérée comme une double dépense — une tentative de dépenser deux fois le même bitcoin — et est donc interdite.

Normalement, une transaction tx aura donc au moins une entrée et au maximum deux sorties: une pour le paiement et, si besoin, une deuxième pour renvoyer la monnaie à l’expéditeur. Ainsi, si Alice a gagné 50BTC en minant un bloc et qu’elle veut envoyer 10BTC à Bob, elle utilise la transaction de 50BTC (elle indique en entrée l’identifiant de transaction et numéro de sortie à utiliser, le montant est déduit) et elle indique deux sorties: 10BTC pour Bob, 40BTC pour Alice.

Une transaction coinbase n’a pas d’entrée et n’a qu’une seule sortie, qui envoie la récompense du bloc au mineur.

Adresse de change

Pour plus d’anonymat, la monnaie n’est généralement pas renvoyée à la même adresse: Alice renvoie l’excédent à Zach et garde en mémoire qu’elle répond aux adresses Alice et Zach. Zach est ce qu’on appelle une adresse de change (change address en anglais).

Les adresses ne sont pas des portefeuilles ni des comptes: elles ne portent pas de soldes, elles ne font que recevoir des fonds. Ce malentendu a mené certaines personnes à ne sauvegarder que l’adresse d’origine et non l’adresse de change, perdant des bitcoins (la différence renvoyée à l’expéditeur) dans le processus.

Frais de transaction

Si la somme des sorties (5BTC pour Bob + 40BTC pour Alice) est inférieure à la somme des entrées (50BTC), alors le mineur peut ajouter une sortie qui lui est destinée (+ 5BTC pour Carole): Alice aura ainsi payé 5 BTC de frais de transaction à Carole — notre mineur.

UTXO

Chaque transaction a au moins une entrée et une sortie. Chaque entrée dépense le montant de la sortie référée. Chaque sortie qui n’a pas encore été utilisée en entrée d’une autre transaction est une sortie de transaction non dépensée: Unspent Transaction Output (UTXO) en anglais.

Lorsque votre portefeuille Bitcoin vous indique que vous avez un solde de 10 000 satoshis, cela signifie que vous avez 10 000 satoshis en attente dans une ou plusieurs UTXO pour lesquelles vous avez les clés associées.

Script

Pay-to-Script-Hash (P2SH)

Pour aller plus loin:
Script
BIP16

Multisig (P2MS)

Pour une transaction nécessitant jusqu’à 3 signatures, on peut simplement créer un script Pubkey multi-signature — dit Pay-to-MultiSignature (P2MS):

scriptPubKey: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG
scriptSig: OP_0 <A sig> [B sig] [C sig...]

P2MS est devenu standard en janvier 2012, P2SH en avril 2012.
P2MS est donc une relique datant d’avant P2SH — P2SH pousse les fonctionnalités de P2MS plus loin.

Pubkey (P2PK)

Le script P2PK est une version simplifiée du script P2PKH. Au lieu d’utiliser une adresse pour sortie, on utilise une clé publique. Bien que toujours valide, ce type de transaction n’est généralement plus utilisé de nos jour. On en retrouve notamment dans les transactions coinbase des premiers blocs de la blockchain.

scriptPubkey: <pubkey> OP_CHECKSIG
scriptSig: <sig>

Pour aller plus loin:
Why do we have P2PKH as well as P2PK?

Null data

Heure de verrouillage

Pour aller plus loin:
(Status: Proposal) BIP125
Transaction replacement

Signature

Le script signature de chaque entrée contient tous les éléments permettant de valider le script pubkey de la sortie parente et notamment… la signature.

La signature permet 1. de prouver que l’émetteur possède la clé privée associée à la clé publique demandée (valide l’entrée), 2. de prouver que personne n’a modifié les sorties de la transaction en cours (sécurise la sortie).

# Données de la transaction
version      = "01000000"

inputCount   = "01"
inputTxid    = "81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48"
inputVout    = "00000000"
inputScriptPubkey = keyUtils.addrHashToScriptPubKey("1MMMMSUb1piy2ufrSguNUdFmAcvqrQF8M5")
inputSeq     = "ffffffff"

outputCount  = "01"
outputValue  = 91234 # satoshis
outputScript = keyUtils.addrHashToScriptPubKey("1KKKK6N21XKo48zWKuQKXdvSsCf95ibHFa")

# Crée la transaction (pre signature)
tx = version
  + inputCount
  + inputTxid.decode('hex')[::-1].encode('hex') # reverse
  + inputVout
  + '%02x' % len(inputScriptPubkey.decode('hex'))
  + inputScriptPubkey
  + inputSeq
  + outputCount
  + struct.pack("<Q", outputValue).encode('hex')
  + '%02x' % len(outputScript.decode('hex'))
  + outputScript

# Crée une signature de la transaction entière
privateKey = keyUtils.wifToPrivateKey("5HusYj2b2x4nroApgfvaSfKYZhRbKFH41bVyPooymbC6KfgSXdD") #1MMMM
tx        += "01000000" # hash code

digest     = hashlib.sha256(hashlib.sha256(tx.decode('hex')).digest()).digest()
signature  = ecdsa.SigningKey
                  .from_string(privateKey.decode('hex'), curve=ecdsa.SECP256k1)
                  .sign_digest(digest, sigencode=ecdsa.util.sigencode_der)
           + '\01' # 01 is hashtype

# Crée le scriptSig à partir de la signature + public key
publicKey      = keyUtils.privateKeyToPublicKey(privateKey)
inputScriptSig = utils.varstr(signature).encode('hex')
               + utils.varstr(publicKey.decode('hex')).encode('hex')

# Crée la transaction signée
# (pareil mais avec scriptSig au lieu de scriptPubkey)
signed_tx = version
  + inputCount
  + inputTxid.decode('hex')[::-1].encode('hex') # reverse
  + inputVout
  + '%02x' % len(inputScriptSig.decode('hex'))
  + inputScriptSig
  + inputSeq
  + outputCount
  + struct.pack("<Q", outputValue).encode('hex')
  + '%02x' % len(outputScript.decode('hex'))
  + outputScript

txnUtils.verifyTxnSignature(signed_tx)
print 'SIGNED TX', signed_tx

Pour aller plus loin:
Bitcoins the hard way: Using the raw Bitcoin protocol