Web Developer DeFi
Share Dialog
Share Dialog
Web Developer DeFi

Subscribe to Smithereens

Subscribe to Smithereens
<100 subscribers
<100 subscribers
以太坊交易需要被包含在一个区块中并被挖掘,然后才能被处理并保存在区块链上。因此,链上交易需要时间和成本来补偿矿工的工作。
相比之下,链下计算可以让您立即执行操作,而无需等待交易被挖掘,并且不消耗任何气体。
在本文中,让我们看看如何使用以太坊签名执行链下计算。加密签名可用于验证消息的来源和完整性。然后,我们将研究现实生活中的链下计算用例,例如去中心化交易所、状态通道和元交易。
在去中心化交易所中,签名和链下计算用于预先授权做市商执行任何由做市商签署的订单。制造商不是直接在链上发布买卖订单,而是使用他们的私钥签署详细说明他们订单的消息。然后,制造商将这些已签署的订单提交到链下订单簿——由第三方中继器托管在中央服务器上的数据库——供接受者浏览和填写。这些链下订单会立即在链下提交,无需支付任何 gas。
使脱链订单簿起作用的神奇成分是加密签名。
加密签名是支持所有区块链技术的基础计算机科学原语。签名可用于代表签名者授权交易。它还可以用于向智能合约证明某个帐户批准了某个消息,以太坊这里采用的是椭圆曲线数字签名算法(ECDSA)
以太坊网络中的每个账户都有一个公钥和一个私钥。以太坊地址本质上是公钥的散列版本。
帐户可以使用他们的私钥签署一段数据,返回该数据的签名。

任何人都可以验证生成的签名:
恢复签名者的公钥/地址,以及
验证消息的完整性,即它与签名者签署的消息相同。
您可以在浏览器中完全脱链地签署消息,而无需与以太坊网络进行交互。ECDSA 签名消息的签名和验证允许在区块链之外进行防篡改通信。
我们可以[eth_sign](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign)通过以太坊客户端调用该方法,例如web3.js:
// Create a SHA3 hash of the message 'Apples'
const messageHash = web3.sha3('Apples');
// Signs the messageHash with a given account
const signature = await web3.eth.personal.sign(messageHash, web3.eth.defaultAccount);
以太坊中的 ECDSA 签名由三个参数r、s和组成v。Solidity 提供了一个全局可用的方法ecrecover,它返回给定这三个参数的地址。如果返回的地址与签名者的地址相同,则签名有效。
生成的签名是、 和web3.js的串联r,因此必要的第一步是将这些参数拆分出来。sv
智能合约和以太坊客户端都具有验证 ECDSA 签名的能力。
使用智能合约进行签名验证
上面定义了一个 splitSignature 函数:
将 拆分signature为其三个组件r, s, 和v, 这里注意 mload(add(sig, 32)) 为啥需要 add 32 是因为 bytes 类型属于动态数组类型,前面第一个槽(32字节)存的是 bytes 的长度。
但是这里存在一个弊端,因为用户看到的都是一串哈希值,EIP712 进行了改进。
以太坊交易需要被包含在一个区块中并被挖掘,然后才能被处理并保存在区块链上。因此,链上交易需要时间和成本来补偿矿工的工作。
相比之下,链下计算可以让您立即执行操作,而无需等待交易被挖掘,并且不消耗任何气体。
在本文中,让我们看看如何使用以太坊签名执行链下计算。加密签名可用于验证消息的来源和完整性。然后,我们将研究现实生活中的链下计算用例,例如去中心化交易所、状态通道和元交易。
在去中心化交易所中,签名和链下计算用于预先授权做市商执行任何由做市商签署的订单。制造商不是直接在链上发布买卖订单,而是使用他们的私钥签署详细说明他们订单的消息。然后,制造商将这些已签署的订单提交到链下订单簿——由第三方中继器托管在中央服务器上的数据库——供接受者浏览和填写。这些链下订单会立即在链下提交,无需支付任何 gas。
使脱链订单簿起作用的神奇成分是加密签名。
加密签名是支持所有区块链技术的基础计算机科学原语。签名可用于代表签名者授权交易。它还可以用于向智能合约证明某个帐户批准了某个消息,以太坊这里采用的是椭圆曲线数字签名算法(ECDSA)
以太坊网络中的每个账户都有一个公钥和一个私钥。以太坊地址本质上是公钥的散列版本。
帐户可以使用他们的私钥签署一段数据,返回该数据的签名。

