ether.js로 이더리움 web3 지갑 개발

ether.js로 이더리움 web3 지갑 개발

이 튜토리얼은 web3, Ethereum 및 지갑의 개념에 대해 너무 많이 설명하지 않고 개발에 대해 더 많이 설명합니다. 기본적으로 반응 기반이 없고 js Basic도 가능하더라도 기본적으로 이미 특정 web3 및 react 기반이 있습니다.

프로젝트 준비

우리가 사용하는 기본 프레임워크는 react+ts 기반 프레임워크인 umi입니다.
공식 웹사이트 주소

https://umijs.org/

ether.js 설치

npm install --에테르 저장

ether.js 소개: 세 가지 방법이 있습니다.

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

웹에서 직접 ethers를 사용하기로 결정했다면 다음과 같이 도입할 수 있습니다.보안상의 이유로 일반적으로 ethers-v4.min.js의 복사본을 자신의 애플리케이션 서버에 복사하는 것이 가장 좋습니다. 프로토타입 경험이 있다면 Ethers CDN을 충분히 사용해야 합니다.

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

지갑 계정 생성

web3 지갑에는 일반적으로 이러한 속성이 있습니다.

  • 계정 관리(개인 키): 계정 생성, 개인 키 가져오기 및 내보내기,
  • 정보 표시: 주로 개인 지갑의 잔액,
  • Transfer function: transfer token
    ether.js를 통해 구현해 보겠습니다.

일반적으로 HD 지갑에 포함된 암호화 알고리즘 BIP32, BIP44 및 BIP39에 의해 결정되는 두 가지 방법으로 계정을 생성할 수 있다는 것을 알고 있습니다. 가장 중요하게 사용합니다)

  • 개인 키로 32바이트 숫자를 무작위로 생성
  • 개인 키는 니모닉을 통한 결정론적 유도에 의해 획득됩니다.

난수로 개인 키 생성

이것은 우리가 위에서 언급한 개인 키를 생성하는 첫 번째 방법입니다. 32바이트 숫자가 개인 키로 무작위로 생성됩니다. ether.js를 올바르게 가져온 후에는 ether.utils.randomBytes() 메소드를 호출하여 다음을 얻을 수 있습니다. 난수를 입력한 다음 이더에서 Wallet을 호출하여 지갑을 연결하면 지갑 인스턴스를 얻습니다.

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

이때 우리가 볼 수 있는 private key는 문자 집합이며, 이를 쉽게 저장할 수 있는 문자열 패턴으로 변환하려면 다른 유틸리티 함수를 호출해야 합니다.

ethers.BigNumber.from(privateKey)._hex

그래서 우리는 이와 같은 개인 키를 얻을 수 있습니다

0x29895776b4c571de60c35e243cb157dade634bc557b9b7090a13d93e48cfa99e

ethers.BigNumber.from()의 이전 호출 방식은 ethers.utils.bigNumberify였으나 나중에 이 함수를 자주 사용하는 것으로 밝혀져 ethers가 v4에서 v5로 변경되고 추가될 때 utils와 같은 수준이었다. 이제 우리는 많은 오래된 문서에서 이 점이 수정되지 않았음을 알 수 있습니다.

니모닉으로 개인 키 생성

니모닉을 통해 개인 키를 생성하는 방법은 현재 주류에서 매우 인기있는 방법입니다.주 프로세스는 먼저 난수를 생성한 다음 난수를 통해 니모닉을 생성한 다음 니모닉을 통해 지갑을 생성하는 것입니다.

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

여기에 BIP44 키 경로에 대한 고정 표기법인 이 경로를 도입해야 할 수도 있습니다 . m / 목적' / 코인' / 계정' / 변경 / address_index m은 고정
된 5개의 미리 정의된 트리 유사 수준으로 구조를 지정합니다 . , 목적도 고정되어 있으며 값은 44(또는 0x8000002C)입니다 . 코인 유형 은 통화를 나타내고, 0은 Bitcoin을 나타내고, 1은 Bitcoin 테스트 체인을 나타내고, 60은 Ethereum 주소의 전체 통화 목록을 나타냅니다. https:///github.com/ satoshilabs/slips/blob/master/slip-0044.md 계정 은 0부터 시작하는 이 코인의 계정 인덱스를 나타냅니다. 외부(수신 주소)에 대해 상수 0을 변경 하고 내부(주소 변경이라고도 함)에 대해 상수 1을 변경합니다. External은 지갑 외부에서 볼 수 있는 주소에 사용됩니다(예: 지불을 받기 위해). 내부 체인은 지갑 외부에서 볼 수 없는 주소에 사용되며 트랜잭션 변경 사항을 반환하는 데 사용됩니다. (그래서 일반적으로 0을 사용함) address_index 0부터 시작하여 생성된 주소의 수를 나타내며, 공식 권장 사항은 각 계정의 address_index가 20을 넘지 않아야 한다는 것입니다.










