* 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
+167
View File
@@ -0,0 +1,167 @@
package types
import (
"fmt"
"strings"
"cosmossdk.io/errors"
)
// BlockchainAccountID represents a blockchain account identifier following CAIP-10 standard
// Format: <namespace>:<chain_id>:<address>
type BlockchainAccountID struct {
Namespace string // "eip155" for Ethereum, "cosmos" for Cosmos chains
ChainID string // "1" for Ethereum mainnet, "cosmoshub-4" for Cosmos Hub
Address string // The account address
}
// String returns the CAIP-10 formatted blockchain account ID
func (b BlockchainAccountID) String() string {
return fmt.Sprintf("%s:%s:%s", b.Namespace, b.ChainID, b.Address)
}
// ParseBlockchainAccountID parses a CAIP-10 formatted blockchain account ID
func ParseBlockchainAccountID(accountID string) (*BlockchainAccountID, error) {
parts := strings.Split(accountID, ":")
if len(parts) != 3 {
return nil, errors.Wrapf(ErrInvalidBlockchainAccountID, "invalid format: %s", accountID)
}
return &BlockchainAccountID{
Namespace: parts[0],
ChainID: parts[1],
Address: parts[2],
}, nil
}
// Validate checks if the blockchain account ID is valid
func (b BlockchainAccountID) Validate() error {
if b.Namespace == "" {
return errors.Wrap(ErrInvalidBlockchainAccountID, "namespace cannot be empty")
}
if b.ChainID == "" {
return errors.Wrap(ErrInvalidBlockchainAccountID, "chain_id cannot be empty")
}
if b.Address == "" {
return errors.Wrap(ErrInvalidBlockchainAccountID, "address cannot be empty")
}
// Validate specific namespaces
switch b.Namespace {
case "eip155":
return b.validateEIP155Address()
case "cosmos":
return b.validateCosmosAddress()
default:
return errors.Wrapf(ErrUnsupportedBlockchainNamespace, "namespace: %s", b.Namespace)
}
}
// validateEIP155Address validates Ethereum addresses
func (b BlockchainAccountID) validateEIP155Address() error {
if !strings.HasPrefix(b.Address, "0x") {
return errors.Wrap(ErrInvalidEthereumAddress, "address must start with 0x")
}
if len(b.Address) != 42 { // 0x + 40 hex characters
return errors.Wrap(ErrInvalidEthereumAddress, "address must be 42 characters long")
}
// Check if all characters after 0x are valid hex
for _, r := range b.Address[2:] {
if !isHexChar(r) {
return errors.Wrap(ErrInvalidEthereumAddress, "address contains invalid hex characters")
}
}
return nil
}
// validateCosmosAddress validates Cosmos addresses
func (b BlockchainAccountID) validateCosmosAddress() error {
// Basic validation - Cosmos addresses typically start with a prefix
if len(b.Address) < 10 {
return errors.Wrap(ErrInvalidCosmosAddress, "address too short")
}
// More detailed validation could be added here based on bech32 format
// For now, we'll do basic length and character checks
if len(b.Address) > 100 {
return errors.Wrap(ErrInvalidCosmosAddress, "address too long")
}
return nil
}
// isHexChar checks if a rune is a valid hexadecimal character
func isHexChar(r rune) bool {
return (r >= '0' && r <= '9') || (r >= 'A' && r <= 'F') || (r >= 'a' && r <= 'f')
}
// WalletType represents the type of external wallet
type WalletType string
const (
WalletTypeEthereum WalletType = "ethereum"
WalletTypeCosmos WalletType = "cosmos"
)
// String returns the string representation of WalletType
func (w WalletType) String() string {
return string(w)
}
// Validate checks if the wallet type is supported
func (w WalletType) Validate() error {
switch w {
case WalletTypeEthereum, WalletTypeCosmos:
return nil
default:
return errors.Wrapf(ErrUnsupportedWalletType, "wallet type: %s", w)
}
}
// ToVerificationMethodType returns the W3C verification method type for the wallet
func (w WalletType) ToVerificationMethodType() string {
switch w {
case WalletTypeEthereum:
return "EcdsaSecp256k1RecoveryMethod2020"
case WalletTypeCosmos:
return "Secp256k1VerificationKey2018"
default:
return "UnknownVerificationMethod"
}
}
// GetNamespace returns the CAIP-10 namespace for the wallet type
func (w WalletType) GetNamespace() string {
switch w {
case WalletTypeEthereum:
return "eip155"
case WalletTypeCosmos:
return "cosmos"
default:
return ""
}
}
// WalletVerification contains verification data for wallet ownership proof
type WalletVerification struct {
Challenge []byte // The challenge message that was signed
Signature []byte // The signature proving ownership
WalletType WalletType // Type of wallet
Verified bool // Whether the verification was successful
}
// Validate checks if the wallet verification data is complete
func (wv WalletVerification) Validate() error {
if len(wv.Challenge) == 0 {
return errors.Wrap(ErrInvalidWalletVerification, "challenge cannot be empty")
}
if len(wv.Signature) == 0 {
return errors.Wrap(ErrInvalidWalletVerification, "signature cannot be empty")
}
if err := wv.WalletType.Validate(); err != nil {
return errors.Wrap(ErrInvalidWalletVerification, err.Error())
}
return nil
}
-165
View File
@@ -1,165 +0,0 @@
package types
import (
"bytes"
"context"
"errors"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/sonr-io/snrd/internal/accounts"
"github.com/sonr-io/snrd/internal/transaction"
)
var (
accountsModuleAddress = address.Module("accounts")
ErrInvalidType = errors.New("invalid type")
)
// AccountsInterface is the exported interface of an Account.
type AccountsInterface = accounts.Account
// AccountsExecuteBuilder is the exported type of AccountsExecuteBuilder.
type AccountsExecuteBuilder = accounts.ExecuteBuilder
// AccountsQueryBuilder is the exported type of AccountsQueryBuilder.
type AccountsQueryBuilder = accounts.QueryBuilder
// AccountsInitBuilder is the exported type of AccountsInitBuilder.
type AccountsInitBuilder = accounts.InitBuilder
// AccountCreatorFunc is the exported type of AccountCreatorFunc.
type AccountCreatorFunc = accounts.AccountCreatorFunc
func DIAccount[A AccountsInterface](name string, constructor func(deps Dependencies) (A, error)) DepinjectAccount {
return DepinjectAccount{MakeAccount: AddAccount(name, constructor)}
}
type DepinjectAccount struct {
MakeAccount AccountCreatorFunc
}
func (DepinjectAccount) IsManyPerContainerType() {}
// Dependencies is the exported type of Dependencies.
type Dependencies = accounts.Dependencies
func RegisterAccountsExecuteHandler[
Req any, ProtoReq accounts.ProtoMsgG[Req], Resp any, ProtoResp accounts.ProtoMsgG[Resp],
](router *AccountsExecuteBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
) {
accounts.RegisterExecuteHandler(router, handler)
}
// RegisterAccountsQueryHandler registers a query handler for a smart account that uses protobuf.
func RegisterAccountsQueryHandler[
Req any, ProtoReq accounts.ProtoMsgG[Req], Resp any, ProtoResp accounts.ProtoMsgG[Resp],
](router *AccountsQueryBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
) {
accounts.RegisterQueryHandler(router, handler)
}
// RegisterAccountsInitHandler registers an initialisation handler for a smart account that uses protobuf.
func RegisterAccountsInitHandler[
Req any, ProtoReq accounts.ProtoMsgG[Req], Resp any, ProtoResp accounts.ProtoMsgG[Resp],
](router *AccountsInitBuilder, handler func(ctx context.Context, req ProtoReq) (ProtoResp, error),
) {
accounts.RegisterInitHandler(router, handler)
}
// AddAccount is a helper function to add a smart account to the list of smart accounts.
func AddAccount[A AccountsInterface](name string, constructor func(deps Dependencies) (A, error)) AccountCreatorFunc {
return func(deps accounts.Dependencies) (string, accounts.Account, error) {
acc, err := constructor(deps)
return name, acc, err
}
}
// Whoami returns the address of the account being invoked.
func Whoami(ctx context.Context) []byte {
return accounts.Whoami(ctx)
}
// Sender returns the sender of the execution request.
func Sender(ctx context.Context) []byte {
return accounts.Sender(ctx)
}
// HasSender checks if the execution context was sent from the provided sender
func HasSender(ctx context.Context, wantSender []byte) bool {
return bytes.Equal(Sender(ctx), wantSender)
}
// SenderIsSelf checks if the sender of the request is the account itself.
func SenderIsSelf(ctx context.Context) bool { return HasSender(ctx, Whoami(ctx)) }
// SenderIsAccountsModule returns true if the sender of the execution request is the accounts module.
func SenderIsAccountsModule(ctx context.Context) bool {
return bytes.Equal(Sender(ctx), accountsModuleAddress)
}
// Funds returns if any funds were sent during the execute or init request. In queries this
// returns nil.
func Funds(ctx context.Context) sdk.Coins { return accounts.Funds(ctx) }
func ExecModule[MsgResp, Msg transaction.Msg](ctx context.Context, msg Msg) (resp MsgResp, err error) {
untyped, err := accounts.ExecModule(ctx, msg)
if err != nil {
return resp, err
}
return assertOrErr[MsgResp](untyped)
}
// QueryModule can be used by an account to execute a module query.
func QueryModule[Resp, Req transaction.Msg](ctx context.Context, req Req) (resp Resp, err error) {
untyped, err := accounts.QueryModule(ctx, req)
if err != nil {
return resp, err
}
return assertOrErr[Resp](untyped)
}
// UnpackAny unpacks a protobuf Any message generically.
func UnpackAny[Msg any, ProtoMsg accounts.ProtoMsgG[Msg]](any *accounts.Any) (*Msg, error) {
return accounts.UnpackAny[Msg, ProtoMsg](any)
}
// PackAny packs a protobuf Any message generically.
func PackAny(msg transaction.Msg) (*accounts.Any, error) {
return accounts.PackAny(msg)
}
// ExecModuleAnys can be used to execute a list of messages towards a module
// when those messages are packed in Any messages. The function returns a list
// of responses packed in Any messages.
func ExecModuleAnys(ctx context.Context, msgs []*accounts.Any) ([]*accounts.Any, error) {
responses := make([]*accounts.Any, len(msgs))
for i, msg := range msgs {
concreteMessage, err := accounts.UnpackAnyRaw(msg)
if err != nil {
return nil, fmt.Errorf("error unpacking message %d: %w", i, err)
}
resp, err := accounts.ExecModule(ctx, concreteMessage)
if err != nil {
return nil, fmt.Errorf("error executing message %d: %w", i, err)
}
// pack again
respAnyPB, err := accounts.PackAny(resp)
if err != nil {
return nil, fmt.Errorf("error packing response %d: %w", i, err)
}
responses[i] = respAnyPB
}
return responses, nil
}
// asserts the given any to the provided generic, returns ErrInvalidType if it can't.
func assertOrErr[T any](r any) (concrete T, err error) {
concrete, ok := r.(T)
if !ok {
return concrete, ErrInvalidType
}
return concrete, nil
}
-52
View File
@@ -1,52 +0,0 @@
package types
import (
"github.com/cosmos/cosmos-sdk/types/bech32"
)
// ComputeSonrAddr computes the Sonr address from a public key
func ComputeSonrAddr(pk []byte) (string, error) {
sonrAddr, err := bech32.ConvertAndEncode("idx", pk)
if err != nil {
return "", err
}
return sonrAddr, nil
}
// ComputeBitcoinAddr computes the Bitcoin address from a public key
func ComputeBitcoinAddr(pk []byte) (string, error) {
btcAddr, err := bech32.ConvertAndEncode("bc", pk)
if err != nil {
return "", err
}
return btcAddr, nil
}
//
// // ComputeEthereumAddr computes the Ethereum address from a public key
// func ComputeEthereumAddr(pk *ecdsa.PublicKey) string {
// // Generate Ethereum address
// address := ethcrypto.PubkeyToAddress(*pk)
//
// // Apply ERC-55 checksum encoding
// addr := address.Hex()
// addr = strings.ToLower(addr)
// addr = strings.TrimPrefix(addr, "0x")
// hash := sha3.NewLegacyKeccak256()
// hash.Write([]byte(addr))
// hashBytes := hash.Sum(nil)
//
// result := "0x"
// for i, c := range addr {
// if c >= '0' && c <= '9' {
// result += string(c)
// } else {
// if hashBytes[i/2]>>(4-i%2*4)&0xf >= 8 {
// result += strings.ToUpper(string(c))
// } else {
// result += string(c)
// }
// }
// }
// return result
// }
+47
View File
@@ -0,0 +1,47 @@
package types
import (
"encoding/hex"
"slices"
"strings"
"lukechampine.com/blake3"
)
var SupportedDIDAssertionMethods = []string{
"sonr",
"btcr",
"ethr",
"ssh",
"tel",
"email",
"github",
"google",
}
func IsSupportedDIDAssertionMethod(method string) bool {
return slices.Contains(SupportedDIDAssertionMethods, method)
}
// HashAssertionValue hashes an assertion value using blake3
func HashAssertionValue(value string) string {
hash := blake3.Sum256([]byte(value))
return hex.EncodeToString(hash[:])
}
type DIDAssertionMethod string
func (m DIDAssertionMethod) Parse() error {
return nil
}
func (m DIDAssertionMethod) String() string {
return string(m)
}
func TrimDIDMethodPrefix(did string) string {
if after, ok := strings.CutPrefix(did, "did:"); ok {
return after
}
return did
}
+22
View File
@@ -0,0 +1,22 @@
package types
// AssertionStats contains statistics about assertions in the system
type AssertionStats struct {
// TotalAssertions is the total number of assertions
TotalAssertions int64 `json:"total_assertions"`
// EmailAssertions is the number of email assertions
EmailAssertions int64 `json:"email_assertions"`
// TelAssertions is the number of telephone assertions
TelAssertions int64 `json:"tel_assertions"`
// SonrAssertions is the number of Sonr account assertions
SonrAssertions int64 `json:"sonr_assertions"`
// WebAuthnAssertions is the number of WebAuthn assertions
WebAuthnAssertions int64 `json:"webauthn_assertions"`
// OtherAssertions is the number of other assertion types
OtherAssertions int64 `json:"other_assertions"`
}
Regular → Executable
+1 -6
View File
@@ -4,7 +4,6 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
@@ -26,14 +25,10 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
}
func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations(
(*cryptotypes.PubKey)(nil),
// &PubKey{},
)
registry.RegisterImplementations(
(*sdk.Msg)(nil),
&MsgUpdateParams{},
)
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}
+399
View File
@@ -0,0 +1,399 @@
package types
import (
apiv1 "github.com/sonr-io/sonr/api/did/v1"
)
// ToORMDIDDocument converts a DIDDocument from the types package to the ORM API type
func (d *DIDDocument) ToORM() *apiv1.DIDDocument {
if d == nil {
return nil
}
ormDoc := &apiv1.DIDDocument{
Id: d.Id,
PrimaryController: d.PrimaryController,
AlsoKnownAs: d.AlsoKnownAs,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
Deactivated: d.Deactivated,
Version: d.Version,
}
// Convert verification methods
ormDoc.VerificationMethod = make([]*apiv1.VerificationMethod, len(d.VerificationMethod))
for i, vm := range d.VerificationMethod {
ormDoc.VerificationMethod[i] = vm.ToORM()
}
// Convert verification method references
ormDoc.Authentication = convertVerificationMethodReferencesToORM(d.Authentication)
ormDoc.AssertionMethod = convertVerificationMethodReferencesToORM(d.AssertionMethod)
ormDoc.KeyAgreement = convertVerificationMethodReferencesToORM(d.KeyAgreement)
ormDoc.CapabilityInvocation = convertVerificationMethodReferencesToORM(d.CapabilityInvocation)
ormDoc.CapabilityDelegation = convertVerificationMethodReferencesToORM(d.CapabilityDelegation)
// Convert services
ormDoc.Service = make([]*apiv1.Service, len(d.Service))
for i, svc := range d.Service {
ormDoc.Service[i] = svc.ToORM()
}
return ormDoc
}
// ToORMVerificationMethod converts a VerificationMethod from the types package to the ORM API type
func (vm *VerificationMethod) ToORM() *apiv1.VerificationMethod {
if vm == nil {
return nil
}
ormVM := &apiv1.VerificationMethod{
Id: vm.Id,
VerificationMethodKind: vm.VerificationMethodKind,
Controller: vm.Controller,
PublicKeyJwk: vm.PublicKeyJwk,
PublicKeyMultibase: vm.PublicKeyMultibase,
PublicKeyBase58: vm.PublicKeyBase58,
PublicKeyBase64: vm.PublicKeyBase64,
PublicKeyPem: vm.PublicKeyPem,
PublicKeyHex: vm.PublicKeyHex,
}
// Convert WebAuthn credential if present
if vm.WebauthnCredential != nil {
ormVM.WebauthnCredential = &apiv1.WebAuthnCredential{
CredentialId: vm.WebauthnCredential.CredentialId,
PublicKey: vm.WebauthnCredential.PublicKey,
Algorithm: vm.WebauthnCredential.Algorithm,
AttestationType: vm.WebauthnCredential.AttestationType,
Origin: vm.WebauthnCredential.Origin,
CreatedAt: vm.WebauthnCredential.CreatedAt,
RpId: vm.WebauthnCredential.RpId,
RpName: vm.WebauthnCredential.RpName,
Transports: vm.WebauthnCredential.Transports,
UserVerified: vm.WebauthnCredential.UserVerified,
SignatureAlgorithm: vm.WebauthnCredential.SignatureAlgorithm,
RawId: vm.WebauthnCredential.RawId,
ClientDataJson: vm.WebauthnCredential.ClientDataJson,
AttestationObject: vm.WebauthnCredential.AttestationObject,
}
}
return ormVM
}
// ToORMService converts a Service from the types package to the ORM API type
func (s *Service) ToORM() *apiv1.Service {
if s == nil {
return nil
}
ormService := &apiv1.Service{
Id: s.Id,
ServiceKind: s.ServiceKind,
SingleEndpoint: s.SingleEndpoint,
ComplexEndpoint: s.ComplexEndpoint,
Properties: s.Properties,
}
// Convert multiple endpoints if present
if s.MultipleEndpoints != nil {
ormService.MultipleEndpoints = &apiv1.ServiceEndpoints{
Endpoints: s.MultipleEndpoints.Endpoints,
}
}
return ormService
}
// ToORMVerifiableCredential converts a VerifiableCredential from the types package to the ORM API type
func (vc *VerifiableCredential) ToORM() *apiv1.VerifiableCredential {
if vc == nil {
return nil
}
ormVC := &apiv1.VerifiableCredential{
Id: vc.Id,
Context: vc.Context,
CredentialKinds: vc.CredentialKinds,
Issuer: vc.Issuer,
IssuanceDate: vc.IssuanceDate,
ExpirationDate: vc.ExpirationDate,
CredentialSubject: vc.CredentialSubject,
Subject: vc.Subject,
IssuedAt: vc.IssuedAt,
ExpiresAt: vc.ExpiresAt,
Revoked: vc.Revoked,
}
// Convert proofs
ormVC.Proof = make([]*apiv1.CredentialProof, len(vc.Proof))
for i, proof := range vc.Proof {
ormVC.Proof[i] = &apiv1.CredentialProof{
ProofKind: proof.ProofKind,
Created: proof.Created,
VerificationMethod: proof.VerificationMethod,
ProofPurpose: proof.ProofPurpose,
Signature: proof.Signature,
Properties: proof.Properties,
}
}
// Convert credential status if present
if vc.CredentialStatus != nil {
ormVC.CredentialStatus = &apiv1.CredentialStatus{
Id: vc.CredentialStatus.Id,
StatusKind: vc.CredentialStatus.StatusKind,
Properties: vc.CredentialStatus.Properties,
}
}
return ormVC
}
// ToORMDIDDocumentMetadata converts DIDDocumentMetadata from the types package to the ORM API type
func (m *DIDDocumentMetadata) ToORM() *apiv1.DIDDocumentMetadata {
if m == nil {
return nil
}
return &apiv1.DIDDocumentMetadata{
Did: m.Did,
Created: m.Created,
Updated: m.Updated,
Deactivated: m.Deactivated,
VersionId: m.VersionId,
NextUpdate: m.NextUpdate,
NextVersionId: m.NextVersionId,
EquivalentId: m.EquivalentId,
CanonicalId: m.CanonicalId,
}
}
// FromORMDIDDocument converts a DIDDocument from the ORM API type to the types package
func DIDDocumentFromORM(ormDoc *apiv1.DIDDocument) *DIDDocument {
if ormDoc == nil {
return nil
}
doc := &DIDDocument{
Id: ormDoc.Id,
PrimaryController: ormDoc.PrimaryController,
AlsoKnownAs: ormDoc.AlsoKnownAs,
CreatedAt: ormDoc.CreatedAt,
UpdatedAt: ormDoc.UpdatedAt,
Deactivated: ormDoc.Deactivated,
Version: ormDoc.Version,
}
// Convert verification methods
doc.VerificationMethod = make([]*VerificationMethod, len(ormDoc.VerificationMethod))
for i, vm := range ormDoc.VerificationMethod {
doc.VerificationMethod[i] = VerificationMethodFromORM(vm)
}
// Convert verification method references
doc.Authentication = convertVerificationMethodReferencesFromORM(ormDoc.Authentication)
doc.AssertionMethod = convertVerificationMethodReferencesFromORM(ormDoc.AssertionMethod)
doc.KeyAgreement = convertVerificationMethodReferencesFromORM(ormDoc.KeyAgreement)
doc.CapabilityInvocation = convertVerificationMethodReferencesFromORM(
ormDoc.CapabilityInvocation,
)
doc.CapabilityDelegation = convertVerificationMethodReferencesFromORM(
ormDoc.CapabilityDelegation,
)
// Convert services
doc.Service = make([]*Service, len(ormDoc.Service))
for i, svc := range ormDoc.Service {
doc.Service[i] = ServiceFromORM(svc)
}
return doc
}
// VerificationMethodFromORM converts a VerificationMethod from the ORM API type to the types package
func VerificationMethodFromORM(ormVM *apiv1.VerificationMethod) *VerificationMethod {
if ormVM == nil {
return nil
}
vm := &VerificationMethod{
Id: ormVM.Id,
VerificationMethodKind: ormVM.VerificationMethodKind,
Controller: ormVM.Controller,
PublicKeyJwk: ormVM.PublicKeyJwk,
PublicKeyMultibase: ormVM.PublicKeyMultibase,
PublicKeyBase58: ormVM.PublicKeyBase58,
PublicKeyBase64: ormVM.PublicKeyBase64,
PublicKeyPem: ormVM.PublicKeyPem,
PublicKeyHex: ormVM.PublicKeyHex,
}
// Convert WebAuthn credential if present
if ormVM.WebauthnCredential != nil {
vm.WebauthnCredential = &WebAuthnCredential{
CredentialId: ormVM.WebauthnCredential.CredentialId,
PublicKey: ormVM.WebauthnCredential.PublicKey,
Algorithm: ormVM.WebauthnCredential.Algorithm,
AttestationType: ormVM.WebauthnCredential.AttestationType,
Origin: ormVM.WebauthnCredential.Origin,
CreatedAt: ormVM.WebauthnCredential.CreatedAt,
RpId: ormVM.WebauthnCredential.RpId,
RpName: ormVM.WebauthnCredential.RpName,
Transports: ormVM.WebauthnCredential.Transports,
UserVerified: ormVM.WebauthnCredential.UserVerified,
SignatureAlgorithm: ormVM.WebauthnCredential.SignatureAlgorithm,
RawId: ormVM.WebauthnCredential.RawId,
ClientDataJson: ormVM.WebauthnCredential.ClientDataJson,
AttestationObject: ormVM.WebauthnCredential.AttestationObject,
}
}
return vm
}
// ServiceFromORM converts a Service from the ORM API type to the types package
func ServiceFromORM(ormService *apiv1.Service) *Service {
if ormService == nil {
return nil
}
svc := &Service{
Id: ormService.Id,
ServiceKind: ormService.ServiceKind,
SingleEndpoint: ormService.SingleEndpoint,
ComplexEndpoint: ormService.ComplexEndpoint,
Properties: ormService.Properties,
}
// Convert multiple endpoints if present
if ormService.MultipleEndpoints != nil {
svc.MultipleEndpoints = &ServiceEndpoints{
Endpoints: ormService.MultipleEndpoints.Endpoints,
}
}
return svc
}
// VerifiableCredentialFromORM converts a VerifiableCredential from the ORM API type to the types package
func VerifiableCredentialFromORM(ormVC *apiv1.VerifiableCredential) *VerifiableCredential {
if ormVC == nil {
return nil
}
vc := &VerifiableCredential{
Id: ormVC.Id,
Context: ormVC.Context,
CredentialKinds: ormVC.CredentialKinds,
Issuer: ormVC.Issuer,
IssuanceDate: ormVC.IssuanceDate,
ExpirationDate: ormVC.ExpirationDate,
CredentialSubject: ormVC.CredentialSubject,
Subject: ormVC.Subject,
IssuedAt: ormVC.IssuedAt,
ExpiresAt: ormVC.ExpiresAt,
Revoked: ormVC.Revoked,
}
// Convert proofs
vc.Proof = make([]*CredentialProof, len(ormVC.Proof))
for i, proof := range ormVC.Proof {
vc.Proof[i] = &CredentialProof{
ProofKind: proof.ProofKind,
Created: proof.Created,
VerificationMethod: proof.VerificationMethod,
ProofPurpose: proof.ProofPurpose,
Signature: proof.Signature,
Properties: proof.Properties,
}
}
// Convert credential status if present
if ormVC.CredentialStatus != nil {
vc.CredentialStatus = &CredentialStatus{
Id: ormVC.CredentialStatus.Id,
StatusKind: ormVC.CredentialStatus.StatusKind,
Properties: ormVC.CredentialStatus.Properties,
}
}
return vc
}
// DIDDocumentMetadataFromORM converts DIDDocumentMetadata from the ORM API type to the types package
func DIDDocumentMetadataFromORM(ormMeta *apiv1.DIDDocumentMetadata) *DIDDocumentMetadata {
if ormMeta == nil {
return nil
}
return &DIDDocumentMetadata{
Did: ormMeta.Did,
Created: ormMeta.Created,
Updated: ormMeta.Updated,
Deactivated: ormMeta.Deactivated,
VersionId: ormMeta.VersionId,
NextUpdate: ormMeta.NextUpdate,
NextVersionId: ormMeta.NextVersionId,
EquivalentId: ormMeta.EquivalentId,
CanonicalId: ormMeta.CanonicalId,
}
}
// Helper functions
func convertVerificationMethodReferencesToORM(
refs []*VerificationMethodReference,
) []*apiv1.VerificationMethodReference {
if refs == nil {
return nil
}
ormRefs := make([]*apiv1.VerificationMethodReference, len(refs))
for i, ref := range refs {
if ref == nil {
continue
}
ormRef := &apiv1.VerificationMethodReference{}
if ref.VerificationMethodId != "" {
ormRef.VerificationMethodId = ref.VerificationMethodId
} else if ref.EmbeddedVerificationMethod != nil {
ormRef.EmbeddedVerificationMethod = ref.EmbeddedVerificationMethod.ToORM()
}
ormRefs[i] = ormRef
}
return ormRefs
}
func convertVerificationMethodReferencesFromORM(
ormRefs []*apiv1.VerificationMethodReference,
) []*VerificationMethodReference {
if ormRefs == nil {
return nil
}
refs := make([]*VerificationMethodReference, len(ormRefs))
for i, ormRef := range ormRefs {
if ormRef == nil {
continue
}
ref := &VerificationMethodReference{}
if ormRef.VerificationMethodId != "" {
ref.VerificationMethodId = ormRef.VerificationMethodId
} else if ormRef.EmbeddedVerificationMethod != nil {
ref.EmbeddedVerificationMethod = VerificationMethodFromORM(ormRef.EmbeddedVerificationMethod)
}
refs[i] = ref
}
return refs
}
+98
View File
@@ -0,0 +1,98 @@
package types_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/sonr-io/sonr/x/did/types"
)
func TestDIDDocumentConversions(t *testing.T) {
// Create a test DID document
doc := &types.DIDDocument{
Id: "did:example:123",
PrimaryController: "controller123",
AlsoKnownAs: []string{"alias1", "alias2"},
VerificationMethod: []*types.VerificationMethod{
{
Id: "did:example:123#key-1",
VerificationMethodKind: "Ed25519VerificationKey2020",
Controller: "did:example:123",
PublicKeyJwk: `{"kty":"OKP"}`,
},
},
Authentication: []*types.VerificationMethodReference{
{VerificationMethodId: "did:example:123#key-1"},
},
Service: []*types.Service{
{
Id: "did:example:123#service-1",
ServiceKind: "LinkedDomains",
SingleEndpoint: "https://example.com",
},
},
CreatedAt: 12345,
UpdatedAt: 12346,
Deactivated: false,
Version: 1,
}
// Convert to ORM
ormDoc := doc.ToORM()
require.NotNil(t, ormDoc)
require.Equal(t, doc.Id, ormDoc.Id)
require.Equal(t, doc.PrimaryController, ormDoc.PrimaryController)
require.Equal(t, doc.AlsoKnownAs, ormDoc.AlsoKnownAs)
require.Len(t, ormDoc.VerificationMethod, 1)
require.Len(t, ormDoc.Authentication, 1)
require.Len(t, ormDoc.Service, 1)
// Convert back from ORM
convertedDoc := types.DIDDocumentFromORM(ormDoc)
require.NotNil(t, convertedDoc)
require.Equal(t, doc.Id, convertedDoc.Id)
require.Equal(t, doc.PrimaryController, convertedDoc.PrimaryController)
require.Equal(t, doc.AlsoKnownAs, convertedDoc.AlsoKnownAs)
require.Len(t, convertedDoc.VerificationMethod, 1)
require.Equal(t, doc.VerificationMethod[0].Id, convertedDoc.VerificationMethod[0].Id)
}
func TestVerifiableCredentialConversions(t *testing.T) {
// Create a test credential
vc := &types.VerifiableCredential{
Id: "https://example.com/credentials/123",
Context: []string{"https://www.w3.org/2018/credentials/v1"},
CredentialKinds: []string{"VerifiableCredential"},
Issuer: "did:example:issuer",
Subject: "did:example:subject",
IssuanceDate: "2024-01-01T00:00:00Z",
ExpirationDate: "2025-01-01T00:00:00Z",
CredentialSubject: []byte(`{"name":"John Doe"}`),
Proof: []*types.CredentialProof{
{
ProofKind: "Ed25519Signature2020",
Created: "2024-01-01T00:00:00Z",
VerificationMethod: "did:example:issuer#key-1",
ProofPurpose: "assertionMethod",
Signature: "signature123",
},
},
}
// Convert to ORM
ormVC := vc.ToORM()
require.NotNil(t, ormVC)
require.Equal(t, vc.Id, ormVC.Id)
require.Equal(t, vc.Issuer, ormVC.Issuer)
require.Equal(t, vc.Subject, ormVC.Subject)
require.Len(t, ormVC.Proof, 1)
// Convert back from ORM
convertedVC := types.VerifiableCredentialFromORM(ormVC)
require.NotNil(t, convertedVC)
require.Equal(t, vc.Id, convertedVC.Id)
require.Equal(t, vc.Issuer, convertedVC.Issuer)
require.Equal(t, vc.Subject, convertedVC.Subject)
require.Equal(t, vc.CredentialSubject, convertedVC.CredentialSubject)
}
+267 -7
View File
@@ -1,10 +1,270 @@
package types
import sdkerrors "cosmossdk.io/errors"
var (
ErrInvalidGenesisState = sdkerrors.Register(ModuleName, 100, "invalid genesis state")
ErrInvalidETHAddressFormat = sdkerrors.Register(ModuleName, 200, "invalid ETH address format")
ErrInvalidBTCAddressFormat = sdkerrors.Register(ModuleName, 201, "invalid BTC address format")
ErrInvalidIDXAddressFormat = sdkerrors.Register(ModuleName, 202, "invalid IDX address format")
import (
"cosmossdk.io/errors"
)
// DID module sentinel errors
var (
// DID Document errors
ErrDIDAlreadyExists = errors.Register(ModuleName, 1, "DID already exists")
ErrDIDNotFound = errors.Register(ModuleName, 2, "DID not found")
ErrDIDDeactivated = errors.Register(ModuleName, 3, "DID is deactivated")
ErrInvalidDIDDocument = errors.Register(ModuleName, 4, "invalid DID document")
ErrUnauthorized = errors.Register(ModuleName, 5, "unauthorized")
// Verification Method errors
ErrInvalidVerificationMethod = errors.Register(ModuleName, 6, "invalid verification method")
ErrVerificationMethodNotFound = errors.Register(ModuleName, 7, "verification method not found")
// Service errors
ErrInvalidService = errors.Register(ModuleName, 8, "invalid service")
ErrServiceNotFound = errors.Register(ModuleName, 9, "service not found")
// Credential errors
ErrCredentialNotFound = errors.Register(ModuleName, 10, "credential not found")
ErrCredentialRevoked = errors.Register(ModuleName, 11, "credential is revoked")
ErrInvalidCredential = errors.Register(ModuleName, 12, "invalid credential")
// Address errors
ErrInvalidControllerAddress = errors.Register(ModuleName, 13, "invalid controller address")
ErrInvalidIssuerAddress = errors.Register(ModuleName, 14, "invalid issuer address")
ErrInvalidAuthorityAddress = errors.Register(ModuleName, 15, "invalid authority address")
// Validation errors
ErrEmptyDID = errors.Register(ModuleName, 16, "DID cannot be empty")
ErrEmptyDIDDocumentID = errors.Register(
ModuleName,
17,
"DID document ID cannot be empty",
)
ErrDIDMismatch = errors.Register(
ModuleName,
18,
"DID and DID document ID must match",
)
ErrEmptyVerificationMethodID = errors.Register(
ModuleName,
19,
"verification method ID cannot be empty",
)
ErrEmptyVerificationMethodKind = errors.Register(
ModuleName,
20,
"verification method kind cannot be empty",
)
ErrEmptyServiceID = errors.Register(ModuleName, 21, "service ID cannot be empty")
ErrEmptyServiceKind = errors.Register(ModuleName, 22, "service kind cannot be empty")
ErrEmptyCredentialID = errors.Register(
ModuleName,
23,
"credential ID cannot be empty",
)
ErrEmptyCredentialIssuer = errors.Register(
ModuleName,
24,
"credential issuer cannot be empty",
)
// DID Document validation errors
ErrInvalidDIDSyntax = errors.Register(ModuleName, 25, "invalid DID syntax")
ErrMissingDIDDocumentID = errors.Register(
ModuleName,
26,
"DID document must have an ID",
)
ErrMissingVerificationMethodID = errors.Register(
ModuleName,
27,
"verification method must have an ID",
)
ErrMissingVerificationMethodKind = errors.Register(
ModuleName,
28,
"verification method must have a kind",
)
ErrMissingVerificationMethodController = errors.Register(
ModuleName,
29,
"verification method must have a controller",
)
ErrMissingVerificationMethodKey = errors.Register(
ModuleName,
30,
"verification method must have public key material",
)
ErrMissingServiceID = errors.Register(
ModuleName,
31,
"service must have an ID",
)
ErrMissingServiceKind = errors.Register(
ModuleName,
32,
"service must have a kind",
)
ErrMissingServiceEndpoint = errors.Register(
ModuleName,
33,
"service must have an endpoint",
)
// Storage errors
ErrFailedToCheckDIDExists = errors.Register(
ModuleName,
34,
"failed to check if DID exists",
)
ErrFailedToStoreDIDDocument = errors.Register(
ModuleName,
35,
"failed to store DID document",
)
ErrFailedToStoreDIDMetadata = errors.Register(
ModuleName,
36,
"failed to store DID document metadata",
)
ErrFailedToUpdateDIDDocument = errors.Register(
ModuleName,
37,
"failed to update DID document",
)
ErrFailedToGetDIDMetadata = errors.Register(ModuleName, 38, "failed to get DID metadata")
ErrFailedToUpdateDIDMetadata = errors.Register(
ModuleName,
39,
"failed to update DID metadata",
)
ErrFailedToDeactivateDIDDocument = errors.Register(
ModuleName,
40,
"failed to deactivate DID document",
)
ErrFailedToCheckCredentialExists = errors.Register(
ModuleName,
41,
"failed to check if credential exists",
)
ErrFailedToStoreCredential = errors.Register(
ModuleName,
42,
"failed to store verifiable credential",
)
ErrFailedToUpdateCredential = errors.Register(
ModuleName,
43,
"failed to update credential",
)
// Existence errors
ErrVerificationMethodAlreadyExists = errors.Register(
ModuleName,
44,
"verification method with ID already exists",
)
ErrServiceAlreadyExists = errors.Register(
ModuleName,
45,
"service with ID already exists",
)
ErrCredentialAlreadyExists = errors.Register(
ModuleName,
46,
"credential ID already exists",
)
ErrDIDAlreadyDeactivated = errors.Register(ModuleName, 47, "DID already deactivated")
ErrCredentialAlreadyRevoked = errors.Register(
ModuleName,
48,
"credential already revoked",
)
// Query errors
ErrInvalidRequest = errors.Register(ModuleName, 49, "invalid request")
// Parameter errors
ErrInvalidParams = errors.Register(ModuleName, 62, "invalid module parameters")
// External Wallet Linking errors
ErrInvalidBlockchainAccountID = errors.Register(
ModuleName,
50,
"invalid blockchain account ID",
)
ErrUnsupportedBlockchainNamespace = errors.Register(
ModuleName,
51,
"unsupported blockchain namespace",
)
ErrUnsupportedWalletType = errors.Register(
ModuleName,
52,
"unsupported wallet type",
)
ErrInvalidEthereumAddress = errors.Register(
ModuleName,
53,
"invalid Ethereum address",
)
ErrInvalidCosmosAddress = errors.Register(ModuleName, 54, "invalid Cosmos address")
ErrInvalidWalletVerification = errors.Register(
ModuleName,
55,
"invalid wallet verification",
)
ErrWalletSignatureVerificationFailed = errors.Register(
ModuleName,
56,
"wallet signature verification failed",
)
ErrWalletAlreadyLinked = errors.Register(
ModuleName,
57,
"wallet already linked to DID",
)
ErrDWNVaultControllerRequired = errors.Register(
ModuleName,
58,
"DID must have active DWN vault controller",
)
// WebAuthn errors
ErrInvalidWebAuthnCredential = errors.Register(
ModuleName,
59,
"invalid WebAuthn credential",
)
ErrWebAuthnCredentialAlreadyExists = errors.Register(
ModuleName,
60,
"WebAuthn credential already exists",
)
ErrMaxWebAuthnCredentialsExceeded = errors.Register(
ModuleName,
61,
"maximum WebAuthn credentials per DID exceeded",
)
ErrAssertionNotFound = errors.Register(
ModuleName,
64,
"assertion DID not found",
)
ErrInvalidAssertion = errors.Register(
ModuleName,
65,
"invalid assertion",
)
ErrNoCredentials = errors.Register(
ModuleName,
66,
"no WebAuthn credentials found",
)
// UCAN authorization errors
ErrUCANValidationFailed = errors.Register(
ModuleName,
63,
"UCAN authorization validation failed",
)
)
File diff suppressed because it is too large Load Diff
+54
View File
@@ -0,0 +1,54 @@
package types
import (
"context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sonr-io/sonr/crypto/mpc"
)
// AccountKeeper defines the expected account keeper interface
type AccountKeeper interface {
GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
HasAccount(ctx context.Context, addr sdk.AccAddress) bool
GetModuleAccount(ctx context.Context, moduleName string) sdk.ModuleAccountI
}
// DWNKeeper interface defines the methods needed from the DWN keeper for vault operations
type DWNKeeper interface {
// CreateVaultForDID creates a vault for a given DID with specified parameters
CreateVaultForDID(
ctx context.Context,
data *mpc.EnclaveData,
) (*CreateVaultResponse, error)
// GetVaultState retrieves vault state by vault ID
GetVaultState(ctx context.Context, vaultID string) (*VaultState, error)
// GetVaultsByDID retrieves all vaults associated with a DID
GetVaultsByDID(ctx context.Context, did string) ([]*VaultState, error)
}
// CreateVaultResponse represents the response from vault creation
type CreateVaultResponse struct {
VaultID string `json:"vault_id"`
VaultPublicKey string `json:"vault_public_key"`
EnclaveID string `json:"enclave_id"`
IpfsCid string `json:"ipfs_cid,omitempty"`
}
// VaultState represents the state of a vault
type VaultState struct {
VaultID string `json:"vault_id"`
DID string `json:"did"`
Controller string `json:"controller"`
Status string `json:"status"` // active, suspended, revoked
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
// ServiceKeeper interface defines the methods needed from the Service keeper for origin validation
type ServiceKeeper interface {
// VerifyOrigin validates a relying party origin for WebAuthn operations
VerifyOrigin(ctx context.Context, origin string) error
}
-1
View File
@@ -1 +0,0 @@
package types
Regular → Executable
-89
View File
@@ -1,37 +1,11 @@
package types
import (
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
"cosmossdk.io/collections"
)
// ParamsKey saves the current module params.
var ParamsKey = collections.NewPrefix(0)
const (
ModuleName = "did"
StoreKey = ModuleName
QuerierRoute = ModuleName
)
var ORMModuleSchema = ormv1alpha1.ModuleSchemaDescriptor{
SchemaFile: []*ormv1alpha1.ModuleSchemaDescriptor_FileEntry{
{Id: 1, ProtoFileName: "did/v1/state.proto"},
},
Prefix: []byte{0},
}
// this line is used by starport scaffolding # genesis/types/import
// DefaultIndex is the default global index
const DefaultIndex uint64 = 1
// DefaultGenesis returns the default genesis state
func DefaultGenesis() *GenesisState {
return &GenesisState{
// this line is used by starport scaffolding # genesis/types/default
Params: DefaultParams(),
}
}
@@ -39,68 +13,5 @@ func DefaultGenesis() *GenesisState {
// Validate performs basic genesis state validation returning an error upon any
// failure.
func (gs GenesisState) Validate() error {
// this line is used by starport scaffolding # genesis/types/validate
return gs.Params.Validate()
}
// Equal checks if two Attenuation are equal
func (a *Attenuation) Equal(that *Attenuation) bool {
if that == nil {
return false
}
if a.Resource != nil {
if that.Resource == nil {
return false
}
if !a.Resource.Equal(that.Resource) {
return false
}
}
if len(a.Capabilities) != len(that.Capabilities) {
return false
}
for i := range a.Capabilities {
if !a.Capabilities[i].Equal(that.Capabilities[i]) {
return false
}
}
return true
}
// Equal checks if two Capability are equal
func (c *Capability) Equal(that *Capability) bool {
if that == nil {
return false
}
if c.Name != that.Name {
return false
}
if c.Parent != that.Parent {
return false
}
// TODO: check description
if len(c.Resources) != len(that.Resources) {
return false
}
for i := range c.Resources {
if c.Resources[i] != that.Resources[i] {
return false
}
}
return true
}
// Equal checks if two Resource are equal
func (r *Resource) Equal(that *Resource) bool {
if that == nil {
return false
}
if r.Kind != that.Kind {
return false
}
if r.Template != that.Template {
return false
}
return true
}
+1019 -981
View File
File diff suppressed because it is too large Load Diff
Regular → Executable
+6 -2
View File
@@ -3,7 +3,7 @@ package types_test
import (
"testing"
"github.com/sonr-io/snrd/x/did/types"
"github.com/sonr-io/sonr/x/did/types"
"github.com/stretchr/testify/require"
)
@@ -19,7 +19,11 @@ func TestGenesisState_Validate(t *testing.T) {
genState: types.DefaultGenesis(),
valid: true,
},
// this line is used by starport scaffolding # types/genesis/testcase
{
desc: "valid genesis state",
genState: types.DefaultGenesis(),
valid: true,
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
+50
View File
@@ -0,0 +1,50 @@
package types
import (
"cosmossdk.io/collections"
ormv1alpha1 "cosmossdk.io/api/cosmos/orm/v1alpha1"
)
// ParamsKey saves the current module params.
var ParamsKey = collections.NewPrefix(0)
const (
ModuleName = "did"
StoreKey = ModuleName
QuerierRoute = ModuleName
)
// Event types and attribute keys
const (
// Event types
EventTypeDIDCreated = "did_created"
EventTypeDIDUpdated = "did_updated"
EventTypeDIDDeactivated = "did_deactivated"
EventTypeVerificationMethodAdded = "verification_method_added"
EventTypeVerificationMethodRemoved = "verification_method_removed"
EventTypeServiceAdded = "service_added"
EventTypeServiceRemoved = "service_removed"
EventTypeCredentialIssued = "credential_issued"
EventTypeCredentialRevoked = "credential_revoked"
EventTypeExternalWalletLinked = "external_wallet_linked"
// Attribute keys
AttributeKeyDID = "did"
AttributeKeyController = "controller"
AttributeKeyVersion = "version"
AttributeKeyVerificationMethod = "verification_method"
AttributeKeyService = "service"
AttributeKeyCredential = "credential"
AttributeKeyIssuer = "issuer"
AttributeKeySubject = "subject"
)
var ORMModuleSchema = ormv1alpha1.ModuleSchemaDescriptor{
SchemaFile: []*ormv1alpha1.ModuleSchemaDescriptor_FileEntry{
{Id: 1, ProtoFileName: "did/v1/state.proto"},
},
Prefix: []byte{0},
}
Regular → Executable
+261 -9
View File
@@ -1,24 +1,35 @@
package types
import (
"fmt"
"cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var _ sdk.Msg = &MsgUpdateParams{}
// ╭───────────────────────────────────────────────────────────╮
// │ MsgUpdateParams type definition │
// ╰───────────────────────────────────────────────────────────╯
var (
_ sdk.Msg = &MsgUpdateParams{}
_ sdk.Msg = &MsgCreateDID{}
_ sdk.Msg = &MsgUpdateDID{}
_ sdk.Msg = &MsgDeactivateDID{}
_ sdk.Msg = &MsgAddVerificationMethod{}
_ sdk.Msg = &MsgRemoveVerificationMethod{}
_ sdk.Msg = &MsgAddService{}
_ sdk.Msg = &MsgRemoveService{}
_ sdk.Msg = &MsgIssueVerifiableCredential{}
_ sdk.Msg = &MsgRevokeVerifiableCredential{}
_ sdk.Msg = &MsgLinkExternalWallet{}
_ sdk.Msg = &MsgRegisterWebAuthnCredential{}
)
// NewMsgUpdateParams creates new instance of MsgUpdateParams
func NewMsgUpdateParams(
sender sdk.Address,
someValue bool,
params Params,
) *MsgUpdateParams {
return &MsgUpdateParams{
Authority: sender.String(),
Params: DefaultParams(),
Params: params,
}
}
@@ -40,10 +51,251 @@ func (msg *MsgUpdateParams) GetSigners() []sdk.AccAddress {
}
// ValidateBasic does a sanity check on the provided data.
func (msg *MsgUpdateParams) Validate() error {
func (msg *MsgUpdateParams) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Authority); err != nil {
return errors.Wrap(err, "invalid authority address")
return errors.Wrap(ErrInvalidAuthorityAddress, err.Error())
}
return msg.Params.Validate()
}
// Validate validates the message.
func (msg *MsgUpdateParams) Validate() error {
return msg.Params.Validate()
}
// ValidateBasic does a sanity check on MsgCreateDID.
func (msg *MsgCreateDID) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.DidDocument.Id == "" {
return ErrEmptyDIDDocumentID
}
return nil
}
// ValidateBasic does a sanity check on MsgUpdateDID.
func (msg *MsgUpdateDID) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Did == "" {
return ErrEmptyDID
}
if msg.DidDocument.Id == "" {
return ErrEmptyDIDDocumentID
}
if msg.Did != msg.DidDocument.Id {
return ErrDIDMismatch
}
return nil
}
// ValidateBasic does a sanity check on MsgDeactivateDID.
func (msg *MsgDeactivateDID) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Did == "" {
return ErrEmptyDID
}
return nil
}
// ValidateBasic does a sanity check on MsgAddVerificationMethod.
func (msg *MsgAddVerificationMethod) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Did == "" {
return ErrEmptyDID
}
if msg.VerificationMethod.Id == "" {
return ErrEmptyVerificationMethodID
}
if msg.VerificationMethod.VerificationMethodKind == "" {
return ErrEmptyVerificationMethodKind
}
return nil
}
// ValidateBasic does a sanity check on MsgRemoveVerificationMethod.
func (msg *MsgRemoveVerificationMethod) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Did == "" {
return ErrEmptyDID
}
if msg.VerificationMethodId == "" {
return ErrEmptyVerificationMethodID
}
return nil
}
// ValidateBasic does a sanity check on MsgAddService.
func (msg *MsgAddService) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Did == "" {
return ErrEmptyDID
}
if msg.Service.Id == "" {
return ErrEmptyServiceID
}
if msg.Service.ServiceKind == "" {
return ErrEmptyServiceKind
}
return nil
}
// ValidateBasic does a sanity check on MsgRemoveService.
func (msg *MsgRemoveService) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Did == "" {
return ErrEmptyDID
}
if msg.ServiceId == "" {
return ErrEmptyServiceID
}
return nil
}
// ValidateBasic does a sanity check on MsgIssueVerifiableCredential.
func (msg *MsgIssueVerifiableCredential) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Issuer); err != nil {
return errors.Wrap(ErrInvalidIssuerAddress, err.Error())
}
if msg.Credential.Id == "" {
return ErrEmptyCredentialID
}
if msg.Credential.Issuer == "" {
return ErrEmptyCredentialIssuer
}
return nil
}
// ValidateBasic does a sanity check on MsgRevokeVerifiableCredential.
func (msg *MsgRevokeVerifiableCredential) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Issuer); err != nil {
return errors.Wrap(ErrInvalidIssuerAddress, err.Error())
}
if msg.CredentialId == "" {
return ErrEmptyCredentialID
}
return nil
}
// ValidateBasic does a sanity check on MsgLinkExternalWallet.
func (msg *MsgLinkExternalWallet) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Did == "" {
return ErrEmptyDID
}
if msg.WalletAddress == "" {
return errors.Wrap(ErrInvalidWalletVerification, "wallet address cannot be empty")
}
if msg.WalletChainId == "" {
return errors.Wrap(ErrInvalidWalletVerification, "chain ID cannot be empty")
}
if msg.WalletType == "" {
return errors.Wrap(ErrInvalidWalletVerification, "wallet type cannot be empty")
}
// Validate wallet type
walletType := WalletType(msg.WalletType)
if err := walletType.Validate(); err != nil {
return err
}
if len(msg.OwnershipProof) == 0 {
return errors.Wrap(ErrInvalidWalletVerification, "ownership proof cannot be empty")
}
if len(msg.Challenge) == 0 {
return errors.Wrap(ErrInvalidWalletVerification, "challenge cannot be empty")
}
if msg.VerificationMethodId == "" {
return ErrEmptyVerificationMethodID
}
// Validate blockchain account ID format
accountID, err := ParseBlockchainAccountID(fmt.Sprintf("%s:%s:%s",
walletType.GetNamespace(), msg.WalletChainId, msg.WalletAddress))
if err != nil {
return err
}
if err := accountID.Validate(); err != nil {
return err
}
return nil
}
// ValidateBasic does a sanity check on MsgRegisterWebAuthnCredential.
func (msg *MsgRegisterWebAuthnCredential) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.Controller); err != nil {
return errors.Wrap(ErrInvalidControllerAddress, err.Error())
}
if msg.Username == "" {
return errors.Wrap(ErrInvalidWebAuthnCredential, "username cannot be empty")
}
if msg.WebauthnCredential.CredentialId == "" {
return errors.Wrap(ErrInvalidWebAuthnCredential, "credential ID cannot be empty")
}
if msg.WebauthnCredential.Origin == "" {
return errors.Wrap(ErrInvalidWebAuthnCredential, "origin cannot be empty")
}
if len(msg.WebauthnCredential.PublicKey) == 0 {
return errors.Wrap(ErrInvalidWebAuthnCredential, "public key cannot be empty")
}
if msg.VerificationMethodId == "" {
return ErrEmptyVerificationMethodID
}
return nil
}
Regular → Executable
+275 -2
View File
@@ -2,11 +2,66 @@ package types
import (
"encoding/json"
"fmt"
"net/url"
"strings"
errors "cosmossdk.io/errors"
)
// DefaultParams returns default module parameters.
func DefaultParams() Params {
return Params{}
return Params{
Document: &DocumentParams{
AutoCreateVault: true,
MaxVerificationMethods: 20, // Maximum verification methods per DID
MaxServiceEndpoints: 10, // Maximum service endpoints per DID
MaxControllers: 5, // Maximum controllers per DID
DidDocumentMaxSize: 65536, // 64KB max DID document size
DidResolutionTimeout: 5, // 5 seconds resolution timeout
KeyRotationInterval: 2592000, // 30 days in seconds
CredentialLifetime: 31536000, // 1 year in seconds
SupportedAssertionMethods: []string{
"Ed25519VerificationKey2018",
"EcdsaSecp256k1VerificationKey2019",
"JsonWebKey2020",
},
SupportedAuthenticationMethods: []string{
"Ed25519VerificationKey2018",
"EcdsaSecp256k1VerificationKey2019",
"JsonWebKey2020",
"WebAuthnAuthentication2023",
},
SupportedInvocationMethods: []string{
"Ed25519VerificationKey2018",
"EcdsaSecp256k1VerificationKey2019",
},
SupportedDelegationMethods: []string{
"Ed25519VerificationKey2018",
"EcdsaSecp256k1VerificationKey2019",
},
},
Webauthn: &WebauthnParams{
ChallengeTimeout: 60, // 60 seconds (W3C recommends 60-300s)
AllowedOrigins: []string{
"http://localhost:8080",
"http://localhost:8081",
"http://localhost:8082",
"http://localhost:8083",
"http://localhost:8084",
"https://localhost:8443",
},
SupportedAlgorithms: []string{
"ES256", // ECDSA with P-256 and SHA-256 (COSE Algorithm -7)
"RS256", // RSASSA-PKCS1-v1_5 with SHA-256 (COSE Algorithm -257)
"EdDSA", // EdDSA signature algorithms (COSE Algorithm -8)
},
RequireUserVerification: true, // FIDO2 Level 2 certification requirement
MaxCredentialsPerDid: 10, // Reasonable limit to prevent resource exhaustion
DefaultRpId: "localhost",
DefaultRpName: "Sonr Identity Platform",
},
}
}
// Stringer method for Params.
@@ -21,6 +76,224 @@ func (p Params) String() string {
// Validate does the sanity check on the params.
func (p Params) Validate() error {
// TODO:
// Check that nested params are not nil
if p.Document == nil {
return errors.Wrap(ErrInvalidParams, "document params cannot be nil")
}
if p.Webauthn == nil {
return errors.Wrap(ErrInvalidParams, "webauthn params cannot be nil")
}
// Validate WebAuthn parameters
if err := validateWebAuthnParams(p.Webauthn); err != nil {
return err
}
// Validate DID module specific parameters
if err := validateDIDParams(p.Document); err != nil {
return err
}
return nil
}
// validateWebAuthnParams validates WebAuthn-specific parameters for FIDO2 compliance
func validateWebAuthnParams(p *WebauthnParams) error {
// Validate challenge timeout (FIDO2: 30-300 seconds recommended)
if p.ChallengeTimeout < 30 || p.ChallengeTimeout > 300 {
return errors.Wrap(
ErrInvalidParams,
"webauthn_challenge_timeout must be between 30-300 seconds",
)
}
// Validate allowed origins
if len(p.AllowedOrigins) == 0 {
return errors.Wrap(ErrInvalidParams, "at least one allowed_origin must be specified")
}
for _, origin := range p.AllowedOrigins {
if err := validateOrigin(origin); err != nil {
return errors.Wrapf(ErrInvalidParams, "invalid origin %s: %v", origin, err)
}
}
// Validate supported algorithms
if len(p.SupportedAlgorithms) == 0 {
return errors.Wrap(ErrInvalidParams, "at least one supported_algorithm must be specified")
}
for _, algo := range p.SupportedAlgorithms {
if !isValidCOSEAlgorithm(algo) {
return errors.Wrapf(ErrInvalidParams, "unsupported algorithm: %s", algo)
}
}
// Validate max credentials per DID (prevent resource exhaustion)
if p.MaxCredentialsPerDid < 1 || p.MaxCredentialsPerDid > 100 {
return errors.Wrap(ErrInvalidParams, "max_credentials_per_did must be between 1-100")
}
// Validate RP ID (must be valid domain or "localhost")
if err := validateRPID(p.DefaultRpId); err != nil {
return errors.Wrapf(ErrInvalidParams, "invalid default_rp_id: %v", err)
}
// Validate RP Name
if len(p.DefaultRpName) == 0 || len(p.DefaultRpName) > 256 {
return errors.Wrap(ErrInvalidParams, "default_rp_name must be between 1-256 characters")
}
return nil
}
// validateDIDParams validates DID-specific module parameters
func validateDIDParams(p *DocumentParams) error {
// Validate max verification methods (1-50)
if p.MaxVerificationMethods < 1 || p.MaxVerificationMethods > 50 {
return errors.Wrap(
ErrInvalidParams,
"max_verification_methods must be between 1-50",
)
}
// Validate max service endpoints (0-20)
if p.MaxServiceEndpoints < 0 || p.MaxServiceEndpoints > 20 {
return errors.Wrap(
ErrInvalidParams,
"max_service_endpoints must be between 0-20",
)
}
// Validate max controllers (1-10)
if p.MaxControllers < 1 || p.MaxControllers > 10 {
return errors.Wrap(
ErrInvalidParams,
"max_controllers must be between 1-10",
)
}
// Validate DID document size limits (1KB-100KB)
if p.DidDocumentMaxSize < 1024 || p.DidDocumentMaxSize > 102400 {
return errors.Wrap(
ErrInvalidParams,
"did_document_max_size must be between 1024-102400 bytes (1KB-100KB)",
)
}
// Validate DID resolution timeout (1-30 seconds)
if p.DidResolutionTimeout < 1 || p.DidResolutionTimeout > 30 {
return errors.Wrap(
ErrInvalidParams,
"did_resolution_timeout must be between 1-30 seconds",
)
}
// Validate key rotation interval (1 day - 1 year in seconds)
if p.KeyRotationInterval < 86400 || p.KeyRotationInterval > 31536000 {
return errors.Wrap(
ErrInvalidParams,
"key_rotation_interval must be between 86400-31536000 seconds (1 day - 1 year)",
)
}
// Validate credential lifetime (1 hour - 10 years in seconds)
if p.CredentialLifetime < 3600 || p.CredentialLifetime > 315360000 {
return errors.Wrap(
ErrInvalidParams,
"credential_lifetime must be between 3600-315360000 seconds (1 hour - 10 years)",
)
}
// Validate supported assertion methods
if len(p.SupportedAssertionMethods) == 0 {
return errors.Wrap(
ErrInvalidParams,
"at least one supported_assertion_method must be specified",
)
}
// Validate supported authentication methods
if len(p.SupportedAuthenticationMethods) == 0 {
return errors.Wrap(
ErrInvalidParams,
"at least one supported_authentication_method must be specified",
)
}
return nil
}
// validateOrigin validates that an origin is a valid URL with http/https scheme
func validateOrigin(origin string) error {
u, err := url.Parse(origin)
if err != nil {
return fmt.Errorf("invalid URL: %w", err)
}
// Check scheme
if u.Scheme != "http" && u.Scheme != "https" {
return fmt.Errorf("origin must use http or https scheme")
}
// Check host is present
if u.Host == "" {
return fmt.Errorf("origin must have a host")
}
// Path should be empty for origins
if u.Path != "" && u.Path != "/" {
return fmt.Errorf("origin should not include path")
}
return nil
}
// isValidCOSEAlgorithm checks if the algorithm is a valid COSE algorithm identifier
func isValidCOSEAlgorithm(algo string) bool {
// Valid COSE algorithms for WebAuthn
// Reference: https://www.w3.org/TR/webauthn-3/#sctn-alg-identifier
validAlgorithms := map[string]bool{
"ES256": true, // ECDSA with P-256 and SHA-256 (-7)
"ES384": true, // ECDSA with P-384 and SHA-384 (-35)
"ES512": true, // ECDSA with P-521 and SHA-512 (-36)
"RS256": true, // RSASSA-PKCS1-v1_5 with SHA-256 (-257)
"RS384": true, // RSASSA-PKCS1-v1_5 with SHA-384 (-258)
"RS512": true, // RSASSA-PKCS1-v1_5 with SHA-512 (-259)
"PS256": true, // RSASSA-PSS with SHA-256 (-37)
"PS384": true, // RSASSA-PSS with SHA-384 (-38)
"PS512": true, // RSASSA-PSS with SHA-512 (-39)
"EdDSA": true, // EdDSA signature algorithms (-8)
}
return validAlgorithms[algo]
}
// validateRPID validates the Relying Party ID according to WebAuthn specs
func validateRPID(rpID string) error {
if rpID == "" {
return fmt.Errorf("rp_id cannot be empty")
}
// localhost is valid for development
if rpID == "localhost" {
return nil
}
// Check if it's a valid domain
// Must not contain scheme, port, or path
if strings.Contains(rpID, "://") || strings.Contains(rpID, "/") {
return fmt.Errorf("rp_id must be a domain name without scheme or path")
}
// Basic domain validation
parts := strings.Split(rpID, ".")
if len(parts) < 2 && rpID != "localhost" {
return fmt.Errorf("rp_id must be a valid domain")
}
for _, part := range parts {
if len(part) == 0 || len(part) > 63 {
return fmt.Errorf("invalid domain label length")
}
}
return nil
}
+237
View File
@@ -0,0 +1,237 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestDefaultParams(t *testing.T) {
params := DefaultParams()
// Test that nested params are not nil
require.NotNil(t, params.Document)
require.NotNil(t, params.Webauthn)
// Test WebAuthn parameters
require.Equal(t, int64(60), params.Webauthn.ChallengeTimeout)
require.NotEmpty(t, params.Webauthn.AllowedOrigins)
require.NotEmpty(t, params.Webauthn.SupportedAlgorithms)
require.True(t, params.Webauthn.RequireUserVerification)
require.Equal(t, int32(10), params.Webauthn.MaxCredentialsPerDid)
require.Equal(t, "localhost", params.Webauthn.DefaultRpId)
require.Equal(t, "Sonr Identity Platform", params.Webauthn.DefaultRpName)
// Test Document parameters
require.True(t, params.Document.AutoCreateVault)
require.Equal(t, int32(20), params.Document.MaxVerificationMethods)
require.Equal(t, int32(10), params.Document.MaxServiceEndpoints)
require.Equal(t, int32(5), params.Document.MaxControllers)
require.Equal(t, int64(65536), params.Document.DidDocumentMaxSize)
require.Equal(t, int64(5), params.Document.DidResolutionTimeout)
require.Equal(t, int64(2592000), params.Document.KeyRotationInterval)
require.Equal(t, int64(31536000), params.Document.CredentialLifetime)
require.NotEmpty(t, params.Document.SupportedAssertionMethods)
require.NotEmpty(t, params.Document.SupportedAuthenticationMethods)
// Validate that default params pass validation
require.NoError(t, params.Validate())
}
func TestParamsValidation(t *testing.T) {
testCases := []struct {
name string
modifyFn func(*Params)
expectErr bool
}{
{
name: "valid default params",
modifyFn: func(p *Params) {
// No modifications - should be valid
},
expectErr: false,
},
{
name: "invalid webauthn challenge timeout - too low",
modifyFn: func(p *Params) {
p.Webauthn.ChallengeTimeout = 29
},
expectErr: true,
},
{
name: "invalid webauthn challenge timeout - too high",
modifyFn: func(p *Params) {
p.Webauthn.ChallengeTimeout = 301
},
expectErr: true,
},
{
name: "empty allowed origins",
modifyFn: func(p *Params) {
p.Webauthn.AllowedOrigins = []string{}
},
expectErr: true,
},
{
name: "invalid origin",
modifyFn: func(p *Params) {
p.Webauthn.AllowedOrigins = []string{"invalid-origin"}
},
expectErr: true,
},
{
name: "empty supported algorithms",
modifyFn: func(p *Params) {
p.Webauthn.SupportedAlgorithms = []string{}
},
expectErr: true,
},
{
name: "invalid algorithm",
modifyFn: func(p *Params) {
p.Webauthn.SupportedAlgorithms = []string{"INVALID"}
},
expectErr: true,
},
{
name: "invalid max credentials per DID - too low",
modifyFn: func(p *Params) {
p.Webauthn.MaxCredentialsPerDid = 0
},
expectErr: true,
},
{
name: "invalid max credentials per DID - too high",
modifyFn: func(p *Params) {
p.Webauthn.MaxCredentialsPerDid = 101
},
expectErr: true,
},
{
name: "invalid max verification methods - too low",
modifyFn: func(p *Params) {
p.Document.MaxVerificationMethods = 0
},
expectErr: true,
},
{
name: "invalid max verification methods - too high",
modifyFn: func(p *Params) {
p.Document.MaxVerificationMethods = 51
},
expectErr: true,
},
{
name: "invalid max service endpoints - too low",
modifyFn: func(p *Params) {
p.Document.MaxServiceEndpoints = -1
},
expectErr: true,
},
{
name: "invalid max service endpoints - too high",
modifyFn: func(p *Params) {
p.Document.MaxServiceEndpoints = 21
},
expectErr: true,
},
{
name: "invalid max controllers - too low",
modifyFn: func(p *Params) {
p.Document.MaxControllers = 0
},
expectErr: true,
},
{
name: "invalid max controllers - too high",
modifyFn: func(p *Params) {
p.Document.MaxControllers = 11
},
expectErr: true,
},
{
name: "invalid DID document max size - too small",
modifyFn: func(p *Params) {
p.Document.DidDocumentMaxSize = 1023
},
expectErr: true,
},
{
name: "invalid DID document max size - too large",
modifyFn: func(p *Params) {
p.Document.DidDocumentMaxSize = 102401
},
expectErr: true,
},
{
name: "invalid DID resolution timeout - too low",
modifyFn: func(p *Params) {
p.Document.DidResolutionTimeout = 0
},
expectErr: true,
},
{
name: "invalid DID resolution timeout - too high",
modifyFn: func(p *Params) {
p.Document.DidResolutionTimeout = 31
},
expectErr: true,
},
{
name: "invalid key rotation interval - too short",
modifyFn: func(p *Params) {
p.Document.KeyRotationInterval = 86399
},
expectErr: true,
},
{
name: "invalid key rotation interval - too long",
modifyFn: func(p *Params) {
p.Document.KeyRotationInterval = 31536001
},
expectErr: true,
},
{
name: "invalid credential lifetime - too short",
modifyFn: func(p *Params) {
p.Document.CredentialLifetime = 3599
},
expectErr: true,
},
{
name: "invalid credential lifetime - too long",
modifyFn: func(p *Params) {
p.Document.CredentialLifetime = 315360001
},
expectErr: true,
},
{
name: "empty supported assertion methods",
modifyFn: func(p *Params) {
p.Document.SupportedAssertionMethods = []string{}
},
expectErr: true,
},
{
name: "empty supported authentication methods",
modifyFn: func(p *Params) {
p.Document.SupportedAuthenticationMethods = []string{}
},
expectErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
params := DefaultParams()
tc.modifyFn(&params)
err := params.Validate()
if tc.expectErr {
require.Error(t, err, "Expected validation to fail but it passed")
} else {
require.NoError(t, err, "Expected validation to pass but it failed: %v", err)
}
})
}
}
-133
View File
@@ -1,133 +0,0 @@
package types
import (
"bytes"
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/crypto/types"
"google.golang.org/protobuf/proto"
)
type PubKeyI interface {
GetRole() string
GetKeyType() string
// GetRawKey() *commonv1.RawKey
// GetJwk() *commonv1.JSONWebKey
}
type PubKeyG[T any] interface {
*T
PublicKey
}
type pubKeyImpl struct {
decode func(b []byte) (PublicKey, error)
validate func(key PublicKey) error
}
// func WithSecp256K1PubKey() Option {
// return WithPubKeyWithValidationFunc(func(pt *secp256k1.PubKey) error {
// _, err := dcrd_secp256k1.ParsePubKey(pt.Key)
// return err
// })
// }
//
// func WithPubKey[T any, PT PubKeyG[T]]() Option {
// return WithPubKeyWithValidationFunc[T, PT](func(_ PT) error {
// return nil
// })
// }
//
// func WithPubKeyWithValidationFunc[T any, PT PubKeyG[T]](validateFn func(PT) error) Option {
// pkImpl := pubKeyImpl{
// decode: func(b []byte) (PublicKey, error) {
// key := PT(new(T))
// err := gogoproto.Unmarshal(b, key)
// if err != nil {
// return nil, err
// }
// return key, nil
// },
// validate: func(k PublicKey) error {
// concrete, ok := k.(PT)
// if !ok {
// return fmt.Errorf(
// "invalid pubkey type passed for validation, wanted: %T, got: %T",
// concrete,
// k,
// )
// }
// return validateFn(concrete)
// },
// }
// return func(a *Account) {
// a.supportedPubKeys[gogoproto.MessageName(PT(new(T)))] = pkImpl
// }
// }
func nameFromTypeURL(url string) string {
name := url
if i := strings.LastIndexByte(url, '/'); i >= 0 {
name = name[i+len("/"):]
}
return name
}
// CustomPubKey represents a custom secp256k1 public key.
type CustomPubKey struct {
proto.Message
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
}
// NewCustomPubKeyFromRawBytes creates a new CustomPubKey from raw bytes.
func NewCustomPubKeyFromRawBytes(key []byte) (*CustomPubKey, error) {
// Validate the key length and format
if len(key) != 33 {
return nil, fmt.Errorf("invalid key length; expected 33 bytes, got %d", len(key))
}
if key[0] != 0x02 && key[0] != 0x03 {
return nil, fmt.Errorf("invalid key format; expected 0x02 or 0x03 as the first byte, got 0x%02x", key[0])
}
return &CustomPubKey{Key: key}, nil
}
// Bytes returns the byte representation of the public key.
func (pk *CustomPubKey) Bytes() []byte {
return pk.Key
}
// Equals checks if two public keys are equal.
func (pk *CustomPubKey) Equals(other sdk.PubKey) bool {
return bytes.EqualFold(pk.Bytes(), other.Bytes())
}
// Type returns the type of the public key.
func (pk *CustomPubKey) Type() string {
return "custom-secp256k1"
}
// Marshal implements the proto.Message interface.
func (pk *CustomPubKey) Marshal() ([]byte, error) {
return proto.Marshal(pk)
}
// Unmarshal implements the proto.Message interface.
func (pk *CustomPubKey) Unmarshal(data []byte) error {
return proto.Unmarshal(data, pk)
}
// Address returns the address derived from the public key.
func (pk *CustomPubKey) Address() []byte {
// Implement address derivation logic here
// For simplicity, this example uses a placeholder
return []byte("derived-address")
}
// VerifySignature verifies a signature using the public key.
func (pk *CustomPubKey) VerifySignature(msg []byte, sig []byte) bool {
// Implement signature verification logic here
// For simplicity, this example uses a placeholder
return true
}
+5319 -898
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-29
View File
@@ -1,29 +0,0 @@
package types
import (
"context"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
"cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/types/tx"
)
type directHandler struct{}
func (s directHandler) Mode() signingv1beta1.SignMode {
return signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX
}
func (s directHandler) GetSignBytes(
_ context.Context,
signerData signing.SignerData,
txData signing.TxData,
) ([]byte, error) {
txDoc := tx.SignDoc{
BodyBytes: txData.BodyBytes,
AuthInfoBytes: txData.AuthInfoBytes,
ChainId: signerData.ChainID,
AccountNumber: signerData.AccountNumber,
}
return txDoc.Marshal()
}
+3865 -1080
View File
File diff suppressed because it is too large Load Diff
+5746 -3033
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
package types
// UCANDelegationChain represents the UCAN delegation chain for a DID
type UCANDelegationChain struct {
// Did is the DID this delegation chain belongs to
Did string `json:"did"`
// RootProof is the validator-issued root capability token
RootProof string `json:"root_proof"`
// OriginToken is the token for wallet admin operations
OriginToken string `json:"origin_token"`
// ValidatorIssuer is the DID of the validator that issued the root proof
ValidatorIssuer string `json:"validator_issuer"`
// CreatedAt is the unix timestamp when the chain was created
CreatedAt int64 `json:"created_at"`
// ExpiresAt is the unix timestamp when the origin token expires
ExpiresAt int64 `json:"expires_at"`
// Metadata contains additional information about the delegation chain
Metadata map[string]string `json:"metadata"`
}
// EventUCANTokenRefreshed is emitted when a UCAN token is refreshed
type EventUCANTokenRefreshed struct {
// Did is the DID whose token was refreshed
Did string `json:"did"`
// OldToken is the prefix of the old token (for security, only log prefix)
OldToken string `json:"old_token"`
// NewToken is the prefix of the new token
NewToken string `json:"new_token"`
// RefreshedAt is the unix timestamp when the token was refreshed
RefreshedAt int64 `json:"refreshed_at"`
}
+476
View File
@@ -0,0 +1,476 @@
package types
import (
"fmt"
"strings"
"github.com/sonr-io/sonr/crypto/ucan"
)
// UCAN Action Constants for DID operations
const (
// Core DID Actions
UCANCreate = "create" // Create new DID document
UCANRegister = "register" // Register DID with controller
UCANUpdate = "update" // Update DID document
UCANDeactivate = "deactivate" // Deactivate DID document
UCANRevoke = "revoke" // Revoke DID document (stronger than deactivate)
// Verification Method Actions
UCANAddVerificationMethod = "add-verification-method" // Add verification method
UCANRemoveVerificationMethod = "remove-verification-method" // Remove verification method
// Service Actions
UCANAddService = "add-service" // Add service endpoint
UCANRemoveService = "remove-service" // Remove service endpoint
// Credential Actions
UCANIssueCredential = "issue-credential" // Issue verifiable credential
UCANRevokeCredential = "revoke-credential" // Revoke verifiable credential
// External Wallet Actions
UCANLinkWallet = "link-wallet" // Link external wallet
// WebAuthn Actions
UCANRegisterWebAuthn = "register-webauthn" // Register WebAuthn credential
// Standard CRUD Actions (for compatibility)
UCANRead = "read" // Read DID document
UCANDelete = "delete" // Delete (same as revoke)
UCANAdmin = "admin" // Administrative actions
UCANAll = "*" // Wildcard for all actions
)
// DIDOperation represents the type of DID operation being performed
type DIDOperation string
const (
DIDOpCreate DIDOperation = "create"
DIDOpRegister DIDOperation = "register"
DIDOpUpdate DIDOperation = "update"
DIDOpDeactivate DIDOperation = "deactivate"
DIDOpRevoke DIDOperation = "revoke"
DIDOpAddVerificationMethod DIDOperation = "add_verification_method"
DIDOpRemoveVerificationMethod DIDOperation = "remove_verification_method"
DIDOpAddService DIDOperation = "add_service"
DIDOpRemoveService DIDOperation = "remove_service"
DIDOpIssueCredential DIDOperation = "issue_credential"
DIDOpRevokeCredential DIDOperation = "revoke_credential"
DIDOpLinkWallet DIDOperation = "link_wallet"
DIDOpRegisterWebAuthn DIDOperation = "register_webauthn"
)
// String returns the string representation of the DID operation
func (op DIDOperation) String() string {
return string(op)
}
// UCANCapabilityMapper provides conversion between DID operations and UCAN capabilities
type UCANCapabilityMapper struct{}
// NewUCANCapabilityMapper creates a new capability mapper
func NewUCANCapabilityMapper() *UCANCapabilityMapper {
return &UCANCapabilityMapper{}
}
// GetUCANCapabilitiesForOperation returns UCAN-specific capabilities for a DID operation
func (m *UCANCapabilityMapper) GetUCANCapabilitiesForOperation(operation DIDOperation) []string {
switch operation {
case DIDOpCreate:
return []string{UCANCreate}
case DIDOpRegister:
return []string{UCANRegister, UCANCreate}
case DIDOpUpdate:
return []string{UCANUpdate}
case DIDOpDeactivate:
return []string{UCANDeactivate, UCANUpdate}
case DIDOpRevoke:
return []string{UCANRevoke, UCANDelete, UCANAdmin}
case DIDOpAddVerificationMethod:
return []string{UCANAddVerificationMethod, UCANUpdate}
case DIDOpRemoveVerificationMethod:
return []string{UCANRemoveVerificationMethod, UCANUpdate}
case DIDOpAddService:
return []string{UCANAddService, UCANUpdate}
case DIDOpRemoveService:
return []string{UCANRemoveService, UCANUpdate}
case DIDOpIssueCredential:
return []string{UCANIssueCredential, UCANCreate}
case DIDOpRevokeCredential:
return []string{UCANRevokeCredential, UCANDelete}
case DIDOpLinkWallet:
return []string{UCANLinkWallet, UCANUpdate}
case DIDOpRegisterWebAuthn:
return []string{UCANRegisterWebAuthn, UCANCreate}
default:
return []string{UCANRead} // Default to read permission
}
}
// CreateDIDResourceURI builds a DID resource URI for UCAN validation
func (m *UCANCapabilityMapper) CreateDIDResourceURI(didPattern string) string {
return fmt.Sprintf("did:%s", didPattern)
}
// CreateDIDAttenuation creates a UCAN attenuation for DID operations
func (m *UCANCapabilityMapper) CreateDIDAttenuation(
actions []string,
didPattern string,
caveats []string,
) ucan.Attenuation {
resourceURI := m.CreateDIDResourceURI(didPattern)
// Extract method and subject from DID pattern
didMethod, didSubject := parseDIDPattern(didPattern)
resource := &ucan.DIDResource{
SimpleResource: ucan.SimpleResource{
Scheme: "did",
Value: didPattern,
URI: resourceURI,
},
DIDMethod: didMethod,
DIDSubject: didSubject,
}
capability := &ucan.DIDCapability{
Actions: actions,
Caveats: caveats,
}
return ucan.Attenuation{
Capability: capability,
Resource: resource,
}
}
// CreateControllerAttenuation creates a UCAN attenuation for controller-specific operations
func (m *UCANCapabilityMapper) CreateControllerAttenuation(
actions []string,
didPattern string,
controllerAddress string,
) ucan.Attenuation {
caveats := []string{"controller"}
attenuation := m.CreateDIDAttenuation(actions, didPattern, caveats)
// Add controller metadata to the DID resource
if resource, ok := attenuation.Resource.(*ucan.DIDResource); ok {
if resource.Metadata == nil {
resource.Metadata = make(map[string]string)
}
resource.Metadata["controller"] = controllerAddress
}
return attenuation
}
// CreateOwnerAttenuation creates a UCAN attenuation for owner-specific operations
func (m *UCANCapabilityMapper) CreateOwnerAttenuation(
actions []string,
didPattern string,
ownerAddress string,
) ucan.Attenuation {
caveats := []string{"owner"}
attenuation := m.CreateDIDAttenuation(actions, didPattern, caveats)
// Add owner metadata to the DID resource
if resource, ok := attenuation.Resource.(*ucan.DIDResource); ok {
if resource.Metadata == nil {
resource.Metadata = make(map[string]string)
}
resource.Metadata["owner"] = ownerAddress
}
return attenuation
}
// CreateWebAuthnDelegationAttenuation creates UCAN attenuation for WebAuthn credential delegation
func (m *UCANCapabilityMapper) CreateWebAuthnDelegationAttenuation(
actions []string,
didPattern string,
credentialID string,
) ucan.Attenuation {
caveats := []string{"webauthn-delegation"}
attenuation := m.CreateDIDAttenuation(actions, didPattern, caveats)
// Add WebAuthn metadata to the capability
if capability, ok := attenuation.Capability.(*ucan.DIDCapability); ok {
if capability.Metadata == nil {
capability.Metadata = make(map[string]string)
}
capability.Metadata["webauthn_credential_id"] = credentialID
capability.Metadata["delegation_type"] = "webauthn"
}
return attenuation
}
// ValidateUCANCapabilities validates that a UCAN capability grants the required DID actions
func (m *UCANCapabilityMapper) ValidateUCANCapabilities(
capability ucan.Capability,
requiredActions []string,
) bool {
return capability.Grants(requiredActions)
}
// ConvertLegacyCapabilities converts old string-based capabilities to UCAN format
func (m *UCANCapabilityMapper) ConvertLegacyCapabilities(legacyCapabilities []string) []string {
var ucanCapabilities []string
for _, legacy := range legacyCapabilities {
switch strings.ToLower(legacy) {
case "create":
ucanCapabilities = append(ucanCapabilities, UCANCreate)
case "register":
ucanCapabilities = append(ucanCapabilities, UCANRegister)
case "update":
ucanCapabilities = append(ucanCapabilities, UCANUpdate)
case "deactivate":
ucanCapabilities = append(ucanCapabilities, UCANDeactivate)
case "revoke":
ucanCapabilities = append(ucanCapabilities, UCANRevoke)
case "read", "get":
ucanCapabilities = append(ucanCapabilities, UCANRead)
case "delete":
ucanCapabilities = append(ucanCapabilities, UCANDelete)
case "admin":
ucanCapabilities = append(ucanCapabilities, UCANAdmin)
case "*":
ucanCapabilities = append(ucanCapabilities, UCANAll)
default:
// Pass through unknown capabilities
ucanCapabilities = append(ucanCapabilities, legacy)
}
}
return ucanCapabilities
}
// IsUCANAction checks if an action string is a valid UCAN action
func IsUCANAction(action string) bool {
validActions := []string{
UCANCreate, UCANRegister, UCANUpdate, UCANDeactivate, UCANRevoke,
UCANAddVerificationMethod, UCANRemoveVerificationMethod,
UCANAddService, UCANRemoveService,
UCANIssueCredential, UCANRevokeCredential,
UCANLinkWallet, UCANRegisterWebAuthn,
UCANRead, UCANDelete, UCANAdmin, UCANAll,
}
for _, validAction := range validActions {
if action == validAction {
return true
}
}
return false
}
// GetDIDCapabilityTemplate returns a preconfigured capability template for DID
func GetDIDCapabilityTemplate() *ucan.CapabilityTemplate {
return ucan.StandardDIDTemplate()
}
// UCANPermissionRegistry extends the basic permission registry with UCAN capabilities
type UCANPermissionRegistry struct {
operationCapabilities map[DIDOperation][]string
mapper *UCANCapabilityMapper
}
// NewUCANPermissionRegistry creates a new UCAN-aware permission registry
func NewUCANPermissionRegistry() *UCANPermissionRegistry {
registry := &UCANPermissionRegistry{
operationCapabilities: make(map[DIDOperation][]string),
mapper: NewUCANCapabilityMapper(),
}
// Initialize default capabilities
registry.initializeDefaultCapabilities()
return registry
}
// initializeDefaultCapabilities sets up default capability mappings
func (r *UCANPermissionRegistry) initializeDefaultCapabilities() {
operations := []DIDOperation{
DIDOpCreate, DIDOpRegister, DIDOpUpdate, DIDOpDeactivate, DIDOpRevoke,
DIDOpAddVerificationMethod, DIDOpRemoveVerificationMethod,
DIDOpAddService, DIDOpRemoveService,
DIDOpIssueCredential, DIDOpRevokeCredential,
DIDOpLinkWallet, DIDOpRegisterWebAuthn,
}
for _, op := range operations {
r.operationCapabilities[op] = r.mapper.GetUCANCapabilitiesForOperation(op)
}
}
// GetRequiredUCANCapabilities returns UCAN-specific capabilities for a DID operation
func (r *UCANPermissionRegistry) GetRequiredUCANCapabilities(operation DIDOperation) ([]string, error) {
capabilities, exists := r.operationCapabilities[operation]
if !exists {
capabilities = r.mapper.GetUCANCapabilitiesForOperation(operation)
}
if len(capabilities) == 0 {
return nil, fmt.Errorf("no UCAN capabilities defined for operation: %s", operation.String())
}
return capabilities, nil
}
// CreateDIDAttenuation creates a UCAN attenuation for DID operations
func (r *UCANPermissionRegistry) CreateDIDAttenuation(
actions []string,
didPattern string,
caveats []string,
) ucan.Attenuation {
return r.mapper.CreateDIDAttenuation(actions, didPattern, caveats)
}
// CreateControllerAttenuation creates a controller-specific attenuation
func (r *UCANPermissionRegistry) CreateControllerAttenuation(
actions []string,
didPattern string,
controllerAddress string,
) ucan.Attenuation {
return r.mapper.CreateControllerAttenuation(actions, didPattern, controllerAddress)
}
// CreateWebAuthnDelegationAttenuation creates WebAuthn delegation attenuation
func (r *UCANPermissionRegistry) CreateWebAuthnDelegationAttenuation(
actions []string,
didPattern string,
credentialID string,
) ucan.Attenuation {
return r.mapper.CreateWebAuthnDelegationAttenuation(actions, didPattern, credentialID)
}
// Helper functions
// parseDIDPattern extracts method and subject from a DID pattern
func parseDIDPattern(didPattern string) (method, subject string) {
// Handle patterns like "sonr:alice" or "key:z6MkV..."
parts := strings.SplitN(didPattern, ":", 2)
if len(parts) == 2 {
return parts[0], parts[1]
}
// If no colon, treat entire pattern as subject with default method
return "sonr", didPattern
}
// CreateDIDResourcePattern creates a DID resource pattern for matching
func CreateDIDResourcePattern(method, subject string) string {
if subject == "*" {
return fmt.Sprintf("%s:*", method)
}
return fmt.Sprintf("%s:%s", method, subject)
}
// MatchesDIDPattern checks if a DID matches a given pattern
func MatchesDIDPattern(did, pattern string) bool {
if pattern == "*" {
return true
}
// Extract DID components
didParts := strings.SplitN(did, ":", 3) // ["did", "method", "subject"]
if len(didParts) != 3 {
return false
}
// Extract pattern components
patternParts := strings.SplitN(pattern, ":", 2) // ["method", "subject"]
if len(patternParts) != 2 {
return false
}
didMethod := didParts[1]
didSubject := didParts[2]
patternMethod := patternParts[0]
patternSubject := patternParts[1]
// Check method match
if patternMethod != "*" && patternMethod != didMethod {
return false
}
// Check subject match
if patternSubject != "*" && patternSubject != didSubject {
return false
}
return true
}
// CreateGaslessAttenuation creates a UCAN attenuation that supports gasless transactions
func CreateGaslessAttenuation(
actions []string,
didPattern string,
gasLimit uint64,
) ucan.Attenuation {
mapper := NewUCANCapabilityMapper()
baseAttenuation := mapper.CreateDIDAttenuation(actions, didPattern, nil)
// Wrap capability with gasless support
gaslessCapability := &ucan.GaslessCapability{
Capability: baseAttenuation.Capability,
AllowGasless: true,
GasLimit: gasLimit,
}
return ucan.Attenuation{
Capability: gaslessCapability,
Resource: baseAttenuation.Resource,
}
}
// WebAuthn-specific helpers
// CreateWebAuthnResourceURI creates a resource URI for WebAuthn operations
func CreateWebAuthnResourceURI(did, credentialID string) string {
return fmt.Sprintf("did:%s/webauthn/%s", strings.TrimPrefix(did, "did:"), credentialID)
}
// ValidateWebAuthnDelegation validates WebAuthn capability delegation
func ValidateWebAuthnDelegation(
capability ucan.Capability,
credentialID string,
) error {
didCapability, ok := capability.(*ucan.DIDCapability)
if !ok {
return fmt.Errorf("capability is not a DID capability")
}
// Check for WebAuthn delegation caveat
hasWebAuthnCaveat := false
for _, caveat := range didCapability.Caveats {
if caveat == "webauthn-delegation" {
hasWebAuthnCaveat = true
break
}
}
if !hasWebAuthnCaveat {
return fmt.Errorf("capability does not include WebAuthn delegation caveat")
}
// Validate credential ID in metadata
if didCapability.Metadata == nil {
return fmt.Errorf("missing WebAuthn metadata")
}
storedCredentialID, exists := didCapability.Metadata["webauthn_credential_id"]
if !exists {
return fmt.Errorf("missing WebAuthn credential ID in metadata")
}
if storedCredentialID != credentialID {
return fmt.Errorf("WebAuthn credential ID mismatch")
}
return nil
}
+95
View File
@@ -0,0 +1,95 @@
// Package types provides x/did module types that delegate WebAuthn validation
// to the centralized types/webauthn package to eliminate circular dependencies.
//
// All WebAuthn validation logic has been moved to types/webauthn/sonr_validation.go
// to leverage the full WebAuthn protocol stack while maintaining API compatibility.
package types
import (
webauthnvalidation "github.com/sonr-io/sonr/types/webauthn"
)
// WebAuthnCredential automatically implements the WebAuthnCredential interface
// through the getter methods generated by protobuf (GetCredentialId, GetPublicKey, etc.)
// This provides compatibility with the centralized validation functions in types/webauthn
// ValidateStructure validates a WebAuthn credential for gasless transaction processing.
// This method delegates to the centralized validation logic in types/webauthn package.
//
// DEPRECATED: This method delegates to webauthnvalidation.ValidateStructure.
// New code should import types/webauthn and use ValidateStructure directly.
func (c *WebAuthnCredential) ValidateStructure() error {
return webauthnvalidation.ValidateStructure(c)
}
// ValidateAttestation performs security validation of WebAuthn credential data.
// This method delegates to the centralized validation logic in types/webauthn package.
//
// DEPRECATED: This method delegates to webauthnvalidation.ValidateAttestation.
// New code should import types/webauthn and use ValidateAttestation directly.
func (c *WebAuthnCredential) ValidateAttestation(challenge, expectedOrigin string) error {
return webauthnvalidation.ValidateAttestation(c, challenge, expectedOrigin)
}
// ValidateForGaslessRegistration performs comprehensive validation for gasless WebAuthn registration.
// This method delegates to the centralized validation logic in types/webauthn package.
//
// DEPRECATED: This method delegates to webauthnvalidation.ValidateForGaslessRegistration.
// New code should import types/webauthn and use ValidateForGaslessRegistration directly.
func (c *WebAuthnCredential) ValidateForGaslessRegistration(
challenge, expectedOrigin string,
) error {
return webauthnvalidation.ValidateForGaslessRegistration(c, challenge, expectedOrigin)
}
// Utility functions that delegate to types/webauthn for enhanced functionality
// ValidateCredentialUniqueness validates that a WebAuthn credential is unique across the system.
func ValidateCredentialUniqueness(credentialID string, existingCredentials []string) error {
return webauthnvalidation.ValidateCredentialUniqueness(credentialID, existingCredentials)
}
// ValidateAlgorithmSupport validates that the specified algorithm is supported.
func ValidateAlgorithmSupport(algorithm int32) error {
return webauthnvalidation.ValidateAlgorithmSupport(algorithm)
}
// ValidateAttestationObjectFormat validates the attestation object format using full WebAuthn protocol.
func ValidateAttestationObjectFormat(attestationObject string) error {
return webauthnvalidation.ValidateAttestationObjectFormat(attestationObject)
}
// ValidateClientDataJSONFormat validates the client data JSON format using WebAuthn protocol structures.
func ValidateClientDataJSONFormat(clientDataJSON string) (*webauthnvalidation.ClientData, error) {
return webauthnvalidation.ValidateClientDataJSONFormat(clientDataJSON)
}
// ValidateWithProtocol has been moved to types/webauthn package
// Use webauthnvalidation.ValidateWithProtocol directly with centralized WebAuthn credentials
// Service binding validation functions have been moved to types/webauthn package
// Use webauthnvalidation.ValidateServiceBinding and webauthnvalidation.ValidateCredentialForDomain directly
// Legacy ClientData type for backward compatibility
// DEPRECATED: Use webauthnvalidation.ClientData instead
type ClientData struct {
Type string `json:"type"`
Challenge string `json:"challenge"`
Origin string `json:"origin"`
}
// parseClientDataJSON functionality has been moved to types/webauthn package
// Use webauthnvalidation.ValidateClientDataJSONFormat instead
// Helper functions that maintain API compatibility while delegating to types/webauthn
// GenerateAddressFromCredential generates a deterministic address from a WebAuthn credential ID.
func GenerateAddressFromCredential(credentialID string) string {
// Import the utility package for address generation
return webauthnvalidation.GenerateAddressFromCredential(credentialID).String()
}
// GenerateDIDFromCredential generates a deterministic DID from a WebAuthn credential.
func GenerateDIDFromCredential(credentialID string, username string) string {
return webauthnvalidation.GenerateDIDFromCredential(credentialID, username)
}