以太坊交易签名详解

交易签名模块

交易是区块链和重中之重,不论是简单的转账还是复杂的智能合约的执行,都是依托于交易来完成。以太坊中的交易指的是从用户账户(EOA)中发出的含有消息的签名数据包,该数据包存储着要从一个帐户发送到区块链上的另一个帐户的消息。

交易的主要结构

&n交易数据结构是经过序列化后在以太坊网络上进行传输的。接收序列化交易的每个客户端和应用程序将使用其自己的内部数据结构将其存储在内存中,并且还会使用交易本身中不存在的元数据对其进行修饰。序列化后的的交易是交易结构的唯一通用标准。

交易数据结构:type txdata struct {
AccountNonce uint64 json:"nonce" gencodec:"required"
Price *big.Int json:"gasPrice" gencodec:"required"
GasLimit uint64 json:"gas" gencodec:"required"
Recipient *common.Address json:"to" rlp:"nil" // nil means contract creation Amount *big.Int json:"value" gencodec:"required"
Payload []byte json:"input" gencodec:"required"
// Signature values
V *big.Int json:"v" gencodec:"required"
R *big.Int json:"r" gencodec:"required"
S *big.Int json:"s" gencodec:"required" // This is only used when marshaling to JSON.
Hash *common.Hash json:"hash" rlp:"-"}l
AccountNonce,交易发起者内部唯一标识交易的字段,避免交易双重支付l Price, 即你愿为该交易支付的每单位 gas 的价格。l GasLimit,此交易允许的最大gas量l Recipient, 即该函数调用被送往的地址。l Amount, 交易转移的ETH数量,单位是weil Payload, 交易数据l V,R,S, 交易签名,通过交易签名可以计算出交易发送者地址。

交易的随机数(nonce)

每个以太坊账户都有一个叫做 nonce 的字段,来记录该账户已执行的交易总数。
为了防止交易重播,ETH(ETC)节点要求每笔交易必须有一个nonce数值。每一个账户从同一个节点发起交易时,这个nonce值从0开始计数,发送一笔nonce对应加1。
当前面的nonce处理完成之后才会处理后面的nonce。前提条件是相同的地址在相同的节点发送交易。 nonce值也用于防止帐户余额的错误计算。在以太坊这样的分布式系统中,节点可能无序地接收交易。nonce强制任何地址的交易按顺序处理,不管间隔时间如何,无论节点接收到的顺序如何。
以下是nonce使用的几条规则:
当nonce太小(小于之前已经有交易使用的nonce值),交易会被拒绝。
当nonce太大,交易会一直处于队列之中;
当发送一个比较大的nonce值,然后补齐开始nonce到那个值之间的nonce,那么交易依旧可以被执行。
当交易处于queue中时停止geth客户端,那么交易queue中的交易会被清除掉。

交易费用(gas)

以太坊的运行环境,也被称为以太坊虚拟机(EVM)。每个参与到网络的节点都会运行EVM作为区块验证协议的一部分。每个网络中的全节点都会进行相同的计算并储存相同的值。合约执行会在所有节点中被多次重复,而且任何人都可以发布执行合约,这使得合约执行的消耗非常昂贵,所以为防止以太坊网络发生蓄意攻击或滥用的现象,以太坊协议规定交易或合约调用的每个运算步骤都需要收费。这笔费用以gas作为单位计数,也就是俗称的燃料。
根据以太坊协议,在合约或交易中执行的每个计算步骤都要收取费用,以防止在以太坊网络上的恶意攻击和滥用。每笔交易都必须包含gas limit和愿意为gas支付的费用。矿工可以选择是否打包交易和收取费用。如果由交易产生的计算步骤所使用的gas总量(gas used ),包括原始消息和可能被触发的任何子消息,小于或等于gas limit,则处理该交易。如果gas总量超过gas limit,那么所有的改变都会回退,除非交易仍然有效并且矿工接受了这个费用。交易执行中未使用的所有多余的gas将以Ether返还给交易发起人。
交易中花费的总共的ether成本取决于2个因素:
1)gasUsed:是交易中消耗的总共的gas
2)gasPrice:在交易中指定一个单位gas的价格(ether)总费用 = gasUsed * gasPrice EVM中的每个操作都指定了要消耗的gas量。 gasUsed是执行所有操作的所有gas的总和。
gasUsed有三种不同构成:
1)计算操作的固定费用
2)交易(合约创建或消息调用)费用
3)存储(内存、存储账户合约数据)费用
其中,存储收费是因为假如你的合约使得状态数据库存储增大,所有节点都会增加存储。所以,以太币是鼓励尽量保持少量存储的。但是如果有操作是清除一个存储条目,这个操作的费用不但会被免除,而且由于释放空间还会获得退款。

