Mit der Bitcoin-Blase 2017 (und Ende 2020) wurden beide Themen aktuell. Bestimmte Dinge sollen hier untersucht werden.

Blockchain ([ErLo18], Kapitel 11)

Blockchain ist ein Beispiel für eine replizierte (auf mehrere Rechner verteilte) Datenbank, welche aus einer Reihe miteinander verketteter Blöcke basiert (mit Zeitstempel versehen, kryptographisch abgesichert), welche eine unveränderliche Historie darstellen.
Bitcoin basiert auf einer Blockchain mit dem 'Proof of work', also "Minen"(=Schürfen von neuen Bitcoin-Blöcken).

Bitcoin

Mehrere Transaktionen werden zu einem Block zusammengefasst und vorher auf Gültigkeit überprüft, mit einem Zeitstempel versehen sowie dem Hashwert des Vorgängerblocks. Im Block gibt es ein 32-Bit-Feld (nonce, number used once), welches von den sog. Minern variiert wird zur Berechnung des Hash mit SHA256, so dass der Hashwert eine Schwierigkeit (difficulty, Anzahl 0en, mit dem der Hashwert beginnen muss) erreicht.
Block ([ErLo18],Kap. 11.9)
Blockheader ([ErLo18],Kap. 11.10)
4 ByteVersionsnummer
32 ByteHashwert des Headers des Vorgängerblocks
32 ByteHash über die nachfolgenden Transaktionen (Merkle Tree)
4 ByteZeitstempel
4 ByteCodierter Schwellenwert(difficulty)
4 ByteNonce
Miner suchen im Netzwert nach zu bestätigenden Transaktionen, prüfen diese auf Plausibilität, bilden einen Block und berechnen Hashwerte. Wenn ein Block gefunden wurde, wird der Block an das Netzwerk weitergeleitet, dort geprüft und ggf. an die Blockchain angehängt, und die Miner erhalten eine Belohnung, was den finanziellen Anreiz für Minen darstellt Es gibt also 4,29*109 Hash-Werte zu berechnen. Die Wahrscheinlichkeit für einen Treffer ist 1 / 232, und es ist auch möglich, dass alle Hashwerte nicht den Schwellwert unterschreiten, dann war alles Energieverschwendung. Auf einem modernen PC/notebooks schafft man heute ca. 200.000 Hashes pro Sekunde (unabhängig von Blockgrösse, da nur ein zweifaches sha256 auf den Header ausgeführt wird). Ohne Asics sind das dann ca. 8 Stunden, um alle Nonce-Werte zu durchlaufen.
Mit python kann man das gut vereinfacht darstellen:

Block-Hashing mit SHA-256
# Vergleicht bei SHA256 hashing mit difficulty und nonce.
import hashlib
import datetime

difficulty = 5  # Bit
nonce = 5   # Bit
block = "Blockchain " + str(datetime.datetime.utcnow())

# Wandelt hex-String in binaer-String um
def binaer(hexEingabe):
  rueck = ""
  for a in hexEingabe:
    teil = a if ord(a) < 97 else "1"+chr(ord(a) - 49)
    binaer = str(bin(int(teil)))
    rueck += binaer[2:].rjust(4,'0')
  return rueck


# hasht ein Wort ueber nonce; testet, ob Schwierigkeit erreicht
def schleife(difficulty, nonce):
  versuche = 2**nonce
  vergleich = "".ljust(difficulty,'0')
  treffer = 0
  for a in range(0,versuche):
    wert = (block + str(a)).ljust(50,' ')
    erg = hashlib.sha256(bytearray(wert,'latin-1'))
    hash = binaer(erg.hexdigest())
    if (hash[0:difficulty] == vergleich):
      print("Treffer ",wert, hash)
      treffer += 1
  if (treffer == 0):
    print("Suche war erfolglos !")


schleife(difficulty,nonce)

