Skip to content

Commit 7ee9a6e

Browse files
authored
signer: implement blob txs sendtxargs, enable blobtx-signing (#28976)
This change makes it possible to sign blob transactions
1 parent 35fcf9c commit 7ee9a6e

File tree

11 files changed

+302
-28
lines changed

11 files changed

+302
-28
lines changed

accounts/external/backend.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
205205
to = &t
206206
}
207207
args := &apitypes.SendTxArgs{
208-
Data: &data,
208+
Input: &data,
209209
Nonce: hexutil.Uint64(tx.Nonce()),
210210
Value: hexutil.Big(*tx.Value()),
211211
Gas: hexutil.Uint64(tx.Gas()),
@@ -215,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
215215
switch tx.Type() {
216216
case types.LegacyTxType, types.AccessListTxType:
217217
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
218-
case types.DynamicFeeTxType:
218+
case types.DynamicFeeTxType, types.BlobTxType:
219219
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
220220
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
221221
default:
@@ -235,6 +235,17 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
235235
accessList := tx.AccessList()
236236
args.AccessList = &accessList
237237
}
238+
if tx.Type() == types.BlobTxType {
239+
args.BlobHashes = tx.BlobHashes()
240+
sidecar := tx.BlobTxSidecar()
241+
if sidecar == nil {
242+
return nil, fmt.Errorf("blobs must be present for signing")
243+
}
244+
args.Blobs = sidecar.Blobs
245+
args.Commitments = sidecar.Commitments
246+
args.Proofs = sidecar.Proofs
247+
}
248+
238249
var res signTransactionResult
239250
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
240251
return nil, err

core/types/transaction.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,26 @@ func (tx *Transaction) WithoutBlobTxSidecar() *Transaction {
446446
return cpy
447447
}
448448

449+
// WithBlobTxSidecar returns a copy of tx with the blob sidecar added.
450+
func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction {
451+
blobtx, ok := tx.inner.(*BlobTx)
452+
if !ok {
453+
return tx
454+
}
455+
cpy := &Transaction{
456+
inner: blobtx.withSidecar(sideCar),
457+
time: tx.time,
458+
}
459+
// Note: tx.size cache not carried over because the sidecar is included in size!
460+
if h := tx.hash.Load(); h != nil {
461+
cpy.hash.Store(h)
462+
}
463+
if f := tx.from.Load(); f != nil {
464+
cpy.from.Store(f)
465+
}
466+
return cpy
467+
}
468+
449469
// SetTime sets the decoding time of a transaction. This is used by tests to set
450470
// arbitrary times and by persistent transaction pools when loading old txs from
451471
// disk.

core/types/tx_blob.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@ func (tx *BlobTx) withoutSidecar() *BlobTx {
191191
return &cpy
192192
}
193193

194+
func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
195+
cpy := *tx
196+
cpy.Sidecar = sideCar
197+
return &cpy
198+
}
199+
194200
func (tx *BlobTx) encode(b *bytes.Buffer) error {
195201
if tx.Sidecar == nil {
196202
return rlp.Encode(b, tx)

internal/ethapi/api.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,15 +1865,14 @@ type SignTransactionResult struct {
18651865
// The node needs to have the private key of the account corresponding with
18661866
// the given from address and it needs to be unlocked.
18671867
func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
1868+
args.blobSidecarAllowed = true
1869+
18681870
if args.Gas == nil {
18691871
return nil, errors.New("gas not specified")
18701872
}
18711873
if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) {
18721874
return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
18731875
}
1874-
if args.IsEIP4844() {
1875-
return nil, errBlobTxNotSupported
1876-
}
18771876
if args.Nonce == nil {
18781877
return nil, errors.New("nonce not specified")
18791878
}
@@ -1889,6 +1888,16 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr
18891888
if err != nil {
18901889
return nil, err
18911890
}
1891+
// If the transaction-to-sign was a blob transaction, then the signed one
1892+
// no longer retains the blobs, only the blob hashes. In this step, we need
1893+
// to put back the blob(s).
1894+
if args.IsEIP4844() {
1895+
signed = signed.WithBlobTxSidecar(&types.BlobTxSidecar{
1896+
Blobs: args.Blobs,
1897+
Commitments: args.Commitments,
1898+
Proofs: args.Proofs,
1899+
})
1900+
}
18921901
data, err := signed.MarshalBinary()
18931902
if err != nil {
18941903
return nil, err

internal/ethapi/api_test.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,11 +1037,8 @@ func TestSignBlobTransaction(t *testing.T) {
10371037
}
10381038

10391039
_, err = api.SignTransaction(context.Background(), argsFromTransaction(res.Tx, b.acc.Address))
1040-
if err == nil {
1041-
t.Fatalf("should fail on blob transaction")
1042-
}
1043-
if !errors.Is(err, errBlobTxNotSupported) {
1044-
t.Errorf("error mismatch. Have: %v, want: %v", err, errBlobTxNotSupported)
1040+
if err != nil {
1041+
t.Fatalf("should not fail on blob transaction")
10451042
}
10461043
}
10471044

internal/ethapi/transaction_args.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (args *TransactionArgs) data() []byte {
9797

9898
// setDefaults fills in default values for unspecified tx fields.
9999
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGasEstimation bool) error {
100-
if err := args.setBlobTxSidecar(ctx, b); err != nil {
100+
if err := args.setBlobTxSidecar(ctx); err != nil {
101101
return err
102102
}
103103
if err := args.setFeeDefaults(ctx, b); err != nil {
@@ -290,7 +290,7 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ
290290
}
291291

292292
// setBlobTxSidecar adds the blob tx
293-
func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, b Backend) error {
293+
func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context) error {
294294
// No blobs, we're done.
295295
if args.Blobs == nil {
296296
return nil

signer/core/api.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,10 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA
590590
return nil, err
591591
}
592592
// Convert fields into a real transaction
593-
var unsignedTx = result.Transaction.ToTransaction()
593+
unsignedTx, err := result.Transaction.ToTransaction()
594+
if err != nil {
595+
return nil, err
596+
}
594597
// Get the password for the transaction
595598
pw, err := api.lookupOrQueryPassword(acc.Address, "Account password",
596599
fmt.Sprintf("Please enter the password for account %s", acc.Address.String()))

signer/core/apitypes/types.go

Lines changed: 128 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package apitypes
1818

1919
import (
2020
"bytes"
21+
"crypto/sha256"
2122
"encoding/json"
2223
"errors"
2324
"fmt"
@@ -34,6 +35,8 @@ import (
3435
"github.com/ethereum/go-ethereum/common/math"
3536
"github.com/ethereum/go-ethereum/core/types"
3637
"github.com/ethereum/go-ethereum/crypto"
38+
"github.com/ethereum/go-ethereum/crypto/kzg4844"
39+
"github.com/holiman/uint256"
3740
)
3841

3942
var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Za-z](\w*)(\[\])?$`)
@@ -92,12 +95,21 @@ type SendTxArgs struct {
9295
// We accept "data" and "input" for backwards-compatibility reasons.
9396
// "input" is the newer name and should be preferred by clients.
9497
// Issue detail: https://github.com/ethereum/go-ethereum/issues/15628
95-
Data *hexutil.Bytes `json:"data"`
98+
Data *hexutil.Bytes `json:"data,omitempty"`
9699
Input *hexutil.Bytes `json:"input,omitempty"`
97100

98101
// For non-legacy transactions
99102
AccessList *types.AccessList `json:"accessList,omitempty"`
100103
ChainID *hexutil.Big `json:"chainId,omitempty"`
104+
105+
// For BlobTxType
106+
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
107+
BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
108+
109+
// For BlobTxType transactions with blob sidecar
110+
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
111+
Commitments []kzg4844.Commitment `json:"commitments,omitempty"`
112+
Proofs []kzg4844.Proof `json:"proofs,omitempty"`
101113
}
102114

103115
func (args SendTxArgs) String() string {
@@ -108,24 +120,56 @@ func (args SendTxArgs) String() string {
108120
return err.Error()
109121
}
110122

123+
// data retrieves the transaction calldata. Input field is preferred.
124+
func (args *SendTxArgs) data() []byte {
125+
if args.Input != nil {
126+
return *args.Input
127+
}
128+
if args.Data != nil {
129+
return *args.Data
130+
}
131+
return nil
132+
}
133+
111134
// ToTransaction converts the arguments to a transaction.
112-
func (args *SendTxArgs) ToTransaction() *types.Transaction {
135+
func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) {
113136
// Add the To-field, if specified
114137
var to *common.Address
115138
if args.To != nil {
116139
dstAddr := args.To.Address()
117140
to = &dstAddr
118141
}
119-
120-
var input []byte
121-
if args.Input != nil {
122-
input = *args.Input
123-
} else if args.Data != nil {
124-
input = *args.Data
142+
if err := args.validateTxSidecar(); err != nil {
143+
return nil, err
125144
}
126-
127145
var data types.TxData
128146
switch {
147+
case args.BlobHashes != nil:
148+
al := types.AccessList{}
149+
if args.AccessList != nil {
150+
al = *args.AccessList
151+
}
152+
data = &types.BlobTx{
153+
To: *to,
154+
ChainID: uint256.MustFromBig((*big.Int)(args.ChainID)),
155+
Nonce: uint64(args.Nonce),
156+
Gas: uint64(args.Gas),
157+
GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)),
158+
GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)),
159+
Value: uint256.MustFromBig((*big.Int)(&args.Value)),
160+
Data: args.data(),
161+
AccessList: al,
162+
BlobHashes: args.BlobHashes,
163+
BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)),
164+
}
165+
if args.Blobs != nil {
166+
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
167+
Blobs: args.Blobs,
168+
Commitments: args.Commitments,
169+
Proofs: args.Proofs,
170+
}
171+
}
172+
129173
case args.MaxFeePerGas != nil:
130174
al := types.AccessList{}
131175
if args.AccessList != nil {
@@ -139,7 +183,7 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
139183
GasFeeCap: (*big.Int)(args.MaxFeePerGas),
140184
GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
141185
Value: (*big.Int)(&args.Value),
142-
Data: input,
186+
Data: args.data(),
143187
AccessList: al,
144188
}
145189
case args.AccessList != nil:
@@ -150,7 +194,7 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
150194
Gas: uint64(args.Gas),
151195
GasPrice: (*big.Int)(args.GasPrice),
152196
Value: (*big.Int)(&args.Value),
153-
Data: input,
197+
Data: args.data(),
154198
AccessList: *args.AccessList,
155199
}
156200
default:
@@ -160,10 +204,81 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
160204
Gas: uint64(args.Gas),
161205
GasPrice: (*big.Int)(args.GasPrice),
162206
Value: (*big.Int)(&args.Value),
163-
Data: input,
207+
Data: args.data(),
164208
}
165209
}
166-
return types.NewTx(data)
210+
211+
return types.NewTx(data), nil
212+
}
213+
214+
// validateTxSidecar validates blob data, if present
215+
func (args *SendTxArgs) validateTxSidecar() error {
216+
// No blobs, we're done.
217+
if args.Blobs == nil {
218+
return nil
219+
}
220+
221+
n := len(args.Blobs)
222+
// Assume user provides either only blobs (w/o hashes), or
223+
// blobs together with commitments and proofs.
224+
if args.Commitments == nil && args.Proofs != nil {
225+
return errors.New(`blob proofs provided while commitments were not`)
226+
} else if args.Commitments != nil && args.Proofs == nil {
227+
return errors.New(`blob commitments provided while proofs were not`)
228+
}
229+
230+
// len(blobs) == len(commitments) == len(proofs) == len(hashes)
231+
if args.Commitments != nil && len(args.Commitments) != n {
232+
return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n)
233+
}
234+
if args.Proofs != nil && len(args.Proofs) != n {
235+
return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n)
236+
}
237+
if args.BlobHashes != nil && len(args.BlobHashes) != n {
238+
return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n)
239+
}
240+
241+
if args.Commitments == nil {
242+
// Generate commitment and proof.
243+
commitments := make([]kzg4844.Commitment, n)
244+
proofs := make([]kzg4844.Proof, n)
245+
for i, b := range args.Blobs {
246+
c, err := kzg4844.BlobToCommitment(b)
247+
if err != nil {
248+
return fmt.Errorf("blobs[%d]: error computing commitment: %v", i, err)
249+
}
250+
commitments[i] = c
251+
p, err := kzg4844.ComputeBlobProof(b, c)
252+
if err != nil {
253+
return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err)
254+
}
255+
proofs[i] = p
256+
}
257+
args.Commitments = commitments
258+
args.Proofs = proofs
259+
} else {
260+
for i, b := range args.Blobs {
261+
if err := kzg4844.VerifyBlobProof(b, args.Commitments[i], args.Proofs[i]); err != nil {
262+
return fmt.Errorf("failed to verify blob proof: %v", err)
263+
}
264+
}
265+
}
266+
267+
hashes := make([]common.Hash, n)
268+
hasher := sha256.New()
269+
for i, c := range args.Commitments {
270+
hashes[i] = kzg4844.CalcBlobHashV1(hasher, &c)
271+
}
272+
if args.BlobHashes != nil {
273+
for i, h := range hashes {
274+
if h != args.BlobHashes[i] {
275+
return fmt.Errorf("blob hash verification failed (have=%s, want=%s)", args.BlobHashes[i], h)
276+
}
277+
}
278+
} else {
279+
args.BlobHashes = hashes
280+
}
281+
return nil
167282
}
168283

169284
type SigFormat struct {

0 commit comments

Comments
 (0)