RLP编码

RLP(Recursive Length Prefix,递归长度前缀)是一种序列化编码算法,用于编码任意的嵌套结构的二进制数据。RLP序列化方法因为简单、短小等诸多优点,现如今已经成为以太坊中数据序列化/反序列化的主要方法,区块、交易等数据结构在持久化时会先经过RLP编码后再存储到数据库中。

代码实现

const char* nonce,            交易nonce值
const char* gas_price,        交易gas_price值
const char* gas_limit,        交易gas_limit值
const char* to,               交易地址
const char* value,            交易数量
const char* data,             交易data值
const char* privkey,          账户私钥
uint8_t* out                  输出经过签名的交易序列

static int sign_transaction(
const char* nonce,
const char* gas_price,
const char* gas_limit,
const char* to,
const char* value,
const char* data,
const char* privkey,
uint8_t* out)
{
EthereumSignTx tx;                  //交易结构体
EthereumSig signature;              //交易签名
uint8_t privkey_bytes[32];          //账户私钥
    uint8_t rawtx_bytes[224];           //交易数据串
    uint8_t sig_rs[64];                 //签名RS
    int rawtx_length;
int resu=0;
    uint8_t tx_hash[32];                //交易哈希值
    int nrawtx_length,i;
    secp256k1_context* ctx;
    secp256k1_ecdsa_signature sig;

//将16进制字符串转为bytes
tx.nonce.size = size_of_bytes(strlen(nonce));
hex2byte_arr(nonce, strlen(nonce), tx.nonce.bytes, tx.nonce.size);
tx.gas_price.size = size_of_bytes(strlen(gas_price));
hex2byte_arr(gas_price, strlen(gas_price), tx.gas_price.bytes, tx.gas_price.size);
tx.gas_limit.size = size_of_bytes(strlen(gas_limit));
hex2byte_arr(gas_limit, strlen(gas_limit), tx.gas_limit.bytes, tx.gas_limit.size);
tx.to.size = size_of_bytes(strlen(to));
hex2byte_arr(to, strlen(to), tx.to.bytes, tx.to.size);
tx.value.size = size_of_bytes(strlen(value));
hex2byte_arr(value, strlen(value), tx.value.bytes, tx.value.size);
tx.data_initial_chunk.size = size_of_bytes(strlen(data));
hex2byte_arr(data, strlen(data), tx.data_initial_chunk.bytes,
tx.data_initial_chunk.size);
hex2bytes_arr(privkey, privkey_bytes);

//将交易结构体进行RLP编码
rawtx_length = ethwallet_assemble_tx(&tx, &signature, rawtx_bytes, false);
//将编码后的交易进行散列计算
keccak256(rawtx_bytes, rawtx_length, tx_hash);

//使用私钥经过椭圆曲线算法对散列值进行签名
ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);    
resu = secp256k1_ecdsa_sign(ctx, &sig, tx_hash, privkey_bytes, NULL, NULL);
memcpy(sig_rs,&sig,64);
printf("%s", "Signature:");
for ( i = 0; i < 64; i++)
printf("%02x", sig_rs[i]);
printf("\n\n");

//填充签名结构体
memcpy(&signature.signature_r.bytes, sig_rs, 32);
memcpy(&signature.signature_s.bytes, sig_rs + 32, 32);
signature.signature_v = 27;
signature.signature_r.size = 32;
signature.signature_s.size = 32;

//将附上签名数据的交易结构体进行RLP编码
nrawtx_length = ethwallet_assemble_tx(&tx, &signature, out, true);
return nrawtx_length;
}

Keccak256哈希算法

/*
const uint8_t *data      需要Hash的数据
uint16_t length          数据长度
uint8_t* result          Hash结果
*/
static void keccak256(const uint8_t *data, uint16_t length, uint8_t* result) {

SHA3_CTX context;
keccak_init(&context);
keccak_update(&context, (const unsigned char*)data, (size_t)length);
keccak_final(&context, (unsigned char*)result);
memset((char*)&context, 0, sizeof(SHA3_CTX));
}