Falls zwei oder mehr Miner parallel einen gültigen Block finden, wird dann immer mit der längsten Block-Kette weitergemacht, und damit auch ggf. schon bestätigte Transaktionen wieder aufgelöst. Die Schwierigkeit wird laufend so angepasst, dass eine möglichst konstante Rate neuer Blockbestätigungen vorkommt.

Wie kann man sich Bitcoin-Transaktionen anzeigen lassen

Blockchain-Übersicht

Wie viele Bitcoins gibt es ([ErLo18],Kap. 11.16)

Es sind maximal ca. 21 Mio. Bitcoins möglich, berechenbar anhand der Belohnungen pro Block.
Für die ersten 210.000 Blöcke gab es 50 Bitcoins Belohnung, für die nächsten 210.000 Blöcke nur noch 25 Bitcoins u.s.w. Damit ergibt sich:
i=1 32 (100 / 2i) * 210.000 = (1 - 1 / 232) * 21.000.000 = 20.999.999
Im Mai 2020 war das letzte sog. Bitcoin-Halving, wodurch sich die Belohnung halbiert hat.

Was sind die Vor- und Nachteile einer Blockchain

Vorteil ist, es wird kein steuernder Intermediator gebraucht, z.B. eine Bank oder ein Makler.
Nachteil ist, dass bei 'Proof of work' nur der erste Finder eine Belohnung erhält, die anderen haben umsonst gearbeitet und Energie verschwendet. Und in einer Blockchain können Daten sichtbar sein, welche nicht für jedermann einsehbar sein sollten.

Gibt es Alternativen zu sequentiellem hashing ?

Da die Veränderung von einem Bit des Eingangswertes alle Ausgangsbits den Hashes beinflusst, sind keine statistischen Methoden bekannt, Eingabewerte für Nonce anders zu bestimmen.

Wie funktioniert bitcoin-core

Bitcoin-core ist die Referenzimplementierung von Satoshi Nakamoto, wird laufend weiterentwickelt und kann unter github abgerufen werden. Es besteht auf einem Daemon-Prozess (bitcoind), welcher z.B. die Blockchain herunter lädt, mit den anderen mining-nodes kommuniziert und einem Aufruf-Programm (bitcoin-cli), mit welchem man z.B. Transaktionen oder Blöcke anzeigen und den mining-Prozess durchführen kann

Infos über Blöcke

Unter src/primitives/block.h ist die Deklaration eines Blockheaders und Blocks.
class CBlockHeader
{
public:
    // header
    int32_t nVersion;
    uint256 hashPrevBlock;
    uint256 hashMerkleRoot;
    uint32_t nTime;
    uint32_t nBits;
    uint32_t nNonce;
  ...
class CBlock : public CBlockHeader
{
public:
    // network and disk
    std::vector<CTransactionRef> vtx;

    // memory only
    mutable bool fChecked;
  ...
Damit ist auch klar, dass man im Quelltext nach "nNonce" suchen muss, wenn man die Zugriffe auf die Nonce eines Block finden will. Oder nach "hashMerkleRoot", wenn man erkennen will, wo die Merkle-root gesetzt wird.

Infos über Transaktionen

Unter src/primitives/transaction.h ist die Deklaration einer Transaktion.
class CTransaction
{
public:
    static const int32_t CURRENT_VERSION=2;
    static const int32_t MAX_STANDARD_VERSION=2;

