Skip to content

Commit

Permalink
Use eth sign typed data v4 in gateway (#1643)
Browse files Browse the repository at this point in the history
  • Loading branch information
zkokelj authored Nov 24, 2023
1 parent 8712e26 commit 378aec6
Show file tree
Hide file tree
Showing 19 changed files with 326 additions and 194 deletions.
36 changes: 30 additions & 6 deletions design/ux/Obscuro_Gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ group Second click
end
group Third click
Alice -> MM: Automatically open MM with request to \nsign over "Register $UserId for $Acct"
Alice -> MM: Automatically open MM with request to \nsign over EIP-712 formatted message
note right
This text will be sent as is
accompanied by the signature and
Expand All @@ -129,7 +129,33 @@ Alice -> OG: All further Ten interactions will be to\nhttps://gateway.ten.org/v1
The onboarding should be done in 3 clicks.
1. The user goes to a website (like "ten.org"), where she clicks "Join Ten". This will add a network to their wallet.
2. User connects the wallet to the page.
3. In the wallet popup, the user has to sign over a message: "Register $UserId for $ACCT"
3. In the wallet popup, the user has to sign over EIP-712 formatted message.

Format of EIP-712 message used for signing viewing keys is:

```
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
],
Authentication: [
{ name: "Encryption Token", type: "address" },
],
},
primaryType: "Authentication",
domain: {
name: "Ten",
version: "1.0",
chainId: obscuroChainIDDecimal,
},
message: {
"Encryption Token": "0x"+userID
},
};
```

##### Click 1
1. Behind the scenes, a js functions calls "gateway.ten.org/v1/join" where it will generate a VK and send back the hash of the Public key. This is the "UserId"
Expand All @@ -139,7 +165,7 @@ Notice that the UserId has to be included as a query parameter because it must b

##### Click 2
After these actions are complete, the same page will now ask the user to connect the wallet and switch to Ten.
Automatically the page will open metamask and ask the user to sign over a text "Register $UserId for $ACCT", where ACCT is the current account selected in metamask.
Automatically, the page will open metamask and ask the user to sign over an EIP-712 formatted message as described above.

##### Click 3
Once signed, this will be submitted in the background to: "https://gateway.ten.org/v1?u=$UserId&action=register"
Expand All @@ -149,8 +175,6 @@ Note: Any further accounts will be registered similarly for the same UserId.

Note: The user must guard the UserId. Anyone who can read it, will be able to read the data of this user.

The ultimate goal of this protocol is to submit the "Register $UserId for $ACCT" text to the gateway, which is required by an Ten node to authenticate viewing keys per address.

Note: Alternative UXes that achieve the same goal are ok.


Expand Down Expand Up @@ -193,7 +217,7 @@ This endpoints responds a json of true or false if the address "a" is already re

### Authenticate address - POST "/authenticate?u=$UserId"
JSON Fields:
- text
- address
- signature

This call will be made by a javascript function after it has collected the signed text containing the UserId and the Address from the wallet.
Expand Down
133 changes: 133 additions & 0 deletions go/common/viewingkey/viewing_key.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package viewingkey

import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/ten-protocol/go-ten/go/wallet"

gethcommon "github.com/ethereum/go-ethereum/common"
Expand All @@ -22,6 +27,18 @@ import (
// signed as-is.
const SignedMsgPrefix = "vk"

const (
EIP712Domain = "EIP712Domain"
EIP712Type = "Authentication"
EIP712DomainName = "name"
EIP712DomainVersion = "version"
EIP712DomainChainID = "chainId"
EIP712EncryptionToken = "Encryption Token"
EIP712DomainNameValue = "Ten"
EIP712DomainVersionValue = "1.0"
UserIDHexLength = 40
)

// ViewingKey encapsulates the signed viewing key for an account for use in encrypted communication with an enclave
type ViewingKey struct {
Account *gethcommon.Address // Account address that this Viewing Key is bound to - Users Pubkey address
Expand Down Expand Up @@ -109,3 +126,119 @@ func GenerateSignMessageOG(vkPubKey []byte, addr *gethcommon.Address) string {
userID := crypto.Keccak256Hash(vkPubKey).Bytes()
return fmt.Sprintf("Register %s for %s", hex.EncodeToString(userID), strings.ToLower(addr.Hex()))
}

// GenerateAuthenticationEIP712RawData generates raw data (bytes)
// for an EIP-712 message used to authenticate an address with user
func GenerateAuthenticationEIP712RawData(userID string, chainID int64) ([]byte, error) {
if len(userID) != UserIDHexLength {
return nil, fmt.Errorf("userID hex length must be %d, received %d", UserIDHexLength, len(userID))
}
encryptionToken := "0x" + userID

types := apitypes.Types{
EIP712Domain: {
{Name: EIP712DomainName, Type: "string"},
{Name: EIP712DomainVersion, Type: "string"},
{Name: EIP712DomainChainID, Type: "uint256"},
},
EIP712Type: {
{Name: EIP712EncryptionToken, Type: "address"},
},
}

domain := apitypes.TypedDataDomain{
Name: EIP712DomainNameValue,
Version: EIP712DomainVersionValue,
ChainId: (*math.HexOrDecimal256)(big.NewInt(chainID)),
}

message := map[string]interface{}{
EIP712EncryptionToken: encryptionToken,
}

typedData := apitypes.TypedData{
Types: types,
PrimaryType: EIP712Type,
Domain: domain,
Message: message,
}

// Now we need to create EIP-712 compliant hash.
// It involves hashing the message with its structure, hashing domain separator,
// and then encoding both hashes with specific EIP-712 bytes to construct the final message format.

// Hash the EIP-712 message using its type and content
typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
if err != nil {
return nil, err
}
// Create the domain separator hash for EIP-712 message context
domainSeparator, err := typedData.HashStruct(EIP712Domain, typedData.Domain.Map())
if err != nil {
return nil, err
}
// Prefix domain and message hashes with EIP-712 version and encoding bytes
rawData := append([]byte("\x19\x01"), append(domainSeparator, typedDataHash...)...)
return rawData, nil
}

// CalculateUserIDHex CalculateUserID calculates userID from a public key
// (we truncate it, because we want it to have length 20) and encode to hex strings
func CalculateUserIDHex(publicKeyBytes []byte) string {
return hex.EncodeToString(CalculateUserID(publicKeyBytes))
}

// CalculateUserID calculates userID from a public key (we truncate it, because we want it to have length 20)
func CalculateUserID(publicKeyBytes []byte) []byte {
return crypto.Keccak256Hash(publicKeyBytes).Bytes()[:20]
}

func VerifySignatureEIP712(userID string, address *gethcommon.Address, signature []byte, chainID int64) (bool, error) {
// get raw data for structured message
rawData, err := GenerateAuthenticationEIP712RawData(userID, chainID)
if err != nil {
return false, err
}

// create a hash of structured message (needed for signature verification)
hashBytes := crypto.Keccak256(rawData)
hash := gethcommon.BytesToHash(hashBytes)

if len(signature) != 65 {
return false, fmt.Errorf("invalid signature length: %d", len(signature))
}

// We transform the V from 27/28 to 0/1. This same change is made in Geth internals, for legacy reasons to be able
// to recover the address: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459
if signature[64] == 27 || signature[64] == 28 {
signature[64] -= 27
}

pubKeyBytes, err := crypto.Ecrecover(hash[:], signature)
if err != nil {
return false, fmt.Errorf("invalid signature: %w", err)
}

pubKey, err := crypto.UnmarshalPubkey(pubKeyBytes)
if err != nil {
return false, fmt.Errorf("cannot unmarshal public key: %w", err)
}

recoveredAddr := crypto.PubkeyToAddress(*pubKey)

if !bytes.Equal(recoveredAddr.Bytes(), address.Bytes()) {
return false, errors.New("address from signature not the same as expected")
}

r := new(big.Int).SetBytes(signature[:32])
s := new(big.Int).SetBytes(signature[32:64])

// Verify the signature
isValid := ecdsa.Verify(pubKey, hashBytes, r, s)

if !isValid {
return false, errors.New("signature is not valid")
}

return true, nil
}
24 changes: 12 additions & 12 deletions go/enclave/enclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func NewEnclave(

crossChainProcessors := crosschain.New(&config.MessageBusAddress, storage, big.NewInt(config.ObscuroChainID), logger)

subscriptionManager := events.NewSubscriptionManager(&rpcEncryptionManager, storage, logger)
subscriptionManager := events.NewSubscriptionManager(&rpcEncryptionManager, storage, config.ObscuroChainID, logger)

gasOracle := gas.NewGasOracle()
blockProcessor := components.NewBlockProcessor(storage, crossChainProcessors, gasOracle, logger)
Expand Down Expand Up @@ -491,7 +491,7 @@ func (e *enclaveImpl) SubmitTx(tx common.EncryptedTx) (*responses.RawTx, common.
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(&viewingKeyAddress, paramList[0])
vkHandler, err := createVKHandler(&viewingKeyAddress, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -629,7 +629,7 @@ func (e *enclaveImpl) ObsCall(encryptedParams common.EncryptedParamsCall) (*resp
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(apiArgs.From, paramList[0])
vkHandler, err := createVKHandler(apiArgs.From, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -689,7 +689,7 @@ func (e *enclaveImpl) GetTransactionCount(encryptedParams common.EncryptedParams
address := gethcommon.HexToAddress(addressStr)

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(&address, paramList[0])
vkHandler, err := createVKHandler(&address, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -748,7 +748,7 @@ func (e *enclaveImpl) GetTransaction(encryptedParams common.EncryptedParamsGetTx
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(&viewingKeyAddress, paramList[0])
vkHandler, err := createVKHandler(&viewingKeyAddress, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -803,7 +803,7 @@ func (e *enclaveImpl) GetTransactionReceipt(encryptedParams common.EncryptedPara
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(&sender, paramList[0])
vkHandler, err := createVKHandler(&sender, paramList[0], e.config.ObscuroChainID)
if err != nil {
e.logger.Trace("error getting the vk ", "txHash", txHash, log.ErrKey, err)
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
Expand Down Expand Up @@ -920,7 +920,7 @@ func (e *enclaveImpl) GetBalance(encryptedParams common.EncryptedParamsGetBalanc
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(encryptAddress, paramList[0])
vkHandler, err := createVKHandler(encryptAddress, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -1015,7 +1015,7 @@ func (e *enclaveImpl) EstimateGas(encryptedParams common.EncryptedParamsEstimate
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(callMsg.From, paramList[0])
vkHandler, err := createVKHandler(callMsg.From, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -1069,7 +1069,7 @@ func (e *enclaveImpl) GetLogs(encryptedParams common.EncryptedParamsGetLogs) (*r
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(forAddress, paramList[0])
vkHandler, err := createVKHandler(forAddress, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -1333,7 +1333,7 @@ func (e *enclaveImpl) GetCustomQuery(encryptedParams common.EncryptedParamsGetSt
}

// extract, create and validate the VK encryption handler
vkHandler, err := createVKHandler(&privateCustomQuery.Address, paramList[0])
vkHandler, err := createVKHandler(&privateCustomQuery.Address, paramList[0], e.config.ObscuroChainID)
if err != nil {
return responses.AsPlaintextError(fmt.Errorf("unable to create VK encryptor - %w", err)), nil
}
Expand Down Expand Up @@ -1593,13 +1593,13 @@ func replayBatchesToValidState(storage storage.Storage, registry components.Batc
return nil
}

func createVKHandler(address *gethcommon.Address, vkIntf interface{}) (*vkhandler.VKHandler, error) {
func createVKHandler(address *gethcommon.Address, vkIntf interface{}, chainID int64) (*vkhandler.VKHandler, error) {
vkPubKeyHexBytes, accountSignatureHexBytes, err := gethencoding.ExtractViewingKey(vkIntf)
if err != nil {
return nil, fmt.Errorf("unable to decode viewing key - %w", err)
}

encryptor, err := vkhandler.New(address, vkPubKeyHexBytes, accountSignatureHexBytes)
encryptor, err := vkhandler.New(address, vkPubKeyHexBytes, accountSignatureHexBytes, chainID)
if err != nil {
return nil, fmt.Errorf("unable to create vk encryption for request - %w", err)
}
Expand Down
6 changes: 4 additions & 2 deletions go/enclave/events/subscription_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ type SubscriptionManager struct {
storage storage.Storage

subscriptions map[gethrpc.ID]*common.LogSubscription
chainID int64
subscriptionMutex *sync.RWMutex // the mutex guards the subscriptions/lastHead pair

logger gethlog.Logger
}

func NewSubscriptionManager(rpcEncryptionManager *rpc.EncryptionManager, storage storage.Storage, logger gethlog.Logger) *SubscriptionManager {
func NewSubscriptionManager(rpcEncryptionManager *rpc.EncryptionManager, storage storage.Storage, chainID int64, logger gethlog.Logger) *SubscriptionManager {
return &SubscriptionManager{
rpcEncryptionManager: rpcEncryptionManager,
storage: storage,

subscriptions: map[gethrpc.ID]*common.LogSubscription{},
chainID: chainID,
subscriptionMutex: &sync.RWMutex{},
logger: logger,
}
Expand All @@ -64,7 +66,7 @@ func (s *SubscriptionManager) AddSubscription(id gethrpc.ID, encryptedSubscripti
}

// create viewing key encryption handler for pushing future logs
encryptor, err := vkhandler.New(subscription.Account, subscription.PublicViewingKey, subscription.Signature)
encryptor, err := vkhandler.New(subscription.Account, subscription.PublicViewingKey, subscription.Signature, s.chainID)
if err != nil {
return fmt.Errorf("unable to create vk encryption for request - %w", err)
}
Expand Down
Loading

0 comments on commit 378aec6

Please sign in to comment.