Entwickeln Sie eine Ethereum-Web3-Wallet mit ether.js

Entwickeln Sie eine Ethereum-Web3-Wallet mit ether.js

In diesem Tutorial wird nicht zu viel über die Konzepte von Web3, Ethereum und Wallets erklärt, sondern mehr über die Entwicklung, und standardmäßig haben Sie bereits eine bestimmte Web3- und React-Foundation, auch wenn es keine React-Foundation gibt und js Basic auch möglich ist

Projektvorbereitung

Das grundlegende Framework, das wir verwenden, ist umi, ein Framework, das auf React + ts basiert.
Offizielle Website-Adresse

https://umijs.org/

installiere ether.js

npm install --save ethers

Einführung in ether.js: Hier sind drei Möglichkeiten

es3:
var ethers = require('ethers');
es5/es6
const ethers = require('ethers');
javascript/typescript es6
import { ethers } from 'ethers';

Wenn Sie sich entscheiden, ethers direkt im Web zu verwenden, können Sie es so einführen.Aus Sicherheitsgründen ist es normalerweise am besten, eine Kopie von ethers-v4.min.js auf Ihren eigenen Anwendungsserver zu kopieren.Wenn Sie eine schnelle haben Prototyp-Erfahrung, sollten Sie das Ethers CDN ausreichend nutzen.

<!-- 会导出一个全局的变量: ethers -->
<script src="https://cdn.ethers.io/scripts/ethers-v4.min.js"
        charset="utf-8"
        type="text/javascript">
</script>

Erstellen Sie ein Wallet-Konto

Web3-Wallets haben normalerweise solche Eigenschaften

  • Kontoverwaltung (privater Schlüssel): Erstellen Sie ein Konto, importieren und exportieren Sie private Schlüssel,
  • Informationsanzeige: hauptsächlich das Guthaben der persönlichen Brieftasche,
  • Übertragungsfunktion: Übertragungstoken
    Wir werden es durch ether.js implementieren

Wir wissen, dass es normalerweise zwei Möglichkeiten gibt, ein Konto zu erstellen, was durch die in die HD-Wallet eingebundenen Verschlüsselungsalgorithmen BIP32, BIP44 und BIP39 bestimmt wird (falls Sie es noch nicht wissen, denken Sie einfach an Folgendes, schließlich Sie wird es am wichtigsten verwenden)

  • Generieren Sie zufällig eine 32-Byte-Zahl als privaten Schlüssel
  • Der private Schlüssel wird durch deterministische Ableitung durch die Mnemonik erhalten

Generieren Sie einen privaten Schlüssel mit einer Zufallszahl

Dies ist die erste oben erwähnte Methode zum Generieren eines privaten Schlüssels. Eine 32-Byte-Zahl wird zufällig als privater Schlüssel generiert. Nachdem wir ether.js korrekt importiert haben, können wir die Methode ether.utils.randomBytes() aufrufen, um zu erhalten Eine Zufallszahl und dann das Wallet in Ether anrufen, um das Wallet zu verbinden, wir erhalten eine Wallet-Instanz

let privateKey = ethers.utils.randomBytes(32)
let wallet = ethers.Wallet(privateKey)

Zu diesem Zeitpunkt ist der private Schlüssel, den wir sehen, ein Zeichensatz. Wenn wir ihn in ein Zeichenfolgenmuster umwandeln möchten, das wir leicht speichern können, müssen wir eine andere Hilfsfunktion aufrufen.

ethers.BigNumber.from(privateKey)._hex

So können wir einen privaten Schlüssel wie diesen erhalten

0x29895776b4c571de60c35e243cb157dade634bc557b9b7090a13d93e48cfa99e

Die vorherige Aufrufmethode von ethers.BigNumber.from() war ethers.utils.bigNumberify. Später, da sich herausstellte, dass diese Funktion häufig verwendet wurde, befand sie sich auf der gleichen Ebene wie utils, als ethers von v4 zu v5 wechselte und mehr hinzufügte Jetzt können wir sehen, dass dieser Punkt in vielen alten Dokumenten nicht geändert wurde

