* clear

* feat: Add everything

* fix: Commenht
This commit is contained in:
Prad Nukala
2025-10-03 14:45:52 -04:00
committed by GitHub
parent 43b4a11c06
commit 13e6c3e84d
1935 changed files with 655061 additions and 40058 deletions
+60
View File
@@ -0,0 +1,60 @@
// Package ante provides ante handler implementations for transaction processing
// in the Sonr blockchain. It supports both Cosmos SDK and Ethereum transactions.
package ante
import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
)
// NewAnteHandler returns an ante handler responsible for attempting to route an
// Ethereum or SDK transaction to an internal ante handler for performing
// transaction-level processing (e.g. fee payment, signature verification) before
// being passed onto it's respective handler.
//
// The handler inspects the transaction type and extension options to determine
// the appropriate processing path:
// - Ethereum transactions with ExtensionOptionsEthereumTx
// - Cosmos SDK transactions with ExtensionOptionDynamicFeeTx
// - Standard Cosmos SDK transactions
func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
return func(
ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, err error) {
var anteHandler sdk.AnteHandler
txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
if ok {
opts := txWithExtensions.GetExtensionOptions()
if len(opts) > 0 {
switch typeURL := opts[0].GetTypeUrl(); typeURL {
case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx":
// handle as *evmtypes.MsgEthereumTx
anteHandler = newMonoEVMAnteHandler(options)
case "/cosmos.evm.vm.v1.ExtensionOptionDynamicFeeTx":
// cosmos-sdk tx with dynamic fee extension
anteHandler = NewCosmosAnteHandler(options)
default:
return ctx, errorsmod.Wrapf(
errortypes.ErrUnknownExtensionOptions,
"rejecting tx with unsupported extension option: %s", typeURL,
)
}
return anteHandler(ctx, tx, sim)
}
}
// handle as totally normal Cosmos SDK tx
switch tx.(type) {
case sdk.Tx:
anteHandler = NewCosmosAnteHandler(options)
default:
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx)
}
return anteHandler(ctx, tx, sim)
}
}
+107
View File
@@ -0,0 +1,107 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
evmoscosmosante "github.com/cosmos/evm/ante/cosmos"
evmante "github.com/cosmos/evm/ante/evm"
evmtypes "github.com/cosmos/evm/x/vm/types"
circuitante "cosmossdk.io/x/circuit/ante"
ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante"
)
// NewCosmosAnteHandler creates the default ante handler for Cosmos SDK transactions.
// It sets up a chain of decorators that perform various checks and operations:
// - Rejects Ethereum transactions in Cosmos context
// - Enforces authz limitations
// - Sets up transaction context
// - Validates basic transaction properties
// - Handles WebAuthn gasless transactions
// - Handles gas consumption and fee deduction
// - Performs signature verification
// - Manages account sequences
// - Handles IBC-specific checks
func NewCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler {
// Determine if we should use enhanced gasless mode
// Enhanced mode allows address generation from credentials for true gasless onboarding
enhancedGaslessMode := options.EnableEnhancedGasless
// Build the decorator chain
decorators := []sdk.AnteDecorator{
// WebAuthn bypass - must be first to intercept WebAuthn transactions
NewWebAuthnBypassDecorator(),
evmoscosmosante.NewRejectMessagesDecorator(), // reject MsgEthereumTxs
evmoscosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field
sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}),
sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}),
),
ante.NewSetUpContextDecorator(),
circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
// UCAN validation - must come before fee deduction for gasless support
NewConditionalUCANDecorator(NewUCANDecorator()),
evmoscosmosante.NewMinGasPriceDecorator(
options.FeeMarketKeeper,
options.EvmKeeper,
options.ControlPanelKeeper,
),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
// WebAuthn gasless transaction support - must come before fee deduction
// Enhanced mode allows true gasless onboarding without pre-existing accounts
NewWebAuthnGaslessDecorator(options.AccountKeeper, options.DidKeeper, enhancedGaslessMode),
// Conditional fee deduction - skips fees for gasless WebAuthn and UCAN
NewUCANGaslessDecorator(
NewConditionalFeeDecorator(ante.NewDeductFeeDecorator(
options.AccountKeeper,
options.BankKeeper,
options.FeegrantKeeper,
options.TxFeeChecker,
)),
),
}
// Add signature verification decorators
// In enhanced gasless mode, we wrap these to be conditional
if enhancedGaslessMode {
// Conditional decorators that skip verification for gasless transactions
decorators = append(
decorators,
NewConditionalPubKeyDecorator(ante.NewSetPubKeyDecorator(options.AccountKeeper)),
NewConditionalSigCountDecorator(
ante.NewValidateSigCountDecorator(options.AccountKeeper),
),
NewConditionalSigGasDecorator(
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
),
NewConditionalSignatureDecorator(
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
),
)
} else {
// Standard signature verification decorators
decorators = append(decorators,
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
)
}
// Add remaining decorators
decorators = append(decorators,
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
)
return sdk.ChainAnteDecorators(decorators...)
}
+23
View File
@@ -0,0 +1,23 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
evmante "github.com/cosmos/evm/ante/evm"
)
// newMonoEVMAnteHandler creates the ante handler for Ethereum Virtual Machine transactions.
// It uses a single decorator that handles all EVM-specific validation and processing,
// including gas calculation, fee market dynamics, and account management.
//
// The mono decorator performs all EVM ante operations in a single pass for efficiency.
func newMonoEVMAnteHandler(options HandlerOptions) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
evmante.NewEVMMonoDecorator(
options.AccountKeeper,
options.FeeMarketKeeper,
options.EvmKeeper,
options.ControlPanelKeeper,
options.MaxTxGasWanted,
),
)
}
+59
View File
@@ -0,0 +1,59 @@
package ante
import (
"context"
sdk "github.com/cosmos/cosmos-sdk/types"
anteinterfaces "github.com/cosmos/evm/ante/interfaces"
)
// Ensure ControlPanelKeeper implements the interface
var _ anteinterfaces.ControlPanelKeeper = (*ControlPanelKeeper)(nil)
// ControlPanelKeeper provides control panel functionality for sponsored transactions.
// This is a simple implementation that can be extended to support sponsored addresses
// and custom transaction priorities in the future.
type ControlPanelKeeper struct {
// sponsoredAddresses could be loaded from state or configuration
sponsoredAddresses map[string]bool
// priority for sponsored transactions
sponsoredTxPriority int64
}
// NewControlPanelKeeper creates a new ControlPanelKeeper instance
func NewControlPanelKeeper() *ControlPanelKeeper {
return &ControlPanelKeeper{
sponsoredAddresses: make(map[string]bool),
sponsoredTxPriority: 0, // Default priority
}
}
// IsSponsoredAddress checks if an address is sponsored for gasless transactions
func (k *ControlPanelKeeper) IsSponsoredAddress(ctx context.Context, addr []byte) bool {
// For now, return false for all addresses
// This can be extended to check against a whitelist or state
return false
}
// GetSponsoredTransactionPriority returns the priority for sponsored transactions
func (k *ControlPanelKeeper) GetSponsoredTransactionPriority(ctx context.Context) int64 {
// Return default priority
// This can be made configurable or dynamic based on chain state
return k.sponsoredTxPriority
}
// SetSponsoredAddress adds or removes an address from the sponsored list
// This is a helper method for future use
func (k *ControlPanelKeeper) SetSponsoredAddress(addr sdk.AccAddress, sponsored bool) {
if sponsored {
k.sponsoredAddresses[addr.String()] = true
} else {
delete(k.sponsoredAddresses, addr.String())
}
}
// SetSponsoredTransactionPriority updates the priority for sponsored transactions
// This is a helper method for future use
func (k *ControlPanelKeeper) SetSponsoredTransactionPriority(priority int64) {
k.sponsoredTxPriority = priority
}
+129
View File
@@ -0,0 +1,129 @@
package ante
import (
"context"
addresscodec "cosmossdk.io/core/address"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
circuitkeeper "cosmossdk.io/x/circuit/keeper"
txsigning "cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
anteinterfaces "github.com/cosmos/evm/ante/interfaces"
ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper"
)
// WebAuthnKeeperInterface defines the required methods from the DID keeper for WebAuthn gasless processing
type WebAuthnKeeperInterface interface {
// HasExistingCredential checks if this credential ID already exists
HasExistingCredential(ctx sdk.Context, credentialId string) bool
}
// BankKeeper defines the contract needed for supply related APIs.
// It provides methods for checking send permissions and transferring coins
// between accounts and modules.
type BankKeeper interface {
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
SendCoins(ctx context.Context, from, to sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromAccountToModule(
ctx context.Context,
senderAddr sdk.AccAddress,
recipientModule string,
amt sdk.Coins,
) error
}
// AccountKeeper defines the account management interface required by ante handlers.
// It provides methods for account creation, retrieval, modification, and
// sequence number management.
type AccountKeeper interface {
NewAccountWithAddress(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
GetModuleAddress(moduleName string) sdk.AccAddress
GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
SetAccount(ctx context.Context, account sdk.AccountI)
RemoveAccount(ctx context.Context, account sdk.AccountI)
GetParams(ctx context.Context) (params authtypes.Params)
GetSequence(ctx context.Context, addr sdk.AccAddress) (uint64, error)
AddressCodec() addresscodec.Codec
}
// HandlerOptions defines the list of module keepers and configurations required
// to run the ante handler decorators. It includes both standard Cosmos SDK
// keepers and EVM-specific components for processing different transaction types.
type HandlerOptions struct {
Cdc codec.BinaryCodec
AccountKeeper AccountKeeper
BankKeeper BankKeeper
FeegrantKeeper ante.FeegrantKeeper
ExtensionOptionChecker ante.ExtensionOptionChecker
SignModeHandler *txsigning.HandlerMap
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
TxFeeChecker ante.TxFeeChecker // safe to be nil
MaxTxGasWanted uint64
FeeMarketKeeper anteinterfaces.FeeMarketKeeper
EvmKeeper anteinterfaces.EVMKeeper
ControlPanelKeeper anteinterfaces.ControlPanelKeeper
IBCKeeper *ibckeeper.Keeper
CircuitKeeper *circuitkeeper.Keeper
// WebAuthn gasless transaction support
DidKeeper WebAuthnKeeperInterface
EnableEnhancedGasless bool // Enable enhanced gasless mode for true onboarding without pre-existing accounts
// UCAN module keepers for permission validation
DwnKeeper interface{} // Will be cast to proper type in decorator
DexKeeper interface{} // Will be cast to proper type in decorator
SvcKeeper interface{} // Will be cast to proper type in decorator
}
// Validate checks if all required keepers and handlers are properly initialized.
// It ensures that the HandlerOptions struct has all necessary components to
// process transactions without nil pointer errors.
func (options HandlerOptions) Validate() error {
if options.Cdc == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "codec is required for AnteHandler")
}
if options.AccountKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "account keeper is required for AnteHandler")
}
if options.BankKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "bank keeper is required for AnteHandler")
}
if options.SigGasConsumer == nil {
return errorsmod.Wrap(
errortypes.ErrLogic,
"signature gas consumer is required for AnteHandler",
)
}
if options.SignModeHandler == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "sign mode handler is required for AnteHandler")
}
if options.CircuitKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "circuit keeper is required for ante builder")
}
if options.TxFeeChecker == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "tx fee checker is required for AnteHandler")
}
if options.FeeMarketKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "fee market keeper is required for AnteHandler")
}
if options.EvmKeeper == nil {
return errorsmod.Wrap(errortypes.ErrLogic, "evm keeper is required for AnteHandler")
}
if options.ControlPanelKeeper == nil {
return errorsmod.Wrap(
errortypes.ErrLogic,
"control panel keeper is required for AnteHandler",
)
}
return nil
}
+127
View File
@@ -0,0 +1,127 @@
package ante
import (
"context"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/crypto/keys"
"github.com/sonr-io/sonr/crypto/ucan"
)
// UCANDecorator validates UCAN tokens in transactions
// This is a placeholder implementation that sets up the infrastructure
// for UCAN validation. In production, UCAN tokens would be passed
// in message fields or transaction extensions.
type UCANDecorator struct {
verifier *ucan.Verifier
}
// NewUCANDecorator creates a new UCAN decorator
func NewUCANDecorator() UCANDecorator {
// Create a basic DID resolver
didResolver := &BasicDIDResolver{}
verifier := ucan.NewVerifier(didResolver)
return UCANDecorator{
verifier: verifier,
}
}
// AnteHandle validates UCAN tokens for transactions requiring authorization
func (ud UCANDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Skip validation in simulation mode
if simulate {
return next(ctx, tx, simulate)
}
// Check if transaction has UCAN extension
// This is where we would extract and validate UCAN tokens
// For now, this is a placeholder that demonstrates the structure
// Future implementation would:
// 1. Extract UCAN token from transaction extensions or memo
// 2. Validate the token using the verifier
// 3. Check capabilities against message types
// 4. Mark transaction as gasless if appropriate
// Check if transaction qualifies for gasless execution
if ud.isGaslessTransaction(ctx, tx) {
// Mark context for gasless processing
ctx = ctx.WithValue("gasless_ucan", true)
}
return next(ctx, tx, simulate)
}
// isGaslessTransaction checks if transaction qualifies for gasless execution
// This is a placeholder implementation
func (ud UCANDecorator) isGaslessTransaction(ctx sdk.Context, tx sdk.Tx) bool {
// In production, this would check for UCAN tokens with gasless capabilities
// For now, return false to maintain normal fee processing
return false
}
// CheckTokenExpiration checks if UCAN token has expired
func (ud UCANDecorator) CheckTokenExpiration(ctx sdk.Context, token *ucan.Token) error {
if token.ExpiresAt > 0 {
currentTime := ctx.BlockTime().Unix()
if currentTime > token.ExpiresAt {
return fmt.Errorf("UCAN token has expired")
}
}
// Check NotBefore
if token.NotBefore > 0 {
currentTime := ctx.BlockTime().Unix()
if currentTime < token.NotBefore {
return fmt.Errorf("UCAN token is not yet valid")
}
}
return nil
}
// ValidateCapabilities validates UCAN capabilities against required permissions
func (ud UCANDecorator) ValidateCapabilities(token *ucan.Token, requiredCapabilities []string) error {
// Check if token grants required capabilities
for _, att := range token.Attenuations {
if att.Capability.Grants(requiredCapabilities) {
return nil
}
}
return fmt.Errorf("UCAN token does not grant required capabilities")
}
// BasicDIDResolver implements ucan.DIDResolver for the ante handler
type BasicDIDResolver struct{}
// ResolveDIDKey resolves DID to public key for UCAN verification
func (r *BasicDIDResolver) ResolveDIDKey(ctx context.Context, did string) (keys.DID, error) {
// This is a basic implementation that accepts all DIDs
// In production, this would query the DID module
return keys.Parse(did)
}
// ConditionalUCANDecorator wraps UCAN decorator to skip for certain transactions
type ConditionalUCANDecorator struct {
decorator sdk.AnteDecorator
}
// NewConditionalUCANDecorator creates a conditional UCAN decorator
func NewConditionalUCANDecorator(decorator sdk.AnteDecorator) ConditionalUCANDecorator {
return ConditionalUCANDecorator{decorator: decorator}
}
// AnteHandle conditionally applies UCAN validation
func (cud ConditionalUCANDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Skip UCAN validation if already marked as gasless WebAuthn
if ctx.Value("bypass_ucan") != nil {
return next(ctx, tx, simulate)
}
// Apply UCAN validation
return cud.decorator.AnteHandle(ctx, tx, simulate, next)
}
+124
View File
@@ -0,0 +1,124 @@
package ante
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/crypto/ucan"
)
// TestUCANDecorator tests the UCAN decorator functionality
func TestUCANDecorator(t *testing.T) {
// Test decorator creation
decorator := NewUCANDecorator()
assert.NotNil(t, decorator)
}
// TestUCANGaslessDecorator tests the gasless decorator
func TestUCANGaslessDecorator(t *testing.T) {
// Create mock fee decorator
mockFeeDecorator := &mockAnteDecorator{}
// Test gasless decorator creation
gaslessDecorator := NewUCANGaslessDecorator(mockFeeDecorator)
assert.NotNil(t, gaslessDecorator)
}
// TestConditionalUCANDecorator tests conditional UCAN decorator
func TestConditionalUCANDecorator(t *testing.T) {
// Create mock UCAN decorator
mockUCANDecorator := &mockAnteDecorator{}
// Test conditional decorator creation
conditionalDecorator := NewConditionalUCANDecorator(mockUCANDecorator)
assert.NotNil(t, conditionalDecorator)
}
// TestTokenExpiration tests UCAN token expiration validation
func TestTokenExpiration(t *testing.T) {
decorator := NewUCANDecorator()
ctx := sdk.Context{}.WithBlockTime(time.Now())
// Test expired token
expiredToken := &ucan.Token{
ExpiresAt: time.Now().Unix() - 3600, // 1 hour ago
}
err := decorator.CheckTokenExpiration(ctx, expiredToken)
require.Error(t, err)
assert.Contains(t, err.Error(), "UCAN token has expired")
// Test valid token
validToken := &ucan.Token{
ExpiresAt: time.Now().Unix() + 3600, // 1 hour from now
}
err = decorator.CheckTokenExpiration(ctx, validToken)
require.NoError(t, err)
// Test token not yet valid
futureToken := &ucan.Token{
NotBefore: time.Now().Unix() + 3600, // 1 hour from now
}
err = decorator.CheckTokenExpiration(ctx, futureToken)
require.Error(t, err)
assert.Contains(t, err.Error(), "UCAN token is not yet valid")
}
// TestValidateCapabilities tests capability validation
func TestValidateCapabilities(t *testing.T) {
decorator := NewUCANDecorator()
// Create token with single capability
token := &ucan.Token{
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{
Action: "did/update",
},
},
},
}
// Test with matching capability
err := decorator.ValidateCapabilities(token, []string{"did/update"})
require.NoError(t, err)
// Test with non-matching capability
err = decorator.ValidateCapabilities(token, []string{"did/delete"})
require.Error(t, err)
assert.Contains(t, err.Error(), "UCAN token does not grant required capabilities")
// Test with multiple capabilities
multiToken := &ucan.Token{
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.MultiCapability{
Actions: []string{"did/update", "did/create"},
},
},
},
}
err = decorator.ValidateCapabilities(multiToken, []string{"did/update"})
require.NoError(t, err)
err = decorator.ValidateCapabilities(multiToken, []string{"did/create"})
require.NoError(t, err)
err = decorator.ValidateCapabilities(multiToken, []string{"did/delete"})
require.Error(t, err)
}
// mockAnteDecorator is a helper for testing
type mockAnteDecorator struct{}
func (m *mockAnteDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
return next(ctx, tx, simulate)
}
+29
View File
@@ -0,0 +1,29 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// UCANGaslessDecorator allows gasless transactions for UCAN-authorized operations
type UCANGaslessDecorator struct {
feeDecorator sdk.AnteDecorator
}
// NewUCANGaslessDecorator creates a new UCAN gasless decorator
func NewUCANGaslessDecorator(feeDecorator sdk.AnteDecorator) UCANGaslessDecorator {
return UCANGaslessDecorator{
feeDecorator: feeDecorator,
}
}
// AnteHandle conditionally skips fee deduction for UCAN gasless transactions
func (ugd UCANGaslessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// Check if transaction is marked as UCAN gasless
if ctx.Value("gasless_ucan") != nil {
// Skip fee deduction for gasless UCAN transaction
return next(ctx, tx, simulate)
}
// Apply normal fee deduction
return ugd.feeDecorator.AnteHandle(ctx, tx, simulate, next)
}
+81
View File
@@ -0,0 +1,81 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
didtypes "github.com/sonr-io/sonr/x/did/types"
)
// WebAuthnBypassDecorator completely bypasses signature verification for WebAuthn registration.
// This decorator must be placed FIRST in the ante handler chain to intercept WebAuthn
// transactions before any signature validation occurs.
type WebAuthnBypassDecorator struct{}
// NewWebAuthnBypassDecorator creates a new WebAuthnBypassDecorator
func NewWebAuthnBypassDecorator() WebAuthnBypassDecorator {
return WebAuthnBypassDecorator{}
}
// AnteHandle validates WebAuthn transactions and marks them for controlled processing
// This decorator performs essential security checks while allowing gasless processing
func (wbd WebAuthnBypassDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
msgs := tx.GetMsgs()
// Check if this is a single WebAuthn registration transaction
if len(msgs) != 1 {
// Not a single message transaction, proceed normally
return next(ctx, tx, sim)
}
msg, ok := msgs[0].(*didtypes.MsgRegisterWebAuthnCredential)
if !ok {
// Not a WebAuthn registration, proceed normally
return next(ctx, tx, sim)
}
// This is a WebAuthn registration - perform security validation
ctx.Logger().Info("Processing WebAuthn registration with controlled bypass",
"username", msg.Username,
"credential_id", msg.WebauthnCredential.CredentialId)
// CRITICAL SECURITY CHECK 1: Validate the WebAuthn credential structure
if err := msg.WebauthnCredential.ValidateStructure(); err != nil {
ctx.Logger().Error("WebAuthn credential structure validation failed", "error", err)
return ctx, err
}
// CRITICAL SECURITY CHECK 2: Handle signatures (dummy signatures are allowed for mempool validation)
if sigTx, ok := tx.(authsigning.SigVerifiableTx); ok {
sigs, err := sigTx.GetSignaturesV2()
if err != nil {
ctx.Logger().Error("Failed to get signatures from WebAuthn transaction", "error", err)
return ctx, err
}
if len(sigs) > 0 {
ctx.Logger().
Debug("WebAuthn transaction has dummy signatures for mempool validation", "sig_count", len(sigs))
// This is expected - dummy signatures are used to pass mempool validation
// The actual signature verification will be bypassed by conditional decorators
} else {
ctx.Logger().Debug("WebAuthn transaction has no signatures - gasless flow")
}
}
// CRITICAL SECURITY CHECK 3: Validate credential uniqueness to prevent replay attacks
// Note: This will be enforced in the WebAuthnGaslessDecorator with keeper access
// Mark context for controlled WebAuthn processing
// These flags will be checked by conditional decorators
ctx = ctx.WithValue("webauthn_bypass_validated", true)
ctx.Logger().Info("WebAuthn transaction validated - proceeding with controlled processing",
"credential_id", msg.WebauthnCredential.CredentialId,
"username", msg.Username)
// Continue to next decorator (WebAuthnGaslessDecorator) for full processing
return next(ctx, tx, sim)
}
+359
View File
@@ -0,0 +1,359 @@
// 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)
}
+271
View File
@@ -0,0 +1,271 @@
package ante_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/app/ante"
)
// MockWebAuthnKeeper implements the WebAuthnKeeperInterface for testing
type MockWebAuthnKeeper struct {
existingCredentials map[string]bool
}
func NewMockWebAuthnKeeper() *MockWebAuthnKeeper {
return &MockWebAuthnKeeper{
existingCredentials: make(map[string]bool),
}
}
func (m *MockWebAuthnKeeper) HasExistingCredential(ctx sdk.Context, credentialId string) bool {
return m.existingCredentials[credentialId]
}
func (m *MockWebAuthnKeeper) AddCredential(credentialId string) {
m.existingCredentials[credentialId] = true
}
// MockAccountKeeper - Simple mock without complex interface implementation
// For comprehensive testing, we'd implement the full AccountKeeper interface
// MockTransaction implements sdk.Tx for testing
type MockTransaction struct {
messages []sdk.Msg
}
func (m *MockTransaction) GetMsgs() []sdk.Msg {
return m.messages
}
func (m *MockTransaction) ValidateBasic() error {
return nil
}
// WebAuthnGaslessTestSuite tests the WebAuthn gasless decorator
type WebAuthnGaslessTestSuite struct {
suite.Suite
didKeeper *MockWebAuthnKeeper
}
func TestWebAuthnGaslessTestSuite(t *testing.T) {
suite.Run(t, new(WebAuthnGaslessTestSuite))
}
func (suite *WebAuthnGaslessTestSuite) SetupTest() {
// Setup minimal test context and keepers
suite.didKeeper = NewMockWebAuthnKeeper()
// We'll focus on testing the logic without complex keeper setup
}
func (suite *WebAuthnGaslessTestSuite) TestGenerateAddressFromCredential() {
// Test that the same credential always generates the same address
credentialID := "test-credential-id-123"
addr1 := ante.GenerateAddressFromCredential(credentialID)
addr2 := ante.GenerateAddressFromCredential(credentialID)
suite.Require().Equal(addr1, addr2, "Same credential should generate same address")
suite.Require().NotNil(addr1, "Generated address should not be nil")
suite.Require().Equal(20, len(addr1), "Address should be 20 bytes")
}
func (suite *WebAuthnGaslessTestSuite) TestGenerateDIDFromCredential() {
// Test DID generation
credentialID := "test-credential-id-123"
username := "testuser"
did1 := ante.GenerateDIDFromCredential(credentialID, username)
did2 := ante.GenerateDIDFromCredential(credentialID, username)
suite.Require().Equal(did1, did2, "Same inputs should generate same DID")
suite.Require().Contains(did1, "did:sonr:", "DID should have correct prefix")
// Different username should generate different DID
did3 := ante.GenerateDIDFromCredential(credentialID, "differentuser")
suite.Require().NotEqual(did1, did3, "Different username should generate different DID")
}
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnGaslessDecorator_StandardMode() {
// Test standard mode behavior
decorator := ante.NewWebAuthnGaslessDecorator(
nil, // accountKeeper would be mocked in full test
suite.didKeeper,
false, // standard mode
)
suite.Require().NotNil(decorator, "Decorator should be created")
}
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnGaslessDecorator_EnhancedMode() {
// Test enhanced mode behavior
decorator := ante.NewWebAuthnGaslessDecorator(
nil, // accountKeeper would be mocked in full test
suite.didKeeper,
true, // enhanced mode
)
suite.Require().NotNil(decorator, "Decorator should be created in enhanced mode")
}
func (suite *WebAuthnGaslessTestSuite) TestConditionalDecorators() {
// Test that conditional decorators are created properly
// We'll use a simple mock decorator for testing
mockDecorator := &MockAnteDecorator{}
feeDecorator := ante.NewConditionalFeeDecorator(mockDecorator)
suite.Require().NotNil(feeDecorator, "Conditional fee decorator should be created")
sigDecorator := ante.NewConditionalSignatureDecorator(mockDecorator)
suite.Require().NotNil(sigDecorator, "Conditional signature decorator should be created")
pubKeyDecorator := ante.NewConditionalPubKeyDecorator(mockDecorator)
suite.Require().NotNil(pubKeyDecorator, "Conditional pubkey decorator should be created")
}
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnCredentialValidation() {
// Test WebAuthn credential structure validation
// This tests the core logic without full ante handler setup
testCases := []struct {
name string
credentialID string
expectError bool
}{
{
name: "valid credential ID",
credentialID: "valid-credential-123",
expectError: false,
},
{
name: "empty credential ID should be caught by message validation",
credentialID: "",
expectError: true, // This would be caught by ValidateStructure
},
{
name: "duplicate credential",
credentialID: "duplicate-cred",
expectError: true,
},
}
// Add a duplicate credential
suite.didKeeper.AddCredential("duplicate-cred")
for _, tc := range testCases {
suite.Run(tc.name, func() {
hasExisting := suite.didKeeper.HasExistingCredential(sdk.Context{}, tc.credentialID)
switch tc.credentialID {
case "duplicate-cred":
suite.Require().True(hasExisting, "Duplicate credential should exist")
case "valid-credential-123":
suite.Require().False(hasExisting, "New credential should not exist")
}
})
}
}
func (suite *WebAuthnGaslessTestSuite) TestEnhancedModeAddressGeneration() {
// Test enhanced mode functionality for address generation
testCredentialID := "test-enhanced-credential"
// Test that the same credential always generates the same address
addr1 := ante.GenerateAddressFromCredential(testCredentialID)
addr2 := ante.GenerateAddressFromCredential(testCredentialID)
suite.Require().Equal(addr1, addr2, "Same credential should generate same address")
suite.Require().Equal(20, len(addr1), "Address should be 20 bytes")
// Test that different credentials generate different addresses
addr3 := ante.GenerateAddressFromCredential("different-credential")
suite.Require().
NotEqual(addr1, addr3, "Different credentials should generate different addresses")
}
// MockAnteDecorator is a simple mock implementation of sdk.AnteDecorator for testing
type MockAnteDecorator struct{}
func (m *MockAnteDecorator) AnteHandle(
ctx sdk.Context,
tx sdk.Tx,
simulate bool,
next sdk.AnteHandler,
) (sdk.Context, error) {
return next(ctx, tx, simulate)
}
// TestAddressGeneration tests the deterministic address generation
func TestAddressGeneration(t *testing.T) {
testCases := []struct {
name string
credentialID string
expectSame bool
}{
{
name: "same credential generates same address",
credentialID: "credential-1",
expectSame: true,
},
{
name: "different credential generates different address",
credentialID: "credential-2",
expectSame: false,
},
}
baseAddr := ante.GenerateAddressFromCredential("base-credential")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
addr := ante.GenerateAddressFromCredential(tc.credentialID)
require.NotNil(t, addr)
require.Equal(t, 20, len(addr))
if tc.expectSame && tc.credentialID == "base-credential" {
require.Equal(t, baseAddr, addr)
} else if !tc.expectSame {
require.NotEqual(t, baseAddr, addr)
}
})
}
}
// TestDIDGeneration tests the deterministic DID generation
func TestDIDGeneration(t *testing.T) {
testCases := []struct {
name string
credentialID string
username string
expectedPrefix string
}{
{
name: "generates valid DID",
credentialID: "cred-1",
username: "user1",
expectedPrefix: "did:sonr:",
},
{
name: "different username generates different DID",
credentialID: "cred-1",
username: "user2",
expectedPrefix: "did:sonr:",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
did := ante.GenerateDIDFromCredential(tc.credentialID, tc.username)
require.NotEmpty(t, did)
require.Contains(t, did, tc.expectedPrefix)
// Verify deterministic generation
did2 := ante.GenerateDIDFromCredential(tc.credentialID, tc.username)
require.Equal(t, did, did2)
})
}
}