EIP85에서 제안한 논의에 따르면 이더리움 지갑도 BIP44 표준을 따르며 결정된 경로는 m/44'/60'/a'/0/n
입니다. a는 계좌 번호, n은 n번째 생성 주소, 그리고 60은 SLIP44 제안 Encoding for Ethereum에서 결정됩니다. 따라서 이더리움 지갑을 개발하려면 비트코인 ​​지갑 제안 BIP32 및 BIP39도 이해해야 합니다.

콘솔에서 12자리 니모닉을 볼 수 있습니다.

후원자 기부하다 총 승리 노래 시가 늑대 스키 단단한 사업 패턴 브로콜리

지갑을 직접 생성

ether는 지갑을 직접 생성할 수 있는 매우 간단한 방법을 제공합니다.

ethers.Wallet.createRandom()

아주 간단한 방법입니다. 직접 지갑을 무작위로 생성하면 콘솔에서 이 인스턴스 데이터를 볼 수 있습니다.

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

계정 keyStore 파일 가져오기 및 내보내기

키 저장소에 대한 자세한 설명

키 저장소 파일이 필요한 이유는 무엇입니까?

개인 키는 실제로 계정을 나타냅니다. 계정을 유지하는 가장 쉬운 방법은 개인 키를 직접 저장하는 것입니다. 개인 키 파일이 도난당하면 디지털 자산이 약탈됩니다.

Keystore 파일은 암호화된 방식으로 키를 저장하는 파일로, 트랜잭션을 시작할 때 암호를 사용하여 Keystore 파일에서 개인 키를 복호화한 후 트랜잭션에 서명합니다. 해커는 키 저장소 파일과 암호를 모두 훔쳐 디지털 자산만 훔칠 수 있기 때문에 훨씬 더 안전합니다.

키 저장소 파일 생성 방법

이더리움은 대칭 암호화 알고리즘을 사용하여 개인 키를 암호화하여 Keystore 파일을 생성하므로 대칭 암호화 키(실제로 트랜잭션 시작에 필요한 암호 해독 키임에 유의) 선택이 매우 중요합니다. 이 키는 다음을 사용하여 파생됩니다. KDF 알고리즘. 따라서 Keystore 파일이 생성되는 방식을 완전히 소개하기 전에 먼저 KDF를 소개할 필요가 있습니다.

KDF를 사용하여 키 생성

암호화 KDF(키 파생 기능)의 기능은 암호에서 하나 이상의 비밀 키를 파생하는 것, 즉 암호에서 암호화 키를 생성하는 것입니다.

니모닉에서 시드를 유도하기 위한 PBKDF2 알고리즘은 KDF 함수이며, 그 원리는 솔트를 추가하고 해시 반복 횟수를 늘리는 것입니다.

Keystore에서는 Scrypt 알고리즘이 사용되며, 이를 수식으로 표현하면 파생된 Key 생성 방정식은 다음과 같습니다.

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

여기서 salt는 임의의 소금이고 dk_len은 출력 해시 값의 길이입니다. n은 CPU/메모리 오버헤드 값이며 오버헤드 값이 높을수록 계산이 더 어렵습니다. r은 블록 크기이고 p는 병렬도입니다.

Litecoin은 scrypt를 POW 알고리즘으로 사용합니다.

개인 키 암호화

KDF 알고리즘은 위의 비밀 키를 생성하는 데 사용되었습니다. 이 비밀 키는 대칭 암호화를 위한 비밀 키입니다. 여기에 사용된 대칭 암호화 알고리즘은 aes-128-ctr입니다. aes-128-ctr 암호화 알고리즘도 다음을 사용해야 합니다. 매개변수 초기화 벡터 iv.

