No commit suggestions generated

This commit is contained in:
Prad Nukala
2025-10-09 15:10:39 -04:00
commit a934caa7d3
323 changed files with 98121 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
package spec
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/sonr-io/sonr/crypto/mpc"
)
// MPCSigningMethod implements the SigningMethod interface for MPC-based signing
type MPCSigningMethod struct {
Name string
enclave mpc.Enclave
}
// NewJWTSigningMethod creates a new MPC signing method with the given enclave
func NewJWTSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
return &MPCSigningMethod{
Name: name,
enclave: enclave,
}
}
// WithEnclave sets the enclave for an existing signing method
func (m *MPCSigningMethod) WithEnclave(enclave mpc.Enclave) *MPCSigningMethod {
return &MPCSigningMethod{
Name: m.Name,
enclave: enclave,
}
}
// NewMPCSigningMethod is an alias for NewJWTSigningMethod for compatibility
func NewMPCSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
return NewJWTSigningMethod(name, enclave)
}
// Alg returns the signing method's name
func (m *MPCSigningMethod) Alg() string {
return m.Name
}
// Verify verifies the signature using the MPC public key
func (m *MPCSigningMethod) Verify(signingString string, signature []byte, key any) error {
// Check if enclave is available
if m.enclave == nil {
return fmt.Errorf("MPC enclave not available for signature verification")
}
// Decode the signature
sig, err := base64.RawURLEncoding.DecodeString(string(signature))
if err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}
// Hash the signing string using SHA-256
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 the data using MPC
func (m *MPCSigningMethod) Sign(signingString string, key any) ([]byte, error) {
// Check if enclave is available
if m.enclave == nil {
return nil, fmt.Errorf("MPC enclave not available for signing")
}
// Hash the signing string using SHA-256
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)
}
// Encode the signature as base64url
encoded := base64.RawURLEncoding.EncodeToString(sig)
return []byte(encoded), nil
}
func init() {
// Register the MPC signing method factory
jwt.RegisterSigningMethod("MPC256", func() jwt.SigningMethod {
// This factory creates a new instance without enclave
// The enclave will be provided when creating tokens
return &MPCSigningMethod{
Name: "MPC256",
}
})
}
// RegisterMPCMethod registers an MPC signing method for the given algorithm name
func RegisterMPCMethod(alg string) {
jwt.RegisterSigningMethod(alg, func() jwt.SigningMethod {
return &MPCSigningMethod{
Name: alg,
}
})
}
+305
View File
@@ -0,0 +1,305 @@
package spec
import (
"fmt"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/sonr-io/sonr/crypto/keys"
"github.com/sonr-io/sonr/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)
}
// NewSource creates a new MPC-based keyshare source from an enclave
func NewSource(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
}
// mpcKeyshareSource implements KeyshareSource using MPC enclave
type mpcKeyshareSource struct {
enclave mpc.Enclave
issuerDID string
addr string
}
// 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
t := jwt.New(NewJWTSigningMethod("MPC256", k.enclave))
// Set UCAN version header
t.Header[UCANVersionKey] = UCANVersion
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
}
// 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
}
// isValidDID validates DID format
func isValidDID(did string) bool {
return did != "" && len(did) > 5 && strings.HasPrefix(did, "did:")
}
// getIssuerDIDFromBytes creates an issuer DID and address from public key bytes
func getIssuerDIDFromBytes(pubKeyBytes []byte) (string, string, error) {
// Convert MPC public key bytes to libp2p crypto.PubKey
pubKey, err := crypto.UnmarshalSecp256k1PublicKey(pubKeyBytes)
if err != nil {
return "", "", fmt.Errorf("failed to unmarshal secp256k1 key: %w", err)
}
// Create DID using the crypto/keys package
did, err := keys.NewDID(pubKey)
if err != nil {
return "", "", fmt.Errorf("failed to create DID: %w", err)
}
didStr := did.String()
// Generate address from DID (simplified implementation)
address := fmt.Sprintf("addr_%x", pubKeyBytes[:8])
return didStr, address, nil
}
+125
View File
@@ -0,0 +1,125 @@
package spec
import (
"encoding/json"
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/types/bech32"
)
// Token represents a UCAN JWT token with parsed claims
type Token struct {
Raw string `json:"raw"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Attenuations []Attenuation `json:"att"`
Proofs []Proof `json:"prf,omitempty"`
Facts []Fact `json:"fct,omitempty"`
}
// Attenuation represents a UCAN capability attenuation
type Attenuation struct {
Capability Capability `json:"can"`
Resource Resource `json:"with"`
}
// Proof represents a UCAN delegation proof (either JWT or CID)
type Proof string
// Fact represents arbitrary facts in UCAN tokens
type Fact struct {
Data json.RawMessage `json:"data"`
}
// Capability defines what actions can be performed
type Capability interface {
GetActions() []string
Grants(abilities []string) bool
Contains(other Capability) bool
String() string
}
// Resource defines what resource the capability applies to
type Resource interface {
GetScheme() string
GetValue() string
GetURI() string
Matches(other Resource) bool
}
// SimpleCapability implements Capability for single actions
type SimpleCapability struct {
Action string `json:"action"`
}
func (c *SimpleCapability) GetActions() []string { return []string{c.Action} }
func (c *SimpleCapability) Grants(abilities []string) bool {
return len(abilities) == 1 && c.Action == abilities[0]
}
func (c *SimpleCapability) Contains(
other Capability,
) bool {
return c.Action == other.GetActions()[0]
}
func (c *SimpleCapability) String() string { return c.Action }
// SimpleResource implements Resource for basic URI resources
type SimpleResource struct {
Scheme string `json:"scheme"`
Value string `json:"value"`
URI string `json:"uri"`
}
func (r *SimpleResource) GetScheme() string { return r.Scheme }
func (r *SimpleResource) GetValue() string { return r.Value }
func (r *SimpleResource) GetURI() string { return r.URI }
func (r *SimpleResource) Matches(other Resource) bool { return r.URI == other.GetURI() }
// UCAN constants
const (
UCANVersion = "0.9.0"
UCANVersionKey = "ucv"
PrfKey = "prf"
FctKey = "fct"
AttKey = "att"
CapKey = "cap"
)
// CreateSimpleAttenuation creates a basic attenuation
func CreateSimpleAttenuation(action, resourceURI string) Attenuation {
return Attenuation{
Capability: &SimpleCapability{Action: action},
Resource: parseResourceURI(resourceURI),
}
}
// parseResourceURI creates a Resource from URI string
func parseResourceURI(uri string) Resource {
parts := strings.SplitN(uri, "://", 2)
if len(parts) != 2 {
return &SimpleResource{
Scheme: "unknown",
Value: uri,
URI: uri,
}
}
return &SimpleResource{
Scheme: parts[0],
Value: parts[1],
URI: uri,
}
}
// getIssuerDIDFromBytes creates an issuer DID and address from public key bytes (alternative implementation)
func getIssuerDIDFromBytesAlt(pubKeyBytes []byte) (string, string, error) {
addr, err := bech32.ConvertAndEncode("idx", pubKeyBytes)
if err != nil {
return "", "", fmt.Errorf("failed to encode address: %w", err)
}
return fmt.Sprintf("did:sonr:%s", addr), addr, nil
}