Generieren Sie einen privaten Schlüssel per Mnemonik

Die Methode, einen privaten Schlüssel durch eine Mnemonik zu generieren, stellt eine sehr beliebte Methode in unserem derzeitigen Mainstream dar. Der Hauptprozess besteht darin, zuerst eine Zufallszahl zu generieren, dann eine Mnemonik durch die Zufallszahl zu generieren und dann eine Brieftasche durch die Mnemonik zu erstellen

const rand = ethers.utils.randomBytes(12)
const mnemonic = ethers.utils.entropyToMnemonic(rand)
var path =  "m/60'/1'/0'/0/0";
//通过助记词创建钱包
  // 检查助记词是否有效。
        if (!ethers.utils.HDNode.isValidMnemonic(inputPhrase.val())) {
    
    
            return;
        }
console.log(mnemonic)
Wallet.fromMnemonic(mnemonic, path);

Es kann notwendig sein, diesen Pfad hier einzuführen, der eine feste Notation für einen BIP44-Schlüsselpfad darstellt und
eine Struktur mit 5 vordefinierten baumartigen Ebenen angibt:
m / Zweck' / Münze' / Konto' / Änderung / Adress_Index
m ist fest , Verwendungszweck ist auch festgelegt, der Wert ist 44 (oder 0x8000002C) Münztyp
steht
für die Währung, 0 steht für Bitcoin, 1 steht für die Bitcoin-Testkette und 60 steht für
die komplette Währungsliste von Ethereum Adresse: https://github.com/ satoshilabs/slips/blob/master/slip-0044.md
Konto
stellt den Kontoindex dieser Münze dar, beginnend bei 0.
Ändern
Sie die Konstante 0 für extern (Empfangsadresse), die Konstante 1 für intern (auch bekannt als Änderungsadresse). Extern wird für Adressen verwendet, die außerhalb der Brieftasche sichtbar sind (z. B. um Zahlungen zu erhalten). Interne Ketten werden für Adressen verwendet, die außerhalb der Brieftasche nicht sichtbar sind, und werden verwendet, um Transaktionsänderungen zurückzugeben. (Also wird im Allgemeinen 0 verwendet)
Adressindex
Dies ist der Adressindex. Beginnend bei 0 stellt er die Anzahl der generierten Adressen dar. Die offizielle Empfehlung lautet, dass der Adressindex unter jedem Konto 20 nicht überschreiten sollte.

Gemäß der von EIP85 vorgeschlagenen Diskussion folgt das Ethereum-Wallet auch dem BIP44-Standard, und der festgelegte Pfad ist m/44'/60'/a'/0/n
a stellt die Kontonummer dar, n ist die n-te generierte Adresse und 60 ist im SLIP44 Proposal Encoding for Ethereum festgelegt. Wenn wir also ein Ethereum-Wallet entwickeln wollen, müssen wir auch die Bitcoin-Wallet-Vorschläge BIP32 und BIP39 verstehen.

Wir können unsere zwölfstellige Mnemonik auf der Konsole sehen

sponsor spenden waffe siegeslied zigarre wolf ski solide business muster brokkoli

Erstellen Sie direkt eine Brieftasche

Ether bietet eine sehr einfache Methode, mit der wir direkt eine Brieftasche erstellen können

ethers.Wallet.createRandom()

Es ist so eine einfache Methode, direkt zufällig eine Brieftasche zu erstellen, und dann können wir diese Instanzdaten in der Konsole sehen

Wallet {
    
    _isSigner: true, address: '0x50321B8585B19D144E2924CB01BE023B752669C9', provider: null, _signingKey: ƒ, _mnemonic: ƒ}
address: "0x50321B8585B19D144E2924CB01BE023B752669C9"
provider: null
_isSigner: true
_mnemonic: () => {
    
    }
_signingKey: () => signingKey
mnemonic: (...)
privateKey: (...)
publicKey: (...)
[[Prototype]]: Signer

Import und Export von Account-KeyStore-Dateien

Ausführliche Erläuterung des Schlüsselspeichers