数字签名

数字签名是用于证明数字消息或文档的真实性的数学方案。有效的数字签名使收件人有理由相信该邮件是由已知发件人(身份验证)创建的,发件人不能拒绝发送邮件(不可否认),并且邮件在传输过程中未被更改(完整性) 。

发送方用自己的私钥对数字指纹进行加密后所得的数据即是数字签名,其中包括非对称密钥加密和数字签名两个过程,在可以给数据加密的同时,也可用于接收方验证发送方身份的合法性。采用数字签名时,接收方需要使用发送方的公钥才能解开数字签名得到数字指纹。

数字指纹又称为信息摘要,是指发送方通过HASH算法对明文信息计算后得出的数据。采用数字指纹时,发送方会将本端对明文进哈希运算后生成的数字指纹(还要经过数字签名),以及采用对端公钥对明文进行加密后生成的密文一起发送给接收方,接收方用同样的HASH算法对明文计算生成的数据指纹,与收到的数字指纹进行匹配,如果一致,便可确定明文信息没有被篡改。

数字签名算法在以太坊中的应用很多,目前已知至少有两处:一是在生成每个交易对象时,对整个交易对象进行数字签名;二是在共识算法的Clique算法实现中,在针对新区块进行授权/封印的Seal()函数里,对新创建区块做了数字签名。这两处应用的签名算法都是椭圆曲线数字签名加密算法。

椭圆曲线签名过程

以太坊中使用的数字签名算法全称Elliptic Curve Digital Signature Algorithm,简称ECDSA。ECDSA是用于基于椭圆曲线私钥/公钥对的数字签名的算法。

椭圆曲线数字签名包含两个部分,第一部分是使用来自消息(交易)的私钥(签名密钥)创建签名的算法。第二部分是一种算法,允许任何人仅使用消息和公钥来验证签名。

创建签名
在以太坊的ECDSA实现中,被签名的“消息”是交易,或者更准确地说,是来自交易的RLP编码数据的Keccak256散列。签名密钥是EOA的私钥。

签名算法:
k是签名的私钥
m是RLP编码后的交易数据
是Keccak256哈希函数
是ECDSA签名算法
Sig是由此产生的签名该签名函数产生一个由两个值组成的签名Sig,通常称为R和S:Sig = (R,S);

签署交易

为了产生有效的交易,交易发起者必须使用椭圆曲线数字签名算法对消息进行数字签名。签署交易实际上是指“签署RLP序列化交易数据的Keccak256哈希”。签名应用于交易数据的哈希,而不是交易本身。
要在以太坊签署交易,发起人必须完成以下步骤:
1)创建一个包含九个字段的交易数据结构:nonce,gasPrice,gasLimit,to,value,data,V,R,S
2)生成RLP编码的交易序列化消息
3)计算此序列化消息的Keccak256哈希值
4)计算ECDSA签名,使用发件人的私钥对哈希进行签名
5)在交易中插入ECDSA签名计算出的R和S值

代码实现

