Add Extism JS Async Lib and State #1

Merged
pn merged 22 commits from feat/sdk into main 2026-01-08 01:06:58 +00:00
Showing only changes of commit 9a641c0e44 - Show all commits

186
main.go
View File

@@ -9,127 +9,25 @@ import (
"strings"
"enclave/internal/keybase"
"enclave/internal/types"
"github.com/extism/go-pdk"
)
// GenerateInput represents the input for the generate function
type GenerateInput struct {
Credential string `json:"credential"` // Base64-encoded PublicKeyCredential
}
// GenerateOutput represents the output of the generate function
type GenerateOutput struct {
DID string `json:"did"`
Database []byte `json:"database"`
}
// LoadInput represents the input for the load function
type LoadInput struct {
Database []byte `json:"database"`
}
// LoadOutput represents the output of the load function
type LoadOutput struct {
Success bool `json:"success"`
DID string `json:"did,omitempty"`
Error string `json:"error,omitempty"`
}
// ExecInput represents the input for the exec function
type ExecInput struct {
Filter string `json:"filter"` // GitHub-style filter: "resource:accounts action:sign"
Token string `json:"token"` // UCAN token for authorization
}
// ExecOutput represents the output of the exec function
type ExecOutput struct {
Success bool `json:"success"`
Result json.RawMessage `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
// QueryInput represents the input for the query function
type QueryInput struct {
DID string `json:"did"`
}
// QueryOutput represents the output of the query function
type QueryOutput struct {
DID string `json:"did"`
Controller string `json:"controller"`
VerificationMethods []VerificationMethod `json:"verification_methods"`
Accounts []Account `json:"accounts"`
Credentials []Credential `json:"credentials"`
}
// VerificationMethod represents a DID verification method
type VerificationMethod struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKey string `json:"public_key"`
Purpose string `json:"purpose"`
}
// Account represents a derived blockchain account
type Account struct {
Address string `json:"address"`
ChainID string `json:"chain_id"`
CoinType int `json:"coin_type"`
AccountIndex int `json:"account_index"`
AddressIndex int `json:"address_index"`
Label string `json:"label"`
IsDefault bool `json:"is_default"`
}
// Credential represents a WebAuthn credential
type Credential struct {
CredentialID string `json:"credential_id"`
DeviceName string `json:"device_name"`
DeviceType string `json:"device_type"`
Authenticator string `json:"authenticator"`
Transports []string `json:"transports"`
CreatedAt string `json:"created_at"`
LastUsed string `json:"last_used"`
}
// FilterParams parsed from GitHub-style filter syntax
type FilterParams struct {
Resource string
Action string
Subject string
}
// Enclave holds the plugin state
type Enclave struct {
var enclave = &struct {
initialized bool
did string
}
var enclave = &Enclave{}
}{}
func main() {}
// PingInput represents the input for the ping function
type PingInput struct {
Message string `json:"message"`
}
// PingOutput represents the output of the ping function
type PingOutput struct {
Success bool `json:"success"`
Message string `json:"message"`
Echo string `json:"echo"`
}
//go:wasmexport ping
func ping() int32 {
pdk.Log(pdk.LogInfo, "ping: received request")
var input PingInput
var input types.PingInput
if err := pdk.InputJSON(&input); err != nil {
output := PingOutput{
output := types.PingOutput{
Success: false,
Message: fmt.Sprintf("failed to parse input: %s", err),
}
@@ -137,7 +35,7 @@ func ping() int32 {
return 0
}
output := PingOutput{
output := types.PingOutput{
Success: true,
Message: "pong",
Echo: input.Message,
@@ -156,7 +54,7 @@ func ping() int32 {
func generate() int32 {
pdk.Log(pdk.LogInfo, "generate: starting database initialization")
var input GenerateInput
var input types.GenerateInput
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(fmt.Errorf("generate: failed to parse input: %w", err))
return 1
@@ -188,7 +86,7 @@ func generate() int32 {
return 1
}
output := GenerateOutput{
output := types.GenerateOutput{
DID: did,
Database: dbBytes,
}
@@ -206,7 +104,7 @@ func generate() int32 {
func load() int32 {
pdk.Log(pdk.LogInfo, "load: loading database from buffer")
var input LoadInput
var input types.LoadInput
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(fmt.Errorf("load: failed to parse input: %w", err))
return 1
@@ -219,7 +117,7 @@ func load() int32 {
did, err := loadDatabase(input.Database)
if err != nil {
output := LoadOutput{
output := types.LoadOutput{
Success: false,
Error: err.Error(),
}
@@ -230,7 +128,7 @@ func load() int32 {
enclave.initialized = true
enclave.did = did
output := LoadOutput{
output := types.LoadOutput{
Success: true,
DID: did,
}
@@ -249,34 +147,34 @@ func exec() int32 {
pdk.Log(pdk.LogInfo, "exec: executing action")
if !enclave.initialized {
output := ExecOutput{Success: false, Error: "database not initialized, call generate or load first"}
output := types.ExecOutput{Success: false, Error: "database not initialized, call generate or load first"}
pdk.OutputJSON(output)
return 0
}
var input ExecInput
var input types.ExecInput
if err := pdk.InputJSON(&input); err != nil {
output := ExecOutput{Success: false, Error: fmt.Sprintf("failed to parse input: %s", err)}
output := types.ExecOutput{Success: false, Error: fmt.Sprintf("failed to parse input: %s", err)}
pdk.OutputJSON(output)
return 0
}
if input.Filter == "" {
output := ExecOutput{Success: false, Error: "filter is required"}
output := types.ExecOutput{Success: false, Error: "filter is required"}
pdk.OutputJSON(output)
return 0
}
params, err := parseFilter(input.Filter)
if err != nil {
output := ExecOutput{Success: false, Error: fmt.Sprintf("invalid filter: %s", err)}
output := types.ExecOutput{Success: false, Error: fmt.Sprintf("invalid filter: %s", err)}
pdk.OutputJSON(output)
return 0
}
if input.Token != "" {
if err := validateUCAN(input.Token, params); err != nil {
output := ExecOutput{
output := types.ExecOutput{
Success: false,
Error: fmt.Sprintf("authorization failed: %s", err.Error()),
}
@@ -287,7 +185,7 @@ func exec() int32 {
result, err := executeAction(params)
if err != nil {
output := ExecOutput{
output := types.ExecOutput{
Success: false,
Error: err.Error(),
}
@@ -295,7 +193,7 @@ func exec() int32 {
return 1
}
output := ExecOutput{
output := types.ExecOutput{
Success: true,
Result: result,
}
@@ -314,7 +212,7 @@ func query() int32 {
return 1
}
var input QueryInput
var input types.QueryInput
if err := pdk.InputJSON(&input); err != nil {
pdk.SetError(fmt.Errorf("query: failed to parse input: %w", err))
return 1
@@ -388,8 +286,8 @@ func loadDatabase(data []byte) (string, error) {
return did, nil
}
func parseFilter(filter string) (*FilterParams, error) {
params := &FilterParams{}
func parseFilter(filter string) (*types.FilterParams, error) {
params := &types.FilterParams{}
parts := strings.FieldsSeq(filter)
for part := range parts {
@@ -419,12 +317,7 @@ func parseFilter(filter string) (*FilterParams, error) {
return params, nil
}
func validateUCAN(token string, params *FilterParams) error {
// TODO: Decode UCAN token
// TODO: Verify signature chain
// TODO: Check capabilities match params
// TODO: Verify not expired or revoked
func validateUCAN(token string, params *types.FilterParams) error {
if token == "" {
return errors.New("token is required")
}
@@ -433,11 +326,7 @@ func validateUCAN(token string, params *FilterParams) error {
return nil
}
func executeAction(params *FilterParams) (json.RawMessage, error) {
// TODO: Route to appropriate handler based on resource/action
// TODO: Execute database queries
// TODO: Return results
func executeAction(params *types.FilterParams) (json.RawMessage, error) {
switch params.Resource {
case "accounts":
return executeAccountAction(params)
@@ -452,10 +341,10 @@ func executeAction(params *FilterParams) (json.RawMessage, error) {
}
}
func executeAccountAction(params *FilterParams) (json.RawMessage, error) {
func executeAccountAction(params *types.FilterParams) (json.RawMessage, error) {
switch params.Action {
case "list":
accounts := []Account{
accounts := []types.Account{
{
Address: "sonr1abc123...",
ChainID: "sonr-mainnet-1",
@@ -474,10 +363,10 @@ func executeAccountAction(params *FilterParams) (json.RawMessage, error) {
}
}
func executeCredentialAction(params *FilterParams) (json.RawMessage, error) {
func executeCredentialAction(params *types.FilterParams) (json.RawMessage, error) {
switch params.Action {
case "list":
credentials := []Credential{
credentials := []types.Credential{
{
CredentialID: "cred_abc123",
DeviceName: "MacBook Pro",
@@ -494,7 +383,7 @@ func executeCredentialAction(params *FilterParams) (json.RawMessage, error) {
}
}
func executeSessionAction(params *FilterParams) (json.RawMessage, error) {
func executeSessionAction(params *types.FilterParams) (json.RawMessage, error) {
switch params.Action {
case "list":
return json.Marshal([]map[string]any{})
@@ -507,7 +396,7 @@ func executeSessionAction(params *FilterParams) (json.RawMessage, error) {
}
}
func executeGrantAction(params *FilterParams) (json.RawMessage, error) {
func executeGrantAction(params *types.FilterParams) (json.RawMessage, error) {
switch params.Action {
case "list":
return json.Marshal([]map[string]any{})
@@ -520,16 +409,11 @@ func executeGrantAction(params *FilterParams) (json.RawMessage, error) {
}
}
func resolveDID(did string) (*QueryOutput, error) {
// TODO: Query database for DID document
// TODO: Fetch verification methods
// TODO: Fetch associated accounts
// TODO: Fetch credentials
output := &QueryOutput{
func resolveDID(did string) (*types.QueryOutput, error) {
output := &types.QueryOutput{
DID: did,
Controller: did,
VerificationMethods: []VerificationMethod{
VerificationMethods: []types.VerificationMethod{
{
ID: did + "#key-1",
Type: "Ed25519VerificationKey2020",
@@ -538,7 +422,7 @@ func resolveDID(did string) (*QueryOutput, error) {
Purpose: "authentication",
},
},
Accounts: []Account{
Accounts: []types.Account{
{
Address: "sonr1abc123...",
ChainID: "sonr-mainnet-1",
@@ -549,7 +433,7 @@ func resolveDID(did string) (*QueryOutput, error) {
IsDefault: true,
},
},
Credentials: []Credential{
Credentials: []types.Credential{
{
CredentialID: "cred_abc123",
DeviceName: "MacBook Pro",