mirror of
https://github.com/sonr-io/common.git
synced 2026-01-11 20:08:57 +00:00
189 lines
6.4 KiB
Go
189 lines
6.4 KiB
Go
// Package webauthn provides Sonr-specific WebAuthn utility functions
|
|
// that support gasless transaction processing and DID generation.
|
|
//
|
|
// This package contains utility functions moved from app/ante/webauthn_gasless.go
|
|
// to eliminate circular dependencies while providing core WebAuthn functionality.
|
|
package webauthn
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
// 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.
|
|
//
|
|
// This function is used by the gasless WebAuthn registration system to create
|
|
// controller addresses automatically when users don't have existing accounts.
|
|
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.
|
|
//
|
|
// The generated DID follows the format: did:sonr:<hex-encoded-hash-prefix>
|
|
// where the hash is derived from the credential ID and username.
|
|
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)
|
|
}
|
|
|
|
// GenerateVerificationMethodID creates a verification method ID for WebAuthn credentials.
|
|
// This follows DID standards for verification method identifiers.
|
|
func GenerateVerificationMethodID(did, credentialID string) string {
|
|
// Create a short hash from the credential ID for uniqueness
|
|
hash := sha256.Sum256([]byte(credentialID))
|
|
hashSuffix := hex.EncodeToString(hash[:8]) // Use first 8 bytes for compactness
|
|
|
|
return fmt.Sprintf("%s#webauthn-%s", did, hashSuffix)
|
|
}
|
|
|
|
// IsValidCredentialID validates that a credential ID meets Sonr's requirements.
|
|
// WebAuthn credential IDs should be base64url-encoded and of reasonable length.
|
|
func IsValidCredentialID(credentialID string) bool {
|
|
if credentialID == "" {
|
|
return false
|
|
}
|
|
|
|
// Credential IDs should be at least 16 characters (reasonable minimum)
|
|
// and not exceed 1024 characters (reasonable maximum)
|
|
if len(credentialID) < 16 || len(credentialID) > 1024 {
|
|
return false
|
|
}
|
|
|
|
// Basic check for base64url characters
|
|
// WebAuthn credential IDs are typically base64url encoded
|
|
for _, r := range credentialID {
|
|
if !((r >= 'A' && r <= 'Z') ||
|
|
(r >= 'a' && r <= 'z') ||
|
|
(r >= '0' && r <= '9') ||
|
|
r == '-' || r == '_') {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// GenerateChallengeHash creates a deterministic challenge hash for WebAuthn operations.
|
|
// This can be used for challenge generation in situations where deterministic challenges are needed.
|
|
func GenerateChallengeHash(input string) string {
|
|
hash := sha256.Sum256([]byte(input))
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
|
|
// ExtractOriginFromURL extracts the origin from a URL for WebAuthn origin validation.
|
|
// Returns the origin in the format expected by WebAuthn (e.g., "https://example.com").
|
|
func ExtractOriginFromURL(url string) string {
|
|
// Simple origin extraction - in production this would use proper URL parsing
|
|
// For now, return the input assuming it's already a valid origin
|
|
return url
|
|
}
|
|
|
|
// ValidateOriginFormat validates that an origin meets WebAuthn requirements.
|
|
func ValidateOriginFormat(origin string) error {
|
|
if origin == "" {
|
|
return fmt.Errorf("origin cannot be empty")
|
|
}
|
|
|
|
// WebAuthn origins must be HTTPS (except localhost for development)
|
|
if origin != "http://localhost" &&
|
|
origin != "http://127.0.0.1" &&
|
|
len(origin) >= 8 &&
|
|
origin[:8] != "https://" {
|
|
return fmt.Errorf("WebAuthn origins must use HTTPS (or localhost for development)")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateDeterministicSeed creates a deterministic seed from multiple inputs.
|
|
// This is useful for generating consistent values across different operations.
|
|
func CreateDeterministicSeed(inputs ...string) []byte {
|
|
var combined string
|
|
for _, input := range inputs {
|
|
combined += input + ":"
|
|
}
|
|
|
|
hash := sha256.Sum256([]byte(combined))
|
|
return hash[:]
|
|
}
|
|
|
|
// FormatCredentialForDisplay formats a credential ID for user-friendly display.
|
|
// Truncates long credential IDs while preserving uniqueness for display purposes.
|
|
func FormatCredentialForDisplay(credentialID string) string {
|
|
if len(credentialID) <= 16 {
|
|
return credentialID
|
|
}
|
|
|
|
// Show first 8 and last 8 characters with ellipsis in between
|
|
return fmt.Sprintf("%s...%s", credentialID[:8], credentialID[len(credentialID)-8:])
|
|
}
|
|
|
|
// ValidateUsernameFormat validates that a username meets Sonr's requirements.
|
|
func ValidateUsernameFormat(username string) error {
|
|
if username == "" {
|
|
return fmt.Errorf("username cannot be empty")
|
|
}
|
|
|
|
if len(username) < 3 {
|
|
return fmt.Errorf("username must be at least 3 characters long")
|
|
}
|
|
|
|
if len(username) > 32 {
|
|
return fmt.Errorf("username cannot exceed 32 characters")
|
|
}
|
|
|
|
// Check for valid characters (alphanumeric and some special characters)
|
|
for _, r := range username {
|
|
if !((r >= 'A' && r <= 'Z') ||
|
|
(r >= 'a' && r <= 'z') ||
|
|
(r >= '0' && r <= '9') ||
|
|
r == '-' || r == '_' || r == '.') {
|
|
return fmt.Errorf("username contains invalid character: %c", r)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenerateAccountSeq generates a deterministic account sequence for gasless transactions.
|
|
// This ensures consistent account sequence handling for WebAuthn gasless transactions.
|
|
func GenerateAccountSeq(credentialID string, blockHeight int64) uint64 {
|
|
data := fmt.Sprintf("%s:%d", credentialID, blockHeight)
|
|
hash := sha256.Sum256([]byte(data))
|
|
|
|
// Convert first 8 bytes to uint64
|
|
var seq uint64
|
|
for i := 0; i < 8; i++ {
|
|
seq = seq<<8 + uint64(hash[i])
|
|
}
|
|
|
|
// Ensure it's not zero
|
|
if seq == 0 {
|
|
seq = 1
|
|
}
|
|
|
|
return seq
|
|
}
|