mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
@@ -0,0 +1,355 @@
|
||||
// Package keys provides key management functionality for the Sonr client SDK.
|
||||
// This package integrates with Sonr's DWN plugin architecture for Decentralized Abstracted Smart Wallets.
|
||||
package keys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sonr-io/sonr/client/errors"
|
||||
"github.com/sonr-io/sonr/x/dwn/client/plugin"
|
||||
)
|
||||
|
||||
// KeyringManager provides an interface for managing Decentralized Abstracted Smart Wallets
|
||||
// through the Sonr DWN plugin architecture.
|
||||
type KeyringManager interface {
|
||||
// Wallet Identity Operations
|
||||
GetIssuerDID(ctx context.Context) (*WalletIdentity, error)
|
||||
GetAddress(ctx context.Context) (string, error)
|
||||
|
||||
// UCAN Token Operations for Authorization
|
||||
CreateOriginToken(ctx context.Context, req *UCANRequest) (*UCANToken, error)
|
||||
CreateAttenuatedToken(ctx context.Context, req *AttenuatedUCANRequest) (*UCANToken, error)
|
||||
|
||||
// Signing Operations using MPC
|
||||
Sign(ctx context.Context, data []byte) (*Signature, error)
|
||||
SignTransaction(ctx context.Context, txBytes []byte) (*Signature, error)
|
||||
|
||||
// Verification Operations
|
||||
Verify(ctx context.Context, data []byte, signature []byte) (bool, error)
|
||||
|
||||
// Plugin Management
|
||||
Plugin() plugin.Plugin
|
||||
Close() error
|
||||
}
|
||||
|
||||
// WalletIdentity represents the identity of a Decentralized Abstracted Smart Wallet.
|
||||
type WalletIdentity struct {
|
||||
DID string `json:"did"` // W3C DID identifier
|
||||
Address string `json:"address"` // Sonr blockchain address
|
||||
ChainCode string `json:"chain_code"` // Deterministic chain code
|
||||
}
|
||||
|
||||
// UCANRequest represents a request to create a UCAN origin token.
|
||||
type UCANRequest struct {
|
||||
AudienceDID string `json:"audience_did"` // Target audience DID
|
||||
Capabilities []map[string]any `json:"capabilities,omitempty"` // Granted capabilities
|
||||
Facts []string `json:"facts,omitempty"` // Additional facts
|
||||
NotBefore *time.Time `json:"not_before,omitempty"` // Validity start time
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"` // Expiration time
|
||||
}
|
||||
|
||||
// AttenuatedUCANRequest represents a request to create an attenuated UCAN token.
|
||||
type AttenuatedUCANRequest struct {
|
||||
ParentToken string `json:"parent_token"` // Parent UCAN token
|
||||
AudienceDID string `json:"audience_did"` // Target audience DID
|
||||
Capabilities []map[string]any `json:"capabilities,omitempty"` // Attenuated capabilities
|
||||
Facts []string `json:"facts,omitempty"` // Additional facts
|
||||
NotBefore *time.Time `json:"not_before,omitempty"` // Validity start time
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"` // Expiration time
|
||||
}
|
||||
|
||||
// UCANToken represents a User-Controlled Authorization Network token.
|
||||
type UCANToken struct {
|
||||
Token string `json:"token"` // The UCAN JWT token
|
||||
Issuer string `json:"issuer"` // Issuer DID
|
||||
Address string `json:"address"` // Issuer address
|
||||
}
|
||||
|
||||
// Signature represents a cryptographic signature.
|
||||
type Signature struct {
|
||||
Signature []byte `json:"signature"` // Signature bytes
|
||||
Algorithm string `json:"algorithm"` // Signature algorithm used
|
||||
}
|
||||
|
||||
// keyringManager implements KeyringManager using the DWN plugin architecture.
|
||||
type keyringManager struct {
|
||||
plugin plugin.Plugin
|
||||
chainID string
|
||||
}
|
||||
|
||||
// KeyringOptions configures keyring creation for Decentralized Abstracted Smart Wallets.
|
||||
type KeyringOptions struct {
|
||||
ChainID string `json:"chain_id"`
|
||||
EnclaveData []byte `json:"enclave_data"` // JSON-encoded MPC enclave data
|
||||
VaultConfig map[string]any `json:"vault_config,omitempty"`
|
||||
UseManager bool `json:"use_manager,omitempty"` // Use plugin manager for production
|
||||
}
|
||||
|
||||
// NewKeyringManager creates a new keyring manager using the DWN plugin architecture.
|
||||
func NewKeyringManager(backend, dir, chainID string) (KeyringManager, error) {
|
||||
if chainID == "" {
|
||||
return nil, fmt.Errorf("chain ID is required")
|
||||
}
|
||||
|
||||
// For backwards compatibility, create with minimal config
|
||||
// In practice, clients should use NewKeyringManagerWithOptions
|
||||
opts := KeyringOptions{
|
||||
ChainID: chainID,
|
||||
EnclaveData: []byte(`{}`), // Minimal enclave data
|
||||
UseManager: false, // Use simple plugin loading
|
||||
}
|
||||
|
||||
return NewKeyringManagerWithOptions(opts)
|
||||
}
|
||||
|
||||
// NewKeyringManagerWithOptions creates a new keyring manager with detailed options
|
||||
// for Decentralized Abstracted Smart Wallets.
|
||||
func NewKeyringManagerWithOptions(opts KeyringOptions) (KeyringManager, error) {
|
||||
if opts.ChainID == "" {
|
||||
return nil, fmt.Errorf("chain ID is required")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
var p plugin.Plugin
|
||||
var err error
|
||||
|
||||
if opts.UseManager {
|
||||
// For production use with plugin manager, we would need to properly construct
|
||||
// the EnclaveConfig with the correct types. For now, fall back to simple loading.
|
||||
// TODO: Implement proper EnclaveConfig construction when plugin manager is ready
|
||||
p, err = plugin.LoadPluginWithEnclave(ctx, opts.ChainID, opts.EnclaveData, opts.VaultConfig)
|
||||
} else {
|
||||
// Use simple plugin loading for development
|
||||
p, err = plugin.LoadPluginWithEnclave(ctx, opts.ChainID, opts.EnclaveData, opts.VaultConfig)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.WrapError(err, errors.ErrSigningFailed, "failed to load DWN plugin for chain %s", opts.ChainID)
|
||||
}
|
||||
|
||||
return &keyringManager{
|
||||
plugin: p,
|
||||
chainID: opts.ChainID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetIssuerDID retrieves the wallet's DID identity information.
|
||||
func (km *keyringManager) GetIssuerDID(ctx context.Context) (*WalletIdentity, error) {
|
||||
resp, err := km.plugin.GetIssuerDID()
|
||||
if err != nil {
|
||||
return nil, errors.WrapError(err, errors.ErrKeyNotFound, "failed to get issuer DID from wallet")
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return nil, fmt.Errorf("plugin error: %s", resp.Error)
|
||||
}
|
||||
|
||||
return &WalletIdentity{
|
||||
DID: resp.IssuerDID,
|
||||
Address: resp.Address,
|
||||
ChainCode: resp.ChainCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAddress retrieves the wallet's blockchain address.
|
||||
func (km *keyringManager) GetAddress(ctx context.Context) (string, error) {
|
||||
identity, err := km.GetIssuerDID(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return identity.Address, nil
|
||||
}
|
||||
|
||||
// CreateOriginToken creates a new UCAN origin token for authorization.
|
||||
func (km *keyringManager) CreateOriginToken(ctx context.Context, req *UCANRequest) (*UCANToken, error) {
|
||||
// Convert to plugin request format
|
||||
pluginReq := &plugin.NewOriginTokenRequest{
|
||||
AudienceDID: req.AudienceDID,
|
||||
Attenuations: req.Capabilities,
|
||||
Facts: req.Facts,
|
||||
}
|
||||
|
||||
if req.NotBefore != nil {
|
||||
pluginReq.NotBefore = req.NotBefore.Unix()
|
||||
}
|
||||
|
||||
if req.ExpiresAt != nil {
|
||||
pluginReq.ExpiresAt = req.ExpiresAt.Unix()
|
||||
}
|
||||
|
||||
resp, err := km.plugin.NewOriginToken(pluginReq)
|
||||
if err != nil {
|
||||
return nil, errors.WrapError(err, errors.ErrSigningFailed, "failed to create origin UCAN token")
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return nil, fmt.Errorf("plugin error: %s", resp.Error)
|
||||
}
|
||||
|
||||
return &UCANToken{
|
||||
Token: resp.Token,
|
||||
Issuer: resp.Issuer,
|
||||
Address: resp.Address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAttenuatedToken creates an attenuated UCAN token by delegating from a parent token.
|
||||
func (km *keyringManager) CreateAttenuatedToken(ctx context.Context, req *AttenuatedUCANRequest) (*UCANToken, error) {
|
||||
// Convert to plugin request format
|
||||
pluginReq := &plugin.NewAttenuatedTokenRequest{
|
||||
ParentToken: req.ParentToken,
|
||||
AudienceDID: req.AudienceDID,
|
||||
Attenuations: req.Capabilities,
|
||||
Facts: req.Facts,
|
||||
}
|
||||
|
||||
if req.NotBefore != nil {
|
||||
pluginReq.NotBefore = req.NotBefore.Unix()
|
||||
}
|
||||
|
||||
if req.ExpiresAt != nil {
|
||||
pluginReq.ExpiresAt = req.ExpiresAt.Unix()
|
||||
}
|
||||
|
||||
resp, err := km.plugin.NewAttenuatedToken(pluginReq)
|
||||
if err != nil {
|
||||
return nil, errors.WrapError(err, errors.ErrSigningFailed, "failed to create attenuated UCAN token")
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return nil, fmt.Errorf("plugin error: %s", resp.Error)
|
||||
}
|
||||
|
||||
return &UCANToken{
|
||||
Token: resp.Token,
|
||||
Issuer: resp.Issuer,
|
||||
Address: resp.Address,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Sign signs arbitrary data using the MPC-based wallet.
|
||||
func (km *keyringManager) Sign(ctx context.Context, data []byte) (*Signature, error) {
|
||||
req := &plugin.SignDataRequest{
|
||||
Data: data,
|
||||
}
|
||||
|
||||
resp, err := km.plugin.SignData(req)
|
||||
if err != nil {
|
||||
return nil, errors.WrapError(err, errors.ErrSigningFailed, "failed to sign data")
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return nil, fmt.Errorf("plugin error: %s", resp.Error)
|
||||
}
|
||||
|
||||
return &Signature{
|
||||
Signature: resp.Signature,
|
||||
Algorithm: "MPC", // The plugin uses MPC-based signing
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignTransaction signs a transaction using the MPC-based wallet.
|
||||
func (km *keyringManager) SignTransaction(ctx context.Context, txBytes []byte) (*Signature, error) {
|
||||
// Transaction signing uses the same underlying data signing mechanism
|
||||
return km.Sign(ctx, txBytes)
|
||||
}
|
||||
|
||||
// Verify verifies a signature against data using the MPC-based wallet.
|
||||
func (km *keyringManager) Verify(ctx context.Context, data []byte, signature []byte) (bool, error) {
|
||||
req := &plugin.VerifyDataRequest{
|
||||
Data: data,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
resp, err := km.plugin.VerifyData(req)
|
||||
if err != nil {
|
||||
return false, errors.WrapError(err, errors.ErrSigningFailed, "failed to verify signature")
|
||||
}
|
||||
|
||||
if resp.Error != "" {
|
||||
return false, fmt.Errorf("plugin error: %s", resp.Error)
|
||||
}
|
||||
|
||||
return resp.Valid, nil
|
||||
}
|
||||
|
||||
// Plugin returns the underlying DWN plugin for advanced operations.
|
||||
func (km *keyringManager) Plugin() plugin.Plugin {
|
||||
return km.plugin
|
||||
}
|
||||
|
||||
// Close closes the keyring manager and releases resources.
|
||||
func (km *keyringManager) Close() error {
|
||||
// Note: The plugin interface doesn't currently expose a Close method
|
||||
// This is here for future compatibility and to satisfy the interface
|
||||
return nil
|
||||
}
|
||||
|
||||
// Utility functions for working with Sonr addresses and DIDs
|
||||
|
||||
// SonrBech32Prefix returns the bech32 prefix used by Sonr addresses.
|
||||
func SonrBech32Prefix() string {
|
||||
return "sonr"
|
||||
}
|
||||
|
||||
// WalletInfo provides formatted information about a Decentralized Abstracted Smart Wallet.
|
||||
type WalletInfo struct {
|
||||
DID string `json:"did"`
|
||||
Address string `json:"address"`
|
||||
ChainCode string `json:"chain_code"`
|
||||
ChainID string `json:"chain_id"`
|
||||
}
|
||||
|
||||
// GetWalletInfo returns formatted information about the wallet.
|
||||
func (km *keyringManager) GetWalletInfo(ctx context.Context) (*WalletInfo, error) {
|
||||
identity, err := km.GetIssuerDID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &WalletInfo{
|
||||
DID: identity.DID,
|
||||
Address: identity.Address,
|
||||
ChainCode: identity.ChainCode,
|
||||
ChainID: km.chainID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateDefaultUCANRequest creates a UCAN request with sensible defaults.
|
||||
func CreateDefaultUCANRequest(audienceDID string) *UCANRequest {
|
||||
// Create a token that expires in 1 hour with basic capabilities
|
||||
expiresAt := time.Now().Add(time.Hour)
|
||||
|
||||
return &UCANRequest{
|
||||
AudienceDID: audienceDID,
|
||||
Capabilities: []map[string]any{
|
||||
{
|
||||
"can": []string{"sign", "verify"},
|
||||
"with": "vault://default",
|
||||
},
|
||||
},
|
||||
ExpiresAt: &expiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTransactionUCANRequest creates a UCAN request specifically for transaction signing.
|
||||
func CreateTransactionUCANRequest(audienceDID string, txHash string) *UCANRequest {
|
||||
// Create a short-lived token specifically for transaction signing
|
||||
expiresAt := time.Now().Add(10 * time.Minute)
|
||||
|
||||
return &UCANRequest{
|
||||
AudienceDID: audienceDID,
|
||||
Capabilities: []map[string]any{
|
||||
{
|
||||
"can": []string{"sign"},
|
||||
"with": fmt.Sprintf("tx://%s", txHash),
|
||||
},
|
||||
},
|
||||
Facts: []string{
|
||||
fmt.Sprintf("transaction:%s", txHash),
|
||||
},
|
||||
ExpiresAt: &expiresAt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user