키 저장소 파일

먼저 키 저장소 파일이 어떻게 생겼는지 살펴보고 이해하기 쉽도록 합시다.

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

각 필드를 해석해 보겠습니다.

  • 주소: 계정 주소
  • version: Keystore 파일의 버전(현재 버전 3, V3 KeyStore라고도 함)입니다.
  • 아이디 : uuid
  • crypto: 암호화 푸시백과 관련된 구성입니다.
  • cipher는 Ethereum 개인 키를 암호화하는 데 사용되는 대칭 암호화 알고리즘입니다. Aes-128-ctr이 사용됩니다.
  • cipherparams는 aes-128-ctr 암호화 알고리즘에 필요한 매개변수입니다. 여기에서 유일한 매개변수 iv가 사용됩니다.
  • ciphertext는 암호화 알고리즘에 의해 출력되는 암호문이며 향후 복호화에 필요한 입력이기도 합니다.
  • kdf: 사용할 알고리즘을 지정합니다. 여기에 scrypt가 있습니다.
  • kdfparams: scrypt 함수에 필요한 매개변수
  • mac: 비밀번호의 정확성을 확인하기 위해 사용, mac= sha3(DK[16:32], ciphertext) 다음 섹션에서는 이를 별도로 분석합니다.

Keystore 파일의 생성을 완전히 정렬해 보겠습니다.

  1. scrypt 기능을 사용하여 비밀 키 생성(비밀번호 및 해당 매개변수 기반)
  2. 이전 단계에서 생성한 비밀 키 + 계정 개인 키 + 대칭 암호화를 위한 파라미터를 사용합니다.
  3. 관련 파라미터 및 출력 암호문을 위 형식의 JSON 파일로 저장

ethers.js를 사용하여 계정 내보내기 및 가져오기

ethers.js는 지갑 객체를 생성하고 keystore 파일을 암호화하기 위해 keystore JSON을 로드하는 메소드를 직접 제공합니다. 메소드는 다음과 같습니다.

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

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

먼저 html에서 비밀번호를 가져온 다음 이 비밀번호를 매개변수로 사용하여 가져오기 및 내보내기

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

파일 가져오기

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

아니면 이렇게 뒤집으세요.

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

지갑 정보 표시 및 서명된 거래 시작

우리는 이전 소개에서 개인 키를 생성하든 지갑을 생성하든 실제로 이더리움 네트워크와 아무 관련이 없음을 알 수 있지만 실제로 전송하려면 트랜잭션 잔액을 확인하십시오. 및 기타 정보는 이더리움 네트워크에 연결되어 있어야 가능합니다.

이전에 web3와 접촉한 적이 있다면 eth 네트워크에 연결하려면 공급자가 필요하다는 것을 알아야 합니다. ether.js 자체는 공급자에 연결할 수 있는 많은 방법을 제공합니다

  • Web3Provider: MetaMask 또는 Mist와 같은 기존 web3 호환 공급자를 사용합니다.

  • EtherscanProvider 및 InfuraProvider: 자체 노드가 없는 경우 Etherscan 및 Infura의 Provider를 사용할 수 있습니다.모두 이더리움의 인프라 서비스 제공자입니다. Ethers.js는 또한 더 간단한 방법을 제공합니다: 기본 제공자를 사용하면 자동으로 도움이 됩니다. Etherscan과 Infura를 연결합니다.

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

공급자에 연결하기 위해 일반적으로 매개변수 네트워크 네트워크 이름이 있으며 값은 homestead, rinkeby, ropsten, kovan입니다.

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

지갑 세부 정보 표시: 잔액 및 Nonce 확인

이더리움 네트워크에 연결한 후 다음 API를 사용하여 네트워크에서 잔액을 요청하고 계정 거래 수를 가져올 수 있습니다.

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

서명된 거래 보내기

서명된 트랜잭션은 오프라인 트랜잭션이라고도 합니다(프로세스가 오프라인으로 수행될 수 있기 때문에: 트랜잭션은 오프라인으로 서명되고 서명된 트랜잭션은 브로드캐스트됨).

