Desarrolle una billetera Ethereum web3 con ether.js

Desarrolle una billetera Ethereum web3 con ether.js

Este tutorial no explicará demasiado sobre los conceptos de web3, Ethereum y billeteras, sino más sobre el desarrollo, y por defecto ya tiene una determinada base de web3 y reaccionar, incluso si no hay una base de reacción y js Basic también es posible.

Preparación del proyecto

El framework básico que utilizamos es umi, un framework basado en react+ts
Dirección del sitio web oficial

https://umijs.org/

instalar ether.js

npm install --save éteres

Presentamos ether.js: aquí hay tres formas

es3:
var éteres = require('éteres');
es5/es6
const éteres = require('éteres');
javascript/typescript es6
import {éteres} de 'éteres';

Si decide usar ethers directamente en la web, puede introducirlo así. Por razones de seguridad, generalmente es mejor copiar una copia de ethers-v4.min.js a su propio servidor de aplicaciones. Si tiene una experiencia de prototipo, debe usar la CDN de Ethers lo suficiente.

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

Crear una cuenta de billetera

Las billeteras web3 generalmente tienen tales propiedades

  • Gestión de cuentas (clave privada): crear una cuenta, importar y exportar claves privadas,
  • Visualización de información: principalmente el saldo de la billetera personal,
  • Transfer function: token de transferencia
    Lo implementaremos a través de ether.js

Sabemos que generalmente hay dos formas de crear una cuenta, que está determinada por los algoritmos de encriptación BIP32, BIP44 y BIP39 involucrados en la billetera HD (si aún no lo sabe, recuerde lo siguiente, después de todo, usted lo usará lo más importante)

  • Genera aleatoriamente un número de 32 bytes como clave privada
  • La clave privada se obtiene por derivación determinista a través de la mnemotécnica

Generar clave privada con número aleatorio

Este es el primer método para generar una clave privada que mencionamos anteriormente. Se genera aleatoriamente un número de 32 bytes como clave privada. Después de importar ether.js correctamente, podemos llamar al método ether.utils.randomBytes() para obtener Un número aleatorio, y luego llamamos a Wallet en ether para conectar la billetera, obtenemos una instancia de billetera

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

En este momento, la clave privada que vemos es un conjunto de caracteres. Si queremos convertirlo en un patrón de cadena que podamos guardar fácilmente, debemos llamar a otra función de utilidad.

ethers.BigNumber.from(privateKey)._hex

Entonces podemos obtener una clave privada como esta

0x29895776b4c571de60c35e243cb157dade634bc557b9b7090a13d93e48cfa99e

El método de llamada anterior de ethers.BigNumber.from() era ethers.utils.bigNumberify Más tarde, debido a que se descubrió que esta función se usaba con frecuencia, estaba al mismo nivel que utils cuando ethers cambió de v4 a v5 y agregó más métodos Ahora podemos ver que en muchos documentos antiguos, este punto no ha sido modificado

Generar clave privada por mnemónico

El método de generar una clave privada a través de un mnemónico es un método muy popular en nuestra corriente principal actual. El proceso principal es generar primero un número aleatorio, luego generar un mnemónico a través del número aleatorio y luego crear una billetera a través del mnemónico.

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);

Puede ser necesario introducir aquí esta ruta, que es una notación fija para una ruta de clave BIP 44.
Especifica una estructura con 5 niveles predefinidos en forma de árbol:
m / propósito' / moneda' / cuenta' / cambio / índice_dirección
m es Fijo , El propósito también es fijo, el valor es 44 (o 0x8000002C)
El tipo
de moneda representa la moneda, 0 representa Bitcoin, 1 representa la cadena de prueba de Bitcoin y 60 representa
la lista completa de monedas de Ethereum Dirección: https:/ /github.com/ satoshilabs/slips/blob/master/slip-0044.md
Account
representa el índice de cuenta de esta moneda, comenzando desde 0.
Cambie la
constante 0 para externa (dirección de recepción), la constante 1 para interna (también conocida como cambio de dirección). Externo se usa para direcciones que son visibles fuera de la billetera (por ejemplo, para recibir pagos). Las cadenas internas se usan para direcciones que no son visibles fuera de la billetera y se usan para devolver cambios en las transacciones. (Por lo tanto, generalmente se usa 0)
address_index
Este es el índice de direcciones. A partir de 0, representa la cantidad de direcciones generadas. La recomendación oficial es que el índice de direcciones debajo de cada cuenta no debe exceder 20.

De acuerdo con la discusión propuesta por EIP85, la billetera Ethereum también sigue el estándar BIP44, y la ruta determinada es m/44'/60'/a'/0/n
a representa el número de cuenta, n es la n-ésima dirección generada y 60 se determina en la propuesta de codificación SLIP44 para Ethereum. Por lo tanto, si queremos desarrollar una billetera Ethereum, también debemos comprender las propuestas de billetera Bitcoin BIP32 y BIP39.

Podemos ver nuestro nemotécnico de doce dígitos en la consola

