Feat/Add Crypto Libs #3

Merged
pn merged 35 commits from feat/add-crypto-libs into main 2026-01-10 21:59:18 +00:00
11 changed files with 487 additions and 1342 deletions
Showing only changes of commit 2dfd8b5a8f - Show all commits

View File

@@ -5,17 +5,19 @@ package main
import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strings"
"enclave/internal/crypto/bip44"
"enclave/internal/crypto/mpc"
"enclave/internal/keybase"
"enclave/internal/state"
"enclave/internal/types"
"github.com/extism/go-pdk"
"github.com/sonr-io/crypto/core/protocol"
)
func main() { state.Default() }
@@ -51,7 +53,7 @@ func ping() int32 {
//go:wasmexport generate
func generate() int32 {
pdk.Log(pdk.LogInfo, "generate: starting database initialization")
pdk.Log(pdk.LogInfo, "generate: starting database initialization with MPC")
var input types.GenerateInput
if err := pdk.InputJSON(&input); err != nil {
@@ -70,9 +72,9 @@ func generate() int32 {
return 1
}
result, err := initializeDatabase(credentialBytes, input.KeyShare)
result, err := initializeWithMPC(credentialBytes)
if err != nil {
pdk.SetError(fmt.Errorf("generate: failed to initialize database: %w", err))
pdk.SetError(fmt.Errorf("generate: failed to initialize: %w", err))
return 1
}
@@ -86,10 +88,11 @@ func generate() int32 {
}
output := types.GenerateOutput{
DID: result.DID,
Database: dbBytes,
KeyShareID: result.KeyShareID,
Account: result.Account,
DID: result.DID,
Database: dbBytes,
EnclaveID: result.EnclaveID,
PublicKey: result.PublicKey,
Accounts: result.Accounts,
}
if err := pdk.OutputJSON(output); err != nil {
@@ -97,7 +100,7 @@ func generate() int32 {
return 1
}
pdk.Log(pdk.LogInfo, fmt.Sprintf("generate: created DID %s", result.DID))
pdk.Log(pdk.LogInfo, fmt.Sprintf("generate: created DID %s with enclave %s", result.DID, result.EnclaveID))
return 0
}
@@ -244,12 +247,13 @@ func query() int32 {
}
type initResult struct {
DID string
KeyShareID string
Account *types.AccountInfo
DID string
EnclaveID string
PublicKey string
Accounts []types.AccountInfo
}
func initializeDatabase(credentialBytes []byte, keyShareInput *types.KeyShareInput) (*initResult, error) {
func initializeWithMPC(credentialBytes []byte) (*initResult, error) {
kb, err := keybase.Open()
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
@@ -261,88 +265,95 @@ func initializeDatabase(credentialBytes []byte, keyShareInput *types.KeyShareInp
return nil, fmt.Errorf("initialize: %w", err)
}
result := &initResult{DID: did}
if keyShareInput != nil {
keyShareID, account, err := createInitialKeyShare(ctx, keyShareInput)
if err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("initializeDatabase: failed to create keyshare: %s", err))
} else {
result.KeyShareID = keyShareID
result.Account = account
pdk.Log(pdk.LogInfo, fmt.Sprintf("initializeDatabase: created keyshare %s", keyShareID))
}
pdk.Log(pdk.LogInfo, "initializeWithMPC: generating MPC enclave")
enclave, err := mpc.NewEnclave()
if err != nil {
return nil, fmt.Errorf("generate MPC enclave: %w", err)
}
pdk.Log(pdk.LogDebug, "initializeDatabase: created schema and initial records")
return result, nil
}
enclaveData := enclave.GetData()
enclaveID := fmt.Sprintf("enc_%x", credentialBytes[:8])
valShareStr, err := protocol.EncodeMessage(enclaveData.ValShare)
if err != nil {
return nil, fmt.Errorf("encode val share: %w", err)
}
userShareStr, err := protocol.EncodeMessage(enclaveData.UserShare)
if err != nil {
return nil, fmt.Errorf("encode user share: %w", err)
}
func createInitialKeyShare(ctx context.Context, input *types.KeyShareInput) (string, *types.AccountInfo, error) {
am, err := keybase.NewActionManager()
if err != nil {
return "", nil, fmt.Errorf("action manager: %w", err)
return nil, fmt.Errorf("action manager: %w", err)
}
ks, err := am.CreateKeyShare(ctx, keybase.NewKeyShareInput{
KeyID: input.KeyID,
PartyIndex: input.PartyIndex,
Threshold: input.Threshold,
TotalParties: input.TotalParties,
Curve: input.Curve,
ShareData: input.ShareData,
PublicKey: input.PublicKey,
ChainCode: input.ChainCode,
DerivationPath: input.DerivationPath,
enc, err := am.CreateEnclave(ctx, keybase.NewEnclaveInput{
EnclaveID: enclaveID,
PublicKeyHex: enclaveData.PubHex,
PublicKey: enclaveData.PubBytes,
ValShare: []byte(valShareStr),
UserShare: []byte(userShareStr),
Nonce: enclaveData.Nonce,
Curve: string(enclaveData.Curve),
})
if err != nil {
return "", nil, fmt.Errorf("create keyshare: %w", err)
return nil, fmt.Errorf("store enclave: %w", err)
}
account, err := createInitialAccount(ctx, am, ks.ID, input.PublicKey)
pdk.Log(pdk.LogInfo, fmt.Sprintf("initializeWithMPC: stored enclave %s", enclaveID))
accounts, err := createDefaultAccounts(ctx, am, enc.ID, enclaveData.PubBytes)
if err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("createInitialKeyShare: failed to create account: %s", err))
return ks.ShareID, nil, nil
pdk.Log(pdk.LogWarn, fmt.Sprintf("initializeWithMPC: failed to create accounts: %s", err))
accounts = []types.AccountInfo{}
}
return ks.ShareID, account, nil
}
func createInitialAccount(ctx context.Context, am *keybase.ActionManager, keyShareID int64, publicKey string) (*types.AccountInfo, error) {
address := deriveCosmosAddress(publicKey)
if address == "" {
return nil, fmt.Errorf("failed to derive address from public key")
}
acc, err := am.CreateAccount(ctx, keybase.NewAccountInput{
KeyShareID: keyShareID,
Address: address,
ChainID: "sonr-testnet-1",
CoinType: 118,
AccountIndex: 0,
AddressIndex: 0,
Label: "Default Account",
})
if err != nil {
return nil, fmt.Errorf("create account: %w", err)
}
return &types.AccountInfo{
Address: acc.Address,
ChainID: acc.ChainID,
CoinType: acc.CoinType,
return &initResult{
DID: did,
EnclaveID: enclaveID,
PublicKey: enclaveData.PubHex,
Accounts: accounts,
}, nil
}
func deriveCosmosAddress(publicKeyHex string) string {
if publicKeyHex == "" {
return ""
func createDefaultAccounts(ctx context.Context, am *keybase.ActionManager, enclaveID int64, pubKeyBytes []byte) ([]types.AccountInfo, error) {
chains := []string{"bitcoin", "ethereum", "sonr"}
derivedAccounts, err := bip44.DeriveAccounts(pubKeyBytes, chains)
if err != nil {
return nil, fmt.Errorf("derive accounts: %w", err)
}
pubBytes, err := hex.DecodeString(publicKeyHex)
if err != nil || len(pubBytes) < 20 {
return ""
accounts := make([]types.AccountInfo, 0, len(derivedAccounts))
for i, derived := range derivedAccounts {
isDefault := int64(0)
if i == 0 {
isDefault = 1
}
acc, err := am.CreateAccount(ctx, keybase.NewAccountInput{
EnclaveID: enclaveID,
Address: derived.Address,
ChainID: derived.ChainID,
CoinType: int64(derived.CoinType),
AccountIndex: int64(derived.AccountIndex),
AddressIndex: int64(derived.AddressIndex),
Label: derived.ChainID,
IsDefault: isDefault,
})
if err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("createDefaultAccounts: failed for %s: %s", derived.ChainID, err))
continue
}
accounts = append(accounts, types.AccountInfo{
Address: acc.Address,
ChainID: acc.ChainID,
CoinType: acc.CoinType,
})
}
return fmt.Sprintf("snr1%x", pubBytes[:20])
return accounts, nil
}
func serializeDatabase() ([]byte, error) {
@@ -523,61 +534,16 @@ func matchResource(pattern, resource string) bool {
}
func executeAction(params *types.FilterParams) (json.RawMessage, error) {
switch params.Resource {
case "accounts":
return executeAccountAction(params)
case "credentials":
return executeCredentialAction(params)
case "sessions":
return executeSessionAction(params)
case "grants":
return executeGrantAction(params)
case "key_shares":
return executeKeyShareAction(params)
case "ucans":
return executeUCANAction(params)
case "delegations":
return executeDelegationAction(params)
case "verification_methods":
return executeVerificationMethodAction(params)
case "services":
return executeServiceAction(params)
default:
return nil, fmt.Errorf("unknown resource: %s", params.Resource)
}
}
func executeAccountAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
if params.Resource == "accounts" {
switch params.Action {
case "balances":
return fetchAccountBalances(params.Subject)
case "sign":
return json.Marshal(map[string]string{"signature": "placeholder"})
}
}
ctx := context.Background()
switch params.Action {
case "list":
accounts, err := am.ListAccounts(ctx)
if err != nil {
return nil, fmt.Errorf("list accounts: %w", err)
}
return json.Marshal(accounts)
case "get":
if params.Subject == "" {
return nil, errors.New("subject (address) required for get action")
}
account, err := am.GetAccountByAddress(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("get account: %w", err)
}
return json.Marshal(account)
case "balances":
return fetchAccountBalances(params.Subject)
case "sign":
return json.Marshal(map[string]string{"signature": "placeholder"})
default:
return nil, fmt.Errorf("unknown action for accounts: %s", params.Action)
}
return keybase.Exec(context.Background(), params.Resource, params.Action, params.Subject)
}
func fetchAccountBalances(address string) (json.RawMessage, error) {
@@ -614,274 +580,6 @@ func fetchAccountBalances(address string) (json.RawMessage, error) {
return body, nil
}
func executeCredentialAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
credentials, err := am.ListCredentials(ctx)
if err != nil {
return nil, fmt.Errorf("list credentials: %w", err)
}
return json.Marshal(credentials)
case "get":
if params.Subject == "" {
return nil, errors.New("subject (credential_id) required for get action")
}
credential, err := am.GetCredentialByID(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("get credential: %w", err)
}
return json.Marshal(credential)
default:
return nil, fmt.Errorf("unknown action for credentials: %s", params.Action)
}
}
func executeSessionAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
sessions, err := am.ListSessions(ctx)
if err != nil {
return nil, fmt.Errorf("list sessions: %w", err)
}
return json.Marshal(sessions)
case "revoke":
if params.Subject == "" {
return nil, errors.New("subject (session_id) required for revoke action")
}
if err := am.RevokeSession(ctx, params.Subject); err != nil {
return nil, fmt.Errorf("revoke session: %w", err)
}
return json.Marshal(map[string]bool{"revoked": true})
default:
return nil, fmt.Errorf("unknown action for sessions: %s", params.Action)
}
}
func executeGrantAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
grants, err := am.ListGrants(ctx)
if err != nil {
return nil, fmt.Errorf("list grants: %w", err)
}
return json.Marshal(grants)
case "revoke":
if params.Subject == "" {
return nil, errors.New("subject (grant_id) required for revoke action")
}
var grantID int64
if _, err := fmt.Sscanf(params.Subject, "%d", &grantID); err != nil {
return nil, fmt.Errorf("invalid grant_id: %w", err)
}
if err := am.RevokeGrant(ctx, grantID); err != nil {
return nil, fmt.Errorf("revoke grant: %w", err)
}
return json.Marshal(map[string]bool{"revoked": true})
default:
return nil, fmt.Errorf("unknown action for grants: %s", params.Action)
}
}
func executeKeyShareAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
shares, err := am.ListKeyShares(ctx)
if err != nil {
return nil, fmt.Errorf("list key shares: %w", err)
}
return json.Marshal(shares)
case "get":
if params.Subject == "" {
return nil, errors.New("subject (share_id) required for get action")
}
share, err := am.GetKeyShareByID(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("get key share: %w", err)
}
return json.Marshal(share)
case "rotate":
if params.Subject == "" {
return nil, errors.New("subject (share_id) required for rotate action")
}
if err := am.RotateKeyShare(ctx, params.Subject); err != nil {
return nil, fmt.Errorf("rotate key share: %w", err)
}
return json.Marshal(map[string]bool{"rotated": true})
case "archive":
if params.Subject == "" {
return nil, errors.New("subject (share_id) required for archive action")
}
if err := am.ArchiveKeyShare(ctx, params.Subject); err != nil {
return nil, fmt.Errorf("archive key share: %w", err)
}
return json.Marshal(map[string]bool{"archived": true})
case "delete":
if params.Subject == "" {
return nil, errors.New("subject (share_id) required for delete action")
}
if err := am.DeleteKeyShare(ctx, params.Subject); err != nil {
return nil, fmt.Errorf("delete key share: %w", err)
}
return json.Marshal(map[string]bool{"deleted": true})
default:
return nil, fmt.Errorf("unknown action for key_shares: %s", params.Action)
}
}
func executeUCANAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
delegations, err := am.ListDelegations(ctx)
if err != nil {
return nil, fmt.Errorf("list delegations: %w", err)
}
return json.Marshal(delegations)
case "get":
if params.Subject == "" {
return nil, errors.New("subject (cid) required for get action")
}
delegation, err := am.GetDelegationByCID(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("get delegation: %w", err)
}
return json.Marshal(delegation)
case "revoke":
if params.Subject == "" {
return nil, errors.New("subject (cid) required for revoke action")
}
if err := am.RevokeDelegation(ctx, keybase.RevokeDelegationParams{
DelegationCID: params.Subject,
RevokedBy: state.GetDID(),
Reason: "user revoked",
}); err != nil {
return nil, fmt.Errorf("revoke delegation: %w", err)
}
return json.Marshal(map[string]bool{"revoked": true})
case "verify":
if params.Subject == "" {
return nil, errors.New("subject (cid) required for verify action")
}
revoked, err := am.IsDelegationRevoked(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("check delegation: %w", err)
}
return json.Marshal(map[string]bool{"valid": !revoked, "revoked": revoked})
case "cleanup":
if err := am.CleanExpiredDelegations(ctx); err != nil {
return nil, fmt.Errorf("cleanup delegations: %w", err)
}
return json.Marshal(map[string]bool{"cleaned": true})
default:
return nil, fmt.Errorf("unknown action for ucans: %s", params.Action)
}
}
func executeDelegationAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
if params.Subject == "" {
return nil, errors.New("subject (issuer DID) required for list action")
}
delegations, err := am.ListDelegationsByIssuer(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("list delegations: %w", err)
}
return json.Marshal(delegations)
case "list_received":
if params.Subject == "" {
return nil, errors.New("subject (audience DID) required for list_received action")
}
delegations, err := am.ListDelegationsByAudience(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("list received delegations: %w", err)
}
return json.Marshal(delegations)
case "list_command":
if params.Subject == "" {
return nil, errors.New("subject (command) required for list_command action")
}
delegations, err := am.ListDelegationsForCommand(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("list delegations for command: %w", err)
}
return json.Marshal(delegations)
case "get":
if params.Subject == "" {
return nil, errors.New("subject (cid) required for get action")
}
delegation, err := am.GetDelegationByCID(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("get delegation: %w", err)
}
return json.Marshal(delegation)
case "revoke":
if params.Subject == "" {
return nil, errors.New("subject (cid) required for revoke action")
}
if err := am.RevokeDelegation(ctx, keybase.RevokeDelegationParams{
DelegationCID: params.Subject,
RevokedBy: state.GetDID(),
Reason: "user revoked",
}); err != nil {
return nil, fmt.Errorf("revoke delegation: %w", err)
}
return json.Marshal(map[string]bool{"revoked": true})
case "verify":
if params.Subject == "" {
return nil, errors.New("subject (cid) required for verify action")
}
revoked, err := am.IsDelegationRevoked(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("check delegation: %w", err)
}
return json.Marshal(map[string]bool{"valid": !revoked, "revoked": revoked})
default:
return nil, fmt.Errorf("unknown action for delegations: %s", params.Action)
}
}
func resolveDID(did string) (*types.QueryOutput, error) {
am, err := keybase.NewActionManager()
if err != nil {
@@ -939,82 +637,3 @@ func resolveDID(did string) (*types.QueryOutput, error) {
Credentials: credentials,
}, nil
}
func executeVerificationMethodAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
vms, err := am.ListVerificationMethodsFull(ctx)
if err != nil {
return nil, fmt.Errorf("list verification methods: %w", err)
}
return json.Marshal(vms)
case "get":
if params.Subject == "" {
return nil, errors.New("subject (method_id) required for get action")
}
vm, err := am.GetVerificationMethod(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("get verification method: %w", err)
}
return json.Marshal(vm)
case "delete":
if params.Subject == "" {
return nil, errors.New("subject (method_id) required for delete action")
}
if err := am.DeleteVerificationMethod(ctx, params.Subject); err != nil {
return nil, fmt.Errorf("delete verification method: %w", err)
}
return json.Marshal(map[string]bool{"deleted": true})
default:
return nil, fmt.Errorf("unknown action for verification_methods: %s", params.Action)
}
}
func executeServiceAction(params *types.FilterParams) (json.RawMessage, error) {
am, err := keybase.NewActionManager()
if err != nil {
return nil, fmt.Errorf("action manager: %w", err)
}
ctx := context.Background()
switch params.Action {
case "list":
services, err := am.ListVerifiedServices(ctx)
if err != nil {
return nil, fmt.Errorf("list verified services: %w", err)
}
return json.Marshal(services)
case "get":
if params.Subject == "" {
return nil, errors.New("subject (origin) required for get action")
}
svc, err := am.GetServiceByOrigin(ctx, params.Subject)
if err != nil {
return nil, fmt.Errorf("get service: %w", err)
}
return json.Marshal(svc)
case "get_by_id":
if params.Subject == "" {
return nil, errors.New("subject (service_id) required for get_by_id action")
}
var serviceID int64
if _, err := fmt.Sscanf(params.Subject, "%d", &serviceID); err != nil {
return nil, fmt.Errorf("invalid service_id: %w", err)
}
svc, err := am.GetServiceByID(ctx, serviceID)
if err != nil {
return nil, fmt.Errorf("get service by ID: %w", err)
}
return json.Marshal(svc)
default:
return nil, fmt.Errorf("unknown action for services: %s", params.Action)
}
}

