mirror of
https://github.com/sonr-io/crypto.git
synced 2026-01-12 04:09:13 +00:00
303 lines
7.6 KiB
Go
303 lines
7.6 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 (
|
|
"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
|