mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
signer/clef: implement EIP-4361 Sign-In With Ethereum (SIWE) support
When Clef receives a text/plain signing request, it now detects and validates EIP-4361 (SIWE) messages: - Parses the full ABNF structure (domain, address, statement, URI, version, chain ID, nonce, issued-at, and all optional fields) with strict field ordering and RFC 3339/3986 format validation - Warns the user when a message looks like SIWE but fails to parse - For HTTP connections, compares the HTTP Origin header against the domain claimed in the message; a mismatch is a CRIT-level warning that causes hard rejection in non-advanced mode (--advanced flag suppresses rejection to a warning instead) - Renders parsed SIWE fields as structured labelled entries in the signing prompt rather than a raw text blob - Adds EIP-55 address checksum validation per spec requirement Test vectors are sourced from the reference SIWE implementation.
This commit is contained in:
parent
8a0223e8da
commit
0b5d47ad55
6 changed files with 1022 additions and 5 deletions
|
|
@ -180,15 +180,39 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, useEthereumV, err
|
return nil, useEthereumV, err
|
||||||
}
|
}
|
||||||
sighash, msg := accounts.TextAndHash(textData)
|
|
||||||
messages := []*apitypes.NameValueType{
|
// EIP-4361 SIWE validation
|
||||||
{
|
messages, callInfo := validateSIWEMessage(string(textData), MetadataFromContext(ctx))
|
||||||
|
|
||||||
|
// In rejectMode (i.e. not --advanced), a CRIT callInfo entry means the
|
||||||
|
// request must be rejected outright rather than shown to the user.
|
||||||
|
// WARN entries (e.g. missing Origin header) still surface to the user.
|
||||||
|
if api.rejectMode {
|
||||||
|
for _, info := range callInfo {
|
||||||
|
if info.Typ == apitypes.CRIT {
|
||||||
|
return nil, useEthereumV, errors.New(info.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if SIWE parsing didn't give us structured messages fall back to raw message
|
||||||
|
if messages == nil {
|
||||||
|
_, msg := accounts.TextAndHash(textData)
|
||||||
|
messages = []*apitypes.NameValueType{{
|
||||||
Name: "message",
|
Name: "message",
|
||||||
Typ: accounts.MimetypeTextPlain,
|
Typ: accounts.MimetypeTextPlain,
|
||||||
Value: msg,
|
Value: msg,
|
||||||
},
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sighash, msg := accounts.TextAndHash(textData)
|
||||||
|
req = &SignDataRequest{
|
||||||
|
ContentType: mediaType,
|
||||||
|
Rawdata: []byte(msg),
|
||||||
|
Messages: messages,
|
||||||
|
Callinfo: callInfo,
|
||||||
|
Hash: sighash,
|
||||||
}
|
}
|
||||||
req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
|
|
||||||
}
|
}
|
||||||
req.Address = addr
|
req.Address = addr
|
||||||
req.Meta = MetadataFromContext(ctx)
|
req.Meta = MetadataFromContext(ctx)
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,40 @@ func TestSignData(t *testing.T) {
|
||||||
} else if have := signature; !bytes.Equal(have, want) {
|
} else if have := signature; !bytes.Equal(have, want) {
|
||||||
t.Fatalf("want %x, have %x", want, have)
|
t.Fatalf("want %x, have %x", want, have)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EIP-4361 SIWE message — valid, no HTTP context so no domain check runs.
|
||||||
|
siweMsg := "example.com wants you to sign in with your Ethereum account:\n" +
|
||||||
|
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n" +
|
||||||
|
"\n" +
|
||||||
|
"\n" +
|
||||||
|
"URI: https://example.com/login\n" +
|
||||||
|
"Version: 1\n" +
|
||||||
|
"Chain ID: 1\n" +
|
||||||
|
"Nonce: 32891757\n" +
|
||||||
|
"Issued At: 2021-09-30T16:25:24Z"
|
||||||
|
control.approveCh <- "Y"
|
||||||
|
control.inputCh <- "a_long_password"
|
||||||
|
signature, err = api.SignData(t.Context(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte(siweMsg)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SIWE sign: unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(signature) != 65 {
|
||||||
|
t.Errorf("SIWE sign: expected 65-byte signature, got %d", len(signature))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Malformed SIWE (contains the trigger phrase but fails parsing) — treated as
|
||||||
|
// raw text/plain with a WARN callInfo entry;
|
||||||
|
badSiweMsg := "example.com wants you to sign in with your Ethereum account:\nbad message"
|
||||||
|
control.approveCh <- "Y"
|
||||||
|
control.inputCh <- "a_long_password"
|
||||||
|
signature, err = api.SignData(t.Context(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte(badSiweMsg)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("malformed SIWE sign: unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(signature) != 65 {
|
||||||
|
t.Errorf("malformed SIWE sign: expected 65-byte signature, got %d", len(signature))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDomainChainId(t *testing.T) {
|
func TestDomainChainId(t *testing.T) {
|
||||||
|
|
|
||||||
410
signer/core/siwe.go
Normal file
410
signer/core/siwe.go
Normal file
|
|
@ -0,0 +1,410 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SIWEMessage represents a parsed EIP-4361 Sign-In with Ethereum message.
|
||||||
|
type SIWEMessage struct {
|
||||||
|
Scheme string // optional
|
||||||
|
Domain string // required
|
||||||
|
Address string // required
|
||||||
|
Statement string // optional
|
||||||
|
URI string // required
|
||||||
|
Version string // required
|
||||||
|
ChainID uint64 // required
|
||||||
|
Nonce string // required
|
||||||
|
IssuedAt time.Time // required
|
||||||
|
ExpirationTime *time.Time // optional
|
||||||
|
NotBefore *time.Time // optional
|
||||||
|
RequestID string // optional
|
||||||
|
Resources []string // optional
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIWEWantedPrefix is the phrase that identifies a SIWE message. Wallet
|
||||||
|
// implementers SHOULD warn users if this appears in any EIP-191 signing
|
||||||
|
// request that does not fully conform to EIP-4361.
|
||||||
|
const (
|
||||||
|
SIWEWantedPrefix = "wants you to sign in with your Ethereum account"
|
||||||
|
|
||||||
|
siweHeaderSuffix = " wants you to sign in with your Ethereum account:"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
siweSchemeRegexp = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9+\-.]*$`)
|
||||||
|
siweNonceRegexp = regexp.MustCompile(`^[a-zA-Z0-9]{8,}$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateSIWEMessage inspects a text/plain signing request for EIP-4361 content.
|
||||||
|
// If the text contains the SIWE phrase it attempts a full parse and domain check,
|
||||||
|
// returning structured display messages and any validation warnings or errors.
|
||||||
|
// Returns nil, nil when the text is not SIWE-related.
|
||||||
|
func validateSIWEMessage(text string, meta Metadata) ([]*apitypes.NameValueType, []apitypes.ValidationInfo) {
|
||||||
|
if !strings.Contains(text, SIWEWantedPrefix) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
siweMsg, err := parseSIWEMessage(text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, []apitypes.ValidationInfo{{
|
||||||
|
Typ: apitypes.WARN,
|
||||||
|
Message: fmt.Sprintf("message appears to be Sign-In With Ethereum but is not valid: %v", err),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
var callInfo []apitypes.ValidationInfo
|
||||||
|
switch meta.Scheme {
|
||||||
|
case "ipc":
|
||||||
|
// no browser involved, no origin to verify
|
||||||
|
case "http":
|
||||||
|
if meta.Origin == "" {
|
||||||
|
callInfo = append(callInfo, apitypes.ValidationInfo{
|
||||||
|
Typ: apitypes.WARN,
|
||||||
|
Message: "could not verify domain: request has no Origin header",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
origin, err := url.Parse(meta.Origin)
|
||||||
|
if err == nil && origin.Host != siweMsg.Domain {
|
||||||
|
callInfo = append(callInfo, apitypes.ValidationInfo{
|
||||||
|
Typ: apitypes.CRIT,
|
||||||
|
Message: fmt.Sprintf("domain mismatch: message claims %q but request origin is %q",
|
||||||
|
siweMsg.Domain, origin.Host),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return siweToAPIMessages(siweMsg), callInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEMessage parses a Sign-In with Ethereum message as defined by EIP-4361.
|
||||||
|
// It validates structure, field order, and the format of each field value.
|
||||||
|
func parseSIWEMessage(msg string) (*SIWEMessage, error) {
|
||||||
|
lines := strings.Split(msg, "\n")
|
||||||
|
|
||||||
|
// Minimum: header, address, blank, blank, URI, Version, Chain ID, Nonce, Issued At
|
||||||
|
if len(lines) < 9 {
|
||||||
|
return nil, errors.New("message too short to be a valid SIWE message")
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := 0
|
||||||
|
scheme, domain, err := parseSIWEHeader(lines[cursor])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
|
||||||
|
if err := validateSIWEAddress(lines[cursor]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
|
||||||
|
if lines[cursor] != "" {
|
||||||
|
return nil, errors.New("expected empty line after address")
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
|
||||||
|
statement, err := parseSIWEStatement(lines, &cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri, version, chainID, nonce, issuedAt, err := parseSIWERequiredFields(lines, &cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
siwe := &SIWEMessage{
|
||||||
|
Scheme: scheme,
|
||||||
|
Domain: domain,
|
||||||
|
Address: lines[1],
|
||||||
|
Statement: statement,
|
||||||
|
URI: uri,
|
||||||
|
Version: version,
|
||||||
|
ChainID: chainID,
|
||||||
|
Nonce: nonce,
|
||||||
|
IssuedAt: issuedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseSIWEOptionalFields(lines, &cursor, siwe); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursor < len(lines) {
|
||||||
|
return nil, fmt.Errorf("unexpected content after SIWE fields: %q", lines[cursor])
|
||||||
|
}
|
||||||
|
|
||||||
|
return siwe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEHeader extracts the scheme (optional) and domain from line 0.
|
||||||
|
func parseSIWEHeader(line string) (scheme, domain string, err error) {
|
||||||
|
if !strings.HasSuffix(line, siweHeaderSuffix) {
|
||||||
|
return "", "", errors.New("first line must end with \" wants you to sign in with your Ethereum account:\"")
|
||||||
|
}
|
||||||
|
prefix := strings.TrimSuffix(line, siweHeaderSuffix)
|
||||||
|
|
||||||
|
if i := strings.Index(prefix, "://"); i != -1 {
|
||||||
|
scheme = prefix[:i]
|
||||||
|
domain = prefix[i+3:]
|
||||||
|
if !siweSchemeRegexp.MatchString(scheme) {
|
||||||
|
return "", "", fmt.Errorf("invalid URI scheme %q", scheme)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domain = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateSIWEDomain(domain); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return scheme, domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSIWEAddress checks that s is a valid hex Ethereum address with EIP-55 checksum.
|
||||||
|
func validateSIWEAddress(s string) error {
|
||||||
|
if !common.IsHexAddress(s) {
|
||||||
|
return errors.New("invalid Ethereum address")
|
||||||
|
}
|
||||||
|
if common.HexToAddress(s).Hex() != s {
|
||||||
|
return errors.New("address does not conform to EIP-55 checksum encoding")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEStatement reads the optional statement and the blank line that follows it.
|
||||||
|
// cursor is left pointing at the first key-value field line.
|
||||||
|
func parseSIWEStatement(lines []string, cursor *int) (string, error) {
|
||||||
|
if lines[*cursor] == "" {
|
||||||
|
*cursor++
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
statement := lines[*cursor]
|
||||||
|
*cursor++
|
||||||
|
if *cursor >= len(lines) || lines[*cursor] != "" {
|
||||||
|
return "", errors.New("expected empty line after statement")
|
||||||
|
}
|
||||||
|
*cursor++
|
||||||
|
return statement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWERequiredFields reads URI through Issued At in strict order.
|
||||||
|
func parseSIWERequiredFields(lines []string, cursor *int) (uri, version string, chainID uint64, nonce string, issuedAt time.Time, err error) {
|
||||||
|
uri, err = parseSIWEField(lines, cursor, "URI: ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = validateSIWEURI(uri); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err = parseSIWEField(lines, cursor, "Version: ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if version != "1" {
|
||||||
|
err = fmt.Errorf("unsupported SIWE version %q, must be \"1\"", version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chainIDStr string
|
||||||
|
chainIDStr, err = parseSIWEField(lines, cursor, "Chain ID: ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chainID, err = strconv.ParseUint(chainIDStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("invalid Chain ID %q: must be a positive integer", chainIDStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, err = parseSIWEField(lines, cursor, "Nonce: ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !siweNonceRegexp.MatchString(nonce) {
|
||||||
|
err = errors.New("nonce must be at least 8 alphanumeric characters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var issuedAtStr string
|
||||||
|
issuedAtStr, err = parseSIWEField(lines, cursor, "Issued At: ")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
issuedAt, err = parseSIWEDateTime(issuedAtStr)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("invalid Issued At: %w", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEOptionalFields reads Expiration Time, Not Before, Request ID, and
|
||||||
|
// Resources in strict order. Any unrecognised line is left for the caller to
|
||||||
|
// detect as unexpected content.
|
||||||
|
func parseSIWEOptionalFields(lines []string, cursor *int, siwe *SIWEMessage) error {
|
||||||
|
if err := parseSIWEOptionalTime(lines, cursor, "Expiration Time: ", &siwe.ExpirationTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := parseSIWEOptionalTime(lines, cursor, "Not Before: ", &siwe.NotBefore); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *cursor < len(lines) && strings.HasPrefix(lines[*cursor], "Request ID: ") {
|
||||||
|
siwe.RequestID = strings.TrimPrefix(lines[*cursor], "Request ID: ")
|
||||||
|
(*cursor)++
|
||||||
|
}
|
||||||
|
return parseSIWEResources(lines, cursor, siwe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEOptionalTime parses an optional datetime field if its prefix is present.
|
||||||
|
func parseSIWEOptionalTime(lines []string, cursor *int, prefix string, dst **time.Time) error {
|
||||||
|
if *cursor >= len(lines) || !strings.HasPrefix(lines[*cursor], prefix) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val := strings.TrimPrefix(lines[*cursor], prefix)
|
||||||
|
t, err := parseSIWEDateTime(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*dst = &t
|
||||||
|
(*cursor)++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEResources reads the Resources section if present.
|
||||||
|
func parseSIWEResources(lines []string, cursor *int, siwe *SIWEMessage) error {
|
||||||
|
if *cursor >= len(lines) || lines[*cursor] != "Resources:" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
(*cursor)++
|
||||||
|
for *cursor < len(lines) {
|
||||||
|
if !strings.HasPrefix(lines[*cursor], "- ") {
|
||||||
|
return fmt.Errorf("invalid resource line %q: must start with \"- \"", lines[*cursor])
|
||||||
|
}
|
||||||
|
resource := strings.TrimPrefix(lines[*cursor], "- ")
|
||||||
|
if err := validateSIWEURI(resource); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
siwe.Resources = append(siwe.Resources, resource)
|
||||||
|
(*cursor)++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEField reads the line at *cursor, strips the expected prefix, advances
|
||||||
|
// the cursor, and returns the value. Returns an error if the line is missing or
|
||||||
|
// does not start with prefix.
|
||||||
|
func parseSIWEField(lines []string, cursor *int, prefix string) (string, error) {
|
||||||
|
if *cursor >= len(lines) {
|
||||||
|
return "", fmt.Errorf("missing required field %q", strings.TrimRight(prefix, " "))
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(lines[*cursor], prefix) {
|
||||||
|
return "", fmt.Errorf("expected field %q, got %q", strings.TrimRight(prefix, " "), lines[*cursor])
|
||||||
|
}
|
||||||
|
val := strings.TrimPrefix(lines[*cursor], prefix)
|
||||||
|
(*cursor)++
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSIWEDateTime parses an RFC 3339 datetime string, with or without
|
||||||
|
// sub-second precision.
|
||||||
|
func parseSIWEDateTime(s string) (time.Time, error) {
|
||||||
|
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
return time.Time{}, fmt.Errorf("not a valid RFC 3339 datetime: %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSIWEURI checks that s is a valid RFC 3986 absolute URI.
|
||||||
|
// url.Parse is too lenient (accepts raw spaces); we check for whitespace
|
||||||
|
// explicitly since RFC 3986 requires spaces to be percent-encoded.
|
||||||
|
func validateSIWEURI(s string) error {
|
||||||
|
if strings.ContainsAny(s, " \t") {
|
||||||
|
return fmt.Errorf("URI %q contains invalid whitespace", s)
|
||||||
|
}
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil || !u.IsAbs() {
|
||||||
|
return fmt.Errorf("URI %q is not a valid RFC 3986 absolute URI", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateSIWEDomain checks that s is a valid RFC 3986 authority (host[:port]).
|
||||||
|
// Per EIP-4361: domain = authority = [ userinfo "@" ] host [ ":" port ]
|
||||||
|
func validateSIWEDomain(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return errors.New("domain is empty")
|
||||||
|
}
|
||||||
|
u, err := url.Parse("http://" + s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("domain %q is not a valid RFC 3986 authority", s)
|
||||||
|
}
|
||||||
|
// Go splits userinfo into u.User and u.Host, so reconstruct the full
|
||||||
|
// authority to verify it round-trips without modification.
|
||||||
|
authority := u.Host
|
||||||
|
if u.User != nil {
|
||||||
|
authority = u.User.String() + "@" + u.Host
|
||||||
|
}
|
||||||
|
if authority != s {
|
||||||
|
return fmt.Errorf("domain %q is not a valid RFC 3986 authority", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func siweToAPIMessages(m *SIWEMessage) []*apitypes.NameValueType {
|
||||||
|
nvts := []*apitypes.NameValueType{
|
||||||
|
{Name: "Domain", Typ: "domain", Value: m.Domain},
|
||||||
|
{Name: "Address", Typ: "address", Value: m.Address},
|
||||||
|
}
|
||||||
|
if m.Statement != "" {
|
||||||
|
nvts = append(nvts, &apitypes.NameValueType{Name: "Statement", Typ: "string", Value: m.Statement})
|
||||||
|
}
|
||||||
|
nvts = append(nvts,
|
||||||
|
&apitypes.NameValueType{Name: "URI", Typ: "uri", Value: m.URI},
|
||||||
|
&apitypes.NameValueType{Name: "Version", Typ: "uint", Value: m.Version},
|
||||||
|
&apitypes.NameValueType{Name: "Chain ID", Typ: "uint", Value: fmt.Sprintf("%d", m.ChainID)},
|
||||||
|
&apitypes.NameValueType{Name: "Nonce", Typ: "string", Value: m.Nonce},
|
||||||
|
&apitypes.NameValueType{Name: "Issued At", Typ: "datetime", Value: m.IssuedAt.String()},
|
||||||
|
)
|
||||||
|
if m.ExpirationTime != nil {
|
||||||
|
nvts = append(nvts, &apitypes.NameValueType{Name: "Expiration Time", Typ: "datetime", Value: m.ExpirationTime.String()})
|
||||||
|
}
|
||||||
|
if m.NotBefore != nil {
|
||||||
|
nvts = append(nvts, &apitypes.NameValueType{Name: "Not Before", Typ: "datetime", Value: m.NotBefore.String()})
|
||||||
|
}
|
||||||
|
if m.RequestID != "" {
|
||||||
|
nvts = append(nvts, &apitypes.NameValueType{Name: "Request ID", Typ: "string", Value: m.RequestID})
|
||||||
|
}
|
||||||
|
if len(m.Resources) > 0 {
|
||||||
|
res := make([]*apitypes.NameValueType, len(m.Resources))
|
||||||
|
for i, r := range m.Resources {
|
||||||
|
res[i] = &apitypes.NameValueType{Name: fmt.Sprintf("%d", i+1), Typ: "uri", Value: r}
|
||||||
|
}
|
||||||
|
nvts = append(nvts, &apitypes.NameValueType{Name: "Resources", Typ: "list", Value: res})
|
||||||
|
}
|
||||||
|
return nvts
|
||||||
|
}
|
||||||
270
signer/core/siwe_test.go
Normal file
270
signer/core/siwe_test.go
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// siwePositiveCase mirrors the structure of siwe_parsing_positive.json.
|
||||||
|
type siwePositiveCase struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Fields siweExpectedFields `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// siweExpectedFields holds the expected parsed values from the test fixture.
|
||||||
|
// Time fields are kept as strings because the fixture preserves the raw wire
|
||||||
|
// format; we parse them via parseSIWEDateTime for comparison.
|
||||||
|
type siweExpectedFields struct {
|
||||||
|
Scheme *string `json:"scheme"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Statement string `json:"statement"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
ChainID uint64 `json:"chainId"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
IssuedAt string `json:"issuedAt"`
|
||||||
|
ExpirationTime *string `json:"expirationTime"`
|
||||||
|
NotBefore *string `json:"notBefore"`
|
||||||
|
RequestID *string `json:"requestId"`
|
||||||
|
Resources []string `json:"resources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimalSIWE is a valid EIP-4361 message used across domain-check tests.
|
||||||
|
const minimalSIWE = "example.com wants you to sign in with your Ethereum account:\n" +
|
||||||
|
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n" +
|
||||||
|
"\n" +
|
||||||
|
"\n" +
|
||||||
|
"URI: https://example.com/login\n" +
|
||||||
|
"Version: 1\n" +
|
||||||
|
"Chain ID: 1\n" +
|
||||||
|
"Nonce: 32891757\n" +
|
||||||
|
"Issued At: 2021-09-30T16:25:24Z"
|
||||||
|
|
||||||
|
func TestParseSIWEMessage_Positive(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/siwe/parsing_positive.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var cases map[string]siwePositiveCase
|
||||||
|
if err := json.Unmarshal(data, &cases); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got, err := parseSIWEMessage(tc.Message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
checkSIWEFields(t, got, tc.Fields)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSIWEMessage_Negative(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/siwe/parsing_negative.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var cases map[string]string
|
||||||
|
if err := json.Unmarshal(data, &cases); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, message := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
_, err := parseSIWEMessage(message)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSIWEMessage(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
text string
|
||||||
|
meta Metadata
|
||||||
|
wantMessages bool
|
||||||
|
wantCRIT bool
|
||||||
|
wantWARN bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-SIWE text returns nil",
|
||||||
|
text: "hello world",
|
||||||
|
meta: Metadata{Scheme: "http"},
|
||||||
|
wantMessages: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid SIWE over IPC skips domain check",
|
||||||
|
text: minimalSIWE,
|
||||||
|
meta: Metadata{Scheme: "ipc"},
|
||||||
|
wantMessages: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid SIWE over HTTP with matching origin",
|
||||||
|
text: minimalSIWE,
|
||||||
|
meta: Metadata{Scheme: "http", Origin: "https://example.com"},
|
||||||
|
wantMessages: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid SIWE over HTTP with mismatched origin",
|
||||||
|
text: minimalSIWE,
|
||||||
|
meta: Metadata{Scheme: "http", Origin: "https://evil.com"},
|
||||||
|
wantMessages: true,
|
||||||
|
wantCRIT: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid SIWE over HTTP with no origin header",
|
||||||
|
text: minimalSIWE,
|
||||||
|
meta: Metadata{Scheme: "http", Origin: ""},
|
||||||
|
wantMessages: true,
|
||||||
|
wantWARN: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed SIWE returns nil messages and a WARN",
|
||||||
|
text: "example.com wants you to sign in with your Ethereum account:\nnot-an-address",
|
||||||
|
meta: Metadata{Scheme: "http"},
|
||||||
|
wantMessages: false,
|
||||||
|
wantWARN: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
messages, callInfo := validateSIWEMessage(tt.text, tt.meta)
|
||||||
|
if tt.wantMessages && messages == nil {
|
||||||
|
t.Error("expected structured messages, got nil")
|
||||||
|
}
|
||||||
|
if !tt.wantMessages && messages != nil {
|
||||||
|
t.Errorf("expected nil messages, got %d entries", len(messages))
|
||||||
|
}
|
||||||
|
checkSIWECallInfo(t, callInfo, tt.wantCRIT, tt.wantWARN)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSIWECallInfo(t *testing.T, callInfo []apitypes.ValidationInfo, wantCRIT, wantWARN bool) {
|
||||||
|
t.Helper()
|
||||||
|
var hasCRIT, hasWARN bool
|
||||||
|
for _, info := range callInfo {
|
||||||
|
hasCRIT = hasCRIT || info.Typ == apitypes.CRIT
|
||||||
|
hasWARN = hasWARN || info.Typ == apitypes.WARN
|
||||||
|
}
|
||||||
|
if wantCRIT != hasCRIT {
|
||||||
|
t.Errorf("CRIT callInfo: want %v, got entries %v", wantCRIT, callInfo)
|
||||||
|
}
|
||||||
|
if wantWARN != hasWARN {
|
||||||
|
t.Errorf("WARN callInfo: want %v, got entries %v", wantWARN, callInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSIWEFields(t *testing.T, got *SIWEMessage, want siweExpectedFields) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
wantScheme := ""
|
||||||
|
if want.Scheme != nil {
|
||||||
|
wantScheme = *want.Scheme
|
||||||
|
}
|
||||||
|
if got.Scheme != wantScheme {
|
||||||
|
t.Errorf("Scheme: got %q, want %q", got.Scheme, wantScheme)
|
||||||
|
}
|
||||||
|
if got.Domain != want.Domain {
|
||||||
|
t.Errorf("Domain: got %q, want %q", got.Domain, want.Domain)
|
||||||
|
}
|
||||||
|
if got.Address != want.Address {
|
||||||
|
t.Errorf("Address: got %q, want %q", got.Address, want.Address)
|
||||||
|
}
|
||||||
|
if got.Statement != want.Statement {
|
||||||
|
t.Errorf("Statement: got %q, want %q", got.Statement, want.Statement)
|
||||||
|
}
|
||||||
|
if got.URI != want.URI {
|
||||||
|
t.Errorf("URI: got %q, want %q", got.URI, want.URI)
|
||||||
|
}
|
||||||
|
if got.Version != want.Version {
|
||||||
|
t.Errorf("Version: got %q, want %q", got.Version, want.Version)
|
||||||
|
}
|
||||||
|
if got.ChainID != want.ChainID {
|
||||||
|
t.Errorf("ChainID: got %d, want %d", got.ChainID, want.ChainID)
|
||||||
|
}
|
||||||
|
if got.Nonce != want.Nonce {
|
||||||
|
t.Errorf("Nonce: got %q, want %q", got.Nonce, want.Nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantIssuedAt, err := parseSIWEDateTime(want.IssuedAt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test data has invalid IssuedAt %q: %v", want.IssuedAt, err)
|
||||||
|
}
|
||||||
|
if !got.IssuedAt.Equal(wantIssuedAt) {
|
||||||
|
t.Errorf("IssuedAt: got %v, want %v", got.IssuedAt, wantIssuedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSIWEOptionalTime(t, "ExpirationTime", got.ExpirationTime, want.ExpirationTime)
|
||||||
|
checkSIWEOptionalTime(t, "NotBefore", got.NotBefore, want.NotBefore)
|
||||||
|
|
||||||
|
wantRequestID := ""
|
||||||
|
if want.RequestID != nil {
|
||||||
|
wantRequestID = *want.RequestID
|
||||||
|
}
|
||||||
|
if got.RequestID != wantRequestID {
|
||||||
|
t.Errorf("RequestID: got %q, want %q", got.RequestID, wantRequestID)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSIWEResources(t, got.Resources, want.Resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSIWEResources(t *testing.T, got, want []string) {
|
||||||
|
t.Helper()
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Errorf("Resources: got %d items, want %d", len(got), len(want))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range want {
|
||||||
|
if got[i] != want[i] {
|
||||||
|
t.Errorf("Resources[%d]: got %q, want %q", i, got[i], want[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSIWEOptionalTime(t *testing.T, field string, got *time.Time, wantStr *string) {
|
||||||
|
t.Helper()
|
||||||
|
if wantStr == nil {
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("%s: got %v, want nil", field, *got)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got == nil {
|
||||||
|
t.Errorf("%s: got nil, want %q", field, *wantStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
want, err := parseSIWEDateTime(*wantStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test data has invalid %s %q: %v", field, *wantStr, err)
|
||||||
|
}
|
||||||
|
if !got.Equal(want) {
|
||||||
|
t.Errorf("%s: got %v, want %v", field, *got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
31
signer/core/testdata/siwe/parsing_negative.json
vendored
Normal file
31
signer/core/testdata/siwe/parsing_negative.json
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"missing domain": " wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"missing address": "service.org wants you to sign in with your Ethereum account:\n\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"missing uri": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\n\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"missing version": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\n\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"missing chainId": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\n\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"missing nonce": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\n\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"missing issuedAt": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\n\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"out of order uri": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nVersion: 1\nURI: https://service.org/login\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"out of order version": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nChain ID: 1\nVersion: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"out of order chainId": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nNonce: 12341234Chain ID: 1\n\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"out of order nonce": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nIssued At: 2022-03-17T12:45:13.610Z\nNonce: 12341234\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"out of order issuedAt": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nExpiration Time: 2023-03-17T12:45:13.610Z\nIssued At: 2022-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"out of order expirationTime": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"out of order notBefore": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nRequest ID: some_id\nNot Before: 2022-03-17T12:45:13.610Z\nResources:\n- https://service.org/login",
|
||||||
|
"out of order requestId": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nResources:\n- https://service.org/login\nRequest ID: some_id",
|
||||||
|
"out of order resources": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nResources:\n- https://service.org/login\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id",
|
||||||
|
"domain not RFC4501 authority": "#notrfc4501 wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"address not EIP-55": "service.org wants you to sign in with your Ethereum account:\n0xe5a12547fe4e872d192e3ececb76f2ce1aea4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"statement has line break": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: \nhttps://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"uri is non-RFC 3986": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: :not_a_rfc3986_valid_uri_\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"version not 1": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 3\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"not a valid chainId": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: ?\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"nonce with less then 8 chars": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 1234567\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"non-ISO 8601 issuedAt": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: Wed Oct 05 2011 16:48:00 GMT+0200 (CEST)\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"non-ISO 8601 expirationTime": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: Wed Oct 05 2011 16:48:00 GMT+0200 (CEST)\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"non-ISO 8601 notBefore": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: Wed Oct 05 2011 16:48:00 GMT+0200 (CEST)\nRequest ID: some_id\nResources:\n- https://service.org/login",
|
||||||
|
"resources not separated by line break": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login - https://service.org/login/2",
|
||||||
|
"first resource not-RFC 3986": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- :not_a_rfc3986_valid_uri_\n- https://service.org/login",
|
||||||
|
"second resource is not-RFC3986": "service.org wants you to sign in with your Ethereum account:\n0xe5A12547fe4E872D192E3eCecb76F2Ce1aeA4946\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 12341234\nIssued At: 2022-03-17T12:45:13.610Z\nExpiration Time: 2023-03-17T12:45:13.610Z\nNot Before: 2022-03-17T12:45:13.610Z\nRequest ID: some_id\nResources:\n- https://service.org/login\n- :not_a_rfc3986_valid_uri_"
|
||||||
|
}
|
||||||
248
signer/core/testdata/siwe/parsing_positive.json
vendored
Normal file
248
signer/core/testdata/siwe/parsing_positive.json
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
{
|
||||||
|
"couple of optional fields": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z\nResources:\n- ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu\n- https://example.com/my-web2-claim.json",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z",
|
||||||
|
"resources": [
|
||||||
|
"ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu",
|
||||||
|
"https://example.com/my-web2-claim.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no optional field": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp without microseconds": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timezone not utc": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24-02:00",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24-02:00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain is RFC 3986 authority with IP": {
|
||||||
|
"message": "127.0.0.1 wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "127.0.0.1",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain is RFC 3986 authority with userinfo": {
|
||||||
|
"message": "test@127.0.0.1 wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "test@127.0.0.1",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain is RFC 3986 authority with port": {
|
||||||
|
"message": "127.0.0.1:8080 wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "127.0.0.1:8080",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain is localhost authority with port": {
|
||||||
|
"message": "localhost:8080 wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "localhost:8080",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain is RFC 3986 authority with userinfo and port": {
|
||||||
|
"message": "test@127.0.0.1:8080 wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "test@127.0.0.1:8080",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no statement": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain ipv6": {
|
||||||
|
"message": "[::cafe] wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "[::cafe]",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uri ipv6": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\nURI: https://[::cafe]\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"uri": "https://[::cafe]",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uri ipv4": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\nURI: https://127.0.0.1\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"uri": "https://127.0.0.1",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uri with port": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\nURI: https://127.0.0.1:4361\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"uri": "https://127.0.0.1:4361",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uri ipv4 query params and fragment": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\nURI: https://127.0.0.1/?query=one#begin\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"uri": "https://127.0.0.1/?query=one#begin",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chainId not 1": {
|
||||||
|
"message": "service.org wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 4\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "service.org",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"uri": "https://service.org/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 4,
|
||||||
|
"nonce": "32891757",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24.000Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"recovery byte starting at 0": {
|
||||||
|
"message": "www.tally.xyz wants you to sign in with your Ethereum account:\n0xc95EB884FE852e241D409234bfC7045CB9E31BD7\n\nSign in with Ethereum to Tally\n\nURI: https://tally.xyz\nVersion: 1\nChain ID: 1\nNonce: 15050747\nIssued At: 2022-06-30T14:08:51.382Z",
|
||||||
|
"fields": {
|
||||||
|
"domain": "www.tally.xyz",
|
||||||
|
"address": "0xc95EB884FE852e241D409234bfC7045CB9E31BD7",
|
||||||
|
"statement": "Sign in with Ethereum to Tally",
|
||||||
|
"uri": "https://tally.xyz",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "15050747",
|
||||||
|
"issuedAt": "2022-06-30T14:08:51.382Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain contains optional scheme": {
|
||||||
|
"message": "https://example.com wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ExampleOrg Terms of Service: https://example.com/tos\n\nURI: https://example.com/login\nVersion: 1\nChain ID: 1\nNonce: 32891756\nIssued At: 2021-09-30T16:25:24Z",
|
||||||
|
"fields": {
|
||||||
|
"scheme": "https",
|
||||||
|
"domain": "example.com",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ExampleOrg Terms of Service: https://example.com/tos",
|
||||||
|
"uri": "https://example.com/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891756",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scheme is not parsed from elsehwere in message": {
|
||||||
|
"message": "localhost:3030 wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ExampleOrg Terms of Service: http://localhost:3030/tos\n\nURI: http://localhost:3030/login\nVersion: 1\nChain ID: 1\nNonce: 32891756\nIssued At: 2021-09-30T16:25:24Z",
|
||||||
|
"fields": {
|
||||||
|
"scheme": null,
|
||||||
|
"domain": "localhost:3030",
|
||||||
|
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||||
|
"statement": "I accept the ExampleOrg Terms of Service: http://localhost:3030/tos",
|
||||||
|
"uri": "http://localhost:3030/login",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"nonce": "32891756",
|
||||||
|
"issuedAt": "2021-09-30T16:25:24Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue