diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/Ethereum_Smart_Contract_Introduction.md b/docs/tech/Ethereum_Smart_Contract_Introduction/Ethereum_Smart_Contract_Introduction.md new file mode 100644 index 0000000..46932b2 --- /dev/null +++ b/docs/tech/Ethereum_Smart_Contract_Introduction/Ethereum_Smart_Contract_Introduction.md @@ -0,0 +1,403 @@ + +# 以太坊智能合约初探 + +*Author: [Autosaida](https://github.com/Autosaida); Written: 10/2022* + +## 前言 + +智能合约作为以太坊最突出的创新点,让区块链拥有了更多可能。 + +什么是智能合约,智能合约的整个生命周期是怎样的,具体是如何部署在链上的,又是如何运行的,其变量又是如何存储的,下面对相关问题进行研究学习。 + +## 简介 + +宏观上来看,智能合约可以理解为一个存储在链上、运行在以太坊虚拟机EVM中的实例化对象。且根据区块链的特性,这个实例化对象将永远存在区块链网络中(除非进行自毁),一旦部署,智能合约的代码就无法改变,修改代码的唯一方法是部署新实例。 + +智能合约通常用高级语言编写,例如solidity。代码经过编译生成字节码,可以在基于栈的虚拟机EVM中执行。合约对象拥有自己的函数(类方法)和状态变量(类变量),但显然对象并不是一个程序,并没有程序入口,因此只有被调用时才能执行其代码。 + +具体来讲,**智能合约是以太坊中的一种账户**(CA,Contract Account),即可以拥有以太币余额、可发起交易的实体,另一种账户就是由用户私钥控制的外部账户(EOA,Externally Owned Account)。 + +![](img/2022-10-21-20-18-44.png) + +如上图所示,两种账户都有自己的地址、余额以及nonce,除此之外合约账户还有存储和代码部分,对应合约实例的状态变量和函数。 + +而以太坊被视为由交易驱动的状态机,交易是由外部帐户发出、进行签名、且最终更新以太坊网络状态的操作。 + +![](img/2022-10-21-20-30-19.png) + +如上图所示,以太坊有几种不同类型的交易: + +1. 常规交易:从一个帐户到另一个帐户的交易,即进行ETH转账,目标可以是外部账户也可以是合约账户。 +2. 合约部署交易:没有目标地址`to`字段的交易,即外部账户创建智能合约,交易的`data`字段即为合约的部署、执行代码。 +3. 执行合约:即外部账户与已经部署在链上的智能合约进行交互,调用其合约函数的交易。 + +因此,**智能合约也是参与交易,对区块链进行状态转换的重要组成部分**。 + +下面将以一个简单的合约为例,探索智能合约的整个生命周期:编译->部署->执行->销毁。 + +## 编译 + +智能合约由高级语言实现后,需要首先经编译器编译为EVM可执行的字节码。 + +这里编译的智能合约如下。 + +``` solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import "hardhat/console.sol"; + +contract Greeter { + string public greeting; + + constructor(string memory _greeting) { + greeting = _greeting; + } + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } +} +``` + +### bytecode + +使用hardhat编译后可以得到两个字节序列,bytecode和deployedBytecode。 + +``` json +"bytecode": "0x60806040523480156200001157600080fd5b5060405162000ca938038062000ca98339818101604052810190620000379190620001e3565b80600090816200004891906200047f565b505062000566565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620000b9826200006e565b810181811067ffffffffffffffff82111715620000db57620000da6200007f565b5b80604052505050565b6000620000f062000050565b9050620000fe8282620000ae565b919050565b600067ffffffffffffffff8211156200012157620001206200007f565b5b6200012c826200006e565b9050602081019050919050565b60005b83811015620001595780820151818401526020810190506200013c565b60008484015250505050565b60006200017c620001768462000103565b620000e4565b9050828152602081018484840111156200019b576200019a62000069565b5b620001a884828562000139565b509392505050565b600082601f830112620001c857620001c762000064565b5b8151620001da84826020860162000165565b91505092915050565b600060208284031215620001fc57620001fb6200005a565b5b600082015167ffffffffffffffff8111156200021d576200021c6200005f565b5b6200022b84828501620001b0565b91505092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200028757607f821691505b6020821081036200029d576200029c6200023f565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620003077fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002c8565b620003138683620002c8565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620003606200035a62000354846200032b565b62000335565b6200032b565b9050919050565b6000819050919050565b6200037c836200033f565b620003946200038b8262000367565b848454620002d5565b825550505050565b600090565b620003ab6200039c565b620003b881848462000371565b505050565b5b81811015620003e057620003d4600082620003a1565b600181019050620003be565b5050565b601f8211156200042f57620003f981620002a3565b6200040484620002b8565b8101602085101562000414578190505b6200042c6200042385620002b8565b830182620003bd565b50505b505050565b600082821c905092915050565b6000620004546000198460080262000434565b1980831691505092915050565b60006200046f838362000441565b9150826002028217905092915050565b6200048a8262000234565b67ffffffffffffffff811115620004a657620004a56200007f565b5b620004b282546200026e565b620004bf828285620003e4565b600060209050601f831160018114620004f75760008415620004e2578287015190505b620004ee858262000461565b8655506200055e565b601f1984166200050786620002a3565b60005b8281101562000531578489015182556001820191506020850194506020810190506200050a565b868310156200055157848901516200054d601f89168262000441565b8355505b6001600288020188555050505b505050505050565b61073380620005766000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063a413686214610046578063cfae321714610062578063ef690cc014610080575b600080fd5b610060600480360381019061005b919061032b565b61009e565b005b61006a6100b1565b60405161007791906103f3565b60405180910390f35b610088610143565b60405161009591906103f3565b60405180910390f35b80600090816100ad919061062b565b5050565b6060600080546100c090610444565b80601f01602080910402602001604051908101604052809291908181526020018280546100ec90610444565b80156101395780601f1061010e57610100808354040283529160200191610139565b820191906000526020600020905b81548152906001019060200180831161011c57829003601f168201915b5050505050905090565b6000805461015090610444565b80601f016020809104026020016040519081016040528092919081815260200182805461017c90610444565b80156101c95780601f1061019e576101008083540402835291602001916101c9565b820191906000526020600020905b8154815290600101906020018083116101ac57829003601f168201915b505050505081565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610238826101ef565b810181811067ffffffffffffffff8211171561025757610256610200565b5b80604052505050565b600061026a6101d1565b9050610276828261022f565b919050565b600067ffffffffffffffff82111561029657610295610200565b5b61029f826101ef565b9050602081019050919050565b82818337600083830152505050565b60006102ce6102c98461027b565b610260565b9050828152602081018484840111156102ea576102e96101ea565b5b6102f58482856102ac565b509392505050565b600082601f830112610312576103116101e5565b5b81356103228482602086016102bb565b91505092915050565b600060208284031215610341576103406101db565b5b600082013567ffffffffffffffff81111561035f5761035e6101e0565b5b61036b848285016102fd565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156103ae578082015181840152602081019050610393565b60008484015250505050565b60006103c582610374565b6103cf818561037f565b93506103df818560208601610390565b6103e8816101ef565b840191505092915050565b6000602082019050818103600083015261040d81846103ba565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061045c57607f821691505b60208210810361046f5761046e610415565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026104d77fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261049a565b6104e1868361049a565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600061052861052361051e846104f9565b610503565b6104f9565b9050919050565b6000819050919050565b6105428361050d565b61055661054e8261052f565b8484546104a7565b825550505050565b600090565b61056b61055e565b610576818484610539565b505050565b5b8181101561059a5761058f600082610563565b60018101905061057c565b5050565b601f8211156105df576105b081610475565b6105b98461048a565b810160208510156105c8578190505b6105dc6105d48561048a565b83018261057b565b50505b505050565b600082821c905092915050565b6000610602600019846008026105e4565b1980831691505092915050565b600061061b83836105f1565b9150826002028217905092915050565b61063482610374565b67ffffffffffffffff81111561064d5761064c610200565b5b6106578254610444565b61066282828561059e565b600060209050601f8311600181146106955760008415610683578287015190505b61068d858261060f565b8655506106f5565b601f1984166106a386610475565b60005b828110156106cb578489015182556001820191506020850194506020810190506106a6565b868310156106e857848901516106e4601f8916826105f1565b8355505b6001600288020188555050505b50505050505056fea26469706673582212209066875f32a43c988b58ada125306c30a7a86fb49abd7bf8abf4a479062fa4a564736f6c63430008110033", +"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063a413686214610046578063cfae321714610062578063ef690cc014610080575b600080fd5b610060600480360381019061005b919061032b565b61009e565b005b61006a6100b1565b60405161007791906103f3565b60405180910390f35b610088610143565b60405161009591906103f3565b60405180910390f35b80600090816100ad919061062b565b5050565b6060600080546100c090610444565b80601f01602080910402602001604051908101604052809291908181526020018280546100ec90610444565b80156101395780601f1061010e57610100808354040283529160200191610139565b820191906000526020600020905b81548152906001019060200180831161011c57829003601f168201915b5050505050905090565b6000805461015090610444565b80601f016020809104026020016040519081016040528092919081815260200182805461017c90610444565b80156101c95780601f1061019e576101008083540402835291602001916101c9565b820191906000526020600020905b8154815290600101906020018083116101ac57829003601f168201915b505050505081565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610238826101ef565b810181811067ffffffffffffffff8211171561025757610256610200565b5b80604052505050565b600061026a6101d1565b9050610276828261022f565b919050565b600067ffffffffffffffff82111561029657610295610200565b5b61029f826101ef565b9050602081019050919050565b82818337600083830152505050565b60006102ce6102c98461027b565b610260565b9050828152602081018484840111156102ea576102e96101ea565b5b6102f58482856102ac565b509392505050565b600082601f830112610312576103116101e5565b5b81356103228482602086016102bb565b91505092915050565b600060208284031215610341576103406101db565b5b600082013567ffffffffffffffff81111561035f5761035e6101e0565b5b61036b848285016102fd565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156103ae578082015181840152602081019050610393565b60008484015250505050565b60006103c582610374565b6103cf818561037f565b93506103df818560208601610390565b6103e8816101ef565b840191505092915050565b6000602082019050818103600083015261040d81846103ba565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061045c57607f821691505b60208210810361046f5761046e610415565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026104d77fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261049a565b6104e1868361049a565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600061052861052361051e846104f9565b610503565b6104f9565b9050919050565b6000819050919050565b6105428361050d565b61055661054e8261052f565b8484546104a7565b825550505050565b600090565b61056b61055e565b610576818484610539565b505050565b5b8181101561059a5761058f600082610563565b60018101905061057c565b5050565b601f8211156105df576105b081610475565b6105b98461048a565b810160208510156105c8578190505b6105dc6105d48561048a565b83018261057b565b50505b505050565b600082821c905092915050565b6000610602600019846008026105e4565b1980831691505092915050565b600061061b83836105f1565b9150826002028217905092915050565b61063482610374565b67ffffffffffffffff81111561064d5761064c610200565b5b6106578254610444565b61066282828561059e565b600060209050601f8311600181146106955760008415610683578287015190505b61068d858261060f565b8655506106f5565b601f1984166106a386610475565b60005b828110156106cb578489015182556001820191506020850194506020810190506106a6565b868310156106e857848901516106e4601f8916826105f1565b8355505b6001600288020188555050505b50505050505056fea26469706673582212209066875f32a43c988b58ada125306c30a7a86fb49abd7bf8abf4a479062fa4a564736f6c63430008110033", +``` + +可以注意到,deployedBytecode是bytecode的子集,位于bytecode的后半部分,其前一个字节为0xfe,是evm中的INVALID指令,将bytecode分隔为前后两部分。 + +前一部分即initial code,或者叫creation bytecode,后一部分为deployedBytecode,或者说是runtime bytecode。 + +初始化代码,即创建智能合约的代码,用于将运行时代码存储在链上,需要执行构造函数、初始化相关状态变量,并计算运行时代码(将一大块数据读入内存),最终将其返回给EVM。显然初始化工作只需要在创建时执行一次,并不需要存储在链上。 + +运行时字节码,是最终存储在链上的合约对象的代码,可以在后续的交易中被执行。 + +上述bytecode就是初始化代码和运行时字节码的合并。 + +### ABI + +除了字节码,还得到了合约接口的ABI。 + +``` json +"abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "greet", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "greeting", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "name": "setGreeting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], +``` + +合约应用二进制接口Application Binary Interface(ABI)是从区块链外部与合约进行交互或者合约与合约间进行交互的一种标准方式。使用的数据会根据其类型按照规定的方法进行编码。 + +更通俗的理解,包含两方面内容: +1. ABI是合约接口的说明。 +2. ABI定义与合约进行交互的数据编码规则。 + +ABI是合约接口的说明,内容包括合约的函数名称、参数名称、参数类型等信息。这些信息以JSON格式保存,如上述编译得到的abi数据。知道这些信息可以便于与合约交互。相关字段含义可见[abi-spec-json](https://docs.soliditylang.org/en/latest/abi-spec.html#json)。 + +而具体来说如何将调用信息传给evm,显然evm需要接收字节序列,那么如何将要调用的函数名称、具体的参数信息传给evm,让虚拟机正确执行我们想要执行的代码逻辑,就是ABI的另一个作用,定义与合约进行交互的数据编码规则,具体可见[abi-spec](https://docs.soliditylang.org/en/latest/abi-spec.html)。 + +## 部署 + +合约编译完成后,就可以部署在链上,供外部用户直接或间接的使用。 + +部署代码如下。 + +``` typescript +import { ethers } from "hardhat"; +import { Greeter__factory } from "../typechain/factories/Greeter__factory"; + +async function main() { + const [signer] = await ethers.getSigners(); + console.log(signer.address); + + let G = await new Greeter__factory(signer); + let g = await G.deploy("123"); + g = await g.deployed(); + + let b = "0x60806040523480156200001157600080fd5b5060405162000ca938038062000ca98339818101604052810190620000379190620001e3565b80600090816200004891906200047f565b505062000566565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620000b9826200006e565b810181811067ffffffffffffffff82111715620000db57620000da6200007f565b5b80604052505050565b6000620000f062000050565b9050620000fe8282620000ae565b919050565b600067ffffffffffffffff8211156200012157620001206200007f565b5b6200012c826200006e565b9050602081019050919050565b60005b83811015620001595780820151818401526020810190506200013c565b60008484015250505050565b60006200017c620001768462000103565b620000e4565b9050828152602081018484840111156200019b576200019a62000069565b5b620001a884828562000139565b509392505050565b600082601f830112620001c857620001c762000064565b5b8151620001da84826020860162000165565b91505092915050565b600060208284031215620001fc57620001fb6200005a565b5b600082015167ffffffffffffffff8111156200021d576200021c6200005f565b5b6200022b84828501620001b0565b91505092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200028757607f821691505b6020821081036200029d576200029c6200023f565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620003077fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002c8565b620003138683620002c8565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620003606200035a62000354846200032b565b62000335565b6200032b565b9050919050565b6000819050919050565b6200037c836200033f565b620003946200038b8262000367565b848454620002d5565b825550505050565b600090565b620003ab6200039c565b620003b881848462000371565b505050565b5b81811015620003e057620003d4600082620003a1565b600181019050620003be565b5050565b601f8211156200042f57620003f981620002a3565b6200040484620002b8565b8101602085101562000414578190505b6200042c6200042385620002b8565b830182620003bd565b50505b505050565b600082821c905092915050565b6000620004546000198460080262000434565b1980831691505092915050565b60006200046f838362000441565b9150826002028217905092915050565b6200048a8262000234565b67ffffffffffffffff811115620004a657620004a56200007f565b5b620004b282546200026e565b620004bf828285620003e4565b600060209050601f831160018114620004f75760008415620004e2578287015190505b620004ee858262000461565b8655506200055e565b601f1984166200050786620002a3565b60005b8281101562000531578489015182556001820191506020850194506020810190506200050a565b868310156200055157848901516200054d601f89168262000441565b8355505b6001600288020188555050505b505050505050565b61073380620005766000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063a413686214610046578063cfae321714610062578063ef690cc014610080575b600080fd5b610060600480360381019061005b919061032b565b61009e565b005b61006a6100b1565b60405161007791906103f3565b60405180910390f35b610088610143565b60405161009591906103f3565b60405180910390f35b80600090816100ad919061062b565b5050565b6060600080546100c090610444565b80601f01602080910402602001604051908101604052809291908181526020018280546100ec90610444565b80156101395780601f1061010e57610100808354040283529160200191610139565b820191906000526020600020905b81548152906001019060200180831161011c57829003601f168201915b5050505050905090565b6000805461015090610444565b80601f016020809104026020016040519081016040528092919081815260200182805461017c90610444565b80156101c95780601f1061019e576101008083540402835291602001916101c9565b820191906000526020600020905b8154815290600101906020018083116101ac57829003601f168201915b505050505081565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610238826101ef565b810181811067ffffffffffffffff8211171561025757610256610200565b5b80604052505050565b600061026a6101d1565b9050610276828261022f565b919050565b600067ffffffffffffffff82111561029657610295610200565b5b61029f826101ef565b9050602081019050919050565b82818337600083830152505050565b60006102ce6102c98461027b565b610260565b9050828152602081018484840111156102ea576102e96101ea565b5b6102f58482856102ac565b509392505050565b600082601f830112610312576103116101e5565b5b81356103228482602086016102bb565b91505092915050565b600060208284031215610341576103406101db565b5b600082013567ffffffffffffffff81111561035f5761035e6101e0565b5b61036b848285016102fd565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156103ae578082015181840152602081019050610393565b60008484015250505050565b60006103c582610374565b6103cf818561037f565b93506103df818560208601610390565b6103e8816101ef565b840191505092915050565b6000602082019050818103600083015261040d81846103ba565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061045c57607f821691505b60208210810361046f5761046e610415565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026104d77fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261049a565b6104e1868361049a565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b600061052861052361051e846104f9565b610503565b6104f9565b9050919050565b6000819050919050565b6105428361050d565b61055661054e8261052f565b8484546104a7565b825550505050565b600090565b61056b61055e565b610576818484610539565b505050565b5b8181101561059a5761058f600082610563565b60018101905061057c565b5050565b601f8211156105df576105b081610475565b6105b98461048a565b810160208510156105c8578190505b6105dc6105d48561048a565b83018261057b565b50505b505050565b600082821c905092915050565b6000610602600019846008026105e4565b1980831691505092915050565b600061061b83836105f1565b9150826002028217905092915050565b61063482610374565b67ffffffffffffffff81111561064d5761064c610200565b5b6106578254610444565b61066282828561059e565b600060209050601f8311600181146106955760008415610683578287015190505b61068d858261060f565b8655506106f5565b601f1984166106a386610475565b60005b828110156106cb578489015182556001820191506020850194506020810190506106a6565b868310156106e857848901516106e4601f8916826105f1565b8355505b6001600288020188555050505b50505050505056fea26469706673582212209066875f32a43c988b58ada125306c30a7a86fb49abd7bf8abf4a479062fa4a564736f6c63430008110033"; + let coder = new ethers.utils.AbiCoder(); + let s = coder.encode(["string"],["123"]).substring(2); + b += s; + + const tx = await signer.sendTransaction({ + data: b, + }); + + console.log(g.deployTransaction.data == b); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +``` + +这里使用两种方式,先是直接使用`ContractFactory`进行部署,之后则通过发送交易的方式来部署,实质上是一样的。 + +如前面所说,合约的创建实际上就是没有目标地址`to`字段的交易,交易的`data`字段即为合约部署、执行的bytecode。这里构造函数需要传入参数,故需要先对传入参数进行abi编码,再直接附加到bytecode后即可。可以验证两种部署方式最终的`data`是一致的。 + +而最终的合约地址是如何得到的,可参考[How is the address of an Ethereum contract computed](https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed)。 + +下面简单分析部署时执行的initial code,使用[在线网站](https://ethervm.io/decompile),直接阅读反编译得到的伪代码,或者参考[evm文档](https://www.evm.codes/),阅读反汇编得到的指令。 + +![](img/2022-10-24-10-12-45.png) + +首先初始化空闲内存指针为0x80,之后判断msg.value的值是否为0,由于这里的合约构造函数并没有声明为`payable`,因此不能接收以太币,value不为0则revert。 + +如下面这样进行部署就会revert。 + +``` typescript +let g = await G.deploy("123", {value : ethers.utils.parseEther("1")}); +let p = ethers.provider; +g = await g.deployed(); +console.log(await p.getBalance(g.address)); +``` + +之后的temp1,将传入的code序列的长度减去0xca9,0xca9就是前面的bytecode的长度,因此这里temp1得到的实际上是传入的参数字节序列的长度。 + +之后调用了两个比较复杂的`func_01E3`和`func_047F`函数。主要就是执行构造函数,即将传入的字符串参数存储到对应的位置。这里greeting状态变量显然应该存储在slot0中,故需要计算出对应的位置,再进行存储。 + +最后将运行时代码读入内存。再返回给EVM。 + +## 执行 + +合约部署完成后,就可以被外部账户直接或间接的调用,在EVM中执行代码。 + +如下所示,可以直接在部署后的合约实例上进行相关调用。 + +``` typescript + const [signer] = await ethers.getSigners(); + + let G = await new Greeter__factory(signer); + let g = await G.deploy("123"); + g = await g.deployed(); + + console.log(await g.greet()); + let tx = await g.setGreeting("456"); + await tx.wait(); + console.log(tx.data); + console.log(await g.greet()); +``` + +这里通过`tx.data`查看调用合约时交易发送的calldata。 + +``` +0xa4136862000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000033435360000000000000000000000000000000000000000000000000000000000 +``` + +实际上就是根据ABI规定的标准进行编码后得到的数据。 + +前4个字节为函数选择器,标识了将要执行目标合约的哪个函数,后面则是使用的参数。 + +而函数选择器实际上就是函数签名的keccak256哈希的前4个字节。 + +因此使用下面两种方式,直接构造calldata,然后发送交易,来调用合约的目标函数,效果也是一样的。 + +``` typescript + const itace = new ethers.utils.Interface(["function setGreeting(string)"]); + const calldata1 = itace.encodeFunctionData("setGreeting", ["789"]); + console.log(calldata1); + tx = await signer.sendTransaction({ + to:g.address, + data:calldata1, + }) + await tx.wait(); + console.log(await g.greet()); + + + let func_selector = ethers.utils.id("setGreeting(string)").substring(0, 10); + let coder = new ethers.utils.AbiCoder(); + let data = coder.encode(["string"],["123"]).substring(2); + let calldata2 = func_selector+data; + console.log(calldata2); + tx = await signer.sendTransaction({ + to:g.address, + data:calldata2, + }) + await tx.wait(); + console.log(await g.greet()); +``` + +下面简单分析合约的runtime bytecode,看函数选择器是如何发挥作用的。 + +![](img/2022-10-24-15-02-02.png) + +可以看到由于合约没有设置`receive`函数,也没有`payable`函数,不能接收以太币,故在最开始判断若value不为0则revert。 + +之后由于函数选择器是4个字节,故要执行函数的calldata至少也应该是4个字节,否则revert。 + +然后取calldata的前4字节,即函数选择器,后续是一个if/else组成的dispatcher,会根据函数选择器的值,进入不同的分支,从而执行对应的函数。 + +这里显示合约有三个函数,多出的一个`greeting()`是编译器为我们自动生成的用于读取状态变量`greeting`的函数。 + +4个字节的函数选择器可以在[ethereum signature database](https://www.4byte.directory/signatures/)查询,得到对应的函数签名。 + +这里区分一下合约函数调用与交易的概念。 + +有些函数为`view`或`pure`,如这里的`greet()`函数,并不会改变状态变量,因此实际上只需要在节点本地执行一下即可,即不改变整个区块链的状态,不需要广播、达成共识最终写入区块链,这样的调用也就不是交易,在底层调用的是`eth_call`。 + +而改变状态的函数调用,才是一个交易,底层调用的是`eth_sendTransaction`。 + +需要注意的是,虽然诸如`view`或`pure`这样的函数,直接调用的话默认情况下执行`eth_call`,不改变区块链状态,但也可以主动发送交易来进行调用(虽然好像只会浪费gas)。 + +如下所示,可以调用`greet()`函数,发起一个交易。 + +``` typescript +const itace = new ethers.utils.Interface(["function greet()"]); + const calldata = itace.encodeFunctionData("greet"); + + let tx = await signer.sendTransaction({ + to: g.address, + data: calldata + }) + await tx.wait(); + console.log(tx.data); +``` + +## 销毁 + +合约可以调用`selfdestruct`函数来实现自毁,可以向合约中添加如下函数。 + +``` solidity +function destroySmartContract(address payable _to) public { + selfdestruct(_to); + } +``` + +自毁函数接收一个参数,会将合约余额转给目标地址。 + +自毁并不会改变过去已经上链的区块中的数据。但在自毁之后的所有区块中,该合约的状态和代码会被删除。 + +如下调用自毁函数后,再调用原合约函数,就会报错。 + +``` typescript +let tx = await g.destroySmartContract(signer.address); +await tx.wait(); +console.log(await g.greet()); +``` + +## 存储空间 + +前面提到EVM是基于栈的虚拟机,在分析bytecode时又提到了内存,而状态变量又是永久存储在链上,显然这些都是不同的存储空间。 + +可参考[Storage, Memory and the Stack](https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html#storage-memory-and-the-stack)。以太坊虚拟机有3个区域用来存储数据:栈(stack)、内存(memory)和存储(storage)。 + +![](img/2022-10-24-16-26-53.png) + +如上图所示,栈和内存在EVM执行代码时临时使用,而状态变量涉及的合约存储部分则永久保存在链上。整个EVM的执行模型如下。 + +![](img/2022-10-24-16-30-49.png) + +三种存储空间的类型如下。 + +![](img/2022-10-24-16-33-41.png) + +### 栈 + +![](img/2022-10-24-16-38-35.png) + +栈是一个256 bits×1024的空间,EVM是基于栈的,所有的计算都在栈上执行,因此通常存储指令的操作数。 + +### 内存 + +![](img/2022-10-24-16-38-48.png) + +内存是线性的,可按字节级寻址,用于存储对象,内存分配方式为顺序分配。编程中,如果一个局部变量属于变长字节数组、字符串、结构体等类型,其通常会被memory修饰符修饰,以表明存储在内存中。 + +内存中前4个32字节的slot被保留,具体如下。 + +0x00 - 0x3f(64 字节):哈希方法的暂存空间 + +0x40 - 0x5f(32 字节):空闲内存指针,指向下一个对象将要被存储的地址 + +0x60 - 0x7f(32 字节):0-slot + +0-slot用作动态内存数组的初始值,并且永远不应该被写入,而由于内存是顺序分配的,空闲内存指针显然会被初始化为0x80,和前面分析合约bytecode时的结果一致。 + +### 存储(storage) + +![](img/2022-10-24-16-39-02.png) + +合约存储是一个256 bits 映射到 256 bits的键值存储,用于持久化存储合约账户的状态变量。 + +具体可见参考[solidity文档](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html),结合知道创宇的例子[智能合约变量储存机制详解](https://zhuanlan.zhihu.com/p/414840665),了解状态变量在存储中的布局。 + +## 参考链接 + +[ethereum_evm_illustrated](https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf) + +[以太坊开发者文档](https://ethereum.org/zh/developers/docs) + +[以太坊技术与实现](https://learnblockchain.cn/books/geth) + +[完全理解以太坊智能合约](https://learnblockchain.cn/2018/01/04/understanding-smart-contracts) + +[智能合约编写之Solidity运行原理](https://www.infoq.cn/article/sZhX9ujD6Bqq3UhkO4Hk) + +[精通以太坊-智能合约](https://www.jianshu.com/p/58cb09b05ead) + +[deconstructing-a-solidity-contract](https://blog.openzeppelin.com/deconstructing-a-solidity-contract-part-i-introduction-832efd2d7737/) + +[evm-deep-dives](https://noxx.substack.com/p/evm-deep-dives-the-path-to-shadowy) + +[浅谈合约ABI](https://www.fisco.com.cn/class_32/380.html) + +[Understanding Bytecode on Ethereum](https://medium.com/authereum/bytecode-and-init-code-and-runtime-code-oh-my-7bcd89065904) + +[The difference between bytecode and deployed bytecode](https://medium.com/coinmonks/the-difference-between-bytecode-and-deployed-bytecode-64594db723df) \ No newline at end of file diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-21-20-18-44.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-21-20-18-44.png new file mode 100644 index 0000000..e481138 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-21-20-18-44.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-21-20-30-19.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-21-20-30-19.png new file mode 100644 index 0000000..c4dffc0 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-21-20-30-19.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-10-12-45.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-10-12-45.png new file mode 100644 index 0000000..1b2ef7d Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-10-12-45.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-15-02-02.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-15-02-02.png new file mode 100644 index 0000000..a1fc44f Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-15-02-02.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-26-53.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-26-53.png new file mode 100644 index 0000000..64b0c23 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-26-53.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-30-49.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-30-49.png new file mode 100644 index 0000000..76a1896 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-30-49.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-33-41.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-33-41.png new file mode 100644 index 0000000..af3a043 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-33-41.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-38-35.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-38-35.png new file mode 100644 index 0000000..84eec19 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-38-35.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-38-48.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-38-48.png new file mode 100644 index 0000000..645df69 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-38-48.png differ diff --git a/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-39-02.png b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-39-02.png new file mode 100644 index 0000000..58d33c5 Binary files /dev/null and b/docs/tech/Ethereum_Smart_Contract_Introduction/img/2022-10-24-16-39-02.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/Hierarchical_Deterministic_Wallets.md b/docs/tech/Hierarchical_Deterministic_Wallets/Hierarchical_Deterministic_Wallets.md new file mode 100644 index 0000000..619ccbb --- /dev/null +++ b/docs/tech/Hierarchical_Deterministic_Wallets/Hierarchical_Deterministic_Wallets.md @@ -0,0 +1,608 @@ + +# 分层确定性钱包初探 + +*Author: [Autosaida](https://github.com/Autosaida); Written: 08/2022* + +## 前言 + +初次使用metamask钱包时,选择创建新账户。在设置密码后,会给出一串英文单词作为助记词,而后**钱包账户**就会成功创建。此时metamask中就会显示一个Account 1,能看到账户对应的地址,也可以输入钱包账户的密码来查看对应的私钥。还可以继续选择create account,此时就会继续生成Account 2、Account 3...每个account也会有对应的地址与私钥。 + +钱包是如何创建一个个account,其私钥、地址以及助记词之间是什么关系,下面对相关问题进行研究学习。 + +## 私钥、公钥与地址 + +有两类以太坊账户可以用来持有和操作以太币:外部账户和合约账户。metamask中创建的一个个地址与私钥就是一个个外部账户。 + +账户的核心是私钥。私钥用来生成数字签名,所有以太坊交易都要求在链上包含有效的数字签名,故数字签名能够证明对应私钥的所有权,拥有私钥即拥有了对应以太坊地址的控制权。创建账号的关键也即生成一个私钥。 + +私钥通过椭圆曲线算法生成公钥,公钥又通过哈希函数生成账户地址。基本过程如下图所示。 + +![](img/2022-08-16-11-09-22.png) + +私钥 -> 公钥 -> 地址,过程均为不可逆的。 + +首先随机生成一个私钥(32字节),然后使用私钥通过椭圆曲线算法生成公钥(64字节),最后将公钥哈希得到账户地址(20字节)。 + +下面逐步说明各个步骤。 + +### 随机生成私钥 + +生成私钥的第一步,也是最重要的一步就是找到一个密码学安全的熵,即随机性来源。 + +私钥为32字节,故实际上就是在0-2256之间随机选取一个数值,只要选择的过程不可预测不可重复,那么通过什么方式获得这个数值并不重要。 + +具体来说,以太坊私钥不能为0,也不能超过一个接近于1.158*1077的数字。 + +故要生成私钥,实质是随机选取一个在上述有效范围内的256位的数值。 + +### 椭圆曲线算法生成公钥 + +在椭圆曲线运算中,质数的模乘运算是非常简单的,但是反向的模除运算却是几乎不可能的,这被称为离散对数难题,是以太坊等加密货币数字签名的基础。 + +公钥正是私钥通过椭圆曲线乘法得来,故只能单向计算,私钥可以算出公钥,但由公钥不能得到私钥。 + +公钥K = k * G,K为公钥,k为私钥,G为一个生成点。 + +以太坊使用跟比特币系统相同的椭圆曲线算法,称为secp256k1。使用了该标准定义的一种特殊的椭圆曲线和一系列的数学常量,公钥就是椭圆曲线上的一个满足椭圆曲线等式的(x,y)坐标,由x、y两个数值组成,通过私钥唯一确定。 + +x、y各32字节,故公钥为64字节,可能会看到65字节表示的公钥,这是由SECG所发布的行业标准的一种序列化编码方式,在最前面加一个字节的前缀,04表示公钥为非压缩格式,即完整存储了x和y的坐标各32字节。但是从secp256k1的椭圆曲线方式可以看到,只要知道其中一个坐标值,另外一个坐标值都是可以通过解方程得出的,因此可以只存储其中一个坐标,这样就可以节约32个字节,从而引入了压缩格式的公钥。如果为压缩格式,则前缀为02或03。 + +故非压缩格式的公钥65字节(后64字节为实际的公钥),压缩格式的公钥33字节。 + +### 哈希生成地址 + +将公钥进行哈希,即可得到地址。 + +对64字节的完整公钥使用Keccak-256算法来进行哈希,哈希结果的最后20字节即为对应私钥的账户地址。 + +哈希结果并不区分大小写,但我们可以发现很多地方,如etherscan中的地址是同时存在大小写的。 + +这是由于[eip-55](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md)引入了checksum机制,通过修改地址中字母的大小写,可以校验地址的准确性,降低用户输入错误地址的风险。 + +### keystore + +keystore文件一般是JSON文件,实质上就是加密后的私钥。故keystore必须用对应密码解密后才能得到私钥,获取账户控制权。即 keystore + 文件密码 = 私钥。 + +使用keystore可以不用记住复杂的私钥,记住设置的密码即可控制对应的账户。 + +一般内容如下。 + +``` json +{ + "address": "df5f03234385f576f8f69e85194a8e02315132f5", + "id": "e0aa3592-e854-43ed-92ae-2082cd012961", + "version": 3, + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "caf873134967841a20a2e341fe4f2c16" + }, + "ciphertext": "ca0cf572f6f5f6e4db7467430ee1b15e25082181a6002cf1d0d954e771b53395", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "a8cc9a642bebe14c32f4e2ed249dd4c30e21379abcccfc3fc0596d7c80b5de2c" + }, + "mac": "2c529cb3be67518c41a3394fa4054e773449bcc34671389c17e453391ca31413" + } +} +``` + +存储了密文、加密算法以及所需的相关参数。 + +### 代码测试 + +下面使用代码来根据私钥生成公钥及对应账户地址,并进行验证。使用coincurve库进行椭圆曲线运算,pycryptodome库进行哈希运算。 + +``` python +import coincurve +from Crypto.Hash import keccak +test_priv = b'\x11'*32 +# test_priv = b'\xff'*32 +# test_priv = b'\x00'*32 +test_pub = coincurve.PublicKey.from_secret(test_priv).format(compressed=False) +print(len(test_pub)) +print(test_pub.hex()) + +test_addr = keccak.new(data = test_pub[1:], digest_bits = 256) +print(test_addr.digest()[-20:].hex()) + +''' +65 +044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1 +19e7e376e7c213b7e7e7e46cc70a5dd086daff2a +''' +``` + +私钥全0或过大都会报错提示不在范围内,且生成公钥要设置`compressed=False`,不然默认生成压缩格式的公钥。可以看到非压缩格式的公钥为65字节,开头前缀为04,最后将公钥(不带前缀)进行keccak-256哈希运算,结果的最后20字节即为私钥对应的账户地址。 + +可以用现成的demo对[ethereum-private-key-to-public-key](https://github.com/miguelmota/ethereum-private-key-to-public-key)和[ethereum-private-key-to-address](https://github.com/miguelmota/ethereum-private-key-to-address)对结果进行测试,证明运算无误。 + +显然这样随意选取的简单的私钥是极不安全的。(但在etherscan中看到甚至还有交易记录) + +## BIP32:分层确定性钱包 + +一个人可能拥有多个账户,因此使用钱包来对多个账户进行管理。一般分为非确定性钱包和确定性钱包。 + +非确定性钱包(Nodeterministic Wallet):钱包中的每个密钥都是从不同的随机数独立生成的,密钥彼此之间没有任何关系,这种钱包也被称为JBOK钱包(Just a Bunch Of Keys)。 + +确定性钱包(Deterministic Wallet):其中所有的密钥都是从一个主密钥派生出来的,在该类型的钱包中,所有的密钥之间都是相互关联的,只要知道了生成主密钥使用的种子,就可以派生得到全部的密钥;在确定性钱包中,可以使用不同的密钥推导方式。目前最常用的推导方法是树状结构,即分层确定性钱包(Hierarchical Deterministic Wallets),在[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)中提出。 + +显然使用分层确定性钱包,用户只需要记住种子,就可以派生出多个账户来使用,不需要麻烦的备份管理众多账户的私钥。 + +通过种子生成主密钥,主密钥再逐层推导得到众多的子密钥。基本过程如下。 + +![](img/2022-08-16-18-47-06.png) + +种子 -> 主密钥 -> 各层子密钥。 + +首先随机生成一个种子(16-64字节),然后对种子进行哈希生成主密钥和主链码(各32字节),根据密钥和链码使用密钥派生函数即可逐层派生子密钥。 + +下面结合python模块[bip32](https://github.com/darosior/python-bip32)代码进行说明。 + +### 种子生成主密钥 + +由seed可推导出对应的全部子密钥,即一个seed可生成管理一串私钥,故这里seed相当于前面生成单个账户的私钥,需要确保其随机性,长度在16-64字节之间,建议为32字节。 + +生成足够随机安全的种子后,对其进行HMAC-SHA512哈希,得到64字节的哈希结果,前32字节即为主私钥,可以生成对应的33字节的压缩主公钥。后32字节为主链码,作为熵在后续扩展密钥过程中使用。具体如下图所示。 + +![](img/2022-08-16-18-51-21.png) + +bip32中的相关代码如下。 + +``` python +# bip32.py +def from_seed(cls, seed, network="main"): + """Get a BIP32 "wallet" out of this seed (maybe after BIP39?) + + :param seed: The seed as bytes. + """ + secret = hmac.new("Bitcoin seed".encode(), seed, hashlib.sha512).digest() + return BIP32(secret[32:], secret[:32], network=network) + +# utils.py +def _privkey_to_pubkey(privkey): + """Takes a 32 bytes privkey and returns a 33 bytes secp256k1 pubkey""" + return coincurve.PublicKey.from_secret(privkey).format() +``` + +方法`from_seed`接受种子seed作为参数,然后对种子进行HMAC-SHA512哈希,前32字节作为主私钥,后32字节作为主链码,来生成BIP32对象。后续调用`_privkey_to_pubkey`函数,使用coincurve库来生成对应的压缩公钥。 + +有了主私钥(公钥)和主链码,即可派生后续一层层的子密钥。 + +### 子密钥派生(Child key derivation,CKD) + +派生子密钥的方法有两种:父私钥->子密钥,父公钥->子密钥。具体如下图所示。 + +![](img/2022-08-16-19-36-26.png) + +![](img/2022-08-16-19-36-37.png) + +即父私钥/公钥+父链码,再加一个索引号,即可调用哈希函数生成子密钥和对应链码。使用父私钥为强化衍生,使用父公钥为常规衍生。强化衍生不会将父级链码暴露到不安全的环境下,所以相较于常规衍生更安全。 + +为了区分两种不同的派生方案,在索引号也进行了区分,索引号小于231用于常规衍生,而231到232-1用于强化衍生,为了方便表示,索引号 i' 就表示索引号 231+i,如0x80000000表示为0'。 + +路径m/5/4'表示主密钥的第五个常规衍生的子密钥的第4个强化衍生的子密钥。 + +由父密钥衍生子密钥可以对密钥树进行深度扩展,使用不同的索引号可以进行水平扩展,从而可以无限的生成私钥,即一个种子生成多个账户。 + +其中子密钥不能推导出同层级的兄弟密钥,也不能推出父密钥。如果没有子链码也不能推导出孙密钥。 + +bip32中的相关代码如下。 + +``` python +# bip32.py +def get_extended_privkey_from_path(self, path): + """Get an extended privkey from a derivation path. + + :param path: A list of integers (index of each depth) or a string with + m/x/x'/x notation. (e.g. m/0'/1/2'/2 or m/0H/1/2H/2). + :return: chaincode (bytes), privkey (bytes) + """ + if self.privkey is None: + raise PrivateDerivationError + + if isinstance(path, str): + path = _deriv_path_str_to_list(path) + + chaincode, privkey = self.chaincode, self.privkey + for index in path: + if index & HARDENED_INDEX: + privkey, chaincode = _derive_hardened_private_child( + privkey, chaincode, index + ) + else: + privkey, chaincode = _derive_unhardened_private_child( + privkey, chaincode, index + ) + + return chaincode, privkey + +# utils.py +def _derive_unhardened_private_child(privkey, chaincode, index): + """A.k.a CKDpriv, in bip-0032 + + :param privkey: The parent's private key, as bytes + :param chaincode: The parent's chaincode, as bytes + :param index: The index of the node to derive, as int + + :return: (child_privatekey, child_chaincode) + """ + assert isinstance(privkey, bytes) and isinstance(chaincode, bytes) + assert not index & HARDENED_INDEX + pubkey = _privkey_to_pubkey(privkey) + # payload is the I from the BIP. Index is 32 bits unsigned int, BE. + payload = hmac.new( + chaincode, pubkey + index.to_bytes(4, "big"), hashlib.sha512 + ).digest() + try: + child_private = coincurve.PrivateKey(payload[:32]).add(privkey) + except ValueError: + raise BIP32DerivationError( + "Invalid private key at index {}, try the " "next one!".format(index) + ) + return child_private.secret, payload[32:] + + +def _derive_hardened_private_child(privkey, chaincode, index): + """A.k.a CKDpriv, in bip-0032, but the hardened way + + :param privkey: The parent's private key, as bytes + :param chaincode: The parent's chaincode, as bytes + :param index: The index of the node to derive, as int + + :return: (child_privatekey, child_chaincode) + """ + assert isinstance(privkey, bytes) and isinstance(chaincode, bytes) + assert index & HARDENED_INDEX + # payload is the I from the BIP. Index is 32 bits unsigned int, BE. + payload = hmac.new( + chaincode, b"\x00" + privkey + index.to_bytes(4, "big"), hashlib.sha512 + ).digest() + try: + child_private = coincurve.PrivateKey(payload[:32]).add(privkey) + except ValueError: + raise BIP32DerivationError( + "Invalid private key at index {}, try the " "next one!".format(index) + ) + return child_private.secret, payload[32:] + +``` + +方法`get_extended_privkey_from_path`获取指定路径下的扩展私钥,即BIP32中定义的`CKDpriv`函数,通过父私钥得到子私钥。 + +使用for循环逐层衍生,其中`HARDENED_INDEX`即0x80000000,用于判断是强化衍生还是常规衍生。`_derive_unhardened_private_child`和`_derive_hardened_private_child`即具体实现了BIP32中定义的[Private parent key → private child key](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#private-parent-key--private-child-key)衍生逻辑。 + +``` python +# bip32.py +def get_extended_pubkey_from_path(self, path): + """Get an extended pubkey from a derivation path. + + :param path: A list of integers (index of each depth) or a string with + m/x/x'/x notation. (e.g. m/0'/1/2'/2 or m/0H/1/2H/2). + :return: chaincode (bytes), pubkey (bytes) + """ + if isinstance(path, str): + path = _deriv_path_str_to_list(path) + + if _hardened_index_in_path(path) and self.privkey is None: + raise PrivateDerivationError + + chaincode, key = self.chaincode, self.privkey + pubkey = self.pubkey + # We'll need the private key at some point anyway, so let's derive + # everything from private keys. + if _hardened_index_in_path(path): + for index in path: + if index & HARDENED_INDEX: + key, chaincode = _derive_hardened_private_child( + key, chaincode, index + ) + else: + key, chaincode = _derive_unhardened_private_child( + key, chaincode, index + ) + pubkey = _privkey_to_pubkey(key) + # We won't need private keys for the whole path, so let's only use + # public key derivation. + else: + for index in path: + pubkey, chaincode = _derive_public_child(pubkey, chaincode, index) + + return chaincode, pubkey + +# utils.py +def _derive_public_child(pubkey, chaincode, index): + """A.k.a CKDpub, in bip-0032. + + :param pubkey: The parent's (compressed) public key, as bytes + :param chaincode: The parent's chaincode, as bytes + :param index: The index of the node to derive, as int + + :return: (child_pubkey, child_chaincode) + """ + assert isinstance(pubkey, bytes) and isinstance(chaincode, bytes) + assert not index & HARDENED_INDEX + # payload is the I from the BIP. Index is 32 bits unsigned int, BE. + payload = hmac.new( + chaincode, pubkey + index.to_bytes(4, "big"), hashlib.sha512 + ).digest() + try: + tmp_pub = coincurve.PublicKey.from_secret(payload[:32]) + except ValueError: + raise BIP32DerivationError( + "Invalid private key at index {}, try the " "next one!".format(index) + ) + parent_pub = coincurve.PublicKey(pubkey) + try: + child_pub = coincurve.PublicKey.combine_keys([tmp_pub, parent_pub]) + except ValueError: + raise BIP32DerivationError( + "Invalid public key at index {}, try the " "next one!".format(index) + ) + return child_pub.format(), payload[32:] +``` + +方法`get_extended_pubkey_from_path`获取指定路径下的扩展公钥,涵盖BIP中定义的`CKDpub`函数,通过父公钥得到子公钥。 + +首先根据路径判断是否存在强化衍生,如果存在则必须通过私钥来生成公钥,故先得到扩展私钥,再调用`_privkey_to_pubkey`函数生成对应公钥,逐层衍生,最终得到对应路径下的公钥。如果不存在强化衍生,则可以只使用父公钥来得到子公钥,调用`_derive_public_child`函数,具体实现了BIP32中定义的[Public parent key → public child key](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#public-parent-key--public-child-key)衍生逻辑。 + +整个树状密钥派生结构可如下图表示。 + +![](img/2022-08-16-20-40-41.png) + +其中右下角为强化衍生,其余均为常规衍生。 + +### 代码测试 + +可使用如下代码对BIP32进行测试。 + +``` python +from bip32 import BIP32 +from base58 import * +seed = b'\x11' * 32 +bip32 = BIP32.from_seed(seed) +print('master private key:', bip32.get_xpriv()) +print('master public key:', bip32.get_xpub()) +priv_key = bip32.get_privkey_from_path("m/5/4'") +pub_key = bip32.get_pubkey_from_path("m/5/4'") +print('privkey from m/5/4\':', priv_key.hex()) +print('pubkey from m/5/4\':', pub_key.hex()) +``` + +其中主扩展私钥和主扩展公钥的输出分别为xprv和xpub开头的字符串,这是由BIP32定义的[Serialization format](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format)。 + +## BIP39:使用助记词生成确定性钱包 + +根据BIP32,用户只需要保存一个种子即可控制其对应的一系列账户。但显然一长串的十六进制种子字符串仍旧不是一个很方便的数据,不利于记录且容易记错,因此产生了[BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki),BIP39提出使用助记词生成确定性钱包,即通过一组便于记忆的单词来生成种子,从而让种子的记忆备份更友好。 + +BIP39说明了如何生成助记词,以及如何将生成的助记词转化成一个二进制种子。如下图所示。 + +![](img/2022-08-17-01-11-00.png) + +种子即可直接用于BIP32,生成管理一系列账户。 + +下面结合python模块[mnemonic](https://github.com/trezor/python-mnemonic)代码进行说明。 + +### 熵生成助记词 + +熵生成助记词的基本过程如下图所示。 + +![](img/2022-08-17-01-16-52.png) + +首先第一步,还是要生成一个密码学安全的熵,即随机性来源,用于生成助记词,因此这个数的选取也要足够随机足够安全。 + +熵的长度记为ENT,其取值范围为128-256位(16到32字节)。按步长32位,可为[128, 160, 192, 224, 256](16、20、24、28、32字节)。上图的生成过程就是使用128位的熵。 + +之后对熵进行SHA256哈希运算,并取出哈希结果的前N位(ENT/32,如:熵长128位,则N = 4)作为熵的校验和(Checksum)。 + +然后将Checksum添加到熵的尾部,并将该新得到的序列按照11位一段进行分隔,这样对于128位熵长的序列就会生成12段(132/11=12),将每段11位对应的值映射到一个预定义的2048个单词的表中(211 = 2048),即可得到一串对应的单词,128位的熵最终得到12个单词。 + +显然不同熵长有不同的Checksum长度,最终生成的助记词的长度也不同,具体对应关系如下表。 + +| Entropy(bits) | Checksum(bits) | Entropy+Checksum(bits) | Mnemonic length(words) | +| :-: | :---: | :---: | :---: | +| 128 | 4 | 132 | 12 | +| 160 | 5 | 165 | 15 | +| 192 | 6 | 198 | 18 | +| 224 | 7 | 231 | 21 | +| 256 | 8 | 264 | 24 | + +mnemonic模块的实现代码如下。 + +``` python +def generate(self, strength: int = 128) -> str: + if strength not in [128, 160, 192, 224, 256]: + raise ValueError( + "Strength should be one of the following [128, 160, 192, 224, 256], but it is not (%d)." + % strength + ) + return self.to_mnemonic(os.urandom(strength // 8)) + +def to_mnemonic(self, data: bytes) -> str: + if len(data) not in [16, 20, 24, 28, 32]: + raise ValueError( + "Data length should be one of the following: [16, 20, 24, 28, 32], but it is not (%d)." + % len(data) + ) + h = hashlib.sha256(data).hexdigest() + b = ( + bin(int.from_bytes(data, byteorder="big"))[2:].zfill(len(data) * 8) + + bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32] + ) + result = [] + for i in range(len(b) // 11): + idx = int(b[i * 11 : (i + 1) * 11], 2) + result.append(self.wordlist[idx]) + if self.language == "japanese": # Japanese must be joined by ideographic space. + result_phrase = u"\u3000".join(result) + else: + result_phrase = " ".join(result) + return result_phrase +``` + +方法`generate`随机生成一组助记词,使用`os.urandom()`来生成熵,且限定了熵长为[128, 160, 192, 224, 256]。 + +之后调用`to_mnemonic`方法根据随机熵生成助记词,实现与前面的描述一致。 + +### 助记词生成种子 + +助记词生成种子的基本过程如下图所示。 + +![](img/2022-08-17-01-47-34.png) + +主要使用密钥拉伸(Key stretching)算法PBKDF2来生成种子。 + +PBKDF2基本原理是通过一个为随机函数(如这里使用的HMAC-SHA512),把助记词明文和盐值作为输入参数,然后重复进行多次运算(这里2048轮),最终产生生成一个更长的(512位)密钥种子。 + +密钥拉伸函数需要两个参数:助记词和盐(salt)。盐可以提高暴力破解的难度。盐由常量字符串"mnemonic"及一个可选的密码组成,不设置密码则salt就为"mnemonic"。若使用了不同的密码,则同一组助记词通过密钥拉伸函数会得到不同的种子。 + +这里盐值中设置的密码可以作为一个额外的安全因子来保护种子,即使助记词的备份被窃取,也可以保证钱包的安全。(当然你也要记得自己加了怎样的盐) + +mnemonic模块的实现代码如下。 + +``` python +def to_seed(cls, mnemonic: str, passphrase: str = "") -> bytes: + mnemonic = cls.normalize_string(mnemonic) + passphrase = cls.normalize_string(passphrase) + passphrase = "mnemonic" + passphrase + mnemonic_bytes = mnemonic.encode("utf-8") + passphrase_bytes = passphrase.encode("utf-8") + stretched = hashlib.pbkdf2_hmac( + "sha512", mnemonic_bytes, passphrase_bytes, PBKDF2_ROUNDS + ) + return stretched[:64] +``` + +方法`to_seed`根据助记词生成种子。可以看到使用的salt为"mnemonic"+passphrase,密码默认为空,最后调用pbkdf2函数生成64字节(512位)的种子。 + +### 代码测试 + +使用如下代码对BIP32及BIP39进行测试。 + +``` python +from mnemonic import Mnemonic +from bip32 import BIP32 + +mnemo = Mnemonic("english") +words = mnemo.generate(strength = 256) +print('mnemonic:', words) +seed = mnemo.to_seed(words, passphrase="") +print('seed:', seed.hex()) + +master_key = mnemo.to_hd_master_key(seed) +print('master_key from mnemo:', master_key) + +bip32 = BIP32.from_seed(seed) +priv_key = bip32.get_xpriv_from_path("m/5/4'") +pub_key = bip32.get_xpub_from_path("m/5/4'") +print('xpriv from m/5/4\':', priv_key) +print('xpub from m/5/4\':', pub_key) +''' +mnemonic: find wool wrestle thought head shoe mobile among find top eager sniff depart zero combine about else later gaze stay journey depth shaft coil +seed: 963f67803815c1c97784dda18260f467e01e58b6d39171357c124fb1e2130487a94869f0b66298346a6c894797bb6bd988cf76e2bc2b85dc9dbcf0c0603116cf +master_key from mnemo: xprv9s21ZrQH143K34L3cV5waZgj6FN9KznoPbNYzRJxcjMvWjZomTJN2JcJcThXK37wriXYsaxqZMVyCsfypR9AbFMZYw4p2rFRbjvEKqLfQay +xpriv from m/5/4': xprv9wcQaJMMuBa2wJ33Z1Tg3HAqUE1WFYDxG5bsvuTMN1HBtLMkaVKd5U8SLz5QC4Z6kGNDEhLRKs9yhRYpLHnjmsfNeYbUteM5F1EH3oEdrjK +xpub from m/5/4': xpub6AbkyotFjZ8L9n7Wf2zgQR7a2FqzezwodJXUjHrxvLpAm8gu82dsdGSvCHvVVJ4YC2B94L7xTgvcZLnSRQdJu7R9JscZxUZuKYfcAq1YXuU +''' +``` + +可使用[在线助记词转换器](https://iancoleman.io/bip39/#english)对结果进行检查. + +![](img/2022-08-17-11-52-52.png) + +![](img/2022-08-17-11-53-11.png) + +结果无误。 + +## BIP44:确定性钱包的多账户层次结构 + +根据BIP32,在密钥树状结构中,每个子密钥都有自己的路径,但并没有明确规定每个路径的具体使用途径,使得钱包开发者可以自定义自己的节点结构,这就很容易导致没有办法100%保证使用了HD钱包A的用户将自己的助记词/种子导入到HD钱包B中还能正常工作;也没有办法保证HD钱包支持多个链的私钥管理。 + +由此产生了[BI433](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)和[BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki),提出确定性钱包的多账户层次结构,对BIP32的子节点派生路径的模式、每段的含义做出具体的规定,同时也扩展了对多币种的支持。 + +事实上现如今的HD钱包都遵循了BIP32和BIP44的规定,也只有遵循了这两个规范的钱包应用才是大概率完全兼容的。 + +### 路径级别 + +BIP44指定了包含5个预定义树状层级的结构: +``` +m / purpose' / coin_type' / account' / change / address_index +``` + +m是固定的,purpose也是固定的,值为44(44'即0x8000002C),代表遵循BIP44。 + +coin_type代表的是币种,0代表比特币,1代表比特币测试链,60代表以太坊,完整的币种列表可见[slip44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md)。 + +account将密钥空间拆分为独立的用户身份,可自主用于不同的功能,如专门用于储蓄的账户,专门用于捐赠的账户。 + +change值为常量0或1。常量0用于外部链,常量1用于内部链。外部链用于在钱包之外可见的地址(例如用于接收付款)。内部链用于在钱包之外不可见的地址,并用于返回交易更改。因此一般为0。 + +address_index就是地址索引,从0开始,代表生成第几个地址。 + +以太坊钱包也同样遵循BIP44,路径为m/44'/60'/a'/0/n,其中60即为以太币对应的coin_type,a表示帐号,n表示生成的第n个地址。 + +### 代码测试 + +使用如下代码进行测试,根据助记词输出以太坊对应路径的账户地址。 + +``` python +from mnemonic import Mnemonic +from bip32 import BIP32 +import coincurve +from Crypto.Hash import keccak + +words = 'your mnemonic' +def get_addr_by_path(path): + global words + mnemo = Mnemonic("english") + seed = mnemo.to_seed(words, passphrase="") + bip32 = BIP32.from_seed(seed) + privkey = bip32.get_privkey_from_path(path) + pubkey = coincurve.PublicKey.from_secret(privkey).format(compressed=False) + addr = keccak.new(data = pubkey[1:], digest_bits = 256) + return addr.digest()[-20:].hex() + +print(get_addr_by_path("m/44'/60'/0'/0/0")) +print(get_addr_by_path("m/44'/60'/0'/0/1")) +``` + +将输出结果与metamask钱包中的地址进行比较,可以看出钱包中生成的第一个账户路径就是`m/44'/60'/0'/0/0`,第二个就是`m/44'/60'/0'/0/1`,以此类推。 + +## 总结 + +回顾一下发展过程,为了避免管理多个账户,出现了BIP32分层确定性钱包,从而可以从一个种子生成管理无限的账户,而种子并不方便,因此出现了BIP39,可以从助记词生成种子,继而生成分层确定性钱包,更加便利,而BIP32并没有明确规定生成的树状密钥层次结构各层的具体用途,因此可能导致各钱包实现不同,也无法支持多币种,因此出现了BIP44,给树状密钥结构的路径赋予了明确的意义,也解决了多币种问题。 + +整个流程基本如下图所示。 + +![](img/2022-08-20-09-30-59.png) + +最后区分一下密码的概念,助记词生成种子使用的密码passphrase用作PBKDF2算法的盐,可以有也可以没有,目前主流钱包的助记词是无密码的;keystore的密码是用于解密加密文件得到私钥的;metamask创建时输入的密码是钱包密码,用于解密存储在本地的助记词等关键数据,类似于keystore的密码。 + + +## 参考链接 + +[详解私钥、密码、keystore和助记词](https://blog.csdn.net/pulong0748/article/details/109022685) + +[ETH钱包助记词、私钥、Keystore创建原理](https://www.jianshu.com/p/84c7dcf3098a) + +[区块链开发之确定性算法bip32,bip39,bip44](https://blog.csdn.net/weixin_39842528/article/details/82224907) + +[精通以太坊4:以太坊背后的密码学](https://blog.csdn.net/tlkj6868xds/article/details/105164619) + +[比特币:账户私钥、公钥、地址的生成](https://www.cnblogs.com/kumata/p/10477369.html) + +[理解开发HD 钱包涉及的 BIP32、BIP44、BIP39](https://learnblockchain.cn/2018/09/28/hdwallet) + +[比特币钱包(3) BIP32 HD钱包之密钥树](https://blog.csdn.net/thefist11cc/article/details/114480937) + +[基于BIP-32和BIP-39规范生成HD钱包](https://stevenocean.github.io/2018/09/23/generate-hd-wallet-by-bip39.html) + +[分层确定性钱包 HD Wallet 剖析:设计和实现](https://www.arcblock.io/blog/zh/post/2018/12/01/hd-wallets-design-and-implementation) + +[以太坊账户管理之keystore文件](https://stevenocean.github.io/2018/04/02/about-ethereum-keystore.html) + +[Understanding BIP39 and Your Mnemonic Phrase](https://privacypros.io/wallets/mnemonic-phrase) \ No newline at end of file diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-11-09-22.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-11-09-22.png new file mode 100644 index 0000000..555cc24 Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-11-09-22.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-18-47-06.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-18-47-06.png new file mode 100644 index 0000000..628935d Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-18-47-06.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-18-51-21.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-18-51-21.png new file mode 100644 index 0000000..4ecf78e Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-18-51-21.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-19-36-26.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-19-36-26.png new file mode 100644 index 0000000..dc0669d Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-19-36-26.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-19-36-37.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-19-36-37.png new file mode 100644 index 0000000..4ae122f Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-19-36-37.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-20-40-41.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-20-40-41.png new file mode 100644 index 0000000..40bc934 Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-16-20-40-41.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-11-00.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-11-00.png new file mode 100644 index 0000000..734ff9d Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-11-00.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-16-52.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-16-52.png new file mode 100644 index 0000000..b7cead1 Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-16-52.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-47-34.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-47-34.png new file mode 100644 index 0000000..cf62e4c Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-01-47-34.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-11-52-52.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-11-52-52.png new file mode 100644 index 0000000..f8e28cf Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-11-52-52.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-11-53-11.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-11-53-11.png new file mode 100644 index 0000000..88460c2 Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-17-11-53-11.png differ diff --git a/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-20-09-30-59.png b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-20-09-30-59.png new file mode 100644 index 0000000..b9d04aa Binary files /dev/null and b/docs/tech/Hierarchical_Deterministic_Wallets/img/2022-08-20-09-30-59.png differ diff --git a/mkdocs.yml b/mkdocs.yml index d9eb20e..14762c7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,8 @@ site_name: ZJUBCA Blockchain Roadmap nav: - 学习路径: index.md - 技术分享: + - 分层确定性钱包初探: tech/Hierarchical_Deterministic_Wallets/Hierarchical_Deterministic_Wallets.md + - 以太坊智能合约初探: tech/Ethereum_Smart_Contract_Introduction/Ethereum_Smart_Contract_Introduction.md - Fluence 空投代码分析: tech/fluence/fluence.md - Solana和Aptos技术对比浅析: tech/A_brief_technical_analysis_of_solana_and_move/A_brief_technical_analysis_of_solana_and_move.md - 每周精选: weekly/weekly.md