目录
DeFi(去中心化金融)综述
基本特点
第一,DeFi 是无许可的金融
第二,DeFi 是无门槛的金融
第三,DeFi 是无人驾驶的金融
典型商业模式
闪电贷
MakerDAO
面临的挑战
DeFi技术要点
椭圆曲线签名
EIP-712: Signed Typed Data
三种签名类型
编码示例
1. 交易编码
2. 消息编码
3. 结构化数据编码
签名和验证
EIP-191: Signed Data Standard
关键概念和步骤
版本字节示例
使用示例
任务实操
作业一:结构化数据(Typed Data)签名
方式1:采用 ethers.js
方式2:采用 MetaMask
作业二:实现 ERC20 Permit
安装 OpenZeppelin
编写 ERC20 Permit 合约
编写 ERC721 Permit 合约
相关链接
DeFi(去中心化金融)综述
基本特点
第一,DeFi 是无许可的金融
第二,DeFi 是无门槛的金融
第三,DeFi 是无人驾驶的金融
典型商业模式
闪电贷
"闪电贷"(Flash Loan)是一种去中心化金融(DeFi)技术,允许用户在无需提供任何抵押的情况下,短时间内借用大量资金。这种贷款通常在同一个区块链交易中完成借贷和还款,因此被称为"闪电贷"。它们主要在以太坊等区块链平台上使用,并且具有高度自动化和无需信任的特点。使用闪电贷时,用户必须确保在交易结束时能够偿还贷款本金加上利息,否则交易将失败,所有操作将回滚,就像从未发生过一样。这种贷款方式可以用于套利、杠杆交易等多种金融操作。
闪电贷通过单交易执行、原子性、智能合约保障和回滚机制确保资金安全。虽然有一定风险,但这些机制共同作用,极大地降低了资金被滥用或借款失败的可能性。用户在使用闪电贷时,选择经过审计的合约和谨慎规划交易步骤也是保障资金安全的重要因素。
MakerDAO
MakerDAO 是 DeFi 生态系统中的核心项目,的主要目标是创建并维护一个去中心化的稳定币,称为 Dai,其价值与1美元挂钩。为了实现这一目标,MakerDAO 允许用户通过抵押数字资产(如以太币)来生成 Dai。用户将他们的资产作为抵押品存入智能合约中,然后根据抵押品的价值获得一定比例的 Dai。如果抵押品的价值下降到某个阈值以下,用户需要增加抵押品或偿还部分 Dai 以避免被清算。这个过程确保了 Dai 的稳定性,使其能够维持与美元的挂钩。
面临的挑战
用户使用 DeFi 产品前,必须选择正确的主网和账户,还要在账户里预留足够的 Gas 费,理解这种操作模式需要用户具备一定的知识储备;因为操作不慎,转账导致资金丢失的事例时有发生,因为私钥泄露而资产被盗的事件也常常出现。
DeFi技术要点
椭圆曲线签名
EIP-712: Signed Typed Data
EIP-712 是一个标准化的以太坊提案,旨在定义一种安全、用户友好且可互操作的数据签名方法。这个标准的核心在于结构化数据的签名,它使得签名的数据更具可读性,并能防止重放攻击等潜在的安全问题。
三种签名类型
交易编码(encode(transaction: T)):
- 使用 RLP 编码(Recursive Length Prefix)对交易进行编码。
- 这种编码方法常用于对交易进行签名。
消息编码(encode(message: B^n)):
- 这种签名方式用于签名普通消息。
- 在哈希计算之前,消息会被前置字符串
"\x19Ethereum Signed Message:\n"
进行处理。- 这个前置字符串是为了确保签名的消息与普通交易数据区分开来,防止混淆。
结构化数据编码(encode(domainSeparator: B^32, message: S)):
- 用于签名结构化数据(如智能合约数据)。
- 先编码域分隔符(domainSeparator),然后编码具体消息(message)。
- 前缀为
"\x19\x01"
,表示这是结构化数据的签名。- 这种方式确保数据的完整性和安全性。
编码示例
1. 交易编码
使用 RLP 编码交易数据:
const encodedTransaction = RLP_encode(transaction);
2. 消息编码
对消息进行编码,并添加前缀:
const message = "Hello, Ethereum!";
const encodedMessage = `\x19Ethereum Signed Message:\n${message.length}${message}`;
3. 结构化数据编码
结构化数据编码需要两个部分:域分隔符和消息。以下是一个结构化数据编码的示例:
const domain = {
name: "MyDApp",
version: "1",
chainId: 1,
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
};
const types = {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
],
Message: [
{ name: "content", type: "string" },
{ name: "author", type: "address" }
]
};
const message = {
content: "Hello, EIP-712!",
author: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
};
// 计算域分隔符哈希
const domainSeparator = ethers.utils._TypedDataEncoder.hashDomain(domain);
// 计算消息哈希
const messageHash = ethers.utils._TypedDataEncoder.hashStruct("Message", types.Message, message);
// 最终编码
const encodedData = `\x19\x01${domainSeparator}${messageHash}`;
签名和验证
使用 EIP-712 标准进行签名和验证:
// 使用 ethers.js 进行签名
async function signTypedData() {
const [signer] = await ethers.getSigners();
const signature = await signer._signTypedData(domain, types, message);
console.log("Signature:", signature);
}
// 验证签名
async function verifySignature(signature) {
const signerAddress = ethers.utils.verifyTypedData(domain, types, message, signature);
console.log("Signer Address:", signerAddress);
}
signTypedData();
EIP-191: Signed Data Standard
EIP-191 是一个关于签名数据的标准,用于确保签名的数据在区块链上是安全和有效的。它的目的是通过标准化签名数据的格式,防止签名数据在被哈希和验证时发生混淆或被误解。
关键概念和步骤
-
签名数据格式: EIP-191 定义了一个数据格式,该格式包含以下部分:
0x19
:一个字节的前缀,确保签名的数据不符合 RLP 编码(Recursive Length Prefix)标准,以防止误用。- 版本字节:表示数据的版本,以区分不同的签名格式。
- 版本特定数据:根据版本字节的不同,可能包含额外的结构化数据。
- 要签名的数据:实际要进行签名的数据。
-
前缀的选择:
0x19
被选择为前缀,因为在personal_sign
方法中进行哈希之前,会预置以下字符串:"\x19Ethereum Signed Message:\n" + len(message)
这确保了数据在哈希时具有唯一性和不可篡改性。
-
版本字节: 版本字节用于区分不同类型的数据签名:
0x00
:数据具有指定的验证者。0x01
:结构化数据(对应 EIP-712 标准)。0x45
:个人签名消息(与personal_sign
方法兼容)。
版本字节示例
Version Byte | EIP | Description |
---|---|---|
0x00 | 191 | Data with intended validator |
0x01 | 712 | Structured data |
0x45 | 191 | personal_sign messages |
使用示例
为了签名一个消息,假设我们使用 0x01
版本字节(结构化数据):
-
准备数据:
const versionByte = '0x01'; const dataToSign = "My data to sign";
-
构建签名消息: 将数据按照 EIP-191 的格式进行构建:
const message = `\x19Ethereum Signed Message:\n${dataToSign.length}${dataToSign}`;
-
签名消息: 使用以太坊私钥进行签名(假设使用 ethers.js 库):
const { ethers } = require('ethers'); const wallet = new ethers.Wallet(privateKey); const signature = await wallet.signMessage(message);
任务实操
作业一:结构化数据(Typed Data)签名
方式1:采用 ethers.js
-
安装 ethers.js:
npm install ethers
-
编写签名代码:
const { ethers } = require("ethers"); // 定义域 const domain = { name: "MyDApp", version: "1", chainId: 1, verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }; // 定义数据结构 const types = { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" } ], Message: [ { name: "content", type: "string" }, { name: "author", type: "address" } ] }; const message = { content: "Hello, EIP-712!", author: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }; async function signTypedData() { const privateKey = "YOUR_PRIVATE_KEY"; const wallet = new ethers.Wallet(privateKey); const signature = await wallet._signTypedData(domain, types, message); console.log("Signature:", signature); } signTypedData();
-
智能合约验证:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract VerifySignature { using ECDSA for bytes32; struct Message { string content; address author; } bytes32 private constant MESSAGE_TYPEHASH = keccak256("Message(string content,address author)"); bytes32 private constant DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("MyDApp")), keccak256(bytes("1")), 1, 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC )); function hashMessage(Message memory message) public pure returns (bytes32) { return keccak256(abi.encode( MESSAGE_TYPEHASH, keccak256(bytes(message.content)), message.author )); } function verify(Message memory message, bytes memory signature) public view returns (address) { bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, hashMessage(message) )); return digest.recover(signature); } }
方式2:采用 MetaMask
-
安装 MetaMask:
- 在浏览器中安装 MetaMask 插件,并创建账户。
-
编写前端代码:
<!DOCTYPE html> <html> <head> <title>MetaMask Typed Data Signing</title> <script src="https://cdn.jsdelivr.net/npm/ethers/dist/ethers.min.js"></script> </head> <body> <button id="sign">Sign Typed Data</button> <script> const domain = { name: "MyDApp", version: "1", chainId: 1, verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }; const types = { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" } ], Message: [ { name: "content", type: "string" }, { name: "author", type: "address" } ] }; const message = { content: "Hello, EIP-712!", author: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" }; document.getElementById('sign').onclick = async function() { if (typeof window.ethereum !== 'undefined') { await ethereum.request({ method: 'eth_requestAccounts' }); const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); const signature = await signer._signTypedData(domain, types, message); console.log("Signature:", signature); } else { console.log("MetaMask is not installed"); } }; </script> </body> </html>
作业二:实现 ERC20 Permit
安装 OpenZeppelin
npm install @openzeppelin/contracts
编写 ERC20 Permit 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
contract MyTokenWithPermit is ERC20Permit {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {
_mint(msg.sender, 1000000 * 10 ** decimals());
}
}
编写 ERC721 Permit 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
contract MyTokenWithPermit is ERC20Permit {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {
_mint(msg.sender, 1000000 * 10 ** decimals());
}
}
相关链接
- Ethers.js SignTypedData
- MetaMask SignTypedData V4