Skip to content

Commit

Permalink
Merge pull request #68 from hyperledger/struct-hashing
Browse files Browse the repository at this point in the history
Expose API to directly call HashStruct from EIP-712 and M+N dims
  • Loading branch information
peterbroadhurst authored Aug 8, 2024
2 parents f070445 + 80a2c1a commit c144978
Show file tree
Hide file tree
Showing 19 changed files with 397 additions and 37 deletions.
5 changes: 4 additions & 1 deletion pkg/abi/abidecode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ func TestDecodeABISignedIntOk(t *testing.T) {
assert.NoError(t, err)

assert.Equal(t, ElementaryTypeInt, cv.Children[0].Component.ElementaryType())
assert.Equal(t, uint16(256), cv.Children[0].Component.ElementaryM())
assert.Equal(t, int64(-0x12345), cv.Children[0].Value.(*big.Int).Int64())

}
Expand Down Expand Up @@ -667,6 +668,8 @@ func TestDecodeABIFixedOk(t *testing.T) {
cv, err := p.DecodeABIData(d, 0)
assert.NoError(t, err)
assert.Equal(t, ElementaryTypeFixed, cv.Children[0].Component.ElementaryType())
assert.Equal(t, uint16(64), cv.Children[0].Component.ElementaryM())
assert.Equal(t, uint16(4), cv.Children[0].Component.ElementaryN())
assert.Equal(t, "-7.4565", cv.Children[0].Value.(*big.Float).String())

}
Expand Down Expand Up @@ -1046,7 +1049,7 @@ func TestDecodeAddressWithNonZeroPadding(t *testing.T) {
"ffffffffffffffffffffffffab0974bbed8afc5212e951c8498873319d02d025" + // (uint256) 0xffffffffffffffffffffffffab0974bbed8afc5212e951c8498873319d02d025
"ffffffffffffffffffffffffab0974bbed8afc5212e951c8498873319d02d025" + // (uint160) 0xab0974bbed8afc5212e951c8498873319d02d025
"ffffffffffffffffffffffffab0974bbed8afc5212e951c8498873319d02d025" + // ( uint64) 0x498873319d02d025
"ffffffffffffffffffffffffab0974bbed8afc5212e951c8498873319d02d025") // ( uint8) 0x25
"ffffffffffffffffffffffffab0974bbed8afc5212e951c8498873319d02d025") // ( uint8) 0x25
assert.NoError(t, err)

