Skip to content

Commit

Permalink
Work on JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
firelizzard18 committed Jul 10, 2024
1 parent 27808c6 commit 5cd7331
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 90 deletions.
87 changes: 70 additions & 17 deletions pkg/types/encoding/eip712.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,20 @@ type EIP712Domain struct {
ChainId *big.Int `json:"chainId,omitempty" form:"chainId" query:"chainId" validate:"required"`
}

var Eip712Domain = EIP712Domain{Name: "Accumulate", Version: "1.0.0", ChainId: big.NewInt(281)}
var EIP712DomainMap map[string]interface{}
var EIP712DomainHash []byte
var Eip712Domain = EIP712Domain{
Name: "Accumulate",
Version: "1.0.0",

// Use a fake domain for the moment
ChainId: big.NewInt(1),
// ChainId: big.NewInt(281),
}

type Eip712Encoder struct {
hasher func(v interface{}) ([]byte, error)
types func(ret map[string]*TypeDefinition, v interface{}, fieldType string) error
types func(ret map[string][]*TypeField, v interface{}, fieldType string) error
}

var eip712EncoderMap map[string]Eip712Encoder
Expand Down Expand Up @@ -141,7 +148,7 @@ func RegisterEnumeratedTypeInterface[T any, R any](op Func[T, R]) {
tp, typesMap := mapEnumTypes(op)
eip712EncoderMap[tp] = NewEncoder(func(v interface{}) ([]byte, error) {
return FromTypedInterfaceToBytes(v, typesMap)
}, func(ret map[string]*TypeDefinition, v interface{}, typeField string) error {
}, func(ret map[string][]*TypeField, v interface{}, typeField string) error {
return FromTypedInterfaceToTypes(ret, v, typesMap)
})
}
Expand Down Expand Up @@ -175,7 +182,7 @@ func FromTypedInterfaceToBytes(v interface{}, typesAliasMap map[string]string) (
return keccak256(b), nil
}

