// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation // for decentralized authorization and capability delegation in the Sonr network. // This package handles JWT-based tokens, cryptographic verification, and resource capabilities. package ucan import ( "fmt" "time" "github.com/golang-jwt/jwt/v5" "github.com/sonr-io/crypto/keys" "github.com/sonr-io/crypto/mpc" "lukechampine.com/blake3" ) // KeyshareSource provides MPC-based UCAN token creation and validation type KeyshareSource interface { Address() string Issuer() string ChainCode() ([]byte, error) OriginToken() (*Token, error) SignData(data []byte) ([]byte, error) VerifyData(data []byte, sig []byte) (bool, error) Enclave() mpc.Enclave // UCAN token creation methods NewOriginToken( audienceDID string, att []Attenuation, fct []Fact, notBefore, expires time.Time, ) (*Token, error) NewAttenuatedToken( parent *Token, audienceDID string, att []Attenuation, fct []Fact, nbf, exp time.Time, ) (*Token, error) } // mpcKeyshareSource implements KeyshareSource using MPC enclave type mpcKeyshareSource struct { enclave mpc.Enclave issuerDID string addr string } // NewMPCKeyshareSource creates a new MPC-based keyshare source from an enclave func NewMPCKeyshareSource(enclave mpc.Enclave) (KeyshareSource, error) { if !enclave.IsValid() { return nil, fmt.Errorf("invalid MPC enclave provided") } pubKeyBytes := enclave.PubKeyBytes() issuerDID, addr, err := getIssuerDIDFromBytes(pubKeyBytes) if err != nil { return nil, fmt.Errorf("failed to derive issuer DID: %w", err) } return &mpcKeyshareSource{ enclave: enclave, issuerDID: issuerDID, addr: addr, }, nil } // Address returns the address derived from the enclave public key func (k *mpcKeyshareSource) Address() string { return k.addr } // Issuer returns the DID of the issuer derived from the enclave public key func (k *mpcKeyshareSource) Issuer() string { return k.issuerDID } // Enclave returns the underlying MPC enclave func (k *mpcKeyshareSource) Enclave() mpc.Enclave { return k.enclave } // ChainCode derives a deterministic chain code from the enclave func (k *mpcKeyshareSource) ChainCode() ([]byte, error) { // Sign the address to create a deterministic chain code sig, err := k.SignData([]byte(k.addr)) if err != nil { return nil, fmt.Errorf("failed to sign address for chain code: %w", err) } // Hash the signature to create a 32-byte chain code hash := blake3.Sum256(sig) return hash[:32], nil } // OriginToken creates a default origin token with basic capabilities func (k *mpcKeyshareSource) OriginToken() (*Token, error) { // Create basic capability for the MPC keyshare resource := &SimpleResource{ Scheme: "mpc", Value: k.addr, URI: fmt.Sprintf("mpc://%s", k.addr), } capability := &SimpleCapability{Action: "sign"} attenuation := Attenuation{ Capability: capability, Resource: resource, } // Create token with no expiration for origin token zero := time.Time{} return k.NewOriginToken(k.issuerDID, []Attenuation{attenuation}, nil, zero, zero) } // SignData signs data using the MPC enclave func (k *mpcKeyshareSource) SignData(data []byte) ([]byte, error) { if !k.enclave.IsValid() { return nil, fmt.Errorf("enclave is not valid") } return k.enclave.Sign(data) } // VerifyData verifies a signature using the MPC enclave func (k *mpcKeyshareSource) VerifyData(data []byte, sig []byte) (bool, error) { if !k.enclave.IsValid() { return false, fmt.Errorf("enclave is not valid") } return k.enclave.Verify(data, sig) } // NewOriginToken creates a new UCAN origin token using MPC signing func (k *mpcKeyshareSource) NewOriginToken( audienceDID string, att []Attenuation, fct []Fact, notBefore, expires time.Time, ) (*Token, error) { return k.newToken(audienceDID, nil, att, fct, notBefore, expires) } // NewAttenuatedToken creates a new attenuated UCAN token using MPC signing func (k *mpcKeyshareSource) NewAttenuatedToken( parent *Token, audienceDID string, att []Attenuation, fct []Fact, nbf, exp time.Time, ) (*Token, error) { // Validate that new attenuations are more restrictive than parent if !isAttenuationSubset(att, parent.Attenuations) { return nil, fmt.Errorf("scope of ucan attenuations must be less than its parent") } // Add parent as proof proofs := []Proof{} if parent.Raw != "" { proofs = append(proofs, Proof(parent.Raw)) } proofs = append(proofs, parent.Proofs...) return k.newToken(audienceDID, proofs, att, fct, nbf, exp) } // newToken creates a new UCAN token with MPC signing func (k *mpcKeyshareSource) newToken( audienceDID string, proofs []Proof, att []Attenuation, fct []Fact, nbf, exp time.Time, ) (*Token, error) { // Validate audience DID if !isValidDID(audienceDID) { return nil, fmt.Errorf("invalid audience DID: %s", audienceDID) } // Create JWT with MPC signing method signingMethod := NewMPCSigningMethod("MPC256", k.enclave) t := jwt.New(signingMethod) // Set UCAN version header t.Header["ucv"] = "0.9.0" var ( nbfUnix int64 expUnix int64 ) if !nbf.IsZero() { nbfUnix = nbf.Unix() } if !exp.IsZero() { expUnix = exp.Unix() } // Convert attenuations to claim format attClaims := make([]map[string]any, len(att)) for i, a := range att { attClaims[i] = map[string]any{ "can": a.Capability.GetActions(), "with": a.Resource.GetURI(), } } // Convert proofs to strings proofStrings := make([]string, len(proofs)) for i, proof := range proofs { proofStrings[i] = string(proof) } // Convert facts to any slice factData := make([]any, len(fct)) for i, fact := range fct { factData[i] = string(fact.Data) } // Set claims claims := jwt.MapClaims{ "iss": k.issuerDID, "aud": audienceDID, "att": attClaims, } if nbfUnix > 0 { claims["nbf"] = nbfUnix } if expUnix > 0 { claims["exp"] = expUnix } if len(proofStrings) > 0 { claims["prf"] = proofStrings } if len(factData) > 0 { claims["fct"] = factData } t.Claims = claims // Sign the token using MPC enclave tokenString, err := t.SignedString(nil) if err != nil { return nil, fmt.Errorf("failed to sign token: %w", err) } return &Token{ Raw: tokenString, Issuer: k.issuerDID, Audience: audienceDID, ExpiresAt: expUnix, NotBefore: nbfUnix, Attenuations: att, Proofs: proofs, Facts: fct, }, nil } // getIssuerDIDFromBytes creates an issuer DID and address from public key bytes func getIssuerDIDFromBytes(pubKeyBytes []byte) (string, string, error) { // Use the enhanced NewFromMPCPubKey method for proper MPC integration did, err := keys.NewFromMPCPubKey(pubKeyBytes) if err != nil { return "", "", fmt.Errorf("failed to create DID from MPC public key: %w", err) } didStr := did.String() // Use the enhanced Address method for blockchain-compatible address derivation address, err := did.Address() if err != nil { return "", "", fmt.Errorf("failed to derive address from DID: %w", err) } return didStr, address, nil } // isAttenuationSubset checks if child attenuations are a subset of parent attenuations func isAttenuationSubset(child, parent []Attenuation) bool { for _, childAtt := range child { if !containsAttenuation(parent, childAtt) { return false } } return true } // containsAttenuation checks if the parent list contains an equivalent attenuation func containsAttenuation(parent []Attenuation, att Attenuation) bool { for _, parentAtt := range parent { if parentAtt.Resource.Matches(att.Resource) && parentAtt.Capability.Contains(att.Capability) { return true } } return false } // Note: MPC signing methods are already implemented in mpc.go // Note: isValidDID is already implemented in stubs.go