    const std::vector<CTxIn> vin;
    const std::vector<CTxOut> vout;
    const int32_t nVersion;
    const uint32_t nLockTime;

private:
    const uint256 hash;
    const uint256 m_witness_hash;
Man sieht, dass die Eingabetransaktionen in "vin" sowie die Ausgabetransaktionen in "vout" gespeichert werden.
class CTxIn
{
public:
    COutPoint prevout;
    CScript scriptSig;
    uint32_t nSequence;
    CScriptWitness scriptWitness;
...

class CTxOut
{
public:
    CAmount nValue;
    CScript scriptPubKey;
...
die nSequence-Nummer wird gesetzt, wenn die Transaktionen in einen Block eingefügt wurde.

Wie kann man im Quelltext suchen ohne IDE wie Eclipse

Im Ablageverzeichnis von bitcoin-core kann man mit fgrep rekursiv suchen, in der Implementierung den *.cpp-Klassen, oder in Deklarationen den *.h-Klassen. Suche nach generateBlocks z.B.:
jurgen@jurgen-LIFEBOOK-S710:~/bitcoin-master/src$ find . -name "*.cpp" -exec fgrep -l "generateBlocks" {} \;
./rpc/mining.cpp
jurgen@jurgen-LIFEBOOK-S710:~/bitcoin-master/src$ find . -name "*.h" -exec fgrep -l "nNonce" {} \;
./chain.h
./primitives/block.h

init.cpp

Dies ist die Startklasse des Daemon-Prozesses, hier ist die Methode AppInitMain. Diese prüft den Zustand der lokal in Dateien abgespeicherten Blockchain und nimmt hier laufende Aktualisierungen vor.
AppInitMain [init.cpp]
 ThreadImport
  LoadExternalBlockfile
   ActiveBestChain [validation.cpp]
    ActiveBestChainStep
     ConnectTip
      UpdateTip
       Meldung "UpdateTip: new best=..."

validation.cpp

Hier finden sich Programmcode zur Validierung/Bau von Blöcken, Transaktionen und Blockchain.

rpc/mining.cpp

Hier finden sich die Aufrufmöglichkeiten durch bitcoin-cli

Ort des Minings, also Variation der Nonce

unter src/rpc/mining.cpp findet man den Zugriff auf nNonce(s.o.) in der statischen Funktion generateBlocks(). Hier ist die while-Schleife, wo Nonce hochgezählt wird (++pblock->nNonce;).
static UniValue generateBlocks(const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
{
    int nHeightEnd = 0;
    int nHeight = 0;

    {   // Don't keep cs_main locked
        LOCK(cs_main);
        nHeight = ::ChainActive().Height();
        nHeightEnd = nHeight+nGenerate;
    }
    unsigned int nExtraNonce = 0;
    UniValue blockHashes(UniValue::VARR);
    while (nHeight < nHeightEnd && !ShutdownRequested())
    {
        std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(mempool, Params()).CreateNewBlock(coinbase_script));
        if (!pblocktemplate.get())
            throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
        CBlock *pblock = &pblocktemplate->block;
        {
            LOCK(cs_main);
            IncrementExtraNonce(pblock, ::ChainActive().Tip(), nExtraNonce);
        }
        while (nMaxTries > 0 && pblock->nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus()) && !ShutdownRequested()) {
            ++pblock->nNonce;
            --nMaxTries;
        }
        if (nMaxTries == 0 || ShutdownRequested()) {
            break;
        }
        if (pblock->nNonce == std::numeric_limits<uint32_t>::max()) {
            continue;
        }
        std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
        if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
            throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
        ++nHeight;
        blockHashes.push_back(pblock->GetHash().GetHex());
    }
    return blockHashes;
}
Wie kann man generateBlocks aufrufen ? Die einzige Verwendung in *.cpp-Dateien ist in mining.cpp selbst, und zwar in den durch bitcoin-cli aufrufbaren Methoden generatetodescriptor oder generatetoaddress. Oben wird mit nGenerate und nMaxTries übergeben, wie viele Blöcke man minen möchte und wie viele Versuche bis zum Abbruch gemacht werden sollen. Mit nHeight wird zuerst die Anzahl der Blöcke der blockchain ermittelt. Aus dem Mempool(unbestätigte Transaktionen) und dem coinbase-Skript wird dann ein neuer Block erstmal zusammengebaut:
        std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(mempool, Params()).CreateNewBlock(coinbase_script));
Zudem wird das minen beendet, wenn der Benutzer bitcoind z.B. mit CTRL-C beendet hat, dann liefert ShutdownRequested() == wahr zurück.
Wenn ein Treffer, also eine passende nNonce gefunden wurde, kommt man hinter die if-Anweisungen:
        std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
        if (!ProcessNewBlock(Params(), shared_pblock, true, nullptr))
            throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
        ++nHeight;
Mit ProcessNewBlock(...)[aus validation.cpp] wird der geminte pblock an das Netzwerk geschickt und auf eine Rückmeldung gewartet, ob ein anderer mining-node diesen akzeptiert hat. Wenn ja, wird die Anzahl der Blöcke hochgezählt. Zurückgegeben wird dann eine ggf. leere Liste an Block-Hashes zu neuen Blöcken.

Ausgaben in bitcoind

Im Ordner mit den Einstellungen werden die Ausgaben in debug.log geschrieben, eine Liste der anderen mining-nodes im Netz in peers.dat, unter Beachtung der bitcoin.conf.
jurgen@jurgen-LIFEBOOK-S710:~/.bitcoin$ ls -lrt
total 11468
drwxrwxr-x 2 jurgen jurgen     4096 Feb  3 21:08 wallets
drwxrwxr-x 5 jurgen jurgen     4096 Feb  3 21:13 testnet3
-rw------- 1 jurgen jurgen       37 Mär 27 17:50 banlist.dat
-rw------- 1 jurgen jurgen       86 Mär 29 09:02 bitcoin.conf
drwx------ 3 jurgen jurgen     4096 Apr  2 17:48 blocks
drwx------ 2 jurgen jurgen     4096 Apr  2 20:49 chainstate
-rw------- 1 jurgen jurgen  1364698 Apr  2 20:52 peers.dat
-rw------- 1 jurgen jurgen       17 Apr  2 20:52 mempool.dat
-rw------- 1 jurgen jurgen   247985 Apr  2 20:52 fee_estimates.dat
-rw------- 1 jurgen jurgen 10094606 Apr  2 20:52 debug.log
Unter chainstate liegt die levelDB.
Die heruntergeladenen Blöcke sind:
jurgen@jurgen-LIFEBOOK-S710:~/.bitcoin/blocks$ ls
blk00000.dat  blk00006.dat  blk00012.dat  rev00004.dat  rev00010.dat
blk00001.dat  blk00007.dat  index         rev00005.dat  rev00011.dat
blk00002.dat  blk00008.dat  rev00000.dat  rev00006.dat  rev00012.dat
blk00003.dat  blk00009.dat  rev00001.dat  rev00007.dat
blk00004.dat  blk00010.dat  rev00002.dat  rev00008.dat
blk00005.dat  blk00011.dat  rev00003.dat  rev00009.dat

Mining aufrufen

Man kann erst minen, wenn die gesamte Blockchain heruntergeladen worden ist, ggf. im prune-Modus, wo alte Blöcke entfernt wurden. Ruft man mit RPC trotzdem getblocktemplate auf, kommt folgende Fehlermeldung: bitcoin core is in initial sync and waiting for blocks ...
    if (::ChainstateActive().IsInitialBlockDownload())
        throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
Die Methode IsInitialBlockDownload() gehört der Klasse CChainState aus validation.cpp.

Zur Vermeidung der Fehlermeldung
Bitcoin Core is in initial sync and waiting for blocks
in rpc/mining.cpp/Methode getblocktemplate:
    if (false) // ::ChainstateActive().IsInitialBlockDownload())
        throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks...");
    LogPrintf("Huerde passiert\n");
Zur Vermeidung der Fehlermeldung
CreateNewBlock: TestBlockValidity failed: bad-fork-prior-to-checkpoint
In valdation.cpp/Methode ContextualCheckBlockHeader:
        if (false) { // pcheckpoint && nHeight < pcheckpoint->nHeight) {
            LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight);
            return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint");
        }
Ein wirkliches sicheres und sinnvolles Minen liegt aber nur vor, wenn die gesamte Blockchain vorliegt.

Das Erzeugen eines Blockmusters geht mit:
bitcoin-cli getblocktemplate '{"rules": ["segwit"]}'
Das Minen geht mit:
bitcoin-cli generatetoaddress 1 bc1qflg52jg3twfech9qvzreavhssw9en50pqxd0cx 10000
Abfrage des mempools mit:
bitcoin-cli getmempoolinfo

Was ist eine Merkle-Root

Es werden immer paarweise zwei hashes einer Transaktion in einem Block aneinander gekettet und dann ein neuer hash mit sha256(doppelt) gebildet. Dazu werden Ebenen gebildet, deren untere Ebene die hashes aller Transaktionen des Blockes sind und auf oberster Ebene der Wurzel-Hash, genannt Merkle-Root. Die Merkle-Root ist im Header enthalten, sie Transaktionen im Block. bitcoin-core nimmt die Berechnung der Merkle-Root schon ab.

Mempool/Transactionpool

Darunter versteht man lokal vorhandene Transaktionen, welche in einen Block einzubauen sind. bitcoind prüft am Start, ob es in mempool.dat schon welche gibt ("Imported mempool transactions from disk", Methode LoadMempool() von validation.cpp). Eine neue Transaktion wird durch AcceptToMemoryPool/validation.cpp addiert. Empfangen werden neue Transaktionen in der Nachricht NetMsgType::TX/ProcessMessage in net_processing.cpp.

Nachrichtensystem

Netzwerknachrichten kann man sich anzeigen lassen, indem man bitcoind mit der Option -debug=net startet. Das Empfangen eintreffender Nachrichten von peer nodes geschieht in bool static ProcessMessage(...) von net_processing.cpp bei der Auswertung von strCommand (Daten liegen in Variable vRecv):
    if (strCommand == NetMsgType::HEADERS)
    ...
Dort können dann erhaltene Daten wie neue Header/neue Blöcke weiter verarbeitet werden. Das Senden eigener Nachrichten kann mit dem ConnectionManager connman
        connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::HEADERS, vHeaders));