patrocinador donar pistolas victoria canción cigarro lobos esquí sólido negocio patrones brócoli

Crear una billetera directamente

ether proporciona un método muy simple que nos permite crear una billetera directamente

ethers.Wallet.createRandom()

Es un método tan simple, crea directamente una billetera al azar, y luego podemos ver los datos de esta instancia en la consola

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

Importación y exportación de archivos keyStore de cuenta

Explicación detallada del almacén de claves

¿Por qué necesita un archivo de almacén de claves?

La clave privada en realidad representa una cuenta. La forma más fácil de mantener la cuenta es guardar la clave privada directamente. Si el archivo de la clave privada es robado, nuestros activos digitales serán saqueados.

El archivo Keystore es un archivo que almacena la clave de forma cifrada. Al iniciar una transacción, la clave privada se descifra del archivo Keystore utilizando la contraseña y luego se firma la transacción. Esto será mucho más seguro porque los piratas informáticos solo pueden robar nuestros activos digitales robando tanto el archivo del almacén de claves como la contraseña.

Cómo se genera el archivo de almacén de claves

Ethereum usa un algoritmo de cifrado simétrico para cifrar la clave privada para generar el archivo Keystore. Por lo tanto, la elección de la clave de cifrado simétrico (tenga en cuenta que en realidad es la clave de descifrado requerida para iniciar transacciones) es muy crítica. Esta clave se obtiene utilizando el Algoritmo KDF. Por lo tanto, antes de presentar completamente cómo se genera el archivo Keystore, primero es necesario presentar KDF.

Generar claves usando KDF

Criptografía KDF (key derivation functions), su función es derivar una o más claves secretas a partir de una contraseña, es decir, generar claves de cifrado a partir de una contraseña.

El algoritmo PBKDF2 para derivar la semilla del mnemotécnico es una función KDF, cuyo principio es agregar sal y aumentar el número de iteraciones de hash.

En Keystore, se utiliza el algoritmo Scrypt.Si se representa mediante una fórmula, la ecuación de generación de clave derivada es:

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

donde salt es una sal aleatoria y dk_len es la longitud del valor hash de salida. n es el valor de sobrecarga de CPU/Memoria, cuanto mayor sea el valor de sobrecarga, más difícil será el cálculo. r es el tamaño del bloque y p es el grado de paralelismo.

Litecoin usa scrypt como su algoritmo POW

Cifrar la clave privada

El algoritmo KDF se ha utilizado para generar una clave secreta anterior. Esta clave secreta es la clave secreta para el cifrado simétrico. El algoritmo de cifrado simétrico utilizado aquí es aes-128-ctr. El algoritmo de cifrado aes-128-ctr también necesita utilizar un vector de inicialización de parámetros iv.

archivo de almacén de claves

Echemos un vistazo primero a cómo se ve el archivo del almacén de claves, para que sea más fácil de entender para nosotros.

{
    
      
   "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
}

Interpretemos cada campo:

  • dirección: dirección de la cuenta
  • versión: La versión del archivo Keystore, actualmente la versión 3, también conocida como V3 KeyStore.
  • identificación: uuid
  • crypto: Configuración relacionada con el pushback criptográfico.
  • cipher es un algoritmo de cifrado simétrico que se utiliza para cifrar claves privadas de Ethereum. Se utiliza Aes-128-ctr.
  • cipherparams son los parámetros requeridos por el algoritmo de cifrado aes-128-ctr. Aquí, se utiliza el único parámetro iv.
  • El texto cifrado es el texto cifrado generado por el algoritmo de cifrado y también es la entrada requerida para el descifrado futuro.
  • kdf: especifique qué algoritmo usar, aquí está scrypt.
  • kdfparams: parámetros requeridos por la función scrypt
  • mac: utilizado para verificar la corrección de la contraseña, mac= sha3(DK[16:32], ciphertext) La siguiente sección lo analiza por separado.

Arreglemos por completo la generación del archivo Keystore:

  1. Genere la clave secreta usando la función scrypt (basada en la contraseña y los parámetros correspondientes)
  2. Utilice la clave secreta generada en el paso anterior + clave privada de la cuenta + parámetros para el cifrado simétrico.
  3. Guarde los parámetros relevantes y el texto cifrado de salida como archivos JSON en el formato anterior

Use ethers.js para exportar e importar cuentas

ethers.js proporciona directamente métodos para cargar JSON de almacén de claves para crear objetos de cartera y cifrar archivos de almacén de claves. Los métodos son los siguientes:

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

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

Primero obtenemos la contraseña de html y luego usamos esta contraseña como parámetro para importar y exportar

 <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);
      }
    });
  };

importación de archivos

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]);

O invertirlo así

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

Mostrar información de billetera e iniciar transacciones firmadas

De hecho, podemos encontrar que en la introducción anterior, ya sea que estemos generando una clave privada o generando una billetera, en realidad encontraremos que no tiene nada que ver con la red Ethereum, pero si realmente queremos transferir, verifique el saldo de transacciones. y otra información, debe estar conectado a la red Ethereum para poder hacerlo,

Si ha estado en contacto con web3 anteriormente, entonces debe saber que conectarse a la red eth debe requerir un proveedor, ether.js en sí mismo proporciona muchas formas de conectarse al proveedor.

  • Web3Provider: use un proveedor existente compatible con web3, como MetaMask o Mist.

  • EtherscanProvider e InfuraProvider: si no tiene su propio nodo, puede usar Etherscan e Infura's Provider. Todos son proveedores de servicios de infraestructura de Ethereum. Ethers.js también proporciona una forma más sencilla: usar un proveedor predeterminado, que ayudará automáticamente conectamos Etherscan e Infura.

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

Para conectarse al proveedor, generalmente hay un nombre de red de red de parámetros, los valores son: homestead, rinkeby, ropsten, kovan.

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

Mostrar detalles de la billetera: consultar saldo y Nonce

Después de conectarse a la red Ethereum, puede solicitar el saldo de la red y obtener la cantidad de transacciones de la cuenta, utilizando la siguiente API:

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

Enviar transacción firmada

Una transacción firmada también se denomina transacción sin conexión (porque el proceso se puede realizar sin conexión: la transacción se firma sin conexión y la transacción firmada se transmite).

Aunque Ethers.js proporciona una API muy concisa para enviar transacciones firmadas, sigue siendo útil explorar los detalles detrás de la API concisa. Este proceso se puede dividir aproximadamente en tres pasos:

  1. transacción de estructura
  2. firma de transacción
  3. enviar (difundir) transacción

transacción de estructura

Echemos un vistazo a cómo se ve una transacción:

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

Al iniciar una transacción, es necesario completar cada campo para construir dicha estructura de transacción.
a y valor: bien entendido, es el destino y la cantidad que el usuario quiere transferir.
datos: es el mensaje adjunto durante la transacción. Si la transacción se inicia en la dirección del contrato, esto se transformará en la ejecución de la función del contrato. Consulte: Cómo entender el Ethereum ABI
nonce: número de serie de la transacción
chainId: cadena id, usado para distinguir diferentes El id de la cadena (cadena bifurcada) se puede consultar en EIP-155.

Una función importante de nonce y chainId es evitar ataques de repetición. Si no hay nonce, el destinatario puede transmitir la transacción firmada nuevamente. Si no hay chainId, la transacción en Ethereum se puede obtener en Ethereum Classic. Transmitir nuevamente.

gasPrice y gasLimit: El gas es el mecanismo de facturación de trabajo de Ethereum, que es la tarifa que cobra el iniciador de la transacción a los mineros. La configuración de los parámetros anteriores es relativamente fija, mientras que la configuración de Gas (especialmente gasPrice) es mucho más flexible.

gasLimit indica la carga de trabajo estimada de instrucciones y espacio de almacenamiento. Si la carga de trabajo no se utiliza, se devolverá al iniciador de la transacción. Si no es suficiente, se producirá un error de falta de gas.
Para una transacción de transferencia ordinaria, la carga de trabajo es fija, el gasLimit es 21000 y el gasLimit de la ejecución del contrato cambia. Algunas personas pueden pensar que se establece directamente en un valor más alto y se devolverá de todos modos, pero si el contrato se ejecuta incorrectamente, se comerá todo el gas. Afortunadamente, tanto web3 como ethers.js brindan métodos para calcular el límite de gas, que se presentarán la próxima vez que envíe tokens.

GasPrice es la tarifa unitaria que el iniciador de la transacción está dispuesto a pagar por la carga de trabajo. Cuando los mineros seleccionan transacciones, se clasifican según el gasPrice, y los postores más altos son atendidos primero. Por lo tanto, si la oferta es demasiado baja, la transacción no ser empaquetado para la confirmación, y la oferta es demasiado alta. Gao pierde dinero con el iniciador.

web3 y ethers.js proporcionan un método getGasPrice() para obtener el precio medio del gas de bloques históricos recientes. También hay algunos terceros que proporcionan interfaces para predecir el precio del gas, como: gasPriceOracle, ethgasAPI, etherscan gastracker, estos servicios generalmente también se referirá al número y precio de las transacciones en el pool de transacciones actual, que es más referencial.

Una práctica convencional es usar estas interfaces para dar al usuario un valor de referencia, y luego el usuario puede hacer ajustes finos de acuerdo con el valor de referencia.

firma de transacción

Una vez construida la transacción, se firma con la clave privada, el código es el siguiente:

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

enviar (difundir) transacción

Luego se trata de enviar (transmitir) la transacción, el código es el siguiente:

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

A través de estos tres pasos, se completa el proceso de envío de una transacción firmada. Se proporciona una interfaz concisa en ethers.js para completar los tres pasos (énfasis agregado, la firma se ha completado para nosotros en la interfaz). La interfaz es la siguiente :


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

Supongo que te gusta

Origin blog.csdn.net/weixin_44846765/article/details/125790067
Recomendado
Clasificación