|
package main |
|
|
|
import ( |
|
"crypto/subtle" |
|
"encoding/base64" |
|
"encoding/json" |
|
"errors" |
|
"fmt" |
|
"log" |
|
"os" |
|
|
|
"github.com/ethereum/go-ethereum/common" |
|
"github.com/ethereum/go-ethereum/common/hexutil" |
|
"github.com/ethereum/go-ethereum/crypto" |
|
"github.com/ethereum/go-ethereum/signer/core/apitypes" |
|
) |
|
|
|
type AuthToken struct { |
|
TypedData string `json:"typedData"` |
|
Signature string `json:"signature"` |
|
Address string `json:"address"` |
|
} |
|
|
|
func main() { |
|
data := os.Args[1] |
|
address, err := verifyAuthTokenAddress(data) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
fmt.Println("Verified address:", address) |
|
} |
|
|
|
func verifyAuthTokenAddress(data string) (string, error) { |
|
var authToken AuthToken |
|
if err := json.Unmarshal([]byte(data), &authToken); err != nil { |
|
return "", fmt.Errorf("unmarshal auth token: %w", err) |
|
} |
|
|
|
signature, err := hexutil.Decode(authToken.Signature) |
|
if err != nil { |
|
return "", fmt.Errorf("decode signature: %w", err) |
|
} |
|
// fmt.Println("SIG:", hexutil.Encode(signature)) |
|
|
|
typedDataBytes, err := base64.StdEncoding.DecodeString(authToken.TypedData) |
|
if err != nil { |
|
return "", fmt.Errorf("decode typed data: %w", err) |
|
} |
|
|
|
typedData := apitypes.TypedData{} |
|
if err := json.Unmarshal(typedDataBytes, &typedData); err != nil { |
|
return "", fmt.Errorf("unmarshal typed data: %w", err) |
|
} |
|
|
|
// EIP-712 typed data marshalling |
|
domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) |
|
if err != nil { |
|
return "", fmt.Errorf("eip712domain hash struct: %w", err) |
|
} |
|
typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) |
|
if err != nil { |
|
return "", fmt.Errorf("primary type hash struct: %w", err) |
|
} |
|
|
|
// add magic string prefix |
|
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) |
|
sighash := crypto.Keccak256(rawData) |
|
// fmt.Println("SIG HASH:", hexutil.Encode(sighash)) |
|
|
|
// update the recovery id |
|
// https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L442 |
|
signature[64] -= 27 |
|
|
|
// get the pubkey used to sign this signature |
|
sigPubkey, err := crypto.Ecrecover(sighash, signature) |
|
if err != nil { |
|
return "", fmt.Errorf("ecrecover: %w", err) |
|
} |
|
// fmt.Println("SIG PUBKEY:", hexutil.Encode(sigPubkey)) |
|
|
|
// get the address to confirm it's the same one in the auth token |
|
pubkey, err := crypto.UnmarshalPubkey(sigPubkey) |
|
if err != nil { |
|
return "", fmt.Errorf("", err) |
|
} |
|
address := crypto.PubkeyToAddress(*pubkey) |
|
// fmt.Println("ADDRESS:", address.Hex()) |
|
|
|
// verify the signature (not sure if this is actually required after ecrecover) |
|
signatureNoRecoverID := signature[:len(signature)-1] |
|
verified := crypto.VerifySignature(sigPubkey, sighash, signatureNoRecoverID) |
|
if !verified { |
|
return "", errors.New("verification failed") |
|
} |
|
// fmt.Println("VERIFIED:", verified) |
|
|
|
tokenAddress := common.HexToAddress(authToken.Address) |
|
if subtle.ConstantTimeCompare(address.Bytes(), tokenAddress.Bytes()) == 0 { |
|
return "", errors.New("address mismatch") |
|
} |
|
|
|
return address.Hex(), nil |
|
} |