Ethers.js는 서명된 트랜잭션을 보내기 위한 매우 간결한 API를 제공하지만 간결한 API의 이면을 자세히 살펴보는 것은 여전히 ​​도움이 됩니다. 이 프로세스는 대략 세 단계로 나눌 수 있습니다.

  1. 구조 거래
  2. 거래 서명
  3. 전송(브로드캐스트) 트랜잭션

구조 거래

트랜잭션이 어떻게 생겼는지 살펴보겠습니다.

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

트랜잭션을 시작할 때 이러한 트랜잭션 구조를 구성하려면 각 필드를 채워야 합니다.
to and value: 사용자가 전송하고자 하는 대상과 금액입니다.
data:는 트랜잭션 중에 첨부된 메시지입니다.트랜잭션이 계약 주소로 시작되면 이것은 계약 기능의 실행으로 변환됩니다.참조: Ethereum ABI를 이해하는 방법
nonce: 트랜잭션 일련 번호
chainId: chain id, 서로 다른 체인(forked chain)을 구별하는 데 사용되는 id는 EIP-155에서 조회할 수 있습니다.

nonce 및 chainId의 중요한 역할은 재생 공격을 방지하는 것입니다. nonce가 없으면 수신자는 서명된 트랜잭션을 다시 브로드캐스트할 수 있습니다. chainId가 없으면 Ethereum의 트랜잭션을 Ethereum Classic에서 얻을 수 있습니다.다시 브로드캐스트합니다.

gasPrice 및 gasLimit: Gas는 거래 개시자가 채굴자에게 부과하는 수수료인 Ethereum의 작업 청구 메커니즘입니다. 위 매개변수의 설정은 상대적으로 고정되어 있지만 Gas(특히 gasPrice)의 설정은 훨씬 더 유연합니다.

gasLimit은 명령어 및 저장 공간의 예상 작업량을 나타내며 작업량이 사용되지 않으면 트랜잭션 개시자에게 반환되며 충분하지 않으면 out-of-gas 오류가 발생합니다.
일반적인 이체 거래의 경우 작업량은 고정되어 있고 gasLimit은 21000 이며 계약 실행의 gasLimit이 변경된다.어떤 사람들은 그것을 더 높은 값으로 직접 설정하고 어쨌든 반환 될 것이라고 생각할 수 있지만 계약의 경우 잘못 실행하면 먹힐 것입니다. 다행히 web3와 ethers.js 모두 Gas Limit 계산 방법을 제공하며, 이는 다음에 토큰을 보낼 때 도입될 것입니다.

GasPrice는 거래 개시자가 작업량에 대해 지불할 용의가 있는 단위 요금으로 채굴자가 거래를 선택하면 gasPrice에 따라 분류되어 높은 입찰자에게 먼저 제공되므로 입찰가가 너무 낮으면 거래가 이루어지지 않습니다. 확인을 위해 포장되고 입찰가가 너무 높습니다. Gao는 개시자에게 돈을 잃습니다.

web3 및 ethers.js는 최근 과거 블록의 중간 가스 가격을 얻기 위해 getGasPrice() 메소드를 제공합니다. 또한 가스 가격 예측을 위한 인터페이스를 제공하는 일부 타사가 있습니다: gasPriceOracle, ethgasAPI, etherscan gastracker, 이러한 서비스는 일반적으로 It 또한 현재 트랜잭션 풀에 있는 트랜잭션의 수와 가격을 참조하며 이는 보다 참조적입니다.

기존의 관행은 이러한 인터페이스를 사용하여 사용자에게 기준 값을 제공한 다음 사용자가 기준 값에 따라 미세 조정을 수행하는 것입니다.

거래 서명

트랜잭션이 생성된 후 개인 키로 서명되며 코드는 다음과 같습니다.

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

전송(브로드캐스트) 트랜잭션

그런 다음 트랜잭션을 전송(브로드캐스트)하는 것입니다. 코드는 다음과 같습니다.

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

이 세 단계를 거쳐 서명된 트랜잭션을 보내는 프로세스가 완료됩니다. 세 단계를 모두 완료할 수 있는 간결한 인터페이스가 ethers.js에 제공됩니다(강조 추가, 인터페이스에서 서명이 완료됨). 인터페이스는 다음과 같습니다. :


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

Guess you like

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