Documentation
¶
Overview ¶
Package siwx verifies Sign-In With X (EIP-4361) messages signed by EVM and Solana wallets.
Index ¶
Examples ¶
Constants ¶
const NoClockSkew time.Duration = -1
NoClockSkew as Config.ClockSkew disables the tolerance for wallet clocks.
Variables ¶
var ( ErrInvalidMessage = &Error{Code: "siwx_invalid_message"} ErrInvalidDomain = &Error{Code: "siwx_invalid_domain"} ErrInvalidNonce = &Error{Code: "siwx_invalid_nonce"} ErrInvalidSignature = &Error{Code: "siwx_invalid_signature"} ErrUnsupportedNamespace = &Error{Code: "siwx_unsupported_namespace"} ErrUnsupportedChain = &Error{Code: "siwx_unsupported_chain"} )
Failures returned by Parse and Verifier.Verify. Match them with errors.Is.
Functions ¶
func NonceHandler ¶
func NonceHandler(store NonceStore) http.Handler
NonceHandler serves GET requests with {"nonce": "..."}. It has no rate limit, and every request stores a nonce, so put a limiter in front of it.
Types ¶
type Config ¶
type Config struct {
// Domains lists allowed domains exactly as wallets send them, with a port if any.
Domains []string
// Chains limits the accepted CAIP-2 chains, compared as signed. Empty allows every chain
// of the enabled verifiers.
Chains []string
// ClockSkew tolerates wallet clocks running ahead. Zero means five minutes.
ClockSkew time.Duration
Nonces NonceStore
// Verifiers lists the enabled namespaces, such as evm.New(nil) and solana.New().
Verifiers []SignatureVerifier
// Now replaces time.Now.
Now func() time.Time
}
Config configures a Verifier. Nonces and Verifiers are required, and an empty Domains rejects every message.
type Error ¶
type Error struct {
Code string
}
Error is a verification failure identified by a machine-readable code.
Example ¶
package main
import (
"errors"
"fmt"
siwx "github.com/LexWebDev/siwx-go"
)
func main() {
_, err := siwx.Parse("not a sign-in message")
var failure *siwx.Error
if errors.As(err, &failure) {
fmt.Println(failure.Code)
}
}
Output: siwx_invalid_message
type Message ¶
type Message struct {
// Raw is the normalised text the signature covers.
Raw string
Scheme string
Domain string
Network string
Address string
// Statement is what the app asked to sign, without the ERC-5573 recap sentence.
Statement string
// SignedStatement is the statement line exactly as it was signed.
SignedStatement string
URI string
Version string
Namespace string
// ChainID is always in CAIP-2 form, such as eip155:1.
ChainID string
Nonce string
IssuedAt time.Time
ExpirationTime *time.Time
NotBefore *time.Time
RequestID string
Resources []string
}
Message is a parsed EIP-4361 message.
func Parse ¶
Parse reads an EIP-4361 message for any network. It rejects what the grammar forbids but leaves the address format to the namespace verifier.
Example ¶
package main
import (
"fmt"
siwx "github.com/LexWebDev/siwx-go"
)
func main() {
message, err := siwx.Parse("app.example wants you to sign in with your Ethereum account:\n" +
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\n\nSign in.\n\n" +
"URI: https://app.example\nVersion: 1\nChain ID: 137\n" +
"Nonce: 8Kc2fQ1pXm9Zr4Lt\nIssued At: 2026-07-30T12:34:56Z")
if err != nil {
panic(err)
}
fmt.Println(message.Domain, message.ChainID, message.Statement)
}
Output: app.example eip155:137 Sign in.
type NonceStore ¶
type NonceStore interface {
Issue(ctx context.Context) (string, error)
Exists(ctx context.Context, nonce string) (bool, error)
Consume(ctx context.Context, nonce string) error
}
NonceStore issues single-use nonces. Consume must be atomic: of two parallel calls with one nonce, exactly one succeeds, and the other returns ErrInvalidNonce.
type Session ¶
type Session struct {
Address string
Namespace string
ChainID string
Domain string
IssuedAt time.Time
// ContractWallet is true when a smart contract, not a key, accepted the signature.
// Such an address can have different owners on different chains.
ContractWallet bool
Message *Message
}
Session is the result of a successful verification.
type SignatureVerifier ¶
type SignatureVerifier interface {
Namespace() string
Verify(ctx context.Context, message *Message, signature string) (SignerKind, error)
NormaliseAddress(address string) string
}
SignatureVerifier verifies signatures of one CAIP-2 namespace. A returned error means the check could not be completed.
type SignerKind ¶ added in v0.2.0
type SignerKind int
SignerKind says what produced a signature.
const ( // SignerNone means the signature is not valid. SignerNone SignerKind = iota // SignerAccount means the private key of the address signed. SignerAccount // SignerContract means a smart contract wallet accepted the signature. SignerContract )
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier checks signed messages. It is safe for concurrent use.
func (*Verifier) Verify ¶
Verify checks a signed message and consumes its nonce. Rejections wrap a *Error; an outage of the contract check also wraps its cause, and a store failure comes through as is.
Example ¶
package main
import (
"context"
"crypto/ed25519"
"errors"
"fmt"
"time"
"github.com/mr-tron/base58"
siwx "github.com/LexWebDev/siwx-go"
"github.com/LexWebDev/siwx-go/evm"
"github.com/LexWebDev/siwx-go/memorynonce"
"github.com/LexWebDev/siwx-go/solana"
)
func main() {
store := memorynonce.New(10 * time.Minute)
verifier, err := siwx.New(siwx.Config{
Domains: []string{"app.example"},
Nonces: store,
Verifiers: []siwx.SignatureVerifier{evm.New(nil), solana.New()},
})
if err != nil {
panic(err)
}
publicKey, privateKey, _ := ed25519.GenerateKey(nil)
nonce, _ := store.Issue(context.Background())
message := "app.example wants you to sign in with your Solana account:\n" +
base58.Encode(publicKey) + "\n\nSign in.\n\n" +
"URI: https://app.example\nVersion: 1\nChain ID: solana:mainnet\n" +
"Nonce: " + nonce + "\nIssued At: " + time.Now().UTC().Format(time.RFC3339)
signature := base58.Encode(ed25519.Sign(privateKey, []byte(message)))
session, err := verifier.Verify(context.Background(), message, signature)
if err != nil {
panic(err)
}
fmt.Println(session.Namespace, session.ChainID)
_, err = verifier.Verify(context.Background(), message, signature)
fmt.Println(errors.Is(err, siwx.ErrInvalidNonce))
}
Output: solana solana:mainnet true
Directories
¶
| Path | Synopsis |
|---|---|
|
Package evm verifies EVM signatures for the eip155 namespace.
|
Package evm verifies EVM signatures for the eip155 namespace. |
|
examples
|
|
|
stand
command
|
|
|
internal
|
|
|
noncetest
Package noncetest holds the behaviour every siwx.NonceStore must have.
|
Package noncetest holds the behaviour every siwx.NonceStore must have. |
|
testvectors
Package testvectors exposes the signature vectors shared with the PHP package.
|
Package testvectors exposes the signature vectors shared with the PHP package. |
|
Package memorynonce stores siwx nonces in one process.
|
Package memorynonce stores siwx nonces in one process. |
|
Package redisnonce stores siwx nonces in Redis 6.2 or newer, shared between processes.
|
Package redisnonce stores siwx nonces in Redis 6.2 or newer, shared between processes. |
|
Package solana verifies ed25519 signatures for the solana namespace.
|
Package solana verifies ed25519 signatures for the solana namespace. |