View File

@@ -72,7 +72,7 @@ func (am *ActionManager) ListAccounts(ctx context.Context) ([]AccountResult, err
AddressIndex: row.AddressIndex,
Label: label,
IsDefault: row.IsDefault == 1,
PublicKey: row.SharePublicKey,
PublicKey: row.PublicKeyHex,
Curve: row.Curve,
CreatedAt: row.CreatedAt,
}
@@ -245,12 +245,19 @@ func (am *ActionManager) ListSessions(ctx context.Context) ([]SessionResult, err
authenticator = *sess.Authenticator
}
var deviceInfo json.RawMessage
if sess.DeviceInfo != nil {
deviceInfo = json.RawMessage(*sess.DeviceInfo)
} else {
deviceInfo = json.RawMessage(`{}`)
}
results[i] = SessionResult{
ID: sess.ID,
SessionID: sess.SessionID,
DeviceName: sess.DeviceName,
Authenticator: authenticator,
DeviceInfo: sess.DeviceInfo,
DeviceInfo: deviceInfo,
IsCurrent: sess.IsCurrent == 1,
LastActivity: sess.LastActivity,
ExpiresAt: sess.ExpiresAt,
@@ -386,8 +393,8 @@ func (am *ActionManager) ListGrants(ctx context.Context) ([]GrantResult, error)
ServiceName: g.ServiceName,
ServiceOrigin: g.ServiceOrigin,
ServiceLogo: serviceLogo,
Scopes: g.Scopes,
Accounts: g.Accounts,
Scopes: json.RawMessage(g.Scopes),
Accounts: json.RawMessage(g.Accounts),
Status: g.Status,
GrantedAt: g.GrantedAt,
LastUsed: lastUsed,
@@ -481,7 +488,7 @@ func (am *ActionManager) ResolveDID(ctx context.Context, did string) (*DIDDocume
AddressIndex: row.AddressIndex,
Label: label,
IsDefault: row.IsDefault == 1,
PublicKey: row.SharePublicKey,
PublicKey: row.PublicKeyHex,
Curve: row.Curve,
CreatedAt: row.CreatedAt,
}

View File

@@ -6,13 +6,14 @@ import (
)
type NewAccountInput struct {
KeyShareID int64 `json:"key_share_id"`
EnclaveID int64 `json:"enclave_id"`
Address string `json:"address"`
ChainID string `json:"chain_id"`
CoinType int64 `json:"coin_type"`
AccountIndex int64 `json:"account_index"`
AddressIndex int64 `json:"address_index"`
Label string `json:"label,omitempty"`
IsDefault int64 `json:"is_default,omitempty"`
}
func (am *ActionManager) CreateAccount(ctx context.Context, params NewAccountInput) (*AccountResult, error) {
@@ -30,7 +31,7 @@ func (am *ActionManager) CreateAccount(ctx context.Context, params NewAccountInp
acc, err := am.kb.queries.CreateAccount(ctx, CreateAccountParams{
DidID: am.kb.didID,
KeyShareID: params.KeyShareID,
EnclaveID: params.EnclaveID,
Address: params.Address,
ChainID: params.ChainID,
CoinType: params.CoinType,

View File

@@ -1,206 +0,0 @@
package keybase
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
)
type KeyShareResult struct {
ID int64 `json:"id"`
ShareID string `json:"share_id"`
KeyID string `json:"key_id"`
PartyIndex int64 `json:"party_index"`
Threshold int64 `json:"threshold"`
TotalParties int64 `json:"total_parties"`
Curve string `json:"curve"`
PublicKey string `json:"public_key"`
ChainCode string `json:"chain_code,omitempty"`
DerivationPath string `json:"derivation_path,omitempty"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
RotatedAt string `json:"rotated_at,omitempty"`
}
type NewKeyShareInput struct {
KeyID string `json:"key_id"`
PartyIndex int64 `json:"party_index"`
Threshold int64 `json:"threshold"`
TotalParties int64 `json:"total_parties"`
Curve string `json:"curve"`
ShareData string `json:"share_data"`
PublicKey string `json:"public_key"`
ChainCode string `json:"chain_code,omitempty"`
DerivationPath string `json:"derivation_path,omitempty"`
}
func (am *ActionManager) CreateKeyShare(ctx context.Context, params NewKeyShareInput) (*KeyShareResult, error) {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if am.kb.didID == 0 {
return nil, fmt.Errorf("DID not initialized")
}
shareID := generateShareID()
var chainCode, derivationPath *string
if params.ChainCode != "" {
chainCode = &params.ChainCode
}
if params.DerivationPath != "" {
derivationPath = &params.DerivationPath
}
ks, err := am.kb.queries.CreateKeyShare(ctx, CreateKeyShareParams{
DidID: am.kb.didID,
ShareID: shareID,
KeyID: params.KeyID,
PartyIndex: params.PartyIndex,
Threshold: params.Threshold,
TotalParties: params.TotalParties,
Curve: params.Curve,
ShareData: params.ShareData,
PublicKey: params.PublicKey,
ChainCode: chainCode,
DerivationPath: derivationPath,
})
if err != nil {
return nil, fmt.Errorf("create key share: %w", err)
}
return keyShareToResult(&ks), nil
}
func (am *ActionManager) ListKeyShares(ctx context.Context) ([]KeyShareResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return []KeyShareResult{}, nil
}
shares, err := am.kb.queries.ListKeySharesByDID(ctx, am.kb.didID)
if err != nil {
return nil, fmt.Errorf("list key shares: %w", err)
}
results := make([]KeyShareResult, len(shares))
for i, ks := range shares {
results[i] = *keyShareToResult(&ks)
}
return results, nil
}
func (am *ActionManager) GetKeyShareByID(ctx context.Context, shareID string) (*KeyShareResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
ks, err := am.kb.queries.GetKeyShareByID(ctx, shareID)
if err != nil {
return nil, fmt.Errorf("get key share: %w", err)
}
return keyShareToResult(&ks), nil
}
func (am *ActionManager) GetKeyShareByKeyID(ctx context.Context, keyID string) (*KeyShareResult, error) {
am.kb.mu.RLock()
defer am.kb.mu.RUnlock()
if am.kb.didID == 0 {
return nil, fmt.Errorf("DID not initialized")
}
ks, err := am.kb.queries.GetKeyShareByKeyID(ctx, GetKeyShareByKeyIDParams{
DidID: am.kb.didID,
KeyID: keyID,
})
if err != nil {
return nil, fmt.Errorf("get key share by key ID: %w", err)
}
return keyShareToResult(&ks), nil
}
func (am *ActionManager) RotateKeyShare(ctx context.Context, shareID string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
ks, err := am.kb.queries.GetKeyShareByID(ctx, shareID)
if err != nil {
return fmt.Errorf("get key share: %w", err)
}
return am.kb.queries.RotateKeyShare(ctx, ks.ID)
}
func (am *ActionManager) ArchiveKeyShare(ctx context.Context, shareID string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
ks, err := am.kb.queries.GetKeyShareByID(ctx, shareID)
if err != nil {
return fmt.Errorf("get key share: %w", err)
}
return am.kb.queries.ArchiveKeyShare(ctx, ks.ID)
}
func (am *ActionManager) DeleteKeyShare(ctx context.Context, shareID string) error {
am.kb.mu.Lock()
defer am.kb.mu.Unlock()
if am.kb.didID == 0 {
return fmt.Errorf("DID not initialized")
}
ks, err := am.kb.queries.GetKeyShareByID(ctx, shareID)
if err != nil {
return fmt.Errorf("get key share: %w", err)
}
return am.kb.queries.DeleteKeyShare(ctx, DeleteKeyShareParams{
ID: ks.ID,
DidID: am.kb.didID,
})
}
func generateShareID() string {
b := make([]byte, 16)
rand.Read(b)
return "ks_" + hex.EncodeToString(b)
}
func keyShareToResult(ks *KeyShare) *KeyShareResult {
chainCode := ""
if ks.ChainCode != nil {
chainCode = *ks.ChainCode
}
derivationPath := ""
if ks.DerivationPath != nil {
derivationPath = *ks.DerivationPath
}
rotatedAt := ""
if ks.RotatedAt != nil {
rotatedAt = *ks.RotatedAt
}
return &KeyShareResult{
ID: ks.ID,
ShareID: ks.ShareID,
KeyID: ks.KeyID,
PartyIndex: ks.PartyIndex,
Threshold: ks.Threshold,
TotalParties: ks.TotalParties,
Curve: ks.Curve,
PublicKey: ks.PublicKey,
ChainCode: chainCode,
DerivationPath: derivationPath,
Status: ks.Status,
CreatedAt: ks.CreatedAt,
RotatedAt: rotatedAt,
}
}

View File

@@ -1,268 +0,0 @@
package keybase
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"sync"
"enclave/internal/migrations"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/ext/hash"
"github.com/ncruces/go-sqlite3/ext/serdes"
"github.com/ncruces/go-sqlite3/ext/uuid"
)
// Keybase encapsulates the encrypted key storage database.
type Keybase struct {
db *sql.DB
conn *sqlite3.Conn // raw connection for serdes
queries *Queries
did string
didID int64
mu sync.RWMutex
}
var (
instance *Keybase
initMu sync.Mutex
)
// Open creates or returns the singleton Keybase instance with an in-memory database.
func Open() (*Keybase, error) {
initMu.Lock()
defer initMu.Unlock()
if instance != nil {
return instance, nil
}
var rawConn *sqlite3.Conn
initCallback := func(conn *sqlite3.Conn) error {
rawConn = conn
if err := hash.Register(conn); err != nil {
return fmt.Errorf("register hash extension: %w", err)
}
if err := uuid.Register(conn); err != nil {
return fmt.Errorf("register uuid extension: %w", err)
}
return nil
}
db, err := driver.Open(":memory:", initCallback)
if err != nil {
return nil, fmt.Errorf("keybase: open database: %w", err)
}
if _, err := db.Exec(migrations.SchemaSQL); err != nil {
db.Close()
return nil, fmt.Errorf("keybase: init schema: %w", err)
}
instance = &Keybase{
db: db,
conn: rawConn,
queries: New(db),
}
return instance, nil
}
// Get returns the existing Keybase instance or nil if not initialized.
func Get() *Keybase {
initMu.Lock()
defer initMu.Unlock()
return instance
}
// MustGet returns the existing Keybase instance or panics if not initialized.
func MustGet() *Keybase {
kb := Get()
if kb == nil {
panic("keybase: not initialized")
}
return kb
}
// Close closes the database connection and clears the singleton.
func Close() error {
initMu.Lock()
defer initMu.Unlock()
if instance == nil {
return nil
}
err := instance.db.Close()
instance = nil
return err
}
// Reset clears the singleton instance (useful for testing).
func Reset() {
initMu.Lock()
defer initMu.Unlock()
if instance != nil {
instance.db.Close()
instance = nil
}
}
// DB returns the underlying sql.DB connection.
func (k *Keybase) DB() *sql.DB {
k.mu.RLock()
defer k.mu.RUnlock()
return k.db
}
// Queries returns the SQLC-generated query interface.
func (k *Keybase) Queries() *Queries {
k.mu.RLock()
defer k.mu.RUnlock()
return k.queries
}
// DID returns the current DID identifier.
func (k *Keybase) DID() string {
k.mu.RLock()
defer k.mu.RUnlock()
return k.did
}
// DIDID returns the database ID of the current DID.
func (k *Keybase) DIDID() int64 {
k.mu.RLock()
defer k.mu.RUnlock()
return k.didID
}
// IsInitialized returns true if a DID has been set.
func (k *Keybase) IsInitialized() bool {
k.mu.RLock()
defer k.mu.RUnlock()
return k.did != ""
}
// SetDID sets the current DID context.
func (k *Keybase) SetDID(did string, didID int64) {
k.mu.Lock()
defer k.mu.Unlock()
k.did = did
k.didID = didID
}
// Initialize creates a new DID document from a WebAuthn credential.
func (k *Keybase) Initialize(ctx context.Context, credentialBytes []byte) (string, error) {
k.mu.Lock()
defer k.mu.Unlock()
did := fmt.Sprintf("did:sonr:%x", credentialBytes[:16])
docJSON, _ := json.Marshal(map[string]any{
"@context": []string{"https://www.w3.org/ns/did/v1"},
"id": did,
})
doc, err := k.queries.CreateDID(ctx, CreateDIDParams{
Did: did,
Controller: did,
Document: docJSON,
Sequence: 0,
})
if err != nil {
return "", fmt.Errorf("keybase: create DID: %w", err)
}
k.did = did
k.didID = doc.ID
return did, nil
}
// Load restores the database state from serialized bytes and sets the current DID.
func (k *Keybase) Load(ctx context.Context, data []byte) (string, error) {
if len(data) < 100 {
return "", fmt.Errorf("keybase: invalid database format")
}
k.mu.Lock()
defer k.mu.Unlock()
if k.conn == nil {
return "", fmt.Errorf("keybase: database not initialized")
}
if err := serdes.Deserialize(k.conn, "main", data); err != nil {
return "", fmt.Errorf("keybase: deserialize database: %w", err)
}
docs, err := k.queries.ListAllDIDs(ctx)
if err != nil {
return "", fmt.Errorf("keybase: list DIDs: %w", err)
}
if len(docs) == 0 {
return "", fmt.Errorf("keybase: no DID found in database")
}
k.did = docs[0].Did
k.didID = docs[0].ID
return k.did, nil
}
// Serialize exports the database state as bytes using native SQLite serialization.
func (k *Keybase) Serialize() ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
if k.conn == nil {
return nil, fmt.Errorf("keybase: database not initialized")
}
return serdes.Serialize(k.conn, "main")
}
func (k *Keybase) RestoreFromDump(data []byte) error {
k.mu.Lock()
defer k.mu.Unlock()
if k.conn == nil {
return fmt.Errorf("keybase: database not initialized")
}
if err := serdes.Deserialize(k.conn, "main", data); err != nil {
return fmt.Errorf("keybase: deserialize database: %w", err)
}
docs, err := k.queries.ListAllDIDs(context.Background())
if err != nil {
return fmt.Errorf("keybase: failed to list DIDs: %w", err)
}
if len(docs) > 0 {
k.did = docs[0].Did
k.didID = docs[0].ID
}
return nil
}
// WithTx executes a function within a database transaction.
func (k *Keybase) WithTx(ctx context.Context, fn func(*Queries) error) error {
tx, err := k.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("keybase: begin tx: %w", err)
}
if err := fn(k.queries.WithTx(tx)); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}

View File

@@ -11,7 +11,7 @@ import (
type Account struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
KeyShareID int64 `json:"key_share_id"`
EnclaveID int64 `json:"enclave_id"`
Address string `json:"address"`
ChainID string `json:"chain_id"`
CoinType int64 `json:"coin_type"`
@@ -64,22 +64,19 @@ type Grant struct {
ExpiresAt *string `json:"expires_at"`
}
type KeyShare struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
ShareID string `json:"share_id"`
KeyID string `json:"key_id"`
PartyIndex int64 `json:"party_index"`
Threshold int64 `json:"threshold"`
TotalParties int64 `json:"total_parties"`
Curve string `json:"curve"`
ShareData string `json:"share_data"`
PublicKey string `json:"public_key"`
ChainCode *string `json:"chain_code"`
DerivationPath *string `json:"derivation_path"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
RotatedAt *string `json:"rotated_at"`
type MpcEnclafe struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
EnclaveID string `json:"enclave_id"`
PublicKeyHex string `json:"public_key_hex"`
PublicKey []byte `json:"public_key"`
ValShare []byte `json:"val_share"`
UserShare []byte `json:"user_share"`
Nonce []byte `json:"nonce"`
Curve string `json:"curve"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
RotatedAt *string `json:"rotated_at"`
}
type Service struct {
@@ -158,6 +155,85 @@ type UcanRevocation struct {
RevokedAt string `json:"revoked_at"`
}
type VAccount struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
EnclaveID int64 `json:"enclave_id"`
Address string `json:"address"`
ChainID string `json:"chain_id"`
CoinType int64 `json:"coin_type"`
AccountIndex int64 `json:"account_index"`
AddressIndex int64 `json:"address_index"`
Label *string `json:"label"`
IsDefault int64 `json:"is_default"`
CreatedAt string `json:"created_at"`
PublicKeyHex string `json:"public_key_hex"`
Curve string `json:"curve"`
EnclaveRef string `json:"enclave_ref"`
}
type VActiveDelegation struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
Cid string `json:"cid"`
Envelope []byte `json:"envelope"`
Iss string `json:"iss"`
Aud string `json:"aud"`
Sub *string `json:"sub"`
Cmd string `json:"cmd"`
Pol *string `json:"pol"`
Nbf *string `json:"nbf"`
Exp *string `json:"exp"`
IsRoot int64 `json:"is_root"`
IsPowerline int64 `json:"is_powerline"`
CreatedAt string `json:"created_at"`
}
type VActiveEnclafe struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
EnclaveID string `json:"enclave_id"`
PublicKeyHex string `json:"public_key_hex"`
PublicKey []byte `json:"public_key"`
ValShare []byte `json:"val_share"`
UserShare []byte `json:"user_share"`
Nonce []byte `json:"nonce"`
Curve string `json:"curve"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
RotatedAt *string `json:"rotated_at"`
}
type VGrant struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
ServiceID int64 `json:"service_id"`
DelegationCid *string `json:"delegation_cid"`
Scopes string `json:"scopes"`
Accounts string `json:"accounts"`
Status string `json:"status"`
GrantedAt string `json:"granted_at"`
LastUsed *string `json:"last_used"`
ExpiresAt *string `json:"expires_at"`
ServiceName string `json:"service_name"`
ServiceOrigin string `json:"service_origin"`
ServiceLogo *string `json:"service_logo"`
}
type VSession struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
CredentialID int64 `json:"credential_id"`
SessionID string `json:"session_id"`
DeviceInfo *string `json:"device_info"`
IsCurrent int64 `json:"is_current"`
LastActivity string `json:"last_activity"`
ExpiresAt string `json:"expires_at"`
CreatedAt string `json:"created_at"`
DeviceName string `json:"device_name"`
Authenticator *string `json:"authenticator"`
}
type VerificationMethod struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`

View File

@@ -9,7 +9,7 @@ import (
)
type Querier interface {
ArchiveKeyShare(ctx context.Context, id int64) error
ArchiveEnclave(ctx context.Context, id int64) error
CleanExpiredDelegations(ctx context.Context) error
CleanOldInvocations(ctx context.Context) error
CountActiveGrants(ctx context.Context, didID int64) (int64, error)
@@ -18,9 +18,9 @@ type Querier interface {
CreateCredential(ctx context.Context, arg CreateCredentialParams) (Credential, error)
CreateDID(ctx context.Context, arg CreateDIDParams) (DidDocument, error)
CreateDelegation(ctx context.Context, arg CreateDelegationParams) (UcanDelegation, error)
CreateEnclave(ctx context.Context, arg CreateEnclaveParams) (MpcEnclafe, error)
CreateGrant(ctx context.Context, arg CreateGrantParams) (Grant, error)
CreateInvocation(ctx context.Context, arg CreateInvocationParams) (UcanInvocation, error)
CreateKeyShare(ctx context.Context, arg CreateKeyShareParams) (KeyShare, error)
// =============================================================================
// UCAN REVOCATION QUERIES
// =============================================================================
@@ -31,8 +31,8 @@ type Querier interface {
DeleteAccount(ctx context.Context, arg DeleteAccountParams) error
DeleteCredential(ctx context.Context, arg DeleteCredentialParams) error
DeleteDelegation(ctx context.Context, arg DeleteDelegationParams) error
DeleteEnclave(ctx context.Context, arg DeleteEnclaveParams) error
DeleteExpiredSessions(ctx context.Context) error
DeleteKeyShare(ctx context.Context, arg DeleteKeyShareParams) error
DeleteSession(ctx context.Context, id int64) error
DeleteVerificationMethod(ctx context.Context, id int64) error
GetAccountByAddress(ctx context.Context, address string) (Account, error)
@@ -49,14 +49,14 @@ type Querier interface {
// =============================================================================
GetDelegationByCID(ctx context.Context, cid string) (UcanDelegation, error)
GetDelegationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error)
GetEnclaveByID(ctx context.Context, enclaveID string) (MpcEnclafe, error)
GetEnclaveByPubKeyHex(ctx context.Context, publicKeyHex string) (MpcEnclafe, error)
GetGrantByService(ctx context.Context, arg GetGrantByServiceParams) (Grant, error)
// =============================================================================
// UCAN INVOCATION QUERIES (v1.0.0-rc.1)
// =============================================================================
GetInvocationByCID(ctx context.Context, cid string) (UcanInvocation, error)
GetInvocationEnvelopeByCID(ctx context.Context, cid string) ([]byte, error)
GetKeyShareByID(ctx context.Context, shareID string) (KeyShare, error)
GetKeyShareByKeyID(ctx context.Context, arg GetKeyShareByKeyIDParams) (KeyShare, error)
GetRevocation(ctx context.Context, delegationCid string) (UcanRevocation, error)
GetServiceByID(ctx context.Context, id int64) (Service, error)
// =============================================================================
@@ -74,7 +74,7 @@ type Querier interface {
// =============================================================================
// ACCOUNT QUERIES
// =============================================================================
ListAccountsByDID(ctx context.Context, didID int64) ([]ListAccountsByDIDRow, error)
ListAccountsByDID(ctx context.Context, didID int64) ([]VAccount, error)
ListAllDIDs(ctx context.Context) ([]DidDocument, error)
// =============================================================================
// CREDENTIAL QUERIES
@@ -86,17 +86,17 @@ type Querier interface {
ListDelegationsBySubject(ctx context.Context, sub *string) ([]UcanDelegation, error)
ListDelegationsForCommand(ctx context.Context, arg ListDelegationsForCommandParams) ([]UcanDelegation, error)
// =============================================================================
// MPC ENCLAVE QUERIES
// =============================================================================
ListEnclavesByDID(ctx context.Context, didID int64) ([]MpcEnclafe, error)
// =============================================================================
// GRANT QUERIES
// =============================================================================
ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrantsByDIDRow, error)
ListGrantsByDID(ctx context.Context, didID int64) ([]VGrant, error)
ListInvocationsByDID(ctx context.Context, arg ListInvocationsByDIDParams) ([]UcanInvocation, error)
ListInvocationsByIssuer(ctx context.Context, arg ListInvocationsByIssuerParams) ([]UcanInvocation, error)
ListInvocationsBySubject(ctx context.Context, arg ListInvocationsBySubjectParams) ([]UcanInvocation, error)
ListInvocationsForCommand(ctx context.Context, arg ListInvocationsForCommandParams) ([]UcanInvocation, error)
// =============================================================================
// KEY SHARE QUERIES
// =============================================================================
ListKeySharesByDID(ctx context.Context, didID int64) ([]KeyShare, error)
ListPendingInvocations(ctx context.Context, didID int64) ([]UcanInvocation, error)
ListPowerlineDelegations(ctx context.Context, didID int64) ([]UcanDelegation, error)
ListRevocationsByRevoker(ctx context.Context, revokedBy string) ([]UcanRevocation, error)
@@ -104,7 +104,7 @@ type Querier interface {
// =============================================================================
// SESSION QUERIES
// =============================================================================
ListSessionsByDID(ctx context.Context, didID int64) ([]ListSessionsByDIDRow, error)
ListSessionsByDID(ctx context.Context, didID int64) ([]VSession, error)
ListSyncCheckpoints(ctx context.Context, didID int64) ([]SyncCheckpoint, error)
// =============================================================================
// VERIFICATION METHOD QUERIES
@@ -115,7 +115,7 @@ type Querier interface {
ReactivateGrant(ctx context.Context, id int64) error
RenameCredential(ctx context.Context, arg RenameCredentialParams) error
RevokeGrant(ctx context.Context, id int64) error
RotateKeyShare(ctx context.Context, id int64) error
RotateEnclave(ctx context.Context, id int64) error
SetCurrentSession(ctx context.Context, arg SetCurrentSessionParams) error
SetDefaultAccount(ctx context.Context, arg SetDefaultAccountParams) error
SuspendGrant(ctx context.Context, id int64) error

View File

@@ -10,12 +10,12 @@ import (
"encoding/json"
)
const archiveKeyShare = `-- name: ArchiveKeyShare :exec
UPDATE key_shares SET status = 'archived' WHERE id = ?
const archiveEnclave = `-- name: ArchiveEnclave :exec
UPDATE mpc_enclaves SET status = 'archived' WHERE id = ?
`
func (q *Queries) ArchiveKeyShare(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, archiveKeyShare, id)
func (q *Queries) ArchiveEnclave(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, archiveEnclave, id)
return err
}
@@ -60,14 +60,14 @@ func (q *Queries) CountCredentialsByDID(ctx context.Context, didID int64) (int64
}
const createAccount = `-- name: CreateAccount :one
INSERT INTO accounts (did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label)
INSERT INTO accounts (did_id, enclave_id, address, chain_id, coin_type, account_index, address_index, label)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at
RETURNING id, did_id, enclave_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at
`
type CreateAccountParams struct {
DidID int64 `json:"did_id"`
KeyShareID int64 `json:"key_share_id"`
EnclaveID int64 `json:"enclave_id"`
Address string `json:"address"`
ChainID string `json:"chain_id"`
CoinType int64 `json:"coin_type"`
@@ -79,7 +79,7 @@ type CreateAccountParams struct {
func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) {
row := q.db.QueryRowContext(ctx, createAccount,
arg.DidID,
arg.KeyShareID,
arg.EnclaveID,
arg.Address,
arg.ChainID,
arg.CoinType,
@@ -91,7 +91,7 @@ func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (A
err := row.Scan(
&i.ID,
&i.DidID,
&i.KeyShareID,
&i.EnclaveID,
&i.Address,
&i.ChainID,
&i.CoinType,
@@ -254,6 +254,54 @@ func (q *Queries) CreateDelegation(ctx context.Context, arg CreateDelegationPara
return i, err
}
const createEnclave = `-- name: CreateEnclave :one
INSERT INTO mpc_enclaves (
did_id, enclave_id, public_key_hex, public_key, val_share, user_share, nonce, curve
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, did_id, enclave_id, public_key_hex, public_key, val_share, user_share, nonce, curve, status, created_at, rotated_at
`
type CreateEnclaveParams struct {
DidID int64 `json:"did_id"`
EnclaveID string `json:"enclave_id"`
PublicKeyHex string `json:"public_key_hex"`
PublicKey []byte `json:"public_key"`
ValShare []byte `json:"val_share"`
UserShare []byte `json:"user_share"`
Nonce []byte `json:"nonce"`
Curve string `json:"curve"`
}
func (q *Queries) CreateEnclave(ctx context.Context, arg CreateEnclaveParams) (MpcEnclafe, error) {
row := q.db.QueryRowContext(ctx, createEnclave,
arg.DidID,
arg.EnclaveID,
arg.PublicKeyHex,
arg.PublicKey,
arg.ValShare,
arg.UserShare,
arg.Nonce,
arg.Curve,
)
var i MpcEnclafe
err := row.Scan(
&i.ID,
&i.DidID,
&i.EnclaveID,
&i.PublicKeyHex,
&i.PublicKey,
&i.ValShare,
&i.UserShare,
&i.Nonce,
&i.Curve,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
)
return i, err
}
const createGrant = `-- name: CreateGrant :one
INSERT INTO grants (did_id, service_id, delegation_cid, scopes, accounts, expires_at)
VALUES (?, ?, ?, ?, ?, ?)
@@ -348,64 +396,6 @@ func (q *Queries) CreateInvocation(ctx context.Context, arg CreateInvocationPara
return i, err
}
const createKeyShare = `-- name: CreateKeyShare :one
INSERT INTO key_shares (
did_id, share_id, key_id, party_index, threshold, total_parties,
curve, share_data, public_key, chain_code, derivation_path
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at
`
type CreateKeyShareParams struct {
DidID int64 `json:"did_id"`
ShareID string `json:"share_id"`
KeyID string `json:"key_id"`
PartyIndex int64 `json:"party_index"`
Threshold int64 `json:"threshold"`
TotalParties int64 `json:"total_parties"`
Curve string `json:"curve"`
ShareData string `json:"share_data"`
PublicKey string `json:"public_key"`
ChainCode *string `json:"chain_code"`
DerivationPath *string `json:"derivation_path"`
}
func (q *Queries) CreateKeyShare(ctx context.Context, arg CreateKeyShareParams) (KeyShare, error) {
row := q.db.QueryRowContext(ctx, createKeyShare,
arg.DidID,
arg.ShareID,
arg.KeyID,
arg.PartyIndex,
arg.Threshold,
arg.TotalParties,
arg.Curve,
arg.ShareData,
arg.PublicKey,
arg.ChainCode,
arg.DerivationPath,
)
var i KeyShare
err := row.Scan(
&i.ID,
&i.DidID,
&i.ShareID,
&i.KeyID,
&i.PartyIndex,
&i.Threshold,
&i.TotalParties,
&i.Curve,
&i.ShareData,
&i.PublicKey,
&i.ChainCode,
&i.DerivationPath,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
)
return i, err
}
const createRevocation = `-- name: CreateRevocation :exec
INSERT INTO ucan_revocations (delegation_cid, revoked_by, invocation_cid, reason)
@@ -592,6 +582,20 @@ func (q *Queries) DeleteDelegation(ctx context.Context, arg DeleteDelegationPara
return err
}
const deleteEnclave = `-- name: DeleteEnclave :exec
DELETE FROM mpc_enclaves WHERE id = ? AND did_id = ?
`
type DeleteEnclaveParams struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
}
func (q *Queries) DeleteEnclave(ctx context.Context, arg DeleteEnclaveParams) error {
_, err := q.db.ExecContext(ctx, deleteEnclave, arg.ID, arg.DidID)
return err
}
const deleteExpiredSessions = `-- name: DeleteExpiredSessions :exec
DELETE FROM sessions WHERE expires_at < datetime('now')
`
@@ -601,20 +605,6 @@ func (q *Queries) DeleteExpiredSessions(ctx context.Context) error {
return err
}
const deleteKeyShare = `-- name: DeleteKeyShare :exec
DELETE FROM key_shares WHERE id = ? AND did_id = ?
`
type DeleteKeyShareParams struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
}
func (q *Queries) DeleteKeyShare(ctx context.Context, arg DeleteKeyShareParams) error {
_, err := q.db.ExecContext(ctx, deleteKeyShare, arg.ID, arg.DidID)
return err
}
const deleteSession = `-- name: DeleteSession :exec
DELETE FROM sessions WHERE id = ?
`
@@ -634,7 +624,7 @@ func (q *Queries) DeleteVerificationMethod(ctx context.Context, id int64) error
}
const getAccountByAddress = `-- name: GetAccountByAddress :one
SELECT id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE address = ? LIMIT 1
SELECT id, did_id, enclave_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE address = ? LIMIT 1
`
func (q *Queries) GetAccountByAddress(ctx context.Context, address string) (Account, error) {
@@ -643,7 +633,7 @@ func (q *Queries) GetAccountByAddress(ctx context.Context, address string) (Acco
err := row.Scan(
&i.ID,
&i.DidID,
&i.KeyShareID,
&i.EnclaveID,
&i.Address,
&i.ChainID,
&i.CoinType,
@@ -749,7 +739,7 @@ func (q *Queries) GetDIDByID(ctx context.Context, id int64) (DidDocument, error)
}
const getDefaultAccount = `-- name: GetDefaultAccount :one
SELECT id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE did_id = ? AND chain_id = ? AND is_default = 1 LIMIT 1
SELECT id, did_id, enclave_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE did_id = ? AND chain_id = ? AND is_default = 1 LIMIT 1
`
type GetDefaultAccountParams struct {
@@ -763,7 +753,7 @@ func (q *Queries) GetDefaultAccount(ctx context.Context, arg GetDefaultAccountPa
err := row.Scan(
&i.ID,
&i.DidID,
&i.KeyShareID,
&i.EnclaveID,
&i.Address,
&i.ChainID,
&i.CoinType,
@@ -817,6 +807,54 @@ func (q *Queries) GetDelegationEnvelopeByCID(ctx context.Context, cid string) ([
return envelope, err
}
const getEnclaveByID = `-- name: GetEnclaveByID :one
SELECT id, did_id, enclave_id, public_key_hex, public_key, val_share, user_share, nonce, curve, status, created_at, rotated_at FROM mpc_enclaves WHERE enclave_id = ? LIMIT 1
`
func (q *Queries) GetEnclaveByID(ctx context.Context, enclaveID string) (MpcEnclafe, error) {
row := q.db.QueryRowContext(ctx, getEnclaveByID, enclaveID)
var i MpcEnclafe
err := row.Scan(
&i.ID,
&i.DidID,
&i.EnclaveID,
&i.PublicKeyHex,
&i.PublicKey,
&i.ValShare,
&i.UserShare,
&i.Nonce,
&i.Curve,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
)
return i, err
}
const getEnclaveByPubKeyHex = `-- name: GetEnclaveByPubKeyHex :one
SELECT id, did_id, enclave_id, public_key_hex, public_key, val_share, user_share, nonce, curve, status, created_at, rotated_at FROM mpc_enclaves WHERE public_key_hex = ? LIMIT 1
`
func (q *Queries) GetEnclaveByPubKeyHex(ctx context.Context, publicKeyHex string) (MpcEnclafe, error) {
row := q.db.QueryRowContext(ctx, getEnclaveByPubKeyHex, publicKeyHex)
var i MpcEnclafe
err := row.Scan(
&i.ID,
&i.DidID,
&i.EnclaveID,
&i.PublicKeyHex,
&i.PublicKey,
&i.ValShare,
&i.UserShare,
&i.Nonce,
&i.Curve,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
)
return i, err
}
const getGrantByService = `-- name: GetGrantByService :one
SELECT id, did_id, service_id, delegation_cid, scopes, accounts, status, granted_at, last_used, expires_at FROM grants WHERE did_id = ? AND service_id = ? LIMIT 1
`
@@ -885,65 +923,6 @@ func (q *Queries) GetInvocationEnvelopeByCID(ctx context.Context, cid string) ([
return envelope, err
}
const getKeyShareByID = `-- name: GetKeyShareByID :one
SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE share_id = ? LIMIT 1
`
func (q *Queries) GetKeyShareByID(ctx context.Context, shareID string) (KeyShare, error) {
row := q.db.QueryRowContext(ctx, getKeyShareByID, shareID)
var i KeyShare
err := row.Scan(
&i.ID,
&i.DidID,
&i.ShareID,
&i.KeyID,
&i.PartyIndex,
&i.Threshold,
&i.TotalParties,
&i.Curve,
&i.ShareData,
&i.PublicKey,
&i.ChainCode,
&i.DerivationPath,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
)
return i, err
}
const getKeyShareByKeyID = `-- name: GetKeyShareByKeyID :one
SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE did_id = ? AND key_id = ? AND status = 'active' LIMIT 1
`
type GetKeyShareByKeyIDParams struct {
DidID int64 `json:"did_id"`
KeyID string `json:"key_id"`
}
func (q *Queries) GetKeyShareByKeyID(ctx context.Context, arg GetKeyShareByKeyIDParams) (KeyShare, error) {
row := q.db.QueryRowContext(ctx, getKeyShareByKeyID, arg.DidID, arg.KeyID)
var i KeyShare
err := row.Scan(
&i.ID,
&i.DidID,
&i.ShareID,
&i.KeyID,
&i.PartyIndex,
&i.Threshold,
&i.TotalParties,
&i.Curve,
&i.ShareData,
&i.PublicKey,
&i.ChainCode,
&i.DerivationPath,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
)
return i, err
}
const getRevocation = `-- name: GetRevocation :one
SELECT id, delegation_cid, revoked_by, invocation_cid, reason, revoked_at FROM ucan_revocations WHERE delegation_cid = ? LIMIT 1
`
@@ -1093,7 +1072,7 @@ func (q *Queries) IsDelegationRevoked(ctx context.Context, delegationCid string)
}
const listAccountsByChain = `-- name: ListAccountsByChain :many
SELECT id, did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE did_id = ? AND chain_id = ? ORDER BY account_index, address_index
SELECT id, did_id, enclave_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at FROM accounts WHERE did_id = ? AND chain_id = ? ORDER BY account_index, address_index
`
type ListAccountsByChainParams struct {
@@ -1113,7 +1092,7 @@ func (q *Queries) ListAccountsByChain(ctx context.Context, arg ListAccountsByCha
if err := rows.Scan(
&i.ID,
&i.DidID,
&i.KeyShareID,
&i.EnclaveID,
&i.Address,
&i.ChainID,
&i.CoinType,
@@ -1138,45 +1117,25 @@ func (q *Queries) ListAccountsByChain(ctx context.Context, arg ListAccountsByCha
const listAccountsByDID = `-- name: ListAccountsByDID :many
SELECT a.id, a.did_id, a.key_share_id, a.address, a.chain_id, a.coin_type, a.account_index, a.address_index, a.label, a.is_default, a.created_at, k.public_key as share_public_key, k.curve
FROM accounts a
JOIN key_shares k ON a.key_share_id = k.id
WHERE a.did_id = ?
ORDER BY a.is_default DESC, a.created_at
SELECT id, did_id, enclave_id, address, chain_id, coin_type, account_index, address_index, label, is_default, created_at, public_key_hex, curve, enclave_ref FROM v_accounts WHERE did_id = ? ORDER BY is_default DESC, created_at
`
type ListAccountsByDIDRow struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
KeyShareID int64 `json:"key_share_id"`
Address string `json:"address"`
ChainID string `json:"chain_id"`
CoinType int64 `json:"coin_type"`
AccountIndex int64 `json:"account_index"`
AddressIndex int64 `json:"address_index"`
Label *string `json:"label"`
IsDefault int64 `json:"is_default"`
CreatedAt string `json:"created_at"`
SharePublicKey string `json:"share_public_key"`
Curve string `json:"curve"`
}
// =============================================================================
// ACCOUNT QUERIES
// =============================================================================
func (q *Queries) ListAccountsByDID(ctx context.Context, didID int64) ([]ListAccountsByDIDRow, error) {
func (q *Queries) ListAccountsByDID(ctx context.Context, didID int64) ([]VAccount, error) {
rows, err := q.db.QueryContext(ctx, listAccountsByDID, didID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAccountsByDIDRow{}
items := []VAccount{}
for rows.Next() {
var i ListAccountsByDIDRow
var i VAccount
if err := rows.Scan(
&i.ID,
&i.DidID,
&i.KeyShareID,
&i.EnclaveID,
&i.Address,
&i.ChainID,
&i.CoinType,
@@ -1185,8 +1144,9 @@ func (q *Queries) ListAccountsByDID(ctx context.Context, didID int64) ([]ListAcc
&i.Label,
&i.IsDefault,
&i.CreatedAt,
&i.SharePublicKey,
&i.PublicKeyHex,
&i.Curve,
&i.EnclaveRef,
); err != nil {
return nil, err
}
@@ -1512,43 +1472,67 @@ func (q *Queries) ListDelegationsForCommand(ctx context.Context, arg ListDelegat
return items, nil
}
const listGrantsByDID = `-- name: ListGrantsByDID :many
const listEnclavesByDID = `-- name: ListEnclavesByDID :many
SELECT g.id, g.did_id, g.service_id, g.delegation_cid, g.scopes, g.accounts, g.status, g.granted_at, g.last_used, g.expires_at, s.name as service_name, s.origin as service_origin, s.logo_url as service_logo
FROM grants g
JOIN services s ON g.service_id = s.id
WHERE g.did_id = ? AND g.status = 'active'
ORDER BY g.last_used DESC NULLS LAST
SELECT id, did_id, enclave_id, public_key_hex, public_key, val_share, user_share, nonce, curve, status, created_at, rotated_at FROM mpc_enclaves WHERE did_id = ? AND status = 'active' ORDER BY created_at
`
type ListGrantsByDIDRow struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
ServiceID int64 `json:"service_id"`
DelegationCid *string `json:"delegation_cid"`
Scopes json.RawMessage `json:"scopes"`
Accounts json.RawMessage `json:"accounts"`
Status string `json:"status"`
GrantedAt string `json:"granted_at"`
LastUsed *string `json:"last_used"`
ExpiresAt *string `json:"expires_at"`
ServiceName string `json:"service_name"`
ServiceOrigin string `json:"service_origin"`
ServiceLogo *string `json:"service_logo"`
// =============================================================================
// MPC ENCLAVE QUERIES
// =============================================================================
func (q *Queries) ListEnclavesByDID(ctx context.Context, didID int64) ([]MpcEnclafe, error) {
rows, err := q.db.QueryContext(ctx, listEnclavesByDID, didID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []MpcEnclafe{}
for rows.Next() {
var i MpcEnclafe
if err := rows.Scan(
&i.ID,
&i.DidID,
&i.EnclaveID,
&i.PublicKeyHex,
&i.PublicKey,
&i.ValShare,
&i.UserShare,
&i.Nonce,
&i.Curve,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listGrantsByDID = `-- name: ListGrantsByDID :many
SELECT id, did_id, service_id, delegation_cid, scopes, accounts, status, granted_at, last_used, expires_at, service_name, service_origin, service_logo FROM v_grants WHERE did_id = ? AND status = 'active' ORDER BY last_used DESC NULLS LAST
`
// =============================================================================
// GRANT QUERIES
// =============================================================================
func (q *Queries) ListGrantsByDID(ctx context.Context, didID int64) ([]ListGrantsByDIDRow, error) {
func (q *Queries) ListGrantsByDID(ctx context.Context, didID int64) ([]VGrant, error) {
rows, err := q.db.QueryContext(ctx, listGrantsByDID, didID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListGrantsByDIDRow{}
items := []VGrant{}
for rows.Next() {
var i ListGrantsByDIDRow
var i VGrant
if err := rows.Scan(
&i.ID,
&i.DidID,
@@ -1778,53 +1762,6 @@ func (q *Queries) ListInvocationsForCommand(ctx context.Context, arg ListInvocat
return items, nil
}
const listKeySharesByDID = `-- name: ListKeySharesByDID :many
SELECT id, did_id, share_id, key_id, party_index, threshold, total_parties, curve, share_data, public_key, chain_code, derivation_path, status, created_at, rotated_at FROM key_shares WHERE did_id = ? AND status = 'active' ORDER BY created_at
`
// =============================================================================
// KEY SHARE QUERIES
// =============================================================================
func (q *Queries) ListKeySharesByDID(ctx context.Context, didID int64) ([]KeyShare, error) {
rows, err := q.db.QueryContext(ctx, listKeySharesByDID, didID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []KeyShare{}
for rows.Next() {
var i KeyShare
if err := rows.Scan(
&i.ID,
&i.DidID,
&i.ShareID,
&i.KeyID,
&i.PartyIndex,
&i.Threshold,
&i.TotalParties,
&i.Curve,
&i.ShareData,
&i.PublicKey,
&i.ChainCode,
&i.DerivationPath,
&i.Status,
&i.CreatedAt,
&i.RotatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listPendingInvocations = `-- name: ListPendingInvocations :many
SELECT id, did_id, cid, envelope, iss, sub, aud, cmd, prf, exp, iat, executed_at, result_cid, created_at FROM ucan_invocations
WHERE did_id = ? AND executed_at IS NULL AND (exp IS NULL OR exp > datetime('now'))
@@ -1995,39 +1932,21 @@ func (q *Queries) ListRootDelegations(ctx context.Context, didID int64) ([]UcanD
const listSessionsByDID = `-- name: ListSessionsByDID :many
SELECT s.id, s.did_id, s.credential_id, s.session_id, s.device_info, s.is_current, s.last_activity, s.expires_at, s.created_at, c.device_name, c.authenticator
FROM sessions s
JOIN credentials c ON s.credential_id = c.id
WHERE s.did_id = ? AND s.expires_at > datetime('now')
ORDER BY s.last_activity DESC
SELECT id, did_id, credential_id, session_id, device_info, is_current, last_activity, expires_at, created_at, device_name, authenticator FROM v_sessions WHERE did_id = ? ORDER BY last_activity DESC
`
type ListSessionsByDIDRow struct {
ID int64 `json:"id"`
DidID int64 `json:"did_id"`
CredentialID int64 `json:"credential_id"`
SessionID string `json:"session_id"`
DeviceInfo json.RawMessage `json:"device_info"`
IsCurrent int64 `json:"is_current"`
LastActivity string `json:"last_activity"`
ExpiresAt string `json:"expires_at"`
CreatedAt string `json:"created_at"`
DeviceName string `json:"device_name"`
Authenticator *string `json:"authenticator"`
}
// =============================================================================
// SESSION QUERIES
// =============================================================================
func (q *Queries) ListSessionsByDID(ctx context.Context, didID int64) ([]ListSessionsByDIDRow, error) {
func (q *Queries) ListSessionsByDID(ctx context.Context, didID int64) ([]VSession, error) {
rows, err := q.db.QueryContext(ctx, listSessionsByDID, didID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListSessionsByDIDRow{}
items := []VSession{}
for rows.Next() {
var i ListSessionsByDIDRow
var i VSession
if err := rows.Scan(
&i.ID,
&i.DidID,
@@ -2213,14 +2132,14 @@ func (q *Queries) RevokeGrant(ctx context.Context, id int64) error {
return err
}
const rotateKeyShare = `-- name: RotateKeyShare :exec
UPDATE key_shares
const rotateEnclave = `-- name: RotateEnclave :exec
UPDATE mpc_enclaves
SET status = 'rotating', rotated_at = datetime('now')
WHERE id = ?
`
func (q *Queries) RotateKeyShare(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, rotateKeyShare, id)
func (q *Queries) RotateEnclave(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, rotateEnclave, id)
return err
}

View File

@@ -72,47 +72,42 @@ DELETE FROM credentials WHERE id = ? AND did_id = ?;
SELECT COUNT(*) FROM credentials WHERE did_id = ?;
-- =============================================================================
-- KEY SHARE QUERIES
-- MPC ENCLAVE QUERIES
-- =============================================================================
-- name: ListKeySharesByDID :many
SELECT * FROM key_shares WHERE did_id = ? AND status = 'active' ORDER BY created_at;
-- name: ListEnclavesByDID :many
SELECT * FROM mpc_enclaves WHERE did_id = ? AND status = 'active' ORDER BY created_at;
-- name: GetKeyShareByID :one
SELECT * FROM key_shares WHERE share_id = ? LIMIT 1;
-- name: GetEnclaveByID :one
SELECT * FROM mpc_enclaves WHERE enclave_id = ? LIMIT 1;
-- name: GetKeyShareByKeyID :one
SELECT * FROM key_shares WHERE did_id = ? AND key_id = ? AND status = 'active' LIMIT 1;
-- name: GetEnclaveByPubKeyHex :one
SELECT * FROM mpc_enclaves WHERE public_key_hex = ? LIMIT 1;
-- name: CreateKeyShare :one
INSERT INTO key_shares (
did_id, share_id, key_id, party_index, threshold, total_parties,
curve, share_data, public_key, chain_code, derivation_path
-- name: CreateEnclave :one
INSERT INTO mpc_enclaves (
did_id, enclave_id, public_key_hex, public_key, val_share, user_share, nonce, curve
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *;
-- name: RotateKeyShare :exec
UPDATE key_shares
-- name: RotateEnclave :exec
UPDATE mpc_enclaves
SET status = 'rotating', rotated_at = datetime('now')
WHERE id = ?;
-- name: ArchiveKeyShare :exec
UPDATE key_shares SET status = 'archived' WHERE id = ?;
-- name: ArchiveEnclave :exec
UPDATE mpc_enclaves SET status = 'archived' WHERE id = ?;
-- name: DeleteKeyShare :exec
DELETE FROM key_shares WHERE id = ? AND did_id = ?;
-- name: DeleteEnclave :exec
DELETE FROM mpc_enclaves WHERE id = ? AND did_id = ?;
-- =============================================================================
-- ACCOUNT QUERIES
-- =============================================================================
-- name: ListAccountsByDID :many
SELECT a.*, k.public_key as share_public_key, k.curve
FROM accounts a
JOIN key_shares k ON a.key_share_id = k.id
WHERE a.did_id = ?
ORDER BY a.is_default DESC, a.created_at;
SELECT * FROM v_accounts WHERE did_id = ? ORDER BY is_default DESC, created_at;
-- name: ListAccountsByChain :many
SELECT * FROM accounts WHERE did_id = ? AND chain_id = ? ORDER BY account_index, address_index;
@@ -124,7 +119,7 @@ SELECT * FROM accounts WHERE address = ? LIMIT 1;
SELECT * FROM accounts WHERE did_id = ? AND chain_id = ? AND is_default = 1 LIMIT 1;
-- name: CreateAccount :one
INSERT INTO accounts (did_id, key_share_id, address, chain_id, coin_type, account_index, address_index, label)
INSERT INTO accounts (did_id, enclave_id, address, chain_id, coin_type, account_index, address_index, label)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *;
@@ -277,11 +272,7 @@ ORDER BY revoked_at DESC;
-- =============================================================================
-- name: ListSessionsByDID :many
SELECT s.*, c.device_name, c.authenticator
FROM sessions s
JOIN credentials c ON s.credential_id = c.id
WHERE s.did_id = ? AND s.expires_at > datetime('now')
ORDER BY s.last_activity DESC;
SELECT * FROM v_sessions WHERE did_id = ? ORDER BY last_activity DESC;
-- name: GetSessionByID :one
SELECT * FROM sessions WHERE session_id = ? LIMIT 1;
@@ -336,11 +327,7 @@ SELECT * FROM services WHERE is_verified = 1 ORDER BY name;
-- =============================================================================
-- name: ListGrantsByDID :many
SELECT g.*, s.name as service_name, s.origin as service_origin, s.logo_url as service_logo
FROM grants g
JOIN services s ON g.service_id = s.id
WHERE g.did_id = ? AND g.status = 'active'
ORDER BY g.last_used DESC NULLS LAST;
SELECT * FROM v_grants WHERE did_id = ? AND status = 'active' ORDER BY last_used DESC NULLS LAST;
-- name: GetGrantByService :one
SELECT * FROM grants WHERE did_id = ? AND service_id = ? LIMIT 1;

View File

@@ -66,46 +66,41 @@ CREATE INDEX idx_credentials_did_id ON credentials(did_id);
CREATE INDEX idx_credentials_credential_id ON credentials(credential_id);
-- =============================================================================
-- MPC KEY SHARES
-- MPC ENCLAVES
-- =============================================================================
-- Key Shares: MPC/TSS key share storage
CREATE TABLE IF NOT EXISTS key_shares (
CREATE TABLE IF NOT EXISTS mpc_enclaves (
id INTEGER PRIMARY KEY,
did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE,
share_id TEXT NOT NULL UNIQUE, -- Unique identifier for this share
key_id TEXT NOT NULL, -- Identifier for the full key (shared across parties)
party_index INTEGER NOT NULL, -- This party's index (1, 2, 3...)
threshold INTEGER NOT NULL, -- Minimum shares needed to sign
total_parties INTEGER NOT NULL, -- Total number of parties
curve TEXT NOT NULL DEFAULT 'secp256k1', -- secp256k1, ed25519
share_data TEXT NOT NULL, -- Encrypted key share (base64)
public_key TEXT NOT NULL, -- Full public key (base64)
chain_code TEXT, -- BIP32 chain code for derivation
derivation_path TEXT, -- BIP44 path: m/44'/60'/0'/0
enclave_id TEXT NOT NULL UNIQUE,
public_key_hex TEXT NOT NULL,
public_key BLOB NOT NULL,
val_share BLOB NOT NULL,
user_share BLOB NOT NULL,
nonce BLOB NOT NULL,
curve TEXT NOT NULL DEFAULT 'secp256k1',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'rotating', 'archived')),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
rotated_at TEXT,
UNIQUE(did_id, key_id, party_index)
UNIQUE(did_id, enclave_id)
);
CREATE INDEX idx_key_shares_did_id ON key_shares(did_id);
CREATE INDEX idx_key_shares_key_id ON key_shares(key_id);
CREATE INDEX idx_mpc_enclaves_did_id ON mpc_enclaves(did_id);
CREATE INDEX idx_mpc_enclaves_public_key_hex ON mpc_enclaves(public_key_hex);
-- Derived Accounts: Wallet accounts derived from key shares
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY,
did_id INTEGER NOT NULL REFERENCES did_documents(id) ON DELETE CASCADE,
key_share_id INTEGER NOT NULL REFERENCES key_shares(id) ON DELETE CASCADE,
address TEXT NOT NULL, -- Derived address
chain_id TEXT NOT NULL, -- sonr-mainnet-1, ethereum, etc.
coin_type INTEGER NOT NULL, -- BIP44 coin type (118=cosmos, 60=eth)
account_index INTEGER NOT NULL DEFAULT 0, -- BIP44 account index
address_index INTEGER NOT NULL DEFAULT 0, -- BIP44 address index
label TEXT DEFAULT '', -- User-assigned label
enclave_id INTEGER NOT NULL REFERENCES mpc_enclaves(id) ON DELETE CASCADE,
address TEXT NOT NULL,
chain_id TEXT NOT NULL,
coin_type INTEGER NOT NULL,
account_index INTEGER NOT NULL DEFAULT 0,
address_index INTEGER NOT NULL DEFAULT 0,
label TEXT DEFAULT '',
is_default INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(key_share_id, chain_id, account_index, address_index)
UNIQUE(enclave_id, chain_id, account_index, address_index)
);
CREATE INDEX idx_accounts_did_id ON accounts(did_id);
@@ -291,3 +286,40 @@ CREATE TRIGGER IF NOT EXISTS did_documents_updated_at
BEGIN
UPDATE did_documents SET updated_at = datetime('now') WHERE id = NEW.id;
END;
-- =============================================================================
-- VIEWS (pre-computed JOINs for common queries)
-- =============================================================================
CREATE VIEW IF NOT EXISTS v_accounts AS
SELECT
a.id, a.did_id, a.enclave_id, a.address, a.chain_id,
a.coin_type, a.account_index, a.address_index,
a.label, a.is_default, a.created_at,
e.public_key_hex, e.curve, e.enclave_id as enclave_ref
FROM accounts a
JOIN mpc_enclaves e ON a.enclave_id = e.id;
CREATE VIEW IF NOT EXISTS v_sessions AS
SELECT
s.id, s.did_id, s.credential_id, s.session_id, s.device_info,
s.is_current, s.last_activity, s.expires_at, s.created_at,
c.device_name, c.authenticator
FROM sessions s
JOIN credentials c ON s.credential_id = c.id
WHERE s.expires_at > datetime('now');
CREATE VIEW IF NOT EXISTS v_grants AS
SELECT
g.id, g.did_id, g.service_id, g.delegation_cid, g.scopes,
g.accounts, g.status, g.granted_at, g.last_used, g.expires_at,
s.name as service_name, s.origin as service_origin, s.logo_url as service_logo
FROM grants g
JOIN services s ON g.service_id = s.id;
CREATE VIEW IF NOT EXISTS v_active_delegations AS
SELECT * FROM ucan_delegations
WHERE exp IS NULL OR exp > datetime('now');
CREATE VIEW IF NOT EXISTS v_active_enclaves AS
SELECT * FROM mpc_enclaves WHERE status = 'active';

View File

@@ -1,39 +1,17 @@
package types
// GenerateInput represents the input for the generate function
type GenerateInput struct {
Credential string `json:"credential"` // Base64-encoded WebAuthn credential
// MPC keyshare data (optional - if provided, creates initial keyshare and account)
KeyShare *KeyShareInput `json:"key_share,omitempty"`
Credential string `json:"credential"`
}
// KeyShareInput represents MPC keyshare data for initialization
type KeyShareInput struct {
KeyID string `json:"key_id"`
PartyIndex int64 `json:"party_index"`
Threshold int64 `json:"threshold"`
TotalParties int64 `json:"total_parties"`
Curve string `json:"curve"`
ShareData string `json:"share_data"`
PublicKey string `json:"public_key"`
ChainCode string `json:"chain_code,omitempty"`
DerivationPath string `json:"derivation_path,omitempty"`
}
// GenerateOutput represents the output of the generate function
type GenerateOutput struct {
DID string `json:"did"`
Database []byte `json:"database"`
// KeyShare info if a keyshare was provided
KeyShareID string `json:"key_share_id,omitempty"`
// Account info if an account was created
Account *AccountInfo `json:"account,omitempty"`
DID string `json:"did"`
Database []byte `json:"database"`
EnclaveID string `json:"enclave_id"`
PublicKey string `json:"public_key"`
Accounts []AccountInfo `json:"accounts"`
}
// AccountInfo represents created account information
type AccountInfo struct {
Address string `json:"address"`
ChainID string `json:"chain_id"`