gemacht werden, hier gibt man den peer node Id an, den Nachrichtentyp (hier schicke Header an peer) und eine Liste mit Infos, in diesem Fall die Hashwerte des Headers in vHeaders.

Um eigene Header anzufordern schickt man die Nachricht MsgType::GETHEADERS
            connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexLast), uint256()));
und erhält dann eine Nachricht MsgType::HEADERS in net_processing.cpp
    if (strCommand == NetMsgType::HEADERS)
...
       for (unsigned int n = 0; n < nCount; n++) {
            vRecv >> headers[n];
            ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
        }

        return ProcessHeadersMessage(pfrom, connman, headers, chainparams, /*via_compact_block=*/false);
Oder ein anderer Node schickt eine Anfrage an einen selbst, das landet dann in net_processing.cpp
    if (strCommand == NetMsgType::GETHEADERS) {
  ...
Wenn die Blockchain nicht vollständig geladen ist, wird aber nicht weiter gemacht:
        if (::ChainstateActive().IsInitialBlockDownload() && !pfrom->HasPermission(PF_NOBAN)) {
            LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom->GetId());
            return true;
        }
Um eigene Blöcke anzufordern schickt man die Nachricht MsgType::GETDATA
                    connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData));
und erhält dann eine Nachricht MsgType::BLOCK in net_processing.cpp
    if (strCommand == NetMsgType::BLOCK)
    {
...
        std::shared_ptr pblock = std::make_shared();
        vRecv >> *pblock;

        LogPrint(BCLog::NET, "received block %s peer=%d\n", pblock->GetHash().ToString(), pfrom->GetId());
...
        ProcessNewBlock(chainparams, pblock, forceProcessing, &fNewBlock);
Blockanfragen von anderen nodes erhält man in:
    if (strCommand == NetMsgType::GETBLOCKS) {
        CBlockLocator locator;
        uint256 hashStop;
        vRecv >> locator >> hashStop;
Um Transaktionen zu bekommen, muss man den MessageType INV auswerten:
    if (strCommand == NetMsgType::INV) {
        std::vector vInv;
        vRecv >> vInv;
...

        for (CInv &inv : vInv)
        {
            if (interruptMsgProc)
                return true;

            bool fAlreadyHave = AlreadyHave(inv);
            LogPrint(BCLog::NET, "got inv: %s  %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->GetId());

            if (inv.type == MSG_TX) {
                inv.type |= nFetchFlags;
            }

            if (inv.type == MSG_BLOCK) {
                UpdateBlockAvailability(pfrom->GetId(), inv.hash);
                if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) {
...
                    connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), inv.hash));
                    LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->GetId());
                }
            }
            else
            {
                pfrom->AddInventoryKnown(inv);
                if (fBlocksOnly) {
...
                } else if (!fAlreadyHave && !fImporting && !fReindex && !::ChainstateActive().IsInitialBlockDownload()) {
                    RequestTx(State(pfrom->GetId()), inv.hash, current_time);
                }
            }
        }

