package common import ( "encoding/binary" "strconv" "time" ethCommon "github.com/ethereum/go-ethereum/common" ethCrypto "github.com/ethereum/go-ethereum/crypto" "github.com/iden3/go-iden3-crypto/babyjub" ) // AccountCreationAuthMsg is the message that is signed to authorize a Hermez // account creation const AccountCreationAuthMsg = "I authorize this babyjubjub key for hermez rollup account creation" // EthMsgPrefix is the prefix for message signing at the Ethereum ecosystem const EthMsgPrefix = "\x19Ethereum Signed Message:\n" // 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"` } func (a *AccountCreationAuth) toHash(chainID uint16, hermezContractAddr ethCommon.Address) []byte { var chainIDBytes [2]byte binary.BigEndian.PutUint16(chainIDBytes[:], chainID) // [EthPrefix | AccountCreationAuthMsg | compressedBJJ | chainID | hermezContractAddr] var b []byte b = append(b, []byte(AccountCreationAuthMsg)...) b = append(b, SwapEndianness(a.BJJ[:])...) // for js implementation compatibility b = append(b, chainIDBytes[:]...) b = append(b, hermezContractAddr[:]...) ethPrefix := EthMsgPrefix + strconv.Itoa(len(b)) return append([]byte(ethPrefix), b...) } // HashToSign returns the hash to be signed by the Etherum address to authorize // the account creation func (a *AccountCreationAuth) HashToSign(chainID uint16, hermezContractAddr ethCommon.Address) ([]byte, error) { b := a.toHash(chainID, hermezContractAddr) return ethCrypto.Keccak256Hash(b).Bytes(), nil } // Sign signs the account creation authorization message using the provided // `signHash` function, and stores the signaure in `a.Signature`. `signHash` // should do an ethereum signature using the account corresponding to // `a.EthAddr`. The `signHash` function is used to make signig flexible: in // tests we sign directly using the private key, outside tests we sign using // the keystore (which never exposes the private key). 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 err } sig, err := signHash(hash) if err != nil { return 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 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 }