cv, err := f.DecodeCallData(d)
Expand Down
4 changes: 2 additions & 2 deletions pkg/abi/inputparsing.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2023 Kaleido, Inc.
// Copyright © 2024 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down Expand Up @@ -460,7 +460,7 @@ func walkTupleInput(ctx context.Context, breadcrumbs string, input interface{},
}
cv = &ComponentValue{
Component: component,
Children: make([]*ComponentValue, len(iMap)),
Children: make([]*ComponentValue, len(component.tupleChildren)),
}
for i, tupleChild := range component.tupleChildren {
if tupleChild.keyName == "" {
Expand Down
12 changes: 12 additions & 0 deletions pkg/abi/typecomponents.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ type TypeComponent interface {
String() string // gives the signature for this type level of the type component hierarchy
ComponentType() ComponentType // classification of the component type (tuple, array or elemental)
ElementaryType() ElementaryTypeInfo // only non-nil for elementary components
ElementaryM() uint16 // only for N dimensioned elementary types
ElementaryN() uint16 // only for M dimensioned elementary types
ElementarySuffix() string // only on elementary types with a suffix - expands "aliases" (so "uint" would have "256")
ElementaryFixed() bool // whether the elementary type if fixed
ArrayChild() TypeComponent // only non-nil for array components
Expand Down Expand Up @@ -380,6 +382,16 @@ func (tc *typeComponent) ElementaryFixed() bool {
return false
}

// M dimension of elementary type (if applicable)
func (tc *typeComponent) ElementaryM() uint16 {
return tc.m
}

// N dimension of elementary type (if applicable)
func (tc *typeComponent) ElementaryN() uint16 {
return tc.n
}

func (tc *typeComponent) KeyName() string {
return tc.keyName
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/eip712/typed_data_v4.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2023 Kaleido, Inc.
// Copyright © 2024 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down Expand Up @@ -223,6 +223,11 @@ func encodeData(ctx context.Context, typeName string, v interface{}, allTypes Ty
return encoded, nil
}

// HashStruct allows hashing of an individual structure, without the EIP-712 domain
func HashStruct(ctx context.Context, typeName string, v interface{}, allTypes TypeSet) (result ethtypes.HexBytes0xPrefix, err error) {
return hashStruct(ctx, typeName, v, allTypes, "")
}

func hashStruct(ctx context.Context, typeName string, v interface{}, allTypes TypeSet, breadcrumbs string) (result ethtypes.HexBytes0xPrefix, err error) {
encoded, err := encodeData(ctx, typeName, v, allTypes, breadcrumbs)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions pkg/eip712/typed_data_v4_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func TestMessage_ExampleFromEIP712Spec(t *testing.T) {
ed, err := EncodeTypedDataV4(ctx, &p)
assert.NoError(t, err)
assert.Equal(t, "0xde26f53b35dd5ffdc13f8297e5cc7bbcb1a04bf33803bd2bf4a45eb251360cb8", ed.String())

hs, err := HashStruct(ctx, p.PrimaryType, p.Message, p.Types)
assert.NoError(t, err)
assert.Equal(t, "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e", hs.String())
}

func TestMessage_EmptyMessage(t *testing.T) {
Expand Down
33 changes: 25 additions & 8 deletions pkg/ethsigner/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ type Transaction struct {
Data ethtypes.HexBytes0xPrefix `ffstruct:"EthTransaction" json:"data"`
}

type TransactionWithOriginalPayload struct {
*Transaction
Payload []byte `json:"-"`
}

func (t *Transaction) BuildLegacy() rlp.List {
rlpList := make(rlp.List, 0, 6)
rlpList = append(rlpList, rlp.WrapInt(t.Nonce.BigInt()))
Expand Down Expand Up @@ -140,13 +145,16 @@ func (t *Transaction) SignLegacyOriginal(signer secp256k1.Signer) ([]byte, error
if signer == nil {
return nil, i18n.NewError(context.Background(), signermsgs.MsgInvalidSigner)
}
signatureData := t.SignaturePayloadLegacyOriginal()
sig, err := signer.Sign(signatureData.data)
signaturePayload := t.SignaturePayloadLegacyOriginal()
sig, err := signer.Sign(signaturePayload.data)
if err != nil {
return nil, err
}
return t.FinalizeLegacyOriginalWithSignature(signaturePayload, sig)
}

rlpList := t.addSignature(signatureData.rlpList, sig)
func (t *Transaction) FinalizeLegacyOriginalWithSignature(signaturePayload *TransactionSignaturePayload, sig *secp256k1.SignatureData) ([]byte, error) {
rlpList := t.addSignature(signaturePayload.rlpList, sig)
return rlpList.Encode(), nil
}

Expand Down Expand Up @@ -174,7 +182,10 @@ func (t *Transaction) SignLegacyEIP155(signer secp256k1.Signer, chainID int64) (
if err != nil {
return nil, err
}
return t.FinalizeLegacyEIP155WithSignature(signaturePayload, sig, chainID)
}

func (t *Transaction) FinalizeLegacyEIP155WithSignature(signaturePayload *TransactionSignaturePayload, sig *secp256k1.SignatureData, chainID int64) ([]byte, error) {
// Use the EIP-155 V value, of (2*ChainID + 35 + Y-parity)
sig.UpdateEIP155(chainID)

Expand Down Expand Up @@ -206,7 +217,10 @@ func (t *Transaction) SignEIP1559(signer secp256k1.Signer, chainID int64) ([]byt
if err != nil {
return nil, err
}
return t.FinalizeEIP1559WithSignature(signaturePayload, sig)
}

func (t *Transaction) FinalizeEIP1559WithSignature(signaturePayload *TransactionSignaturePayload, sig *secp256k1.SignatureData) ([]byte, error) {
// Use the direct 0/1 Y-parity value
sig.UpdateEIP2930()

Expand All @@ -216,7 +230,7 @@ func (t *Transaction) SignEIP1559(signer secp256k1.Signer, chainID int64) ([]byt
return append([]byte{TransactionType1559}, rlpList.Encode()...), nil
}

func RecoverLegacyRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *Transaction, error) {
func RecoverLegacyRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *TransactionWithOriginalPayload, error) {

decoded, _, err := rlp.Decode(rawTx)
if err != nil {
Expand Down Expand Up @@ -264,7 +278,7 @@ func RecoverLegacyRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xP

}

func recoverCommon(tx *Transaction, message []byte, chainID int64, v int64, r, s []byte) (*ethtypes.Address0xHex, *Transaction, error) {
func recoverCommon(tx *Transaction, message []byte, chainID int64, v int64, r, s []byte) (*ethtypes.Address0xHex, *TransactionWithOriginalPayload, error) {
foundSig := &secp256k1.SignatureData{
V: new(big.Int),
R: new(big.Int),
Expand All @@ -279,10 +293,13 @@ func recoverCommon(tx *Transaction, message []byte, chainID int64, v int64, r, s
return nil, nil, err
}

return signer, tx, nil
return signer, &TransactionWithOriginalPayload{
Transaction: tx,
Payload: message,
}, nil
}

func RecoverEIP1559Transaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *Transaction, error) {
func RecoverEIP1559Transaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *TransactionWithOriginalPayload, error) {

if len(rawTx) == 0 || rawTx[0] != TransactionType1559 {
return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidEIP1559Transaction, "TransactionType")
Expand Down Expand Up @@ -325,7 +342,7 @@ func RecoverEIP1559Transaction(ctx context.Context, rawTx ethtypes.HexBytes0xPre
)
}

func RecoverRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *Transaction, error) {
func RecoverRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *TransactionWithOriginalPayload, error) {

// The first byte of the payload (per EIP-2718) is either `>= 0xc0` for legacy transactions,
// or a transaction type selector (up to `0x7f`).
Expand Down
11 changes: 10 additions & 1 deletion pkg/ethtypes/hexbytes.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Kaleido, Inc.
// Copyright © 2024 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand All @@ -17,6 +17,7 @@
package ethtypes

import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
Expand All @@ -29,6 +30,10 @@ type HexBytesPlain []byte
// HexBytes0xPrefix are serialized to JSON as hex with an `0x` prefix
type HexBytes0xPrefix []byte

func (h HexBytesPlain) Equals(h2 HexBytesPlain) bool {
return bytes.Equal(h, h2)
}

func (h *HexBytesPlain) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
Expand All @@ -54,6 +59,10 @@ func (h *HexBytes0xPrefix) UnmarshalJSON(b []byte) error {
return ((*HexBytesPlain)(h)).UnmarshalJSON(b)
}

func (h HexBytes0xPrefix) Equals(h2 HexBytes0xPrefix) bool {
return bytes.Equal(h, h2)
}

func (h HexBytes0xPrefix) String() string {
return "0x" + hex.EncodeToString(h)
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/ethtypes/hexbytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,15 @@ func TestHexByteConstructors(t *testing.T) {
MustNewHexBytes0xPrefix("!wrong")
})
}

func TestHexByteEqual(t *testing.T) {
assert.True(t, HexBytesPlain(nil).Equals(nil))
assert.False(t, HexBytesPlain(nil).Equals(HexBytesPlain{0x00}))
assert.False(t, (HexBytesPlain{0x00}).Equals(nil))
assert.True(t, (HexBytesPlain{0x00}).Equals(HexBytesPlain{0x00}))

assert.True(t, HexBytes0xPrefix(nil).Equals(nil))
assert.False(t, HexBytes0xPrefix(nil).Equals(HexBytes0xPrefix{0x00}))
assert.False(t, (HexBytes0xPrefix{0x00}).Equals(nil))
assert.True(t, (HexBytes0xPrefix{0x00}).Equals(HexBytes0xPrefix{0x00}))
}
85 changes: 85 additions & 0 deletions pkg/ethtypes/hexuint64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright © 2024 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ethtypes

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/hyperledger/firefly-common/pkg/i18n"
)

// HexUint64 is a positive integer - serializes to JSON as an 0x hex string (no leading zeros), and parses flexibly depending on the prefix (so 0x for hex, or base 10 for plain string / float64)
type HexUint64 uint64

func (h *HexUint64) String() string {
if h == nil {
return "0x0"
}
return "0x" + strconv.FormatUint(uint64(*h), 16)
}

func (h HexUint64) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, h.String())), nil
}

func (h *HexUint64) UnmarshalJSON(b []byte) error {
var i interface{}
_ = json.Unmarshal(b, &i)
switch i := i.(type) {
case float64:
*h = HexUint64(i)
return nil
case string:
i64, err := strconv.ParseUint(i, 0, 64)
if err != nil {
return fmt.Errorf("unable to parse integer: %s", i)
}
*h = HexUint64(i64)
return nil
default:
return fmt.Errorf("unable to parse integer from type %T", i)
}
}

func (h HexUint64) Uint64() uint64 {
return uint64(h)
}

func (h *HexUint64) Uint64OrZero() uint64 {
if h == nil {
return 0
}
return uint64(*h)
}

func (h *HexUint64) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case int64:
*h = HexUint64(src)
return nil
case uint64:
*h = HexUint64(src)
return nil
default:
return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, h)
}
}
Loading

0 comments on commit c144978

Please sign in to comment.