이더리움 거래 서명 과정 원본 코드 분석
1. 준비 작업
나는 간단한 계약을 예로 들면 계약의 setA 방법을 호출하고 파라미터는 123이다.계약 코드는 다음과 같습니다.pragma solidity >=0.4.22 <0.6.0;
contract Test {
uint256 internal a;
event SetA(address indexed _from, uint256 _value);
function setA(uint256 _a) public {
a = _a;
emit SetA(msg.sender, _a);
}
function getA() public view returns (uint256) {
return a;
}
}
호출 코드는 다음과 같다.package main
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"math/big"
)
func main() {
// 、ABI
methodId := crypto.Keccak256([]byte("setA(uint256)"))[:4]
fmt.Println("methodId: ", common.Bytes2Hex(methodId))
paramValue := math.U256Bytes(new(big.Int).Set(big.NewInt(123)))
fmt.Println("paramValue: ", common.Bytes2Hex(paramValue))
input := append(methodId, paramValue...)
fmt.Println("input: ", common.Bytes2Hex(input))
// 、
nonce := uint64(24)
value := big.NewInt(0)
gasLimit := uint64(3000000)
gasPrice := big.NewInt(20000000000)
rawTx := types.NewTransaction(nonce, common.HexToAddress("0x05e56888360ae54acf2a389bab39bd41e3934d2b"), value, gasLimit, gasPrice, input)
jsonRawTx, _ := rawTx.MarshalJSON()
fmt.Println("rawTx: ", string(jsonRawTx))
// 、
signer := types.NewEIP155Signer(big.NewInt(1))
key, err := crypto.HexToECDSA("e8e14120bb5c085622253540e886527d24746cd42d764a5974be47090d3cbc42")
if err != nil {
fmt.Println("crypto.HexToECDSA failed: ", err.Error())
return
}
sigTransaction, err := types.SignTx(rawTx, signer, key)
if err != nil {
fmt.Println("types.SignTx failed: ", err.Error())
return
}
jsonSigTx, _ := sigTransaction.MarshalJSON()
fmt.Println("sigTransaction: ", string(jsonSigTx))
// 、
ethClient, err := ethclient.Dial("http://127.0.0.1:7545")
if err != nil {
fmt.Println("ethclient.Dial failed: ", err.Error())
return
}
err = ethClient.SendTransaction(context.Background(), sigTransaction)
if err != nil {
fmt.Println("ethClient.SendTransaction failed: ", err.Error())
return
}
fmt.Println("send transaction success,tx: ", sigTransaction.Hash().Hex())
}
요청 코드에서 볼 수 있듯이 데이터 흐름의 과정은 다음과 같다.
pragma solidity >=0.4.22 <0.6.0;
contract Test {
uint256 internal a;
event SetA(address indexed _from, uint256 _value);
function setA(uint256 _a) public {
a = _a;
emit SetA(msg.sender, _a);
}
function getA() public view returns (uint256) {
return a;
}
}
package main
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"math/big"
)
func main() {
// 、ABI
methodId := crypto.Keccak256([]byte("setA(uint256)"))[:4]
fmt.Println("methodId: ", common.Bytes2Hex(methodId))
paramValue := math.U256Bytes(new(big.Int).Set(big.NewInt(123)))
fmt.Println("paramValue: ", common.Bytes2Hex(paramValue))
input := append(methodId, paramValue...)
fmt.Println("input: ", common.Bytes2Hex(input))
// 、
nonce := uint64(24)
value := big.NewInt(0)
gasLimit := uint64(3000000)
gasPrice := big.NewInt(20000000000)
rawTx := types.NewTransaction(nonce, common.HexToAddress("0x05e56888360ae54acf2a389bab39bd41e3934d2b"), value, gasLimit, gasPrice, input)
jsonRawTx, _ := rawTx.MarshalJSON()
fmt.Println("rawTx: ", string(jsonRawTx))
// 、
signer := types.NewEIP155Signer(big.NewInt(1))
key, err := crypto.HexToECDSA("e8e14120bb5c085622253540e886527d24746cd42d764a5974be47090d3cbc42")
if err != nil {
fmt.Println("crypto.HexToECDSA failed: ", err.Error())
return
}
sigTransaction, err := types.SignTx(rawTx, signer, key)
if err != nil {
fmt.Println("types.SignTx failed: ", err.Error())
return
}
jsonSigTx, _ := sigTransaction.MarshalJSON()
fmt.Println("sigTransaction: ", string(jsonSigTx))
// 、
ethClient, err := ethclient.Dial("http://127.0.0.1:7545")
if err != nil {
fmt.Println("ethclient.Dial failed: ", err.Error())
return
}
err = ethClient.SendTransaction(context.Background(), sigTransaction)
if err != nil {
fmt.Println("ethClient.SendTransaction failed: ", err.Error())
return
}
fmt.Println("send transaction success,tx: ", sigTransaction.Hash().Hex())
}
Transaction 거래 대상 2. ABI 인코딩 요청 매개 변수 setA(123) ABI 인코딩을 통해 얻은 데이터: 0xee919d50000000000000000000000000000000000000000000000000000000000000007b이 데이터에는
methodId, 함수 표지 코드(4바이트), setA(uint256)에 Keccak256을 구하고 4위를 취한다. 값은 ee919d50이다.paramValue, 함수 매개 변수(32바이트), 123의 BigInt 유형에 대해byte, 000000000000000000000000000000000000000000000000000000000000007b 3. 구조 Transaction 대상
거래 객체를 구성하는 데 필요한 매개 변수는 다음과 같습니다.
nonce, 요청 계정 nonce 값 address, 계약 주소 value, 이체의 이더리움 화폐 개수, 단위 wei gasLimit, 최대 소모gas gasPrice,gas가격 input, 요청된 계약 입력 매개 변수 address이 비어 있습니다.만약 태화 이체 거래라면 input은 비어 있고 address은 수신자 주소이다.거래의 핵심 데이터 구조는
txdata이다.// go-ethereum/core/types/transaction.go
type Transaction struct {
data txdata
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}
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:"-"`
}
func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
if len(data) > 0 {
data = common.CopyBytes(data)
}
d := txdata{
AccountNonce: nonce,
Recipient: to,
Payload: data,
Amount: new(big.Int),
GasLimit: gasLimit,
Price: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
if amount != nil {
d.Amount.Set(amount)
}
if gasPrice != nil {
d.Price.Set(gasPrice)
}
return &Transaction{data: d}
}
txdata 중 V, R, S 세 필드는 서명과 관련이 있다.구성된 거래 대상의 출력 결과는 (현재 v, r, s는 기본값)입니다.rawTx: {"nonce":"0x18","gasPrice":"0x4a817c800","gas":"0x2dc6c0","to":"0x05e56888360ae54acf2a389bab39bd41e3934d2b","value":"0x0","input":"0xee919d50000000000000000000000000000000000000000000000000000000000000007b","v":"0x0","r":"0x0","s":"0x0","hash":"0x629d42fd16be0b5dc22d53d63dcce8144d5fc843e056465bc2bea25f4ebe8249"}
4. 거래 서명
거래 서명 핵심은 types.SignTx 방법을 사용하고 원본 코드는 다음과 같다.// go-ethereum/core/types/transaction_signing.go
// SignTx signs the transaction using the given signer and private key
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := s.Hash(tx)
sig, err := crypto.Sign(h[:], prv)
if err != nil {
return nil, err
}
return tx.WithSignature(s, sig)
}
SignTx 방법에는 세 가지 매개 변수가 있습니다.
// go-ethereum/core/types/transaction_signing.go
// SignTx signs the transaction using the given signer and private key
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
h := s.Hash(tx)
sig, err := crypto.Sign(h[:], prv)
if err != nil {
return nil, err
}
return tx.WithSignature(s, sig)
}
tx *Transaction, 구조 Transaction 대상 s Signer,signer 서명 방식은 EIP155Signer HomesteadSigner과 FrontierSigner을 포함하고 그 중에서 HomesteadSigner이 FrontierSigner을 계승한다.이 필드가 필요한 이유는 EIP155에서 단순 반복 공격 취약점을 복구한 후 기존 블록체인의 서명 방식을 그대로 유지해야 하지만 새로운 버전의 서명 방식을 제공해야 하기 때문이다.따라서 블록 높이에 따라 다른 서명기를 만듭니다.prv *ecdsa.PrivateKey, secp256k1 표준 개인 키 SignTx 방법의 서명 과정은 세 단계로 나뉜다.V, R, S 필드 4.1 rlpHash 계산
EIP155Signer이 실현한hash 알고리즘은 FrontierSigner에 비해 하나의 체인 ID와 두 개의 uint 빈값이 많다. 그러면 서명된 거래는 하나의 체인에 속할 수 있다.Hash 계산 코드는 다음과 같습니다.
// go-ethereum/core/types/transaction_signing.go
func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
return rlpHash([]interface{}{
tx.data.AccountNonce,
tx.data.Price,
tx.data.GasLimit,
tx.data.Recipient,
tx.data.Amount,
tx.data.Payload,
s.chainId, uint(0), uint(0),
})
}
rlpHash의 계산 결과: 0x9ef7f101dae55081553998d52d0ce57c4cf37271f800b70c0863c4a749977ef14.2 개인 키 서명
crypto.Sign(h[:], prv) 소스 코드는 다음과 같습니다.// go-ethereum/crypto/signature_cgo.go
func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
if len(hash) != 32 {
return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash))
}
seckey := math.PaddedBigBytes(prv.D, prv.Params().BitSize/8)
defer zeroBytes(seckey)
return secp256k1.Sign(hash, seckey)
}
Sign 방법은 secp256k1의 타원 곡선 알고리즘을 호출하여 서명한 후 되돌아오는 결과는 41c4a2eb073e6df89c3f467b3516e9c313590d8d57f7c217fe7e72a7b4a6b8ed5f20a758396a5e681ce1ab4cec749f8560e28c9eb91072ec7a8acc002a11bb1d00이다.4.3 거래 대상의 V, R, S 필드 채우기
tx.WithSignature(s, sig) 소스 코드는 다음과 같습니다.// go-ethereum/core/types/transaction_signing.go
func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) {
r, s, v, err := signer.SignatureValues(tx, sig)
if err != nil {
return nil, err
}
cpy := &Transaction{data: tx.data}
cpy.data.R, cpy.data.S, cpy.data.V = r, s, v
return cpy, nil
}
func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig)
if err != nil {
return nil, nil, nil, err
}
if s.chainId.Sign() != 0 {
V = big.NewInt(int64(sig[64] + 35))
V.Add(V, s.chainIdMul)
}
return R, S, V, nil
}
func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
return hs.FrontierSigner.SignatureValues(tx, sig)
}
func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
if len(sig) != 65 {
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
}
r = new(big.Int).SetBytes(sig[:32])
s = new(big.Int).SetBytes(sig[32:64])
if tx.IsPrivate() {
v = new(big.Int).SetBytes([]byte{sig[64] + 37})
} else {
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
}
return r, s, v, nil
}
WithSignature 방법 중 핵심은 SignatureValues 방법을 사용했다.EIP155Signer의 SignatureValues 방법은 FrontierSigner의 방법에 비해 V의 값을 계산하는 데 차이가 있다.FrontierSigner의 SignatureValues 방법에서 서명 결과 41c4a2eb073e6df89c3f467b3516e9c313590d8d57f7c217fe7e72a7b4a6b8ed5f20a758396a5e681ce1ab4cec749f8560e28c9eb91072ec7a8acc002a11bb1d00을 세 부로 나눈다. 각각:R, 41c4a2eb073e6df89c3f467b3516e9c313590d8d57f7c217fe7e72a7b4a6b8ed S, 5f20a758396a5e681ce1ab4cec749f8560e28c9eb91072ec7a8acc002a11bb1d 00 + 27, V, 10진수 27 EIP155Signer의 SignatureValues 방법에서 체인 ID에 따라 V값을 다시 계산했다. 여기서 체인 ID는 1이고 다시 계산한 V값의 10진법 결과는 37이다.서명 후 거래 대상 결과:
{"nonce":"0x18","gasPrice":"0x4a817c800","gas":"0x2dc6c0","to":"0x05e56888360ae54acf2a389bab39bd41e3934d2b","value":"0x0","input":"0xee919d50000000000000000000000000000000000000000000000000000000000000007b","v":"0x25","r":"0x41c4a2eb073e6df89c3f467b3516e9c313590d8d57f7c217fe7e72a7b4a6b8ed","s":"0x5f20a758396a5e681ce1ab4cec749f8560e28c9eb91072ec7a8acc002a11bb1d","hash":"0xf8a3bf13828d50b107da40188c8e772b83a613f0044593a4e49438a214a79c83"}5. 거래 발송
거래 SendTransaction 발송 방법은 먼저 서명 정보를 가진 거래 대상에 대해 rlp 인코딩을 하고 인코딩된 jsonrpc의 eth_sendRawTransaction 방법으로 거래를 발송한다.소스 코드는 다음과 같습니다.// go-ethereum/ethclient/ethclient.go
func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return err
}
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))
}
최종 계산된 서명 후의 거래 데이터는 0xf889188504a817c800832dc6c09405e56888360ae54acf2a389bab39bd41e3934d2b80a4ee919d50000000000000000000000000000000000000000000000000000000000000007b25a041c4a2eb073e6df89c3f467b3516e9c313590d8d57f7c217fe7e72a7b4a6b8eda05f20a758396a5e681ce1ab4cec749f8560e28c9eb91072ec7a8acc002a11bb1d이다
6. 총결산
이로써 거래의 서명이 완료되어 서명 데이터를 얻었다.원본 데이터에서 서명 데이터까지 핵심 기술 포인트는 다음과 같습니다.
// go-ethereum/ethclient/ethclient.go
func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return err
}
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))
}
이로써 거래의 서명이 완료되어 서명 데이터를 얻었다.원본 데이터에서 서명 데이터까지 핵심 기술 포인트는 다음과 같습니다.
secp256k1, V, R 이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
소스 코드에서 이더리움 Replacementtransaction underpriced 이상 분석프로젝트에서 이더리움 이체 기능을 사용했는데 어느 날 이더리움 네트워크가 막혀서 이체가 Replacementtransaction underpriced 이상을 보고했습니다. 이 이상 키워드에 따라 이더리움 원본 코드를...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.