626 lines
18 KiB
Go
626 lines
18 KiB
Go
// 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 (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/multiformats/go-multihash"
|
|
"github.com/sonr-io/crypto/keys"
|
|
"github.com/sonr-io/crypto/mpc"
|
|
)
|
|
|
|
// MPCSigningMethod implements JWT signing using MPC enclaves
|
|
type MPCSigningMethod struct {
|
|
Name string
|
|
enclave mpc.Enclave
|
|
}
|
|
|
|
// NewMPCSigningMethod creates a new MPC-based JWT signing method
|
|
func NewMPCSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
|
|
return &MPCSigningMethod{
|
|
Name: name,
|
|
enclave: enclave,
|
|
}
|
|
}
|
|
|
|
// Alg returns the signing method algorithm name
|
|
func (m *MPCSigningMethod) Alg() string {
|
|
return m.Name
|
|
}
|
|
|
|
// Verify verifies a JWT signature using the MPC enclave
|
|
func (m *MPCSigningMethod) Verify(signingString string, signature []byte, key any) error {
|
|
// signature is already decoded bytes
|
|
sig := signature
|
|
|
|
// Hash the signing string
|
|
hasher := sha256.New()
|
|
hasher.Write([]byte(signingString))
|
|
digest := hasher.Sum(nil)
|
|
|
|
// Use MPC enclave to verify signature
|
|
valid, err := m.enclave.Verify(digest, sig)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to verify signature: %w", err)
|
|
}
|
|
|
|
if !valid {
|
|
return fmt.Errorf("signature verification failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Sign signs a JWT string using the MPC enclave
|
|
func (m *MPCSigningMethod) Sign(signingString string, key any) ([]byte, error) {
|
|
// Hash the signing string
|
|
hasher := sha256.New()
|
|
hasher.Write([]byte(signingString))
|
|
digest := hasher.Sum(nil)
|
|
|
|
// Use MPC enclave to sign the digest
|
|
sig, err := m.enclave.Sign(digest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to sign with MPC: %w", err)
|
|
}
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
// MPCTokenBuilder creates UCAN tokens using MPC signing
|
|
type MPCTokenBuilder struct {
|
|
enclave mpc.Enclave
|
|
issuerDID string
|
|
address string
|
|
signingMethod *MPCSigningMethod
|
|
}
|
|
|
|
// NewMPCTokenBuilder creates a new MPC-based UCAN token builder
|
|
func NewMPCTokenBuilder(enclave mpc.Enclave) (*MPCTokenBuilder, error) {
|
|
if !enclave.IsValid() {
|
|
return nil, fmt.Errorf("invalid MPC enclave provided")
|
|
}
|
|
|
|
// Derive issuer DID and address from enclave public key
|
|
pubKeyBytes := enclave.PubKeyBytes()
|
|
issuerDID, address := deriveIssuerDIDFromBytes(pubKeyBytes)
|
|
|
|
signingMethod := NewMPCSigningMethod("MPC256", enclave)
|
|
|
|
return &MPCTokenBuilder{
|
|
enclave: enclave,
|
|
issuerDID: issuerDID,
|
|
address: address,
|
|
signingMethod: signingMethod,
|
|
}, nil
|
|
}
|
|
|
|
// GetIssuerDID returns the issuer DID derived from the enclave
|
|
func (b *MPCTokenBuilder) GetIssuerDID() string {
|
|
return b.issuerDID
|
|
}
|
|
|
|
// GetAddress returns the address derived from the enclave
|
|
func (b *MPCTokenBuilder) GetAddress() string {
|
|
return b.address
|
|
}
|
|
|
|
// CreateOriginToken creates a new origin UCAN token using MPC signing
|
|
func (b *MPCTokenBuilder) CreateOriginToken(
|
|
audienceDID string,
|
|
attenuations []Attenuation,
|
|
facts []Fact,
|
|
notBefore, expiresAt time.Time,
|
|
) (*Token, error) {
|
|
return b.createToken(audienceDID, nil, attenuations, facts, notBefore, expiresAt)
|
|
}
|
|
|
|
// CreateDelegatedToken creates a delegated UCAN token using MPC signing
|
|
func (b *MPCTokenBuilder) CreateDelegatedToken(
|
|
parent *Token,
|
|
audienceDID string,
|
|
attenuations []Attenuation,
|
|
facts []Fact,
|
|
notBefore, expiresAt time.Time,
|
|
) (*Token, error) {
|
|
proofs, err := prepareDelegationProofs(parent, attenuations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.createToken(audienceDID, proofs, attenuations, facts, notBefore, expiresAt)
|
|
}
|
|
|
|
// createToken creates a UCAN token with MPC signing
|
|
func (b *MPCTokenBuilder) createToken(
|
|
audienceDID string,
|
|
proofs []Proof,
|
|
attenuations []Attenuation,
|
|
facts []Fact,
|
|
notBefore, expiresAt time.Time,
|
|
) (*Token, error) {
|
|
// Validate inputs
|
|
if !isValidDID(audienceDID) {
|
|
return nil, fmt.Errorf("invalid audience DID format: %s", audienceDID)
|
|
}
|
|
if len(attenuations) == 0 {
|
|
return nil, fmt.Errorf("at least one attenuation is required")
|
|
}
|
|
|
|
// Create JWT token with MPC signing method
|
|
token := jwt.New(b.signingMethod)
|
|
|
|
// Set UCAN version in header
|
|
token.Header["ucv"] = "0.9.0"
|
|
|
|
// Prepare time claims
|
|
var nbfUnix, expUnix int64
|
|
if !notBefore.IsZero() {
|
|
nbfUnix = notBefore.Unix()
|
|
}
|
|
if !expiresAt.IsZero() {
|
|
expUnix = expiresAt.Unix()
|
|
}
|
|
|
|
// Convert attenuations to claim format
|
|
attClaims := make([]map[string]any, len(attenuations))
|
|
for i, att := range attenuations {
|
|
attClaims[i] = map[string]any{
|
|
"can": att.Capability.GetActions(),
|
|
"with": att.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(facts))
|
|
for i, fact := range facts {
|
|
// Facts are stored as raw JSON, convert to any
|
|
factData[i] = string(fact.Data)
|
|
}
|
|
|
|
// Set claims
|
|
claims := jwt.MapClaims{
|
|
"iss": b.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
|
|
}
|
|
|
|
token.Claims = claims
|
|
|
|
// Sign the token using MPC enclave (key parameter is ignored for MPC signing)
|
|
tokenString, err := token.SignedString(nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to sign token with MPC: %w", err)
|
|
}
|
|
|
|
return &Token{
|
|
Raw: tokenString,
|
|
Issuer: b.issuerDID,
|
|
Audience: audienceDID,
|
|
ExpiresAt: expUnix,
|
|
NotBefore: nbfUnix,
|
|
Attenuations: attenuations,
|
|
Proofs: proofs,
|
|
Facts: facts,
|
|
}, nil
|
|
}
|
|
|
|
// CreateVaultCapabilityToken creates a vault-specific UCAN token
|
|
func (b *MPCTokenBuilder) CreateVaultCapabilityToken(
|
|
audienceDID string,
|
|
vaultAddress string,
|
|
enclaveDataCID string,
|
|
actions []string,
|
|
expiresAt time.Time,
|
|
) (*Token, error) {
|
|
// Create vault-specific attenuation
|
|
attenuation := CreateVaultAttenuation(actions, enclaveDataCID, vaultAddress)
|
|
|
|
return b.CreateOriginToken(
|
|
audienceDID,
|
|
[]Attenuation{attenuation},
|
|
nil,
|
|
time.Time{}, // No not-before restriction
|
|
expiresAt,
|
|
)
|
|
}
|
|
|
|
// MPCDIDResolver resolves DIDs with special handling for MPC-derived DIDs
|
|
type MPCDIDResolver struct {
|
|
enclave mpc.Enclave
|
|
issuerDID string
|
|
fallback DIDResolver
|
|
}
|
|
|
|
// NewMPCDIDResolver creates a new MPC DID resolver
|
|
func NewMPCDIDResolver(enclave mpc.Enclave, fallback DIDResolver) *MPCDIDResolver {
|
|
pubKeyBytes := enclave.PubKeyBytes()
|
|
issuerDID, _ := deriveIssuerDIDFromBytes(pubKeyBytes)
|
|
|
|
return &MPCDIDResolver{
|
|
enclave: enclave,
|
|
issuerDID: issuerDID,
|
|
fallback: fallback,
|
|
}
|
|
}
|
|
|
|
// ResolveDIDKey resolves DID keys with MPC enclave support
|
|
func (r *MPCDIDResolver) ResolveDIDKey(ctx context.Context, didStr string) (keys.DID, error) {
|
|
// Check if this is the MPC-derived DID
|
|
if didStr == r.issuerDID {
|
|
return r.createDIDFromEnclave()
|
|
}
|
|
|
|
// Fall back to standard DID resolution
|
|
if r.fallback != nil {
|
|
return r.fallback.ResolveDIDKey(ctx, didStr)
|
|
}
|
|
|
|
// Default fallback to string parsing
|
|
return keys.Parse(didStr)
|
|
}
|
|
|
|
// createDIDFromEnclave creates a DID from the MPC enclave's public key
|
|
func (r *MPCDIDResolver) createDIDFromEnclave() (keys.DID, error) {
|
|
// This would need to be implemented based on how MPC public keys
|
|
// are converted to the keys.DID format
|
|
// For now, parse from the derived DID string
|
|
return keys.Parse(r.issuerDID)
|
|
}
|
|
|
|
// MPCVerifier provides UCAN verification with MPC support
|
|
type MPCVerifier struct {
|
|
*Verifier
|
|
enclave mpc.Enclave
|
|
}
|
|
|
|
// NewMPCVerifier creates a UCAN verifier with MPC support
|
|
func NewMPCVerifier(enclave mpc.Enclave) *MPCVerifier {
|
|
resolver := NewMPCDIDResolver(enclave, StringDIDResolver{})
|
|
verifier := NewVerifier(resolver)
|
|
|
|
return &MPCVerifier{
|
|
Verifier: verifier,
|
|
enclave: enclave,
|
|
}
|
|
}
|
|
|
|
// VerifyMPCToken verifies a UCAN token that may be signed with MPC
|
|
func (v *MPCVerifier) VerifyMPCToken(ctx context.Context, tokenString string) (*Token, error) {
|
|
// Try standard verification first
|
|
token, err := v.VerifyToken(ctx, tokenString)
|
|
if err == nil {
|
|
return token, nil
|
|
}
|
|
|
|
// If standard verification fails, try MPC-specific verification
|
|
return v.verifyWithMPC(ctx, tokenString)
|
|
}
|
|
|
|
// verifyWithMPC attempts to verify using MPC signing method
|
|
func (v *MPCVerifier) verifyWithMPC(_ context.Context, tokenString string) (*Token, error) {
|
|
// Create MPC signing method for verification
|
|
mpcMethod := NewMPCSigningMethod("MPC256", v.enclave)
|
|
|
|
// Parse with MPC method
|
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
|
|
// Ensure the token uses MPC signing method
|
|
if token.Method.Alg() != mpcMethod.Alg() {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Method)
|
|
}
|
|
// For MPC verification, the key is not used
|
|
return nil, nil
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("MPC token verification failed: %w", err)
|
|
}
|
|
|
|
// Extract and parse claims
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid token claims type")
|
|
}
|
|
|
|
ucanToken, err := v.parseUCANClaims(claims, tokenString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse UCAN claims: %w", err)
|
|
}
|
|
|
|
return ucanToken, nil
|
|
}
|
|
|
|
// MPCTokenValidator provides comprehensive UCAN token validation with MPC support
|
|
type MPCTokenValidator struct {
|
|
*MPCVerifier
|
|
enclaveValidation bool
|
|
}
|
|
|
|
// NewMPCTokenValidator creates a comprehensive UCAN token validator with MPC support
|
|
func NewMPCTokenValidator(enclave mpc.Enclave, enableEnclaveValidation bool) *MPCTokenValidator {
|
|
verifier := NewMPCVerifier(enclave)
|
|
return &MPCTokenValidator{
|
|
MPCVerifier: verifier,
|
|
enclaveValidation: enableEnclaveValidation,
|
|
}
|
|
}
|
|
|
|
// ValidateTokenForVaultOperation performs comprehensive validation for vault operations
|
|
func (v *MPCTokenValidator) ValidateTokenForVaultOperation(
|
|
ctx context.Context,
|
|
tokenString string,
|
|
enclaveDataCID string,
|
|
requiredAction string,
|
|
vaultAddress string,
|
|
) (*Token, error) {
|
|
// Step 1: Verify token signature and structure
|
|
token, err := v.VerifyMPCToken(ctx, tokenString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("token verification failed: %w", err)
|
|
}
|
|
|
|
// Step 2: Validate vault-specific capability
|
|
if err := ValidateVaultTokenCapability(token, enclaveDataCID, requiredAction); err != nil {
|
|
return nil, fmt.Errorf("vault capability validation failed: %w", err)
|
|
}
|
|
|
|
// Step 3: Validate enclave data CID if enabled
|
|
if v.enclaveValidation {
|
|
if err := v.validateEnclaveDataCID(token, enclaveDataCID); err != nil {
|
|
return nil, fmt.Errorf("enclave data validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Step 4: Validate vault address if provided
|
|
if vaultAddress != "" {
|
|
if err := v.validateVaultAddress(token, vaultAddress); err != nil {
|
|
return nil, fmt.Errorf("vault address validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Step 5: Verify delegation chain if proofs exist
|
|
if len(token.Proofs) > 0 {
|
|
if err := v.VerifyDelegationChain(ctx, tokenString); err != nil {
|
|
return nil, fmt.Errorf("delegation chain validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// ValidateTokenForResource validates token capabilities for a specific resource
|
|
func (v *MPCTokenValidator) ValidateTokenForResource(
|
|
ctx context.Context,
|
|
tokenString string,
|
|
resourceURI string,
|
|
requiredAbilities []string,
|
|
) (*Token, error) {
|
|
token, err := v.VerifyCapability(ctx, tokenString, resourceURI, requiredAbilities)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("capability verification failed: %w", err)
|
|
}
|
|
|
|
// Additional MPC-specific validation
|
|
if v.enclaveValidation {
|
|
if err := v.validateMPCIssuer(token); err != nil {
|
|
return nil, fmt.Errorf("MPC issuer validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// validateEnclaveDataCID validates that the token contains the expected enclave data CID
|
|
func (v *MPCTokenValidator) validateEnclaveDataCID(token *Token, expectedCID string) error {
|
|
tokenCID, err := GetEnclaveDataCID(token)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to extract enclave data CID from token: %w", err)
|
|
}
|
|
|
|
if tokenCID != expectedCID {
|
|
return fmt.Errorf("enclave data CID mismatch: token=%s, expected=%s", tokenCID, expectedCID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateVaultAddress validates the vault address in token capabilities
|
|
func (v *MPCTokenValidator) validateVaultAddress(token *Token, expectedAddress string) error {
|
|
for _, att := range token.Attenuations {
|
|
if vaultCap, ok := att.Capability.(*VaultCapability); ok {
|
|
if vaultCap.VaultAddress != "" && vaultCap.VaultAddress != expectedAddress {
|
|
return fmt.Errorf("vault address mismatch: token=%s, expected=%s",
|
|
vaultCap.VaultAddress, expectedAddress)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateMPCIssuer validates that the token issuer matches the MPC enclave
|
|
func (v *MPCTokenValidator) validateMPCIssuer(token *Token) error {
|
|
expectedIssuer, _ := deriveIssuerDIDFromBytes(v.enclave.PubKeyBytes())
|
|
|
|
if token.Issuer != expectedIssuer {
|
|
return fmt.Errorf("token issuer does not match MPC enclave: token=%s, expected=%s",
|
|
token.Issuer, expectedIssuer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createMPCVaultAttenuation creates MPC-specific vault attenuations
|
|
func createMPCVaultAttenuation(actions []string, enclaveDataCID, vaultAddress string) Attenuation {
|
|
// Use the existing CreateVaultAttenuation function but add MPC-specific validation
|
|
return CreateVaultAttenuation(actions, enclaveDataCID, vaultAddress)
|
|
}
|
|
|
|
// containsAdminAction checks if actions contain admin-level permissions
|
|
func containsAdminAction(actions []string) bool {
|
|
adminActions := map[string]bool{
|
|
"admin": true, "export": true, "import": true, "delete": true,
|
|
}
|
|
|
|
for _, action := range actions {
|
|
if adminActions[action] {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidateEnclaveDataIntegrity validates enclave data against IPFS CID
|
|
func ValidateEnclaveDataIntegrity(enclaveData *mpc.EnclaveData, expectedCID string) error {
|
|
if enclaveData == nil {
|
|
return fmt.Errorf("enclave data cannot be nil")
|
|
}
|
|
|
|
// Basic validation of enclave structure
|
|
if len(enclaveData.PubBytes) == 0 {
|
|
return fmt.Errorf("enclave public key bytes cannot be empty")
|
|
}
|
|
|
|
if enclaveData.PubHex == "" {
|
|
return fmt.Errorf("enclave public key hex cannot be empty")
|
|
}
|
|
|
|
// Implement IPFS CID validation against enclave data hash
|
|
// Serialize the enclave data for consistent hashing
|
|
enclaveDataBytes, err := enclaveData.Marshal()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal enclave data: %w", err)
|
|
}
|
|
|
|
// 1. Hash the enclave data using SHA-256
|
|
hasher := sha256.New()
|
|
hasher.Write(enclaveDataBytes)
|
|
digest := hasher.Sum(nil)
|
|
|
|
// 2. Create multihash with SHA-256 prefix
|
|
mhash, err := multihash.EncodeName(digest, "sha2-256")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create multihash: %w", err)
|
|
}
|
|
|
|
// 3. Create CID and compare with expected
|
|
parsedExpectedCID, err := cid.Parse(expectedCID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse expected CID: %w", err)
|
|
}
|
|
|
|
// Create CID v1 with dag-pb codec (IPFS default)
|
|
calculatedCID := cid.NewCidV1(cid.DagProtobuf, mhash)
|
|
|
|
// Compare CIDs
|
|
if !parsedExpectedCID.Equals(calculatedCID) {
|
|
return fmt.Errorf(
|
|
"CID verification failed: expected %s, calculated %s",
|
|
parsedExpectedCID.String(),
|
|
calculatedCID.String(),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MPCCapabilityBuilder helps build MPC-specific capabilities
|
|
type MPCCapabilityBuilder struct {
|
|
enclave mpc.Enclave
|
|
builder *MPCTokenBuilder
|
|
}
|
|
|
|
// NewMPCCapabilityBuilder creates a new MPC capability builder
|
|
func NewMPCCapabilityBuilder(enclave mpc.Enclave) (*MPCCapabilityBuilder, error) {
|
|
builder, err := NewMPCTokenBuilder(enclave)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create MPC token builder: %w", err)
|
|
}
|
|
|
|
return &MPCCapabilityBuilder{
|
|
enclave: enclave,
|
|
builder: builder,
|
|
}, nil
|
|
}
|
|
|
|
// CreateVaultAdminCapability creates admin-level vault capabilities
|
|
func (b *MPCCapabilityBuilder) CreateVaultAdminCapability(
|
|
vaultAddress, enclaveDataCID string,
|
|
) Attenuation {
|
|
allActions := []string{"read", "write", "sign", "export", "import", "delete", "admin"}
|
|
return CreateVaultAttenuation(allActions, enclaveDataCID, vaultAddress)
|
|
}
|
|
|
|
// CreateVaultReadOnlyCapability creates read-only vault capabilities
|
|
func (b *MPCCapabilityBuilder) CreateVaultReadOnlyCapability(
|
|
vaultAddress, enclaveDataCID string,
|
|
) Attenuation {
|
|
readActions := []string{"read"}
|
|
return CreateVaultAttenuation(readActions, enclaveDataCID, vaultAddress)
|
|
}
|
|
|
|
// CreateVaultSigningCapability creates signing-specific vault capabilities
|
|
func (b *MPCCapabilityBuilder) CreateVaultSigningCapability(
|
|
vaultAddress, enclaveDataCID string,
|
|
) Attenuation {
|
|
signActions := []string{"read", "sign"}
|
|
return CreateVaultAttenuation(signActions, enclaveDataCID, vaultAddress)
|
|
}
|
|
|
|
// CreateCustomCapability creates a custom capability with specified actions
|
|
func (b *MPCCapabilityBuilder) CreateCustomCapability(
|
|
actions []string,
|
|
vaultAddress, enclaveDataCID string,
|
|
) Attenuation {
|
|
return CreateVaultAttenuation(actions, enclaveDataCID, vaultAddress)
|
|
}
|
|
|
|
// Utility functions
|
|
|
|
// deriveIssuerDIDFromBytes creates issuer DID and address from public key bytes
|
|
// Enhanced version using the crypto/keys package
|
|
func deriveIssuerDIDFromBytes(pubKeyBytes []byte) (string, string) {
|
|
// Use the enhanced NewFromMPCPubKey method from crypto/keys
|
|
did, err := keys.NewFromMPCPubKey(pubKeyBytes)
|
|
if err != nil {
|
|
// Fallback to simplified implementation
|
|
address := fmt.Sprintf("addr_%x", pubKeyBytes[:8])
|
|
issuerDID := fmt.Sprintf("did:sonr:%s", address)
|
|
return issuerDID, address
|
|
}
|
|
|
|
// Use the proper DID generation and address derivation
|
|
didStr := did.String()
|
|
address, err := did.Address()
|
|
if err != nil {
|
|
// Fallback to simplified address
|
|
address = fmt.Sprintf("addr_%x", pubKeyBytes[:8])
|
|
}
|
|
|
|
return didStr, address
|
|
}
|