Les sources du projet sont disponibles sur GitHub : adrien-chinour/discover-blockchain
Introduction
Une adresse en cryptomonnaie correspond à un RIB. Cela vous permet d'identifier le portefeuille (le compte). Vous pouvez donc la partager a n'importe qui pour recevoir de l'argent.
Transfer/receive transactions of Bitcoins (Cryptocurrency, BTC) can be performed via address like the work with e-mail messages. (https://bitcoinwiki.org/wiki/address#Purpose_and_opportunities)
Il y a différents types d'adresses, comme présenté dans cet article : Bitcoin address types compared: P2PKH, P2SH, P2WPKH, and more.
Les types les plus utilisés sont :
- P2PKH : Pay-To-Public-Key-Hash (43%)
- P2WPKH : Pay-to-Witness-Public-Key-Hash (20%)
- P2SH : Pay-To-Script-Hash (24%)
- P2WSH : Pay-to-Witness-Script-Hash (4%)
Voyons d'abord le format, encore aujourd'hui, le plus commun : P2PKH ; même s'il tend à être remplacé par son évolution P2WPKH.
Génération d'une adresse P2PKH
Ce type d'adresse est le plus commun est fonctionne sur le mécanisme de clé publique et clé privée. La clé publique est partagée et permet de valider une transaction alors que notre clé privée permet de signer une transaction.
On peut y lire qu'une adresse utilise ECDSA comme algorithme de génération de notre couple clé publique et clé privée :
Bitcoin uses the Elliptic Curve Digital Signature Algorithm (ECDSA) with the secp256k1 curve; secp256k1 private keys are 256 bits of random data. https://developer.bitcoin.org/devguide/transactions.html
Dans les grandes lignes parce que l'objectif n'est pas de rentrer dans le détail de ECDSA, on va fournir en entrée un entier aléatoire, nommé k, dans l'intervalle [1, n - 1 ] avec n égal à 2^256 pour secp256k1.
L'algorithme va ensuite nous donner les coordonnées x et y de notre point k x G sur la courbe elliptique. G étant le point initial de la courbe. (x,y) correspond à notre clé publique. Et la complexité de cet algorithme réside dans la difficulté à obtenir k sachant (x,y).
Passons au code, on va utiliser les fonctions crypto déjà fournies en go pour calculer notre clé privée et notre clé publique :
Ce qu'il faut savoir et qui est très important, c'est que le seed que l'on va fournir contient le seul secret qui importe dans notre clé. La clé privée générée à partir de celui-ci sera toujours identique et par conséquent la clé publique aussi.
Passons au code, ici, on va utiliser les fonctions crypto déjà fournies en go pour calculer notre clé privée et notre clé publique :
func main() { // Generate a private key based on secp256k1 ellipsis curve privateKey, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) if err != nil { fmt.Println("Cannot generate private key.", err) } // Display private key as hexadecimal string fmt.Printf("Private Key: %s\n", hex.EncodeToString(privateKey.D.Bytes())) // Display public key as hexadecimal string publicKey := append(privateKey.X.Bytes(), privateKey.Y.Bytes()...) fmt.Printf("Public Key: %s\n", hex.EncodeToString(publicKey)) }
Mais on n'a pas encore fini parce que notre clé publique ne correspond pas à une adresse Bitcoin. Pour ça il nous manque quelques étapes :
L'avantage de cette opération est de sécuriser encore plus notre clé publique en fournissant un hash de celle-ci et surtout d'économiser de la place dans la blockchain en réduisant le nombre de bit par adresse. https://privacypros.io/btc-faq/how-many-btc-addresses
func hash(input []byte, hash crypto.Hash) []byte { hasher := hash.New() hasher.Write(input) return hasher.Sum(nil) } func publicKeyToAddress(publicKey ecdsa.PublicKey) (string, error) { publicKeyBytes := append(publicKey.X.Bytes(), publicKey.Y.Bytes()...) network := byte(0x00) fingerprint := hash(hash(publicKeyBytes, crypto.SHA256), crypto.RIPEMD160) address := append([]byte{network}, fingerprint...) checksum := hash(hash(fingerprint, crypto.SHA256), crypto.SHA256)[:4] return base58.Encode(append(address, checksum...)), nil }
0x00
correspond a notre type d'adresse sur le réseau principal : MainNet.
Il existe un réseau de test : TestNet. Dans le TestNet il faudra utiliser 0x6f
.
Le détail de l'algorithme est détaillé dans cette documentation : https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
Et pour valider que notre adresse est utilisable et vérifier l'état du portefeuille on peut utiliser ce site : https://www.blockchain.com/explorer.
Passons a l'évolution de cette première génération d'adresse : P2WPKH.
Génération d'une adresse P2WPKH
Tout d'abord, il faut comprendre pourquoi ce nouveau terme : "Witness".
SegWit (abréviation de Segregated Witness) est une mise à niveau rétrocompatible du protocole Bitcoin qui modifie en profondeur la structure des transactions en déplaçant les données de signature (le témoin ou witness) dans une base de données séparée (segregated). https://cryptoast.fr/segwit-bitcoin-explication-definition/
Il s'agit d'un changement dans le protocole qui permet d'accélérer le débit des transactions en intégrant un plus grand nombre de transactions au sein d'un même bloc. Et tout ça, simplement en réduisant la taille d'une transaction.
Au niveau de notre adresse, cela ne change pas énormément. En utilisant la documentation suivante https://en.bitcoin.it/wiki/Bech32, on peut écrire le code suivant :
func P2WPKH() BitcoinAddress { privateKey, _ := ecdsa.GenerateKey(btcec.S256(), rand.Reader) // Compressed public key based on Elliptic-Curve-Point-to-Octet-String Conversion (https://www.secg.org/sec1-v2.pdf) publicKey := elliptic.MarshalCompressed(privateKey.Curve, privateKey.PublicKey.X, privateKey.PublicKey.Y) // Witness version, current is 0 version := byte(0x00) // Hash public key with SHA256 then RIPEMD160 hashedKey := hash(hash(publicKey, crypto.SHA256), crypto.RIPEMD160) // 8-bit unsigned integer to 5-bit unsigned integers fingerprint, _ := bech32.ConvertBits(hashedKey, 8, 5, true) // Encode to bech32 format with network (bc = main network) and checksum calculated address, _ := bech32.Encode("bc", append([]byte{version}, fingerprint...)) return BitcoinAddress{address: address, privateKey: hex.EncodeToString(privateKey.D.Bytes())} }
Pas beaucoup de changement, simplement des améliorations techniques par rapport à notre version P2PKH.
Maintenant que l'on a vu le fonctionnement d'une adresse par clé publique, voyons les différences avec les adresses par script.
Génération d'une adresse P2SH
Les deux formats P2SH et son évolution P2WSH repose sur l'exécution d'un script qui renverra True ou False pour valider ou non une transaction.
Le script permet plus de flexibilité dans la signature d'une transaction. On retrouve plusieurs usages comme :
- Les transactions multi-signature : plusieurs clés publiques doivent être utilisées pour autoriser une transaction
- Le dépôt fiduciaire : service dans lequel une tierce partie, appelée escrow, retient temporairement les fonds pendant que les parties impliquées dans la transaction satisfont aux conditions convenues.
- Transactions Atomic Swaps : permettent l'échange atomique de cryptomonnaies entre deux parties sans nécessiter de tiers de confiance. Qu'est-ce qu'un Atomic Swap ?
Un article en français qui parle des différents usages : Les smart contracts avec Bitcoin
Pour un exemple assez simple, je vais générer une adresse qui nécessite la signature de 2 clés publiques. Pour ça je me base sur la documentation présente ici : https://en.bitcoin.it/wiki/Script et qui me donne les instructions que je peux définir dans mon script :
OP_2 <publicKey1> <publicKey2> OP_2 OP_CHECKMULTISIG
On peut générer notre adresse avec le code suivant :
func AddressPubKey() []byte { privateKey, _ := ecdsa.GenerateKey(btcec.S256(), rand.Reader) return elliptic.MarshalCompressed(privateKey.Curve, privateKey.PublicKey.X, privateKey.PublicKey.Y) } // MultiSignAddress demonstrate P2SH address func MultiSignAddress() { // In this exemple, we use 2 initial P2PK addresses address1, _ := btcutil.NewAddressPubKey(AddressPubKey(), &chaincfg.MainNetParams) address2, _ := btcutil.NewAddressPubKey(AddressPubKey(), &chaincfg.MainNetParams) pubKeys := []*btcutil.AddressPubKey{address1, address2} // Create MultiSign Script // OP_2 <publicKey1> <publicKey2> OP_2 OP_CHECKMULTISIG script, _ := txscript.MultiSigScript(pubKeys, 2) // 0x05 for MainNet, Oxc4 for TestNet network := byte(0x05) hashedScript := hash(hash(script, crypto.SHA256), crypto.RIPEMD160) address := append([]byte{network}, hashedScript...) checksum := hash(hash(address, crypto.SHA256), crypto.SHA256)[:4] print(base58.Encode(append(address, checksum...)))
La méthodologie est assez proche de ce que l'on a pour P2PKH, à la différence qu'on ne part pas d'une clé mais d'un script. Pour générer une adresse P2WSH, on applique bech32 sur notre script.
En conclusion
On a vu les principaux types d'adresses disponibles sur la blockchain Bitcoin. Rendez-vous dans la deuxième partie pour voir comment utiliser notre adresse. Et en bonus voici un résumé graphique de cette génération d'adresse Bitcoin.