|
package common
|
|
|
|
import (
|
|
"time"
|
|
|
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
|
ethMath "github.com/ethereum/go-ethereum/common/math"
|
|
ethCrypto "github.com/ethereum/go-ethereum/crypto"
|
|
ethSigner "github.com/ethereum/go-ethereum/signer/core"
|
|
"github.com/hermeznetwork/tracerr"
|
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
|
)
|
|
|
|
const (
|
|
// AccountCreationAuthMsg is the message that is signed to authorize a
|
|
// Hermez account creation
|
|
AccountCreationAuthMsg = "Account creation"
|
|
// EIP712Version is the used version of the EIP-712
|
|
EIP712Version = "1"
|
|
// EIP712Provider defines the Provider for the EIP-712
|
|
EIP712Provider = "Hermez Network"
|
|
)
|
|
|
|
var (
|
|
// EmptyEthSignature is an ethereum signature of all zeroes
|
|
EmptyEthSignature = make([]byte, 65)
|
|
)
|
|
|
|
// AccountCreationAuth authorizations sent by users to the L2DB, to be used for
|
|
// account creations when necessary
|
|
type AccountCreationAuth struct {
|
|
EthAddr ethCommon.Address `meddler:"eth_addr"`
|
|
BJJ babyjub.PublicKeyComp `meddler:"bjj"`
|
|
Signature []byte `meddler:"signature"`
|
|
Timestamp time.Time `meddler:"timestamp,utctime"`
|
|
}
|
|
|
|
// toHash returns a byte array to be hashed from the AccountCreationAuth, which
|
|
// follows the EIP-712 encoding
|
|
func (a *AccountCreationAuth) toHash(chainID uint16,
|
|
hermezContractAddr ethCommon.Address) ([]byte, error) {
|
|
chainIDFormatted := ethMath.NewHexOrDecimal256(int64(chainID))
|
|
|
|
signerData := ethSigner.TypedData{
|
|
Types: ethSigner.Types{
|
|
"EIP712Domain": []ethSigner.Type{
|
|
{Name: "name", Type: "string"},
|
|
{Name: "version", Type: "string"},
|
|
{Name: "chainId", Type: "uint256"},
|
|
{Name: "verifyingContract", Type: "address"},
|
|
},
|
|
"Authorise": []ethSigner.Type{
|
|
{Name: "Provider", Type: "string"},
|
|
{Name: "Authorisation", Type: "string"},
|
|
{Name: "BJJKey", Type: "bytes32"},
|
|
},
|
|
},
|
|
PrimaryType: "Authorise",
|
|
Domain: ethSigner.TypedDataDomain{
|
|
Name: EIP712Provider,
|
|
Version: EIP712Version,
|
|
ChainId: chainIDFormatted,
|
|
VerifyingContract: hermezContractAddr.Hex(),
|
|
},
|
|
Message: ethSigner.TypedDataMessage{
|
|
"Provider": EIP712Provider,
|
|
"Authorisation": AccountCreationAuthMsg,
|
|
"BJJKey": SwapEndianness(a.BJJ[:]),
|
|
},
|
|
}
|
|
|
|
domainSeparator, err := signerData.HashStruct("EIP712Domain", signerData.Domain.Map())
|
|
if err != nil {
|
|
return nil, tracerr.Wrap(err)
|
|
}
|
|
typedDataHash, err := signerData.HashStruct(signerData.PrimaryType, signerData.Message)
|
|
if err != nil {
|
|
return nil, tracerr.Wrap(err)
|
|
}
|
|
|
|
rawData := []byte{0x19, 0x01} // "\x19\x01"
|
|
rawData = append(rawData, domainSeparator...)
|
|
rawData = append(rawData, typedDataHash...)
|
|
return rawData, nil
|
|
}
|
|
|
|
// HashToSign returns the hash to be signed by the Ethereum address to authorize
|
|
// the account creation, which follows the EIP-712 encoding
|
|
func (a *AccountCreationAuth) HashToSign(chainID uint16,
|
|
hermezContractAddr ethCommon.Address) ([]byte, error) {
|
|
b, err := a.toHash(chainID, hermezContractAddr)
|
|
if err != nil {
|
|
return nil, tracerr.Wrap(err)
|
|
}
|
|
return ethCrypto.Keccak256(b), nil
|
|
}
|
|
|
|
// Sign signs the account creation authorization message using the provided
|
|
// `signHash` function, and stores the signature in `a.Signature`. `signHash`
|
|
// should do an ethereum signature using the account corresponding to
|
|
// `a.EthAddr`. The `signHash` function is used to make signing flexible: in
|
|
// tests we sign directly using the private key, outside tests we sign using
|
|
// the keystore (which never exposes the private key). Sign follows the EIP-712
|
|
// encoding.
|
|
func (a *AccountCreationAuth) Sign(signHash func(hash []byte) ([]byte, error),
|
|
chainID uint16, hermezContractAddr ethCommon.Address) error {
|
|
hash, err := a.HashToSign(chainID, hermezContractAddr)
|
|
if err != nil {
|
|
return tracerr.Wrap(err)
|
|
}
|
|
sig, err := signHash(hash)
|
|
if err != nil {
|
|
return tracerr.Wrap(err)
|
|
}
|
|
sig[64] += 27
|
|
a.Signature = sig
|
|
a.Timestamp = time.Now()
|
|
return nil
|
|
}
|
|
|
|
// VerifySignature ensures that the Signature is done with the EthAddr, for the
|
|
// chainID and hermezContractAddress passed by parameter. VerifySignature
|
|
// follows the EIP-712 encoding.
|
|
func (a *AccountCreationAuth) VerifySignature(chainID uint16,
|
|
hermezContractAddr ethCommon.Address) bool {
|
|
// Calculate hash to be signed
|
|
hash, err := a.HashToSign(chainID, hermezContractAddr)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
var sig [65]byte
|
|
copy(sig[:], a.Signature[:])
|
|
sig[64] -= 27
|
|
|
|
// Get public key from Signature
|
|
pubKBytes, err := ethCrypto.Ecrecover(hash, sig[:])
|
|
if err != nil {
|
|
return false
|
|
}
|
|
pubK, err := ethCrypto.UnmarshalPubkey(pubKBytes)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
// Get addr from pubK
|
|
addr := ethCrypto.PubkeyToAddress(*pubK)
|
|
return addr == a.EthAddr
|
|
}
|