이더리움 거래 서명 과정 원본 코드 분석
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
의 계산 결과: 0x9ef7f101dae55081553998d52d0ce57c4cf37271f800b70c0863c4a749977ef1
4.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에 따라 라이센스가 부여됩니다.