Man sieht, dass weitere Transaktionen nur erhalten werden (RequestTx()), wenn die Blockchain vollständig heruntergeladen wurde. AlreadyHave() prüft zum Hash, ob schon was im Mempool ist.

Aktualisierungsprozess der Blockchain

Beim Start von bitcoind erfolgt immer ein Untersuchen, wie die aktuelle Höhe der Blockchain ist der Peer nodes im Vergleich zur lokal gespeicherten blockchain (ProcessNewBlockHeaders() von validation.cpp).
2020-04-13T07:44:37Z Synchronizing blockheaders, height: 625731 (~100.00%)
Momentan werden neue Blöcke zuerst durch neue BlockHeader über das Netzwerk propagiert, damit kann man sich dann die ganzen Blöcke holen. In FindNextBlocksToDownload/net_processing.cpp werden die neuen Blöcke bestimmt, was von PeerLogicValidation::SendMessages/net_processing.cpp aufgerufen wird.
        //
        // Message: getdata (blocks)
        //
        std::vector vGetData;
        if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !::ChainstateActive().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
            std::vector vToDownload;
            NodeId staller = -1;
            FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller, consensusParams);
            for (const CBlockIndex *pindex : vToDownload) {
                uint32_t nFetchFlags = GetFetchFlags(pto);
                vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash()));
                MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), pindex);
                LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(),
                    pindex->nHeight, pto->GetId());
            }
