mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 18:31:41 +00:00
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
// }
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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 +0,0 @@
|
||||
package types
|
||||
Regular → Executable
-89
@@ -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
File diff suppressed because it is too large
Load Diff
Regular → Executable
+6
-2
@@ -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) {
|
||||
|
||||
Executable
+50
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(¶ms)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+948
-85
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
+5746
-3033
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user