func FromTypedInterfaceToTypes(ret map[string]*TypeDefinition, v interface{}, typesAliasMap map[string]string) error {
func FromTypedInterfaceToTypes(ret map[string][]*TypeField, v interface{}, typesAliasMap map[string]string) error {
//this is a complex structure, so upcast it to an interface map
vv, ok := v.(map[string]interface{})
if !ok {
Expand Down Expand Up @@ -210,11 +217,11 @@ type TypedData struct {
Types []TypeField
}

func (td *TypeDefinition) types(ret map[string]*TypeDefinition, d interface{}, typeName string) error {
func (td *TypeDefinition) types(ret map[string][]*TypeField, d interface{}, typeName string) error {
var err error
//define the type structure
if ret == nil {
ret = make(map[string]*TypeDefinition)
ret = make(map[string][]*TypeField)
}

data := d.(map[string]interface{})
Expand All @@ -223,8 +230,6 @@ func (td *TypeDefinition) types(ret map[string]*TypeDefinition, d interface{}, t

//the stripping shouldn't be necessary, but do it as a precaution
strippedType, _ := stripSlice(typeName)
tdr := &TypeDefinition{}
ret[strippedType] = tdr

for i, field := range *td.Fields {
value, ok := data[field.Name]
Expand All @@ -233,7 +238,7 @@ func (td *TypeDefinition) types(ret map[string]*TypeDefinition, d interface{}, t
}

//append the fields
*tdr.Fields = append(*tdr.Fields, (*td.Fields)[i])
ret[strippedType] = append(ret[strippedType], (*td.Fields)[i])

//breakdown field further if required
err = field.types(ret, value, field.Type)
Expand Down Expand Up @@ -288,15 +293,15 @@ func (td *TypeDefinition) hash(v interface{}, typeName string) ([]byte, error) {
return keccak256(append(keccak256(header.Bytes()), body.Bytes()...)), nil
}

func (t *TypeField) types(ret map[string]*TypeDefinition, v interface{}, fieldType string) error {
func (t *TypeField) types(ret map[string][]*TypeField, v interface{}, fieldType string) error {
if t.encoder.types != nil {
//process more complex type
return t.encoder.types(ret, v, fieldType)
}
return nil
}

func NewEncoder[T any](hasher func(T) ([]byte, error), types func(ret map[string]*TypeDefinition, v interface{}, typeField string) error) Eip712Encoder {
func NewEncoder[T any](hasher func(T) ([]byte, error), types func(ret map[string][]*TypeField, v interface{}, typeField string) error) Eip712Encoder {
return Eip712Encoder{func(v interface{}) ([]byte, error) {
// JSON always decodes numbers as floats
if u, ok := v.(float64); ok {
Expand All @@ -314,8 +319,7 @@ func NewEncoder[T any](hasher func(T) ([]byte, error), types func(ret map[string
return nil, fmt.Errorf("eip712 value of type %T does not match type field", v)
}
return hasher(t)
}, types,
}
}, types}
}

func NewTypeField(n string, tp string) *TypeField {
Expand Down Expand Up @@ -385,10 +389,49 @@ func NewTypeField(n string, tp string) *TypeField {
return nil, err
}
return b, nil
}, func(ret map[string]*TypeDefinition, v interface{}, fieldType string) error {
return nil
},
},
}, func(ret map[string][]*TypeField, v interface{}, fieldType string) error {
strippedType, slices := stripSlice(tp)
encoder, ok := eip712EncoderMap[strippedType]
if ok {
if encoder.types == nil {
return nil
}
if slices > 0 {
vv, ok := v.([]interface{})
if !ok {
return fmt.Errorf("eip712 field %s is not of an array of interfaces", n)
}
for _, vvv := range vv {
err := encoder.types(ret, vvv, fieldType)
if err != nil {
return err
}
}
return nil
}
return encoder.types(ret, v, fieldType)
}

fields, ok := SchemaDictionary[strippedType]
if !ok {
return fmt.Errorf("eip712 field %s", tp)
}
if slices > 0 {
vv, ok := v.([]interface{})
if !ok {
return fmt.Errorf("eip712 field %s is not of an array of interfaces", n)
}
for _, vvv := range vv {
err := fields.types(ret, vvv, fieldType)
if err != nil {
return err
}
}
return nil
}

return fields.types(ret, v, fieldType)
}},
}
}

Expand Down Expand Up @@ -444,6 +487,16 @@ func Eip712Hash(v map[string]interface{}, typeName string, td *TypeDefinition) (
return keccak256(append(EIP712DomainHash, messageHash...)), nil
}

func Eip712Types(v map[string]any, typeName string, td *TypeDefinition) (map[string][]*TypeField, error) {
ret := map[string][]*TypeField{}
err := td.types(ret, v, typeName)
return ret, err
}

func Eip712DomainType() *TypeDefinition {
return SchemaDictionary["EIP712Domain"]
}

func FromstringToBytes(s string) ([]byte, error) {
return keccak256([]byte(s)), nil
}
Expand Down
77 changes: 54 additions & 23 deletions protocol/signature_eip712.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
_ "embed"
"encoding/json"
"fmt"
"strings"

"gitlab.com/accumulatenetwork/accumulate/pkg/types/encoding"
)
Expand Down Expand Up @@ -33,43 +34,64 @@ func NewEip712TransactionDefinition(txn *Transaction) *encoding.TypeDefinition {
encoding.NewTypeField("signature", "SignatureMetadata"),
}

return &encoding.TypeDefinition{txnSchema}
return &encoding.TypeDefinition{Fields: txnSchema}
}

// MarshalEip712 This will create an EIP712 json message needed to submit to a wallet
func MarshalEip712(transaction Transaction) (ret []byte, err error) {
// MarshalEip712 This will create an EIP-712 json message needed to submit to a
// wallet.
func MarshalEip712(txn *Transaction, sig Signature) (ret []byte, err error) {
// Convert the transaction and signature to an EIP-712 message
jtx, err := makeEIP712Message(txn, sig)
if err != nil {
return nil, err
}

// Construct the wallet RPC call
type eip712 struct {
PrimaryType string `json:"primary_type"`
Types []encoding.TypeDefinition
EIP712Domain encoding.EIP712Domain `json:"EIP712Domain"`
Message json.RawMessage `json:"message"`
Types map[string][]*encoding.TypeField `json:"types"`
PrimaryType string `json:"primaryType"`
Domain encoding.EIP712Domain `json:"domain"`
Message any `json:"message"`
}
e := eip712{}
e.PrimaryType = "Transaction"
e.Message, err = transaction.MarshalJSON()
e.EIP712Domain = encoding.Eip712Domain
//go through transaction and build types list
txMap := map[string]interface{}{}
txj, err := transaction.MarshalJSON()
if err != nil {
return nil, err
}
e.Domain = encoding.Eip712Domain

err = json.Unmarshal(txj, &txMap)
// Reformat the message JSON to be compatible with Ethereum
td := NewEip712TransactionDefinition(txn)
formatEIP712Message(jtx, td)
e.Message = jtx

e.Types, err = encoding.Eip712Types(jtx, "Transaction", td)
if err != nil {
return nil, err
}
e.Types["EIP712Domain"] = *encoding.Eip712DomainType().Fields

//capture types from txMap
return json.Marshal(e)
}

j, err := json.Marshal(e)
if err != nil {
return nil, err
func formatEIP712Message(v map[string]any, td *encoding.TypeDefinition) {
for _, field := range *td.Fields {
fv, ok := v[field.Name]
if !ok {
continue
}

switch field.Type {
case "bytes", "bytes32":
v[field.Name] = fmt.Sprintf("0x%v", fv)
continue
}

sch, ok := encoding.SchemaDictionary[strings.TrimPrefix(field.Type, "[]")]
if ok {
formatEIP712Message(fv.(map[string]any), sch)
}
}
return j, nil
} //
}

func Eip712Hasher(txn *Transaction, sig Signature) ([]byte, error) {
func makeEIP712Message(txn *Transaction, sig Signature) (map[string]any, error) {
var delegators []any
var inner *Eip712TypedDataSignature
for inner == nil {
Expand Down Expand Up @@ -109,6 +131,15 @@ func Eip712Hasher(txn *Transaction, sig Signature) ([]byte, error) {
}
jtx["signature"] = jsig

return jtx, nil
}

func Eip712Hasher(txn *Transaction, sig Signature) ([]byte, error) {
jtx, err := makeEIP712Message(txn, sig)
if err != nil {
return nil, err
}

h, err := encoding.Eip712Hash(jtx, "Transaction", NewEip712TransactionDefinition(txn))
if err != nil {
return nil, err
Expand Down
85 changes: 37 additions & 48 deletions protocol/signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,54 +492,6 @@ func TestTypesFromCerts(t *testing.T) {
}

func TestEip712TypedDataSignature(t *testing.T) {
tokenTransaction := []byte(`{
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
],
"SendTokens": [
{ "name": "type", "type": "string" },
{ "name": "to", "type": "TokenRecipient[]" }
],
"TokenRecipient": [
{ "name": "url", "type": "string" },
{ "name": "amount", "type": "uint256" },
],
"TransactionHeader": [
{ "name": "principal", "type": "string" },
{ "name": "initiator", "type": "bytes32" },
],
"Transaction": [
{ "name": "header", "type": "TransactionHeader" },
{ "name": "body", "type": "SendTokens" },
],
},
"primaryType": "SendTokens",
"domain": {
"name": "Accumulate",
"version": "1.0.0",
"chainId": "281",
},
"message": {
"header": {
"principal": "acc://adi.acme/ACME",
"initiator": "84e032fba8a5456f631c822a2b2466c18b3fa7804330ab87088ed6e30d690505"
},
"body": {
"type": "sendTokens",
"to": [
{
"url": "acc://other.acme/ACME",
"amount": "10000000000"
}
]
}
}
}`)
_ = tokenTransaction

txn := &Transaction{}
err := txn.UnmarshalJSON([]byte(`{
"header": {
Expand Down Expand Up @@ -606,3 +558,40 @@ func TestEIP712DelegatedKeyPageUpdate(t *testing.T) {
// Verify the signature
require.True(t, outer.Verify(nil, txn))
}

func TestEIP712MessageForWallet(t *testing.T) {
txn := &Transaction{}
err := txn.UnmarshalJSON([]byte(`{
"header": {
"principal": "acc://adi.acme/ACME"
},
"body": {
"type": "sendTokens",
"to": [{
"url": "acc://other.acme/ACME",
"amount": "10000000000"
}]
}
}`))
require.NoError(t, err)

pub, err := hex.DecodeString("04c4755e0a7a0f7082749bf46cdae4fcddb784e11428446a01478d656f588f94c17d02f3312b43364a0c480d628483c4fb4e3e9f687ac064717d90fdc42cfb6e0e")
require.NoError(t, err)
sig := &Eip712TypedDataSignature{
PublicKey: pub,
Signer: url.MustParse("acc://adi.acme/book/1"),
SignerVersion: 1,
Timestamp: 1720564975623,
Vote: VoteTypeAccept,
}
txn.Header.Initiator = [32]byte(sig.Metadata().Hash())

b, err := MarshalEip712(txn, sig)
require.NoError(t, err)
fmt.Printf("%s\n", b)

// Result from metamask
sig.Signature, err = hex.DecodeString("d420cddc64babaa548a09a9b05ae4b5cab6ab78fcb715870bd3a794be84b608763f52b044f672e4e2152beb42dcea00b8b5e36a1eecf6aa26ae62436c6e6d70f1b")
require.NoError(t, err)
require.True(t, sig.Verify(nil, txn))
}
6 changes: 4 additions & 2 deletions test/cmd/gen-testdata/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,11 +523,13 @@ func printStructFields(typeName string, t reflect.Type, indent string) *TypedDat
parts = strings.Split(fieldTypeName, ".")
fieldTypeName = parts[len(parts)-1]

encoding.RegisterTypeDefinitionResolver("KeyPageOperation", func() {
encoding.RegisterTypeDefinitionResolver("KeyPageOperation", func() error {
_ = typeFields(&typeEntries, structName, "KeyPageOperation", NewKeyPageOperation)
return nil
})
encoding.RegisterTypeDefinitionResolver("DataEntry", func() {
encoding.RegisterTypeDefinitionResolver("DataEntry", func() error {
_ = typeFields(&typeEntries, structName, "DataEntry", NewDataEntry)
return nil
})
_ = typeFields(&typeEntries, structName, "KeyPageOperation", NewKeyPageOperation)

Expand Down

0 comments on commit 5cd7331

Please sign in to comment.