/*
const char* nonce,            交易nonce值
const char* gas_price,        交易gas_price值
const char* gas_limit,        交易gas_limit值
const char* to,               交易地址
const char* value,            交易数量
const char* data,             交易data值
const char* privkey,          账户私钥
uint8_t* out                  输出经过签名的交易序列
*/
static int sign_transaction(
const char* nonce,
const char* gas_price,
const char* gas_limit,
const char* to,
const char* value,
const char* data,
const char* privkey,
uint8_t* out)
{
EthereumSignTx tx;                  //交易结构体
EthereumSig signature;              //交易签名
uint8_t privkey_bytes[32];          //账户私钥
    uint8_t rawtx_bytes[224];           //交易数据串
    uint8_t sig_rs[64];                 //签名RS
    int rawtx_length;
int resu=0;
    uint8_t tx_hash[32];                //交易哈希值
    int nrawtx_length,i;
    secp256k1_context* ctx;
    secp256k1_ecdsa_signature sig;

//将16进制字符串转为bytes
tx.nonce.size = size_of_bytes(strlen(nonce));
hex2byte_arr(nonce, strlen(nonce), tx.nonce.bytes, tx.nonce.size);
tx.gas_price.size = size_of_bytes(strlen(gas_price));
hex2byte_arr(gas_price, strlen(gas_price), tx.gas_price.bytes, tx.gas_price.size);
tx.gas_limit.size = size_of_bytes(strlen(gas_limit));
hex2byte_arr(gas_limit, strlen(gas_limit), tx.gas_limit.bytes, tx.gas_limit.size);
tx.to.size = size_of_bytes(strlen(to));
hex2byte_arr(to, strlen(to), tx.to.bytes, tx.to.size);
tx.value.size = size_of_bytes(strlen(value));
hex2byte_arr(value, strlen(value), tx.value.bytes, tx.value.size);
tx.data_initial_chunk.size = size_of_bytes(strlen(data));
hex2byte_arr(data, strlen(data), tx.data_initial_chunk.bytes,
tx.data_initial_chunk.size);
hex2bytes_arr(privkey, privkey_bytes);

//将交易结构体进行RLP编码
rawtx_length = ethwallet_assemble_tx(&tx, &signature, rawtx_bytes, false);
//将编码后的交易进行散列计算
keccak256(rawtx_bytes, rawtx_length, tx_hash);

//使用私钥经过椭圆曲线算法对散列值进行签名
ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);    
resu = secp256k1_ecdsa_sign(ctx, &sig, tx_hash, privkey_bytes, NULL, NULL);
memcpy(sig_rs,&sig,64);
printf("%s", "Signature:");
for ( i = 0; i < 64; i++)
printf("%02x", sig_rs[i]);
printf("\n\n");

//填充签名结构体
memcpy(&signature.signature_r.bytes, sig_rs, 32);
memcpy(&signature.signature_s.bytes, sig_rs + 32, 32);
signature.signature_v = 27;
signature.signature_r.size = 32;
signature.signature_s.size = 32;

//将附上签名数据的交易结构体进行RLP编码
nrawtx_length = ethwallet_assemble_tx(&tx, &signature, out, true);
return nrawtx_length;
}

这篇文章是我参加比赛的作品报告中的部分模块节选,其中有很多我们自己的修改和想法,同时参考了许多大牛,参考来源如下(我把整个报告的参考文献都列在这里了,对trustzone感兴趣的朋友可以研究研究)
[1]仇伟民. 应用于ARM TrustZone的高可信运行环境的设计与实现[D].山东大学,2018.
[2] 帅峰云,黄腾,宋洋. 手机安全和可信应用开发指南:TrustZone与OP-TEE技术详解,2018
[3] 王熙友. ARM TrustZone 安全隔离技术研究与应用[D]. 电子科技大学,2013.1
[4] Johannes Winter. Trusted computing building blocks for embedded linux-based ARM trustzone platforms. 2008
[5] Shunrui Huang, Chuanchang Liu, Zhiyuan Su. Secure Storage Model Based on TrustZone. 2019
[6] Brian McGillion, Tanel Dettenborn, Thomas Nyman, N. Asokan. Open-TEE – An Open Virtual Trusted Execution Environment. 2015
[7] Kirill los . 存储安全技术 : SAN、NAS和DAS的安全保护 . 2004
[8] 王红 . 操作系统原理及应用 (Linux) . 2005
[9] 吴寿鹤 . 区块链开发实战, 以太坊关键技术与案例分析 . 2018
[10] 李宁 . 第一行代码 : 以太坊 . 2018
[11] Gavin Wood . Ethereum: A secure decentralised generalised transaction ledger . 2014
[12] 任伟 . 数字签名与安全协议 . 2015
[13] H Mayer . ECDSA security in bitcoin and ethereum: a research survey . 2016
[14] C Dannen . Proving Ethereum for the clearing use case . 2016
[15] J Langschaedel, BD Armstrong. Bitcoin private key splitting for cold storage. 2015
[16] C Marchand, A Aubert, L Bossuet. On the security evaluation of the ARM TrustZone extension in a heterogeneous SoC. 2017
[17] Eben Upton , Gareth Halfacree. Raspberry Pi用户指南,2013
[18] 柯元旦. Android内核剖析, 2011.9

发布了31 篇原创文章 · 获赞 33 · 访问量 2866

猜你喜欢

转载自blog.csdn.net/qq_40742298/article/details/95939188