...
        }
...
        if (!vGetData.empty())
            connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
in vGetData stehen die Hashwerte der zu ladenden Blöcke drin.

Zusammenhang zwischen private key und public key ([AnAn18], Kap. 4)

Der private Schlüssel besteht aus 256 zufällig gewählten Bits. Der öffentliche Schlüssel wird nach dem Standard secp256k1 vom NIST bestimmt. Es gibt einen Basispunkt auf einer elliptischen Kurve, welcher mit dem privaten Schlüssel verknüpft wird. Der Zielpunkt besteht aus (x,y), der public key ist dann (04 x y). Wenn man zu einem bekannten public key den private key bestimmen muss, entspricht das dem Logarithmieren über elliptischen Kurven, was sehr schwer ist. Ein Beispielprogramm dazu ist hier.

Abgeleitete Bitcoin-Blockchains

Von dem Original-Bitcoin gab es in der Vergangenheit Ableitungen(forks), welche ab einem bestimmten Zeitpunkt eine andere Blockchain nützen. Diese verwenden weiterhin den SHA256d-Algorithmus. * Hashrate des Netzwerks 10/2020.

Litecoin

Litecoin (LTC) ist auch eine Ableitung, verwendet aber statt sha256d das Verfahren scrypt, was Mining mit ASICS erschweren soll. Im Jahr 2021 will sich das Projekt mehr in einen private coin umwandeln. Das letzte litecoin-halving war im August 2019.
Quellen (gelesener und empfehlenswerter Bücher)
KürzelAutor TitelVerlag
[AnAn18]Andreas AntonopolousBitcoin und Blockchain O'Really 2. Auflage 2018
[ErLo18]Wolfgang Ertel,Ekkehard LöhmannAngewandte KryptographieHanser Verlag 2018