mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
360 lines
14 KiB
Go
360 lines
14 KiB
Go
// Package ante provides ante handler implementations for transaction processing
|
|
// in the Sonr blockchain. It supports both Cosmos SDK and Ethereum transactions.
|
|
package ante
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
didtypes "github.com/sonr-io/sonr/x/did/types"
|
|
)
|
|
|
|
// WebAuthnGaslessDecorator provides gasless transaction processing for WebAuthn registration.
|
|
// This decorator identifies WebAuthn credential registration messages and bypasses fee deduction,
|
|
// enabling users to create their first decentralized identity without requiring existing tokens.
|
|
//
|
|
// The decorator supports two modes:
|
|
// 1. Standard mode: Requires a controller address (for users with existing accounts)
|
|
// 2. Enhanced mode: Generates controller address from credential (for brand new users)
|
|
//
|
|
// Security considerations:
|
|
// - Only applies to MsgRegisterWebAuthnCredential messages
|
|
// - Validates WebAuthn credential authenticity before fee waiving
|
|
// - Prevents abuse through cryptographic WebAuthn requirements
|
|
// - Limited to one gasless transaction per unique credential
|
|
type WebAuthnGaslessDecorator struct {
|
|
accountKeeper AccountKeeper
|
|
didKeeper WebAuthnKeeperInterface
|
|
enhancedMode bool // If true, allows address generation from credentials
|
|
}
|
|
|
|
// NewWebAuthnGaslessDecorator creates a new WebAuthn gasless transaction decorator.
|
|
// This decorator must be placed in the ante handler chain BEFORE the fee deduction decorator
|
|
// to effectively bypass fee requirements for qualifying WebAuthn transactions.
|
|
//
|
|
// Set enhancedMode to true to enable automatic address generation from credentials,
|
|
// allowing truly gasless onboarding without pre-existing accounts.
|
|
func NewWebAuthnGaslessDecorator(
|
|
accountKeeper AccountKeeper,
|
|
didKeeper WebAuthnKeeperInterface,
|
|
enhancedMode bool,
|
|
) WebAuthnGaslessDecorator {
|
|
return WebAuthnGaslessDecorator{
|
|
accountKeeper: accountKeeper,
|
|
didKeeper: didKeeper,
|
|
enhancedMode: enhancedMode,
|
|
}
|
|
}
|
|
|
|
// AnteHandle processes the transaction and determines if WebAuthn gasless processing applies.
|
|
// For qualifying WebAuthn registration transactions, it sets transaction fees to zero
|
|
// and validates the WebAuthn credential to prevent abuse.
|
|
//
|
|
// Gasless criteria:
|
|
// 1. Transaction contains exactly one MsgRegisterWebAuthnCredential
|
|
// 2. WebAuthn credential passes cryptographic validation
|
|
// 3. Credential ID has not been used before (prevents replay attacks)
|
|
// 4. Transaction sender account exists or can be created
|
|
//
|
|
// In enhanced mode, if no controller is provided, it generates one from the credential.
|
|
func (wgd WebAuthnGaslessDecorator) AnteHandle(
|
|
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
|
) (newCtx sdk.Context, err error) {
|
|
// Process during both CheckTx (simulation) and DeliverTx (execution)
|
|
// This ensures gasless flags are set during mempool validation
|
|
|
|
msgs := tx.GetMsgs()
|
|
|
|
// Debug: Log entry into gasless decorator
|
|
ctx.Logger().Debug("WebAuthnGaslessDecorator: processing transaction",
|
|
"msg_count", len(msgs), "sim", sim)
|
|
|
|
// Check if this is a single WebAuthn registration transaction
|
|
if len(msgs) != 1 {
|
|
// Multi-message transactions don't qualify for gasless processing
|
|
return next(ctx, tx, sim)
|
|
}
|
|
|
|
msg, ok := msgs[0].(*didtypes.MsgRegisterWebAuthnCredential)
|
|
if !ok {
|
|
// Not a WebAuthn registration message
|
|
return next(ctx, tx, sim)
|
|
}
|
|
|
|
// Check if WebAuthn bypass validation already occurred
|
|
if bypassed, ok := ctx.Value("webauthn_bypass_validated").(bool); !ok || !bypassed {
|
|
// Validate the WebAuthn credential to prevent abuse (if not already validated)
|
|
if err := msg.WebauthnCredential.ValidateStructure(); err != nil {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrInvalidRequest,
|
|
"invalid WebAuthn credential for gasless transaction: %v", err,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Prevent credential reuse (anti-replay protection)
|
|
if wgd.didKeeper.HasExistingCredential(ctx, msg.WebauthnCredential.CredentialId) {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrInvalidRequest,
|
|
"WebAuthn credential already registered: %s", msg.WebauthnCredential.CredentialId,
|
|
)
|
|
}
|
|
|
|
// Handle controller address
|
|
var controllerAddr sdk.AccAddress
|
|
|
|
// Enhanced mode: Generate address from credential if not provided
|
|
if wgd.enhancedMode && msg.GetController() == "" {
|
|
// Generate deterministic address from credential ID
|
|
controllerAddr = GenerateAddressFromCredential(msg.WebauthnCredential.CredentialId)
|
|
|
|
// Note: We can't modify the message directly in most cases,
|
|
// but we can pass the generated address through context
|
|
ctx = ctx.WithValue("generated_controller", controllerAddr.String())
|
|
|
|
ctx.Logger().Info(
|
|
"Generated controller address for gasless WebAuthn registration",
|
|
"generated_address", controllerAddr.String(),
|
|
"credential_id", msg.WebauthnCredential.CredentialId,
|
|
)
|
|
} else {
|
|
// Standard mode or enhanced mode with provided controller
|
|
controllerStr := msg.GetController()
|
|
if controllerStr == "" {
|
|
return ctx, errorsmod.Wrap(
|
|
errortypes.ErrInvalidAddress,
|
|
"controller address required for WebAuthn registration",
|
|
)
|
|
}
|
|
|
|
controllerAddr, err = sdk.AccAddressFromBech32(controllerStr)
|
|
if err != nil {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrInvalidAddress,
|
|
"invalid controller address: %v", err,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Ensure the account exists (simulation-safe)
|
|
account := wgd.accountKeeper.GetAccount(ctx, controllerAddr)
|
|
if account == nil {
|
|
// Create account if it doesn't exist (common for first-time WebAuthn users)
|
|
// Only actually create during execution, not simulation
|
|
if !sim {
|
|
account = wgd.accountKeeper.NewAccountWithAddress(ctx, controllerAddr)
|
|
wgd.accountKeeper.SetAccount(ctx, account)
|
|
|
|
ctx.Logger().Info(
|
|
"Created new account for gasless WebAuthn registration",
|
|
"address", controllerAddr.String(),
|
|
)
|
|
} else {
|
|
// During simulation (CheckTx), just create a temporary account for validation
|
|
// We don't assign it back to account variable since it's only for validation
|
|
wgd.accountKeeper.NewAccountWithAddress(ctx, controllerAddr)
|
|
}
|
|
}
|
|
|
|
// Mark this transaction as gasless
|
|
gaslessCtx := ctx.WithValue("webauthn_gasless", true)
|
|
|
|
// In enhanced mode, also mark to skip signature verification
|
|
if wgd.enhancedMode {
|
|
gaslessCtx = gaslessCtx.WithValue("skip_sig_verification", true)
|
|
gaslessCtx = gaslessCtx.WithValue("skip_pubkey_verification", true)
|
|
}
|
|
|
|
// Log the gasless transaction for monitoring and security purposes
|
|
ctx.Logger().Info(
|
|
"Processing gasless WebAuthn registration",
|
|
"controller", controllerAddr.String(),
|
|
"credential_id", msg.WebauthnCredential.CredentialId,
|
|
"username", msg.Username,
|
|
"auto_vault", msg.AutoCreateVault,
|
|
"enhanced_mode", wgd.enhancedMode,
|
|
)
|
|
|
|
return next(gaslessCtx, tx, sim)
|
|
}
|
|
|
|
// GenerateAddressFromCredential generates a deterministic address from a WebAuthn credential ID.
|
|
// This ensures the same credential always generates the same address, allowing for
|
|
// predictable account creation without requiring pre-existing blockchain state.
|
|
func GenerateAddressFromCredential(credentialID string) sdk.AccAddress {
|
|
// Create a deterministic hash from the credential ID
|
|
// Add a domain separator to prevent collisions with other address generation methods
|
|
domainSeparator := "webauthn_gasless_v1"
|
|
data := domainSeparator + credentialID
|
|
|
|
// Generate SHA256 hash
|
|
hash := sha256.Sum256([]byte(data))
|
|
|
|
// Take the first 20 bytes for the address (Ethereum-compatible)
|
|
return sdk.AccAddress(hash[:20])
|
|
}
|
|
|
|
// GenerateDIDFromCredential generates a deterministic DID from a WebAuthn credential.
|
|
// This creates a unique, reproducible DID for each WebAuthn credential.
|
|
func GenerateDIDFromCredential(credentialID string, username string) string {
|
|
// Create a deterministic hash from credential ID and username
|
|
data := credentialID + ":" + username
|
|
hash := sha256.Sum256([]byte(data))
|
|
|
|
// Create a DID with the sonr method
|
|
// Format: did:sonr:<hex-encoded-hash-prefix>
|
|
didSuffix := hex.EncodeToString(hash[:16]) // Use first 16 bytes for shorter DIDs
|
|
return fmt.Sprintf("did:sonr:%s", didSuffix)
|
|
}
|
|
|
|
// ConditionalFeeDecorator wraps the standard fee deduction decorator to conditionally
|
|
// skip fee deduction for gasless WebAuthn transactions marked by WebAuthnGaslessDecorator.
|
|
// This is the simplest possible implementation that reuses all existing SDK infrastructure.
|
|
type ConditionalFeeDecorator struct {
|
|
standardFeeDecorator sdk.AnteDecorator
|
|
}
|
|
|
|
// NewConditionalFeeDecorator creates a decorator that conditionally skips fee deduction
|
|
// for gasless transactions while maintaining all standard fee logic for other transactions.
|
|
func NewConditionalFeeDecorator(standardFeeDecorator sdk.AnteDecorator) ConditionalFeeDecorator {
|
|
return ConditionalFeeDecorator{
|
|
standardFeeDecorator: standardFeeDecorator,
|
|
}
|
|
}
|
|
|
|
// AnteHandle processes transactions and conditionally skips fee deduction for gasless WebAuthn transactions.
|
|
func (cfd ConditionalFeeDecorator) AnteHandle(
|
|
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
|
) (newCtx sdk.Context, err error) {
|
|
// Check if this transaction was marked as gasless by WebAuthnGaslessDecorator
|
|
if gasless, ok := ctx.Value("webauthn_gasless").(bool); ok && gasless {
|
|
// Skip fee deduction for gasless WebAuthn transactions
|
|
ctx.Logger().Info("Waiving fees for gasless WebAuthn registration")
|
|
return next(ctx, tx, sim)
|
|
}
|
|
|
|
// For all other transactions, use the standard fee deduction decorator
|
|
return cfd.standardFeeDecorator.AnteHandle(ctx, tx, sim, next)
|
|
}
|
|
|
|
// ConditionalSignatureDecorator wraps signature verification to skip it for gasless transactions
|
|
// This is used in enhanced mode where WebAuthn itself is the authentication mechanism.
|
|
type ConditionalSignatureDecorator struct {
|
|
sigVerifyDecorator sdk.AnteDecorator
|
|
}
|
|
|
|
// NewConditionalSignatureDecorator creates a decorator that conditionally skips signature verification
|
|
func NewConditionalSignatureDecorator(
|
|
sigVerifyDecorator sdk.AnteDecorator,
|
|
) ConditionalSignatureDecorator {
|
|
return ConditionalSignatureDecorator{
|
|
sigVerifyDecorator: sigVerifyDecorator,
|
|
}
|
|
}
|
|
|
|
// AnteHandle conditionally skips signature verification for gasless transactions
|
|
func (csd ConditionalSignatureDecorator) AnteHandle(
|
|
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
|
) (newCtx sdk.Context, err error) {
|
|
// Check if signature verification should be skipped
|
|
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
|
|
ctx.Logger().Debug("Skipping signature verification for gasless transaction")
|
|
return next(ctx, tx, sim)
|
|
}
|
|
|
|
// For all other transactions, use the standard signature verification
|
|
return csd.sigVerifyDecorator.AnteHandle(ctx, tx, sim, next)
|
|
}
|
|
|
|
// ConditionalPubKeyDecorator wraps pubkey setting to skip it for gasless transactions
|
|
// This is used in enhanced mode where accounts are created without pre-existing keys.
|
|
type ConditionalPubKeyDecorator struct {
|
|
setPubKeyDecorator sdk.AnteDecorator
|
|
}
|
|
|
|
// NewConditionalPubKeyDecorator creates a decorator that conditionally skips pubkey setting
|
|
func NewConditionalPubKeyDecorator(
|
|
setPubKeyDecorator sdk.AnteDecorator,
|
|
) ConditionalPubKeyDecorator {
|
|
return ConditionalPubKeyDecorator{
|
|
setPubKeyDecorator: setPubKeyDecorator,
|
|
}
|
|
}
|
|
|
|
// AnteHandle conditionally skips pubkey setting for gasless transactions
|
|
func (cpd ConditionalPubKeyDecorator) AnteHandle(
|
|
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
|
) (newCtx sdk.Context, err error) {
|
|
// Check if pubkey verification should be skipped
|
|
if skip, ok := ctx.Value("skip_pubkey_verification").(bool); ok && skip {
|
|
ctx.Logger().Debug("Skipping pubkey setting for gasless transaction")
|
|
return next(ctx, tx, sim)
|
|
}
|
|
|
|
// For all other transactions, use the standard pubkey decorator
|
|
return cpd.setPubKeyDecorator.AnteHandle(ctx, tx, sim, next)
|
|
}
|
|
|
|
// ConditionalSigCountDecorator wraps signature count validation to skip it for gasless transactions
|
|
// This is critical for gasless WebAuthn transactions which have no signatures to validate.
|
|
type ConditionalSigCountDecorator struct {
|
|
sigCountDecorator sdk.AnteDecorator
|
|
}
|
|
|
|
// NewConditionalSigCountDecorator creates a decorator that conditionally skips signature count validation
|
|
func NewConditionalSigCountDecorator(
|
|
sigCountDecorator sdk.AnteDecorator,
|
|
) ConditionalSigCountDecorator {
|
|
return ConditionalSigCountDecorator{
|
|
sigCountDecorator: sigCountDecorator,
|
|
}
|
|
}
|
|
|
|
// AnteHandle conditionally skips signature count validation for gasless transactions
|
|
func (cscd ConditionalSigCountDecorator) AnteHandle(
|
|
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
|
) (newCtx sdk.Context, err error) {
|
|
// Check if signature verification should be skipped (implies no signatures to count)
|
|
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
|
|
ctx.Logger().Debug("Skipping signature count validation for gasless transaction")
|
|
return next(ctx, tx, sim)
|
|
}
|
|
|
|
// For all other transactions, use the standard signature count validator
|
|
return cscd.sigCountDecorator.AnteHandle(ctx, tx, sim, next)
|
|
}
|
|
|
|
// ConditionalSigGasDecorator wraps signature gas consumption to skip it for gasless transactions
|
|
// This prevents "no signatures supplied" errors for gasless WebAuthn transactions.
|
|
type ConditionalSigGasDecorator struct {
|
|
sigGasDecorator sdk.AnteDecorator
|
|
}
|
|
|
|
// NewConditionalSigGasDecorator creates a decorator that conditionally skips signature gas consumption
|
|
func NewConditionalSigGasDecorator(
|
|
sigGasDecorator sdk.AnteDecorator,
|
|
) ConditionalSigGasDecorator {
|
|
return ConditionalSigGasDecorator{
|
|
sigGasDecorator: sigGasDecorator,
|
|
}
|
|
}
|
|
|
|
// AnteHandle conditionally skips signature gas consumption for gasless transactions
|
|
func (csgd ConditionalSigGasDecorator) AnteHandle(
|
|
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
|
) (newCtx sdk.Context, err error) {
|
|
// Check if signature verification should be skipped (implies no signature gas consumption needed)
|
|
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
|
|
ctx.Logger().Debug("Skipping signature gas consumption for gasless transaction")
|
|
return next(ctx, tx, sim)
|
|
}
|
|
|
|
// For all other transactions, use the standard signature gas decorator
|
|
return csgd.sigGasDecorator.AnteHandle(ctx, tx, sim, next)
|
|
}
|