Warum benötigen Sie eine Keystore-Datei?

Der private Schlüssel stellt eigentlich ein Konto dar. Der einfachste Weg, das Konto zu behalten, ist das direkte Speichern des privaten Schlüssels. Wenn die private Schlüsseldatei gestohlen wird, werden unsere digitalen Vermögenswerte geplündert.

Die Keystore-Datei ist eine Datei, die den Schlüssel verschlüsselt speichert.Bei der Initiierung einer Transaktion wird der private Schlüssel mithilfe des Passworts aus der Keystore-Datei entschlüsselt und anschließend die Transaktion signiert. Dies ist viel sicherer, da Hacker unsere digitalen Assets nur stehlen können, indem sie sowohl die Keystore-Datei als auch das Passwort stehlen.

Wie die Keystore-Datei generiert wird

Ethereum verwendet einen symmetrischen Verschlüsselungsalgorithmus, um den privaten Schlüssel zum Generieren der Keystore-Datei zu verschlüsseln. Daher ist die Wahl des symmetrischen Verschlüsselungsschlüssels (beachten Sie, dass es sich tatsächlich um den Entschlüsselungsschlüssel handelt, der zum Initiieren von Transaktionen erforderlich ist) sehr kritisch. Dieser Schlüssel wird mithilfe von abgeleitet KDF-Algorithmus aus. Bevor Sie also vollständig vorstellen, wie die Keystore-Datei generiert wird, müssen Sie zunächst KDF vorstellen.

Generieren Sie Schlüssel mit KDF

Kryptographie KDF (Schlüsselableitungsfunktionen), ihre Funktion besteht darin, einen oder mehrere geheime Schlüssel von einem Passwort abzuleiten, dh Verschlüsselungsschlüssel aus einem Passwort zu generieren.

Der PBKDF2-Algorithmus zum Ableiten des Seeds aus der Mnemonik ist eine KDF-Funktion, deren Prinzip darin besteht, Salt hinzuzufügen und die Anzahl der Hash-Iterationen zu erhöhen.

In Keystore wird der Scrypt-Algorithmus verwendet. Wenn er durch eine Formel dargestellt wird, lautet die abgeleitete Schlüsselgenerierungsgleichung:

DK = Scrypt(salt, dk_len, n, r, p)

Dabei ist Salt ein zufälliges Salt und dk_len die Länge des Ausgabe-Hash-Werts. n ist der CPU/Speicher-Overhead-Wert, je höher der Overhead-Wert, desto schwieriger die Berechnung. r ist die Blockgröße und p ist der Parallelitätsgrad.

Litecoin verwendet Scrypt als POW-Algorithmus

Verschlüsseln Sie den privaten Schlüssel

Der KDF-Algorithmus wurde verwendet, um oben einen geheimen Schlüssel zu generieren. Dieser geheime Schlüssel ist der geheime Schlüssel für die symmetrische Verschlüsselung. Der hier verwendete symmetrische Verschlüsselungsalgorithmus ist aes-128-ctr. Der aes-128-ctr-Verschlüsselungsalgorithmus muss auch a verwenden Parameterinitialisierungsvektor iv.

Keystore-Datei

Schauen wir uns zuerst an, wie die Keystore-Datei aussieht, damit wir sie besser verstehen können

{
    
      
   "address":"856e604698f79cef417aab...",
   "crypto":{
    
      
      "cipher":"aes-128-ctr",
      "ciphertext":"13a3ad2135bef1ff228e399dfc8d7757eb4bb1a81d1b31....",
      "cipherparams":{
    
      
         "iv":"92e7468e8625653f85322fb3c..."
      },
      "kdf":"scrypt",
      "kdfparams":{
    
      
         "dklen":32,
         "n":262144,
         "p":1,
         "r":8,
         "salt":"3ca198ce53513ce01bd651aee54b16b6a...."
      },
      "mac":"10423d837830594c18a91097d09b7f2316..."
   },
   "id":"5346bac5-0a6f-4ac6-baba-e2f3ad464f3f",
   "version":3
}

Lassen Sie uns jedes Feld interpretieren:

  • Adresse: Kontoadresse
  • version: Die Version der Keystore-Datei, derzeit Version 3, auch bekannt als V3 KeyStore.
  • id: uuid
  • crypto: Konfiguration im Zusammenhang mit kryptografischem Pushback.
  • Cipher ist ein symmetrischer Verschlüsselungsalgorithmus, der zum Verschlüsseln privater Schlüssel von Ethereum verwendet wird. Aes-128-ctr wird verwendet.
  • cipherparams sind die Parameter, die vom aes-128-ctr-Verschlüsselungsalgorithmus benötigt werden. Hier wird nur der Parameter iv verwendet.
  • Chiffretext ist der Chiffretext, der vom Verschlüsselungsalgorithmus ausgegeben wird, und es ist auch die Eingabe, die für die zukünftige Entschlüsselung erforderlich ist.
  • kdf: Geben Sie an, welcher Algorithmus verwendet werden soll, hier ist scrypt.
  • kdfparams: Parameter, die von der Verschlüsselungsfunktion benötigt werden
  • mac: Wird verwendet, um die Korrektheit des Passworts zu überprüfen, mac= sha3(DK[16:32], Chiffretext) Der folgende Abschnitt analysiert es separat.

Lassen Sie uns die Generierung der Keystore-Datei vollständig klären:

  1. Generieren Sie den geheimen Schlüssel mit der scrypt-Funktion (basierend auf dem Passwort und den entsprechenden Parametern)
  2. Verwenden Sie den im vorherigen Schritt generierten geheimen Schlüssel + privaten Kontoschlüssel + Parameter für die symmetrische Verschlüsselung.
  3. Speichern Sie die relevanten Parameter und geben Sie Chiffretext als JSON-Dateien im obigen Format aus

Verwenden Sie ethers.js, um Konten zu exportieren und zu importieren

ethers.js stellt direkt Methoden zum Laden von Keystore-JSON bereit, um Wallet-Objekte zu erstellen und Keystore-Dateien zu verschlüsseln.Die Methoden lauten wie folgt:

