În primele două articole am prezentat arhitectura și filozofia criptomonedelor (criptocurrency) în general și Bitcoin în particular. După ce am prezentat detalii legate de implementarea în cadrul unui server distribuit (cu mostre de cod C#), în al treilea articol ne propunem să discutăm despre componenta client a bitcoin sau despre portofelul electronic.
Exemplele pornesc de la aspecte discutate în volumul online "Programming the Blockchain in C#".
Pentru început, vom pregăti codul sursă al proiectului. Ca data trecută, vom folosi Visual Studio unde vom merge pe ultima versiune .Net, care la data redactării acestui articol, este 4.7.2. Vom crea o aplicație consolă în .Net.
Prin intermediul Nuget Package Manager, vom mai adăuga două librării externe de care vor depinde exemplele noastre: NBitcoin și QBitNinja.Client.
În .Net 4.7.x întâlnim o eroare frecventă la runtime, în ceea ce privește librăria System.Net.Http. Problema poate fi rezolvată comentând secțiunea corespunzătoare configurației app.config și îndepărtând manual, din referințele proiectului, secțiunea ce se referă la System.Net.Http.
O altă posibilă eroare mai puțin întâlnită este ca librăria QBitNinja.Client să nu fie adusă prin nuget împreună cu toate fișierele corespunzătoare .dll, ci doar cu un singur fișier cu extensia .nupkg (de exemplu la data scrierii acestui articol, ultima versiune a librăriei este 1.0.3.47 și s-ar putea să fie descărcat doar fișierul QBitNinja.Client.1.0.3.47.nupkg). În acest caz va trebui să deschideți acest fișier cu o aplicație de gestionat fișiere arhivate, să zicem 7zip, și să copiați manual subdirectorul 'lib' care conține fișierele .dll înăuntrul folderului corespunzător de pachete nuget (spre exemplu să copiați 'lib' înăuntrul folderului packages\QBitNinja.Client.1.0.3.47) și să adăugați manual referința la fișierul \librăria packages \QBitNinja.Client.1.0.3.47\lib\net461\QBitNinja.Client.dll. După ce ați setat tehnic proiectul cu librăriile de mai sus, sunteți gata de scris cod!
Cel mai simplu model posibil de tranzacție financiară este cel în care o persoană oferă unei alte persoane bani. În lumea electronică emițătorul este identificat printr-o adresă publică, iar destinatarul prin alta. În lumea bitcoin tranzacțiile sunt pseudo-anonime, deci oricine poate descărca pe laptopul personal întregul istoric de tranzacții financiare realizate de la începutul existenței bitcoin (adică ianuarie 2009). Mai jos, vom folosi un serviciu web public pentru a interoga adrese și tranzacții. De exemplu, aceasta este una din adresele noastre personale de bitcoin: 17m6PrDFLJeWDTDqiYSBkoFDpR1cLd9dLr.
Puteți consulta toate detaliile asociate acestei adrese, accesând următorul link: https://www.blockchain.com/ro/btc/address/17m6PrDFLJeWDTDqiYSBkoFDpR1cLd9dLr
După cum se poate observa, există câteva tranzacții atașate adresei, așadar vom analiza una specială, având următorul identificator de tranzacție:
38279e5d6037959ce7868f8e3df5533cbf0c7c8a59cd22e39e3a61b15d3b2e31:
Pe data de 17 decembrie 2017, adresa noastră a înregistrat (4) 0.0084 bitcoin (1), ceea ce înseamnă aproximativ 8 miimi de bitcoin, ceea ce, pentru o medie de 1 bitcoin = 10000 dolari americani, duce la aproximativ 84 de dolari americani. Am trimis către prima adresă de intrare (2 - cea care începe cu 146...), 0.003 bitcoin, adică aproximativ 30 de dolari. Am mai trimis către a doua adresă de ieșire (cea care începe cu 14w...) restul sumei, adică 0.0054 bitcoin. Ca să fim sinceri, aplicația "e-wallet" (portofel electronic) pe care o folosim, adică aplicația mobilă Mycelium, a creat complet automat a doua adresă de ieșire (3), deci aceasta ne aparține! Am fi putut transfera restul sumei rămase (0.0054 BTC) înapoi către adresa originală (6), dar aplicația Mycelium a fost configurată pentru a crea automat o nouă adresă pentru fiecare rest rezultat din tranzacție, pentru a-i deruta pe intrușii care vor să urmărească circulația banilor.
Cel mai interesant aspect pe care dorim să îl prezentăm este legat de data la care a fost realizată tranzacția. Tranzacția a fost publicată la data de 17 decembrie 2017 (4), dar a fost acceptată de întreaga rețea bitcoin doar cinci săptămâni mai târziu, pe 28 ianuarie 2018! De ce s-a întâmplat acest lucru? Noi am publicat doar intenția de tranzacționare, ceea ce înseamnă: vreau să trimit 30 de dolari către adresa 2, iar restul de 54 de dolari către adresa 3, iar pentru acest lucru sunt dispus să plătesc taxa de tranzacționare de 0.00000892 BTC (aproximativ 8 cenți). Această taxă de tranzacționare este asemănătoare celei solicitate de sistemul bancar clasic. Problema este că o tranzacție bitcoin consumă de 4000 de ori mai multă energie electrică decât o tranzacție uzuală efectuată cu cardul de credit! De aceea, taxa efectuată de un plătitor bitcoin este de obicei de zece ori mai mare decât cea pe care am ales-o eu, astfel că tranzacția mea a fost acceptată doar de câțiva mineri mai săraci (adică fără putere mare de calcul). La un moment dat, există 50000 mineri (sau bănci distribuite) ce sunt mereu online, încercând să calculeze mitica problemă de matematică SHA256 ce are drept date tranzacțiile de intrare ce așteaptă să fie confirmate. Dacă acești bancheri au suficientă putere de calcul la dispoziție, ei nu vor fi interesați de tranzacția mea ieftină, ci vor favoriza tranzacții cu taxe de tranzacționare mai mari pentru a câștiga bani mai rapid. Dacă mi-aș fi dorit să trimit banii aceștia în cel mai rapid mod cu putință, ceea ce în lumea Bitcoin înseamnă zece minute, ar fi trebuit să aleg o taxă de tranzacționare cam de zece ori mai mare, adică pentru un transfer de 30 de dolari să plătesc cam 80 de cenți (dar noi nu ne-am dorit asta, tocmai pentru a testa limitările sistemului bitcoin). Acesta este motivul pentru care atât de puțini mineri au acceptat tranzacția mea, au păstrat-o în listă cinci săptămâni, până când în sfârșit unul dintre ei a câștigat problema SHA256. Aceasta demonstrează că au avut capacități de calcul relativ slabe, de aceea noi a trebuit să așteptăm exagerat de mult timp.
Momentan, acesta este cel mai mare dezavantaj al bitcoin: din cauza popularității sale, sunt prea mulți clienți dornici să tranzacționeze și prea puține servere distribuite, suficient de puternice. Prin urmare, având în vedere prețul curent al electricității (în special, cel de pe piața chineză) o tranzacție minoră costă indecent de mult. Deci, ori se plătește un comision de zece ori mai mare decât cel pentru cardul de credit sau se așteaptă câteva săptămâni pentru ca transferul de câteva sute de dolari să fie acceptat. Acest dezavantaj este resimțit doar la micro-tranzacții. Pentru transferul unor sume mari, de mii de dolari sau chiar mai mult, comisioanele devin mult mai mici decât cele percepute în sistemul bancar tradițional.
Acum urmează să vedem cum putem crea adrese și tranzacții. Adresele sunt create prin intermediul librăriei NBitcoin după cum urmează:
Key privateKey = new Key();
PubKey publicKey = privateKey.PubKey;
Console.WriteLine(publicKey);
Console.WriteLine(
publicKey.GetAddress(Network.Main));
Console.WriteLine(
publicKey.GetAddress(Network.TestNet));
Vom crea o cheie privată (linia 1), iar pe baza acestui lucru vom crea o cheie publică (linia 2) pe baza căreia obținem adresa publică (liniile 4 și 5). Rețeaua specificată în linia 4 este adevărata rețea bitcoin, cu monedele bitcoin "reale" ce pot fi schimbate în bani adevărați și multă putere de calcul, majoritar consumată de minerii chinezi. A doua rețea, TestNet, este folosită de dezvoltatorii (programatorii) de bitcoin pentru a testa și transfera monedele bitcoin "false", numite și faucets. Rețeaua TestNet network este extrem de rapidă comparativ cu rețeaua Main (principală).
BitcoinSecret mainNetPrivateKey = privateKey.GetBitcoinSecret(Network.Main);
BitcoinSecret testNetPrivateKey = privateKey.GetBitcoinSecret(Network.TestNet);
Console.WriteLine(mainNetPrivateKey);
Console.WriteLine(testNetPrivateKey);
bool WifIsBitcoinSecret =
mainNetPrivateKey==privateKey.GetWif(Network.Main);
Console.WriteLine(WifIsBitcoinSecret); // True
Key samePrivateKey = mainNetPrivateKey.PrivateKey;
Console.WriteLine(samePrivateKey == privateKey);
// True
În acest al doilea exemplu (liniile 1 și 2), vom genera secretele Bitcoin în ambele rețele, cunoscute drept Wallet Import Format sau WIF, din cheia privată. Notăm că putem afla cheia privată din secret, dar nu putem afla cheia privată din cheia publică. Putem trimite cheia publică sau adresa publică oricui, dar nu avem voie să trimitem nimănui, niciodată, cheia privată sau secretul WIF!
În a treia mostră de cod vom prezenta, în manieră programatică, detalii despre tranzacția descrisă mai sus. Pentru acest lucru, vom folosi a doua dependință de tip librărie, QBitNinja.Client care, în fundal folosește librăria System.Net.Http pentru a apela un serviciu web Azure Cloud, unul din cei 50000 de mineri ce se află mereu online.
QBitNinjaClient client =
new QBitNinjaClient(Network.Main);
// Parsează ID-ul tranzacției în NBitcoin.uint256
// pentru a putea fi consumat de client
var transactionId = uint256.Parse(
"38279e5d6037959ce7868f8e3df5533cbf0c7c8a59cd22"+
"e39e3a61b15d3b2e31");
// Interoghează tranzacția
GetTransactionResponse transactionResponse =
client.GetTransaction(transactionId).Result;
NBitcoin.Transaction transaction =
transactionResponse.Transaction;
Console.WriteLine(transactionResponse.TransactionId);
Console.WriteLine(
transactionResponse.TransactionId ==
transaction.GetHash()); // True
List receivedCoins =
transactionResponse.ReceivedCoins;
foreach (var coin in receivedCoins)
{
Money amount = (Money)coin.Amount;
Console.WriteLine(amount.ToDecimal(
MoneyUnit.BTC));
var paymentScript = coin.TxOut.ScriptPubKey;
Console.WriteLine(paymentScript);
// It's the ScriptPubKey
var address = paymentScript.
GetDestinationAddress(Network.Main);
Console.WriteLine(address);
Console.WriteLine();
}
var outputs = transaction.Outputs;
foreach (TxOut output in outputs)
{
Money amount = output.Value;
Console.WriteLine(amount.ToDecimal(MoneyUnit.BTC));
var paymentScript = output.ScriptPubKey;
Console.WriteLine(paymentScript);
// It's the ScriptPubKey
var address = paymentScript.
GetDestinationAddress(Network.Main);
Console.WriteLine(address);
Console.WriteLine();
}
var inputs = transaction.Inputs;
foreach (TxIn input in inputs)
{
OutPoint previousOutpoint = input.PrevOut;
Console.WriteLine(previousOutpoint.Hash);
// hash of prev tx
Console.WriteLine(previousOutpoint.N);
// idx of out from prev tx, spent in the current tx
Console.WriteLine();
}
În a patra mostră, vom interoga prima tranzacție efectuată vreodată, aflată în primul grup de tranzacții (la momentul redactării există peste 500 de mii de blocuri de tranzacții în bitcoin, conținând aproximativ 360 de milioane de tranzacții).
Block genesisBlock = Network.Main.GetGenesis();
Transaction firstTransactionEver = genesisBlock
.Transactions.First();
var firstOutputEver = firstTransactionEver
.Outputs.First();
var firstScriptPubKeyEver = firstOutputEver
.ScriptPubKey;
var firstBitcoinAddressEver = firstScriptPubKeyEver
.GetDestinationAddress(Network.Main);
Console.WriteLine(firstBitcoinAddressEver == null);
// True
Console.WriteLine(firstTransactionEver);
De abia acum, lucrurile devin interesante. Pentru a face toate testele, ajustările și transferul de bitcoin de la una din adresele voastre la alte adrese ale voastre, va trebui să obțineți bitcoini de test în cadrul rețelei TestNet. Prin intermediul unei simple căutări pe Google, puteți găsi un astfel de site (de exemplu https://coinfaucet.eu/en/btc-testnet/) unde puteți comanda gratuit bitcoini falși, de folosit în rețeaua TestNet. Ca temă pentru acasă, va trebui să: 1. generați o cheie privată și să-i salvați secretul, iar 2. din acel secret să recreați cheia privată, apoi cheia publică, iar apoi adresa sa bitcoin, toate în cadrul rețelei TestNet.
Să încercăm să trimitem niște bitcoini de test către autorii volumului C# "Programming blockchain". Adresa lor este mzp4No5cmCXjZUpf112B1XWsvWBfws5bbB, deci vom trimite câțiva din bitcoinii obținuți pe baza informației din paragraful de mai sus. Vom crea adresa de destinație:
var hallOfTheMakersAddress = BitcoinAddress
.Create("mzp4No5cmCXjZUpf112B1XWsvWBfws5bbB",
Network.TestNet);
Apoi vom crea tranzacția (unde trebuie să obținem ID-ul tranzacției sau al tranzacțiilor de intrare și să înlocuim numerele cu cantitățile exacte dorite. ID-ul de tranzacție al bitcoinilor de test primiți corespund adresei publice generate pe baza cheii private testNetPrivateKey):
client = new QBitNinjaClient(Network.TestNet);
transactionId = uint256.Parse("0acb6e97b228b838049ffbd528571c5e3edd003f0ca8ef61940166dc3081b78a");
transactionResponse = Client
.GetTransaction(transactionId).Result;
Console.WriteLine(transactionResponse.TransactionId);
Console.WriteLine(transactionResponse.Block
.Confirmations);
receivedCoins = transactionResponse.ReceivedCoins;
OutPoint outPointToSpend = null;
foreach (var coin in receivedCoins)
{
if (coin.TxOut.ScriptPubKey ==
testNetPrivateKey.ScriptPubKey)
{
outPointToSpend = coin.Outpoint;
}
}
if (outPointToSpend == null)
Console.WriteLine(
"TxOut doesn't contain our ScriptPubKey");
else
Console.WriteLine("We want to spend {0}."+
" outpoint:", outPointToSpend.N + 1);
transaction = Transaction.Create(Network.TestNet);
transaction.Inputs.Add(new TxIn()
{
PrevOut = outPointToSpend
});
TxOut hallOfTheMakersTxOut = new TxOut()
{
Value = new Money(0.0004m, MoneyUnit.BTC),
ScriptPubKey = hallOfTheMakersAddress.ScriptPubKey
};
TxOut changeBackTxOut = new TxOut()
{
Value = new Money(0.00053m, MoneyUnit.BTC),
ScriptPubKey = testNetPrivateKey.ScriptPubKey
};
transaction.Outputs.Add(hallOfTheMakersTxOut);
transaction.Outputs.Add(changeBackTxOut);
Am efectuat niște ajustări (variabila changeBackTxOut) pe baza taxei de tranzacționare dorite pentru minerit, deci taxa va fi proporțional mai mică (trebuie să alegeți cantitatea în funcție de prețul de piață, cantitățile din exemple fiind pur orientative). Putem adăuga chiar și un mesaj în cadrul tranzacției!
var message = "Long live NBitcoin and its makers!";
var bytes = Encoding.UTF8.GetBytes(message);
transaction.Outputs.Add(new TxOut()
{
Value = Money.Zero,
ScriptPubKey = TxNullDataTemplate.Instance
.GenerateScriptPubKey(bytes)
});
La final, trebuie să semnăm tranzacția:
transaction.Sign(testNetPrivateKey, false);
Apoi, publicăm tranzacția în rețea, ca minerii să o poată accepta sau respinge (pe baza taxei de tranzacționare și a verificării semnăturii):
BroadcastResponse broadcastResponse = client.Broadcast(transaction).Result;
if (!broadcastResponse.Success)
{
Console.Error.WriteLine("ErrorCode: "
+ broadcastResponse.Error.ErrorCode);
Console.Error.WriteLine("Error message: "
+ broadcastResponse.Error.Reason);
}
else
{
Console.WriteLine("Success! You can check out"+
" the hash of the transaction in any block"+
" explorer:");
Console.WriteLine(transaction.GetHash());
}
Asta e tot! Desigur, ar mai fi multe de discutat, în special pentru tranzacțiile financiare complexe. De exemplu, puteți trimite banii unui grup de oameni care își combină cheile publice într-un script hash mare și care pot accesa banii trimiși pe baza regulilor incluse în script. De exemplu, un grup de trei persoane ar putea decide ca, dacă oricare dintre ei doreste, banii să poată să fie cheltuiți; sau ar putea decide ca fiecare să aibă putere de veto, deci doar dacă toți trei semnează tranzacția, banii să poată fi cheltuiți; sau ar putea decide ca majoritatea democratică (doi din trei) să semneze pentru ca banii să fie cheltuiți. Aceste tipuri de tranzacții se numesc tranzacții cu semnătură multiplă m-din-n (m-of-n multi-signature transactions). Există și tranzacții unde se ia în considerare timpul, de exemplu banii trimiși într-o tranzacție pot fi cheltuiți doar la 3 luni de la data la care a fost acceptată tranzacția de rețeaua bitcoin! Mai există și o versiune viitoare (deocamdată experimentală) a rețelei bitcoin, unde tranzacțiile vor fi complet anonime, asemănător rețelei Tor. De asemenea, există alte propuneri impresionante, dar încă la nivel experimental, legate de tranzacțiile bitcoin care sunt generate și calculate la fiecare secundă în timp ce utilizatorul urmărește un film într-o rețea de streaming/difuzare, ceea ce ne arată că se poate plăti pentru un film la fiecare secundă vizionată! Bineînțeles că ați putea combina scenariile descrise aici, oricum doriți, cu actori ce nu au încredere unul în celălalt, ci doar într-un sistem ce garantează logico-matematic că nu se trișează! Tehnic vorbind, acesta pare a fi viitorul domeniului financiar, în fața căruia doar voința politică pare a fi singurul obstacol ce trebuie îndepărtat.