任何人都可以验证生成的签名:
恢复签名者的公钥/地址,以及
验证消息的完整性,即它与签名者签署的消息相同。
您可以在浏览器中完全脱链地签署消息,而无需与以太坊网络进行交互。ECDSA 签名消息的签名和验证允许在区块链之外进行防篡改通信。
我们可以[eth_sign](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign)通过以太坊客户端调用该方法,例如web3.js:
// Create a SHA3 hash of the message 'Apples'
const messageHash = web3.sha3('Apples');
// Signs the messageHash with a given account
const signature = await web3.eth.personal.sign(messageHash, web3.eth.defaultAccount);
以太坊中的 ECDSA 签名由三个参数r、s和组成v。Solidity 提供了一个全局可用的方法ecrecover,它返回给定这三个参数的地址。如果返回的地址与签名者的地址相同,则签名有效。
生成的签名是、 和web3.js的串联r,因此必要的第一步是将这些参数拆分出来。sv
智能合约和以太坊客户端都具有验证 ECDSA 签名的能力。
使用智能合约进行签名验证
上面定义了一个 splitSignature 函数:
将 拆分signature为其三个组件r, s, 和v, 这里注意 mload(add(sig, 32)) 为啥需要 add 32 是因为 bytes 类型属于动态数组类型,前面第一个槽(32字节)存的是 bytes 的长度。
但是这里存在一个弊端,因为用户看到的都是一串哈希值,EIP712 进行了改进。
// SPDX-License-Identifier: MIT
/* Signature Verification
How to Sign and Verify
# Signing
1. Create message to sign
2. Hash the message
3. Sign the hash (off chain, keep your private key secret)
# Verify
1. Recreate hash from the original message
2. Recover signer from signature and hash
3. Compare recovered signer to claimed signer
*/
contract VerifySignature {
/* 1. Unlock MetaMask account
ethereum.enable()
*/
/* 2. Get message hash to sign
getMessageHash(
0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C,
123,
"coffee and donuts",
1
)
hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"
*/
function getMessageHash(
address _to,
uint _amount,
string memory _message,
uint _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_to, _amount, _message, _nonce));
}
/* 3. Sign message hash
# using browser
account = "copy paste account of signer here"
ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)
# using web3
web3.personal.sign(hash, web3.eth.defaultAccount, console.log)
Signature will be different for different accounts
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function getEthSignedMessageHash(bytes32 _messageHash)
public
pure
returns (bytes32)
{
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
return
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
);
}
/* 4. Verify signature
signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd
to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C
amount = 123
message = "coffee and donuts"
nonce = 1
signature =
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function verify(
address _signer,
address _to,
uint _amount,
string memory _message,
uint _nonce,
bytes memory signature
) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == _signer;
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig)
public
pure
returns (
bytes32 r,
bytes32 s,
uint8 v
)
{
require(sig.length == 65, "invalid signature length");
assembly {
/*
First 32 bytes stores the length of the signature
add(sig, 32) = pointer of sig + 32
effectively, skips first 32 bytes of signature
mload(p) loads next 32 bytes starting at the memory address p into memory
*/
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
// implicitly return (r, s, v)
}
}
// SPDX-License-Identifier: MIT
/* Signature Verification
How to Sign and Verify
# Signing
1. Create message to sign
2. Hash the message
3. Sign the hash (off chain, keep your private key secret)
# Verify
1. Recreate hash from the original message
2. Recover signer from signature and hash
3. Compare recovered signer to claimed signer
*/
contract VerifySignature {
/* 1. Unlock MetaMask account
ethereum.enable()
*/
/* 2. Get message hash to sign
getMessageHash(
0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C,
123,
"coffee and donuts",
1
)
hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"
*/
function getMessageHash(
address _to,
uint _amount,
string memory _message,
uint _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_to, _amount, _message, _nonce));
}
/* 3. Sign message hash
# using browser
account = "copy paste account of signer here"
ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)
# using web3
web3.personal.sign(hash, web3.eth.defaultAccount, console.log)
Signature will be different for different accounts
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function getEthSignedMessageHash(bytes32 _messageHash)
public
pure
returns (bytes32)
{
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
return
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
);
}
/* 4. Verify signature
signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd
to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C
amount = 123
message = "coffee and donuts"
nonce = 1
signature =
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function verify(
address _signer,
address _to,
uint _amount,
string memory _message,
uint _nonce,
bytes memory signature
) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == _signer;
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig)
public
pure
returns (
bytes32 r,
bytes32 s,
uint8 v
)
{
require(sig.length == 65, "invalid signature length");
assembly {
/*
First 32 bytes stores the length of the signature
add(sig, 32) = pointer of sig + 32
effectively, skips first 32 bytes of signature
mload(p) loads next 32 bytes starting at the memory address p into memory
*/
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
// implicitly return (r, s, v)
}
}
No activity yet