📌靶场刷题遇得到很多关于验证签名的题,在这里汇总一下,消息签名的工具和方法。
📌靶场刷题遇得到很多关于验证签名的题,在这里汇总一下,消息签名的工具和方法。
这是不符合当前 以太坊 规定的签名,即未加入
\x19Ethereum Signed Message:\n32
,适合用于平时在本地复现靶场的简单使用。
### 这是没加 \x19Ethereum Signed Message:\n32
from eth_account import Account
from web3 import Web3
message = ""
privatekey = ""
messagehash = Web3.keccak(text=message)
signMessage = Account.signHash(message_hash=messagehash, private_key=privatekey)
print("message =", message)
print("message's hash =",messagehash.hex())
print("v =", Web3.to_hex(signMessage.v))
print("r =", Web3.to_hex(signMessage.r))
print("s =", Web3.to_hex(signMessage.s))
print("signature =", Web3.to_hex(signMessage.signature))
如下是遵循
EIP191
协议的签名规则的代码,即加入\x19Ethereum Signed Message:\n32
。如下这两种方法和metamask的签名结果一样。
但是如下这里个并不是按照如下的计算方式:
bytes memory prefix = "\x19Ethereum Signed Message:\n32"; bytes32 result = keccak256(abi.encodePacked(prefix, hash));
# 加入了 \x19Ethereum Signed Message:\n32
from web3.auto import w3
from eth_account.messages import encode_defunct
msg = ""
private_key = ""
message = encode_defunct(text=msg)
signed_message = w3.eth.account.sign_message(message, private_key=private_key)
print("message =", msg)
print("messageHash =", w3.to_hex(signed_message.messageHash))
print("r =", w3.to_hex(signed_message.r))
print("s =", w3.to_hex(signed_message.s))
print("v =", w3.to_hex(signed_message.v))
print("signature =", w3.to_hex(signed_message.signature))
或
from web3 import Web3, HTTPProvider
from eth_account.messages import encode_defunct
# 私钥
private_key = ""
rpc = 'https://rpc.ankr.com/eth' # 遵从主网规则
w3 = Web3(HTTPProvider(rpc))
msg = ""
#构造可签名信息
message = encode_defunct(text=msg)
# sign
signed_message = w3.eth.account.sign_message(message, private_key=private_key)
print("msg =", msg)
print("msgHash =", w3.to_hex(signed_message.messageHash))
print("r =", w3.to_hex(signed_message.r))
print("s =", w3.to_hex(signed_message.s))
print("v =", w3.to_hex(signed_message.v))
print("signature = ", w3.to_hex(signed_message.signature))
代码结果运行图:
metamask签名结果图:
web3.js的版本为:
"version": "1.8.0"
这个方法就很牛皮了:
- 如果输入的data是string类型的:那么ta的运算结果和metamask的结果一样
- 如果输入的data是hash:那么ta的处理方式就是如下
bytes memory prefix = "\x19Ethereum Signed Message:\n32"; bytes32 result = keccak256(abi.encodePacked(prefix, hash));
remix代码结果:
web3js代码结果:
代码:
var Web3 = require('web3');
var web3 = new Web3(Web3.givenProvider);
let dataHash = "";
let privateKey = ""
let sign = web3.eth.accounts.sign(dataHash, privateKey);
console.log(sign);
版本为:^6.2.3。
该脚本的签名结果和方式和metamask也是一样,遵循EIP191,不会像web3js那样,输入为hash时,不遵守
bytes memory prefix = "\x19Ethereum Signed Message:\n32"; bytes32 result = keccak256(abi.encodePacked(prefix, hash));
import { ethers } from "ethers";
const RPC = "";
const provider = new ethers.JsonRpcProvider(RPC);
const privateKey = "";
const wallet = new ethers.Wallet(privateKey, provider);
const message = "";
const messageHash = ethers.hashMessage(message);
const signature = await wallet.signMessage(message);
console.log(`message = ${messageHash}`);
console.log(`signatrue = ${signature}`);
这是未遵循
EIP191
协议的签名方式
const ethereumjsUtil = require('ethereumjs-util');
// 要签名的消息
const message = 'stage1';
// 私钥(注意:这只是一个示例私钥,不应该在实际项目中使用)
const privateKey = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex');
// 生成消息的 Keccak-256 哈希
let buffer_message = Buffer.from(message); // 将message转成字节流
const messageHash = ethereumjsUtil.keccak256(buffer_message);
// 使用私钥对消息哈希进行签名
const signature = ethereumjsUtil.ecsign(messageHash, privateKey);
// 将签名结果进行格式化
const formattedSignature = {
v: signature.v,
r: signature.r.toString('hex'),
s: signature.s.toString('hex')
};
console.log('Message:', message);
console.log('Message Hash:', messageHash.toString('hex'));
console.log('Signature:', formattedSignature);
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol";
contract SignMessage {
using ECDSA for bytes32;
function verifyMessage(string memory message, bytes memory signature) public view returns(address, bool) {
//hash the plain text message
bytes32 messagehash = keccak256(bytes(message));
address signeraddress = messagehash.recover(signature);
if (msg.sender==signeraddress) {
//The message is authentic
return (signeraddress, true);
} else {
//msg.sender didnt sign this message.
return (signeraddress, false);
}
}
}
或
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SignMessage {
// @dev 从_msgHash和签名_signature中恢复signer地址
function recoverSigner(bytes32 _msgHash, bytes memory _signature) public pure returns (address){
// 检查签名长度,65是标准r,s,v签名的长度
require(_signature.length == 65, "invalid signature length");
bytes32 r;
bytes32 s;
uint8 v;
// 目前只能用assembly (内联汇编)来从签名中获得r,s,v的值
assembly {
/*
前32 bytes存储签名的长度 (动态数组存储规则)
add(sig, 32) = sig的指针 + 32
等效为略过signature的前32 bytes
mload(p) 载入从内存地址p起始的接下来32 bytes数据
*/
// 读取长度数据后的32 bytes
r := mload(add(_signature, 0x20))
// 读取之后的32 bytes
s := mload(add(_signature, 0x40))
// 读取最后一个byte
v := byte(0, mload(add(_signature, 0x60)))
}
// 使用ecrecover(全局函数):利用 msgHash 和 r,s,v 恢复 signer 地址
return ecrecover(_msgHash, v, r, s);
}
}
ethereum.js
实现这需要修改
ethereum.js
的源码,版本为:"version": "7.1.5"
。修改一:
node_modules\@types\secp256k1\index.d.ts
// 源代码 noncefn?: ((message: Uint8Array, privateKey: Uint8Array, algo: Uint8Array | null, data: Uint8Array | null, attempt: number) => Uint8Array)
这里可以看到data的值被写死了,被默认写成
null
,所以导致了生成的 options的值是new Uint8Array(0),这就影响了生成的签名是唯一的"错觉"。如何将这个默认值给去掉,在调用的时候传入随机的options(通过生成随机的Uint8Array
数组实现),所以将这里的代码修改为// 去掉data的默认值 noncefn?: ((message: Uint8Array, privateKey: Uint8Array, algo: Uint8Array | null, data: Uint8Array, attempt: number) => Uint8Array) | undefined;
修改二:
node_modules\ethereumjs-util\dist\signature.js
// 源码 const { signature, recid: recovery } = (0, secp256k1_1.ecdsaSign)(msgHash, privateKey);
可以看到这里没有传入option,即使用了源码的默认option(null),因为上一步修改了option的默认值,所以这里可以传参了,可以引入
crypto
库,随机生成Uint8Array
数组,这样就可以随机生成签名,所以将这里的代码修改为:const crypto = require('crypto'); // 先导库 const { signature, recid: recovery } = (0, secp256k1_1.ecdsaSign)(msgHash, privateKey, {data:crypto.randomBytes(32)}); // 生成随机数组
在 Node.js 中,
crypto.randomBytes()方法是一个常见的随机数生成方法
。综上,经过两次修改便可以实现使用同一私钥,对同一消息,进行签名可以得到不同的签名值
举例:
签名脚本,使用私钥
1
,对消息stage1
进行签名const ethereumjsUtil = require('ethereumjs-util'); // 要签名的消息 const message = 'stage1'; // 私钥(注意:这只是一个示例私钥,不应该在实际项目中使用) const privateKey = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'); // 生成消息的 Keccak-256 哈希 let buffer_message = Buffer.from(message); // 将message转成字节流 const messageHash = ethereumjsUtil.keccak256(buffer_message); // 使用私钥对消息哈希进行签名 const signature = ethereumjsUtil.ecsign(messageHash, privateKey); // 将签名结果进行格式化 const formattedSignature = { v: signature.v, r: signature.r.toString('hex'), s: signature.s.toString('hex') }; console.log('Message:', message); console.log('Message Hash:', messageHash.toString('hex')); console.log('Signature:', formattedSignature);
运行结果:
可以从结果中看到,生成的签名值不一样,到合约中验证:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Verify { // private key = 0x1 function verify(uint8 v, bytes32 r, bytes32 s) public { require(ecrecover(keccak256("stage1"), v, r, s) == 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, "who are you?"); } }
运行结果:
从结果中可以看到,生成的这些签名都可以通过验证。
这种方法不能准确获取signature,因为最后的v无法确定,只能猜测,但是v只能是 0x1b 或者 0x1c所以猜对的可能性为0.5,但是也可以实现生成不同签名的功能。
const secp256k1 = require('secp256k1');
const { randomBytes } = require('crypto');
/**
*
* @param {*} numSignatures :生成签名数量
* @param {*} PKey :私钥
* @param {*} MessageHash :消息的hash值,不带 `0x`
* 这种方法不能准确获取signature,因为最后的v无法确定,只能猜测,但是v只能是 0x1b 或者 0x1c所以猜对的可能性为0.5
*/
function batchSign(numSignatures, PKey, MessageHash) {
const privateKey = Buffer.from(PKey, 'hex');
const messageHash = Buffer.from(MessageHash, 'hex');
for (let i = 0; i < numSignatures; i++) {
const { signature } = secp256k1.ecdsaSign(messageHash, privateKey, { data: randomBytes(32) });
signatureBytes = Buffer.from(signature)
signatureHex = signatureBytes.toString('hex');
const r = signatureHex.slice(0, 64);
const s = signatureHex.slice(64);
console.log(`Signature ${i + 1}:`);
console.log("Signature:",`0x${signatureHex}`);
// console.log('Signature (v):', v);
console.log('Signature (r):', `0x${r}`);
console.log('Signature (s):', `0x${s}`);
console.log('Signature (s):',"1b or 1c");
console.log('----------------------');
}
}
//test
batchSign(5, "0000000000000000000000000000000000000000000000000000000000000001", "8252a7072c69c0cdba0c0bc059898f7992314306b3f0845bbb76593da6b98311")
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!