// 导入keystore Json
    ethers.Wallet.fromEncryptedJson(json, password, [progressCallback]).then(function(wallet) {
    
    
       // wallet
    });

    // 使用钱包对象 导出keystore Json
    wallet.encrypt(pwd, [progressCallback].then(function(json) {
    
    
        // 保存json
    });

Wir erhalten zuerst das Passwort aus HTML und verwenden dieses Passwort dann als Parameter zum Importieren und Exportieren

 <input
        type="text"
        placeholder="请输入密码"
        onChange={(e) => {
          setPassword(e.target.value);
        }}
      />
      <button onClick={putKeyStore}>keyStore导出</button>
 //获得keyStore文件
  const putKeyStore = () => {
    
    
    walletInstance.encrypt(password).then((json: string) => {
    
    
      console.log(json);
      getKeyStore(json)
      try {
    
    
        var blob = new Blob([json], {
    
     type: "text/plain;charset=utf-8" });
        let blobUrl = window.URL.createObjectURL(blob);
        let link = document.createElement("a");
        link.download = "keyStore.txt" || "defaultName";
        link.style.display = "none";
        link.href = blobUrl;
        // 触发点击
        document.body.appendChild(link);
        link.click();
        // 移除
        document.body.removeChild(link);
      } catch (error) {
    
    
        console.error(error);
      }
    });
  };

Datei importieren

var fileReader = new FileReader();
 fileReader.onload = function(e) {
    
    
   var json = e.target.result;

   // 从加载
   ethers.Wallet.fromEncryptedJson(json, password).then(function(wallet) {
    
    

   }function(error) {
    
    

   });

 };
fileReader.readAsText(inputFile.files[0]);

Oder es so umkehren

  //反向推导出钱包地址
  const getKeyStore = (json:string)=>{
    
    
    ethers.Wallet.fromEncryptedJson(json,password).then(res=>{
    
    
      console.log(res);
    })
  

Wallet-Informationen anzeigen und signierte Transaktionen initiieren

Wir können tatsächlich feststellen, dass in der vorherigen Einführung, ob wir einen privaten Schlüssel oder eine Brieftasche generieren, wir tatsächlich feststellen werden, dass es nichts mit dem Ethereum-Netzwerk zu tun hat, aber wenn wir wirklich transferieren wollen, überprüfen Sie den Saldo der Transaktionen und andere Informationen, es muss mit dem Ethereum-Netzwerk verbunden sein, um dies tun zu können,

Wenn Sie schon einmal mit web3 in Kontakt waren, müssen Sie wissen, dass für die Verbindung mit dem eth-Netzwerk ein Provider erforderlich ist. ether.js selbst bietet viele Möglichkeiten, sich mit dem Provider zu verbinden

  • Web3Provider: Verwenden Sie einen vorhandenen Web3-kompatiblen Anbieter wie MetaMask oder Mist.

  • EtherscanProvider und InfuraProvider: Wenn Sie keinen eigenen Knoten haben, können Sie Etherscan und Infuras Provider verwenden. Sie sind alle Infrastrukturdienstanbieter von Ethereum. Ethers.js bietet auch einen einfacheren Weg: Verwenden Sie einen Standardanbieter, der automatisch hilft wir verbinden Etherscan und Infura.

let defaultProvider = ethers.getDefaultProvider('ropsten');

Um sich mit dem Provider zu verbinden, gibt es normalerweise einen Parameter network network name, die Werte sind: homestead, rinkeby, ropsten, kovan.

    let provider = ethers.getDefaultProvider('ropsten');
    //activeWallet是我们前面创建的钱包实例
    activeWallet = walletInstance.connect(provider)

Brieftaschendetails anzeigen: Kontostand und Nonce prüfen

Nachdem Sie sich mit dem Ethereum-Netzwerk verbunden haben, können Sie den Kontostand vom Netzwerk anfordern und die Anzahl der Kontotransaktionen abrufen, indem Sie die folgende API verwenden:

  //获取余额
    activeWallet.getBalance().then((res)=>{
    
    
      console.log(res);
    })
    //获取交易数量
    activeWallet.getTransactionCount().then((res)=>{
    
    
      console.log(res);
    })

Unterzeichnete Transaktion senden

Eine signierte Transaktion wird auch als Offline-Transaktion bezeichnet (weil der Vorgang offline durchgeführt werden kann: Die Transaktion wird offline signiert und die signierte Transaktion wird gesendet).

Obwohl Ethers.js eine sehr prägnante API zum Senden signierter Transaktionen bietet, ist es dennoch hilfreich, die Details hinter der prägnanten API zu untersuchen. Dieser Prozess kann grob in drei Schritte unterteilt werden:

  1. Transaktion strukturieren
  2. Transaktionssignatur
  3. Transaktion senden (broadcasten).

Transaktion strukturieren

Schauen wir uns an, wie eine Transaktion aussieht:

const txParams = {
    
    
  nonce: '0x00',
  gasPrice: '0x09184e72a000',
  gasLimit: '0x2710',
  to: '0x0000000000000000000000000000000000000000',
  value: '0x00',
  data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057',
  // EIP 155 chainId - mainnet: 1, ropsten: 3
  chainId: 3
}

Beim Einleiten einer Transaktion ist es notwendig, jedes Feld auszufüllen, um eine solche Transaktionsstruktur aufzubauen.
zu und Wert: Gut verstanden, es ist das Ziel und der Betrag, den der Benutzer überweisen möchte.
data: ist die während der Transaktion angehängte Nachricht. Wenn die Transaktion an die Vertragsadresse initiiert wird, wird diese in die Ausführung der Vertragsfunktion umgewandelt. Siehe: So verstehen Sie die Ethereum ABI
nonce: Transaktionsseriennummer
chainId: chain id, dient zur Unterscheidung verschiedener Die Chain (forked chain) id kann in EIP-155 abgefragt werden.

Eine wichtige Rolle von Nonce und ChainId besteht darin, Replay-Angriffe zu verhindern. Wenn es keine Nonce gibt, kann der Empfänger die signierte Transaktion erneut übertragen. Wenn es keine ChainId gibt, kann die Transaktion auf Ethereum auf Ethereum Classic abgerufen werden. Broadcast erneut.

gasPrice und gasLimit: Gas ist der Arbeitsabrechnungsmechanismus von Ethereum, bei dem es sich um die Gebühr handelt, die der Transaktionsinitiator den Minern in Rechnung stellt. Die Einstellungen der oben genannten Parameter sind relativ fest, während die Einstellungen von Gas (insbesondere gasPrice) viel flexibler sind.

gasLimit gibt die geschätzte Auslastung von Anweisungen und Speicherplatz an. Wenn die Auslastung nicht aufgebraucht ist, wird sie an den Transaktionsinitiator zurückgegeben. Wenn sie nicht ausreicht, tritt ein „Out-of-Gas“-Fehler auf.
Bei einer gewöhnlichen Überweisungstransaktion ist die Arbeitslast fest, das gasLimit ist 21000 , und das gasLimit der Vertragsausführung ändert sich.Manche Leute denken vielleicht, dass es direkt auf einen höheren Wert gesetzt wird, und es wird trotzdem zurückgegeben, aber wenn der Vertrag falsch ausgeführt wird, wird alles Gas gefressen. Glücklicherweise bieten sowohl web3 als auch ethers.js Methoden zur Berechnung des Gaslimits, die eingeführt werden, wenn Sie das nächste Mal Token senden.

GasPrice ist die Einheitsgebühr, die der Transaktionsinitiator bereit ist, für die Arbeitslast zu zahlen. Wenn Miner Transaktionen auswählen, werden sie nach gasPrice sortiert, und die höheren Bieter werden zuerst bedient. Wenn das Gebot zu niedrig ist, wird die Transaktion daher nicht ausgeführt zur Bestätigung verpackt werden und das Gebot zu hoch ist Gao verliert Geld an den Initiator.

web3 und ethers.js stellen eine Methode getGasPrice() bereit, um den mittleren Gaspreis der jüngsten historischen Blöcke zu erhalten.Es gibt auch einige Drittanbieter, die Schnittstellen zur Vorhersage des Gaspreises bereitstellen, wie zum Beispiel: gasPriceOracle, ethgasAPI, etherscan gastracker, diese Dienste sind normalerweise It bezieht sich auch auf die Anzahl und den Preis der Transaktionen im aktuellen Transaktionspool, der eher referenziell ist.

Eine herkömmliche Praxis besteht darin, diese Schnittstellen zu verwenden, um dem Benutzer einen Referenzwert zu geben, und dann kann der Benutzer Feineinstellungen gemäß dem Referenzwert vornehmen.

Transaktionssignatur

Nachdem die Transaktion erstellt wurde, wird sie mit dem privaten Schlüssel signiert, der Code lautet wie folgt:

const tx = new EthereumTx(txParams)
tx.sign(privateKey)
const serializedTx = tx.serialize()

Transaktion senden (broadcasten).

Dann soll die Transaktion gesendet (ausgestrahlt) werden, der Code lautet wie folgt:

web3.eth.sendRawTransaction(serializedTx, function (err, transactionHash) {
    
    
    console.log(err);
    console.log(transactionHash);
});

Durch diese drei Schritte ist der Prozess des Sendens einer signierten Transaktion abgeschlossen.In ethers.js wird eine übersichtliche Schnittstelle bereitgestellt, um alle drei Schritte abzuschließen (Hervorhebung hinzugefügt, die Signatur wurde für uns in der Schnittstelle abgeschlossen).Die Schnittstelle sieht wie folgt aus :


 activeWallet.sendTransaction({
    
    
            to: targetAddress,
            value: amountWei,
            gasPrice: activeWallet.provider.getGasPrice(),
            gasLimit: 21000,
        }).then(function(tx) {
    
    
        });

Ich denke du magst

Origin blog.csdn.net/weixin_44846765/article/details/125790067
Empfohlen
Rangfolge