133 lines
3.9 KiB
Go
133 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
ethmath "github.com/ethereum/go-ethereum/common/math"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
|
)
|
|
|
|
func buildTypedData(cfg Config, rec designationRecord) apitypes.TypedData {
|
|
return apitypes.TypedData{
|
|
Types: apitypes.Types{
|
|
"EIP712Domain": []apitypes.Type{
|
|
{Name: "name", Type: "string"},
|
|
{Name: "version", Type: "string"},
|
|
{Name: "chainId", Type: "uint256"},
|
|
{Name: "verifyingContract", Type: "address"},
|
|
},
|
|
"DesignationIntent": []apitypes.Type{
|
|
{Name: "designationCode", Type: "string"},
|
|
{Name: "designationToken", Type: "string"},
|
|
{Name: "nonce", Type: "string"},
|
|
{Name: "issuedAt", Type: "string"},
|
|
{Name: "origin", Type: "string"},
|
|
},
|
|
},
|
|
PrimaryType: "DesignationIntent",
|
|
Domain: apitypes.TypedDataDomain{
|
|
Name: cfg.DomainName,
|
|
Version: "1",
|
|
ChainId: mathHexOrDecimal256(cfg.ChainID),
|
|
VerifyingContract: cfg.VerifyingContract,
|
|
},
|
|
Message: apitypes.TypedDataMessage{
|
|
"designationCode": rec.Code,
|
|
"designationToken": rec.DisplayToken,
|
|
"nonce": rec.Nonce,
|
|
"issuedAt": rec.IssuedAt.UTC().Format(time.RFC3339Nano),
|
|
"origin": rec.Origin,
|
|
},
|
|
}
|
|
}
|
|
|
|
func recoverSignerAddress(typedData apitypes.TypedData, signatureHex string) (string, error) {
|
|
dataHash, _, err := apitypes.TypedDataAndHash(typedData)
|
|
if err != nil {
|
|
return "", fmt.Errorf("typed data hash: %w", err)
|
|
}
|
|
|
|
signatureHex = strings.TrimPrefix(strings.TrimSpace(signatureHex), "0x")
|
|
sig, err := hex.DecodeString(signatureHex)
|
|
if err != nil {
|
|
return "", fmt.Errorf("decode signature: %w", err)
|
|
}
|
|
if len(sig) != 65 {
|
|
return "", fmt.Errorf("invalid signature length: %d", len(sig))
|
|
}
|
|
if sig[64] >= 27 {
|
|
sig[64] -= 27
|
|
}
|
|
|
|
pubKey, err := crypto.SigToPub(dataHash, sig)
|
|
if err != nil {
|
|
return "", fmt.Errorf("recover pubkey: %w", err)
|
|
}
|
|
return strings.ToLower(crypto.PubkeyToAddress(*pubKey).Hex()), nil
|
|
}
|
|
|
|
func normalizeAddress(address string) (string, error) {
|
|
address = strings.TrimSpace(strings.ToLower(address))
|
|
if !common.IsHexAddress(address) {
|
|
return "", fmt.Errorf("invalid wallet address")
|
|
}
|
|
return strings.ToLower(common.HexToAddress(address).Hex()), nil
|
|
}
|
|
|
|
func payerProofMessage(designationCode, ownerWallet, payerWallet string, chainID int64) string {
|
|
return fmt.Sprintf(
|
|
"EDUT-PAYER-AUTH:%s:%s:%s:%d",
|
|
strings.TrimSpace(designationCode),
|
|
strings.ToLower(strings.TrimSpace(ownerWallet)),
|
|
strings.ToLower(strings.TrimSpace(payerWallet)),
|
|
chainID,
|
|
)
|
|
}
|
|
|
|
func recoverPersonalSignAddress(message string, signatureHex string) (string, error) {
|
|
signatureHex = strings.TrimPrefix(strings.TrimSpace(signatureHex), "0x")
|
|
sig, err := hex.DecodeString(signatureHex)
|
|
if err != nil {
|
|
return "", fmt.Errorf("decode signature: %w", err)
|
|
}
|
|
if len(sig) != 65 {
|
|
return "", fmt.Errorf("invalid signature length: %d", len(sig))
|
|
}
|
|
if sig[64] >= 27 {
|
|
sig[64] -= 27
|
|
}
|
|
hash := accounts.TextHash([]byte(message))
|
|
pubKey, err := crypto.SigToPub(hash, sig)
|
|
if err != nil {
|
|
return "", fmt.Errorf("recover pubkey: %w", err)
|
|
}
|
|
return strings.ToLower(crypto.PubkeyToAddress(*pubKey).Hex()), nil
|
|
}
|
|
|
|
func verifyDistinctPayerProof(designationCode, ownerWallet, payerWallet string, chainID int64, signatureHex string) error {
|
|
msg := payerProofMessage(designationCode, ownerWallet, payerWallet, chainID)
|
|
recovered, err := recoverPersonalSignAddress(msg, signatureHex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
owner, err := normalizeAddress(ownerWallet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if recovered != owner {
|
|
return fmt.Errorf("ownership proof signer mismatch")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func mathHexOrDecimal256(v int64) *ethmath.HexOrDecimal256 {
|
|
out := ethmath.NewHexOrDecimal256(v)
|
|
return out
|
|
}
|