Skip to content

Commit 7c78bf0

Browse files
committed
feat: add subspace manager sdk & unit test & example
1 parent 5bb7ee4 commit 7c78bf0

File tree

5 files changed

+835
-11
lines changed

5 files changed

+835
-11
lines changed

README.md

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,66 @@
1-
[![Run Tests](https://github.com/nbd-wtf/go-nostr/actions/workflows/test.yml/badge.svg)](https://github.com/nbd-wtf/go-nostr/actions/workflows/test.yml)
2-
[![Go Reference](https://pkg.go.dev/badge/github.com/nbd-wtf/go-nostr.svg)](https://pkg.go.dev/github.com/nbd-wtf/go-nostr)
3-
[![Go Report Card](https://goreportcard.com/badge/github.com/nbd-wtf/go-nostr)](https://goreportcard.com/report/github.com/nbd-wtf/go-nostr)
1+
## Overview
42

5-
<a href="https://nbd.wtf"><img align="right" height="196" src="https://user-images.githubusercontent.com/1653275/194609043-0add674b-dd40-41ed-986c-ab4a2e053092.png" /></a>
3+
This project combines three key technologies:
64

7-
go-nostr
8-
========
5+
1. Nostr (decentralized messaging protocol)
6+
2. Ethereum EIP-191 signatures (secure cryptographic signing)
7+
3. VLC (Verifiable Logical Clock)
98

10-
A set of useful things for [Nostr](https://github.com/nostr-protocol/nostr)-related software.
9+
Together, these components enable:
1110

12-
```bash
13-
go get github.com/nbd-wtf/go-nostr
14-
```
11+
- Decentralized authentication
12+
- Secure message signing using Ethereum standards
13+
- Distributed event ordering and causality tracking
14+
- Message consistency in distributed systems
15+
16+
This integration creates a robust system for secure, decentralized communication with verifiable event ordering.
17+
18+
## Features
19+
20+
🔗 Nostr Integration
21+
22+
- Decentralized communication protocol
23+
- Publish and subscribe to events securely
24+
- Relay-based message broadcasting
25+
- Censorship-resistant communication
26+
27+
🔑 Ethereum EIP-191 Signature
28+
29+
- Secure message signing using Ethereum wallets
30+
- Identity verification via cryptographic proofs
31+
- EIP-191 ensures structured, tamper-proof signatures
32+
- Compatible with Ethereum ecosystem tools
33+
34+
⏰ Verifiable Logical Clock
35+
36+
- Track causality between distributed events
37+
- Maintain consistent ordering across nodes
38+
- Detect concurrent operations
39+
- Enable distributed consensus
40+
41+
## How It Works
42+
43+
- Nostr Events: Messages are exchanged using Nostr's relay-based architecture
44+
- Ethereum Signing: Messages are signed with Ethereum private keys following the EIP-191 standard
45+
- Verifiable Clock: Each node maintains a vector timestamp to track event causality
46+
- Verification: Signatures and timestamps are verified to ensure message authenticity and ordering
47+
48+
## Security Considerations
49+
50+
- Private Key Safety: Never expose your Ethereum private key; always sign messages in a secure environment
51+
- Relay Trust: Use trusted Nostr relays to prevent data interception
52+
- Message Verification: Ensure EIP-191 signatures are correctly validated before processing messages
53+
- Clock Synchronization: Maintain accurate vector clocks to prevent causality violations
54+
55+
## Future Enhancements
56+
57+
- Multiple relay support for increased redundancy
58+
- Advanced vector clock optimization for better scalability
59+
- Integration with additional signing standards
60+
- Enhanced privacy features
61+
- Performance optimizations for large-scale deployments
62+
63+
## Examples
1564

1665
### Generating a key
1766

example/publish/publish.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
)
99

1010
func main() {
11+
relays := []string{"ws://161.97.129.166:10547"}
1112
sk := nostr.GeneratePrivateKey()
1213
pub, _ := nostr.GetPublicKey(sk)
1314
ev := nostr.Event{
@@ -23,7 +24,7 @@ func main() {
2324

2425
// publish the event to self relays
2526
ctx := context.Background()
26-
for _, url := range []string{"ws://161.97.129.166:10547"} {
27+
for _, url := range relays {
2728
relay, err := nostr.RelayConnect(ctx, url)
2829
if err != nil {
2930
fmt.Println(err)

example/subspace/subspace.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/nbd-wtf/go-nostr"
8+
)
9+
10+
// Business operation types
11+
const (
12+
OpModel = "model" // 5
13+
OpData = "data" // 6
14+
OpCompute = "compute" // 7
15+
OpAlgo = "algo" // 8
16+
OpValid = "valid" // 9
17+
)
18+
19+
// BusinessOps returns the business operations string
20+
func BusinessOps() string {
21+
return "model=5,data=6,compute=7,algo=8,valid=9"
22+
}
23+
24+
// AllOps returns the combined operations string including both basic and business operations
25+
func AllOps() string {
26+
return nostr.DefaultSubspaceOps + "," + BusinessOps()
27+
}
28+
29+
// ValidateBusinessOp validates a business operation event
30+
func ValidateBusinessOp(evt *nostr.SubspaceOpEvent) error {
31+
// Verify operation type
32+
validOps := map[string]bool{
33+
OpModel: true,
34+
OpData: true,
35+
OpCompute: true,
36+
OpAlgo: true,
37+
OpValid: true,
38+
}
39+
if !validOps[evt.Operation] {
40+
return fmt.Errorf("invalid business operation type: %s", evt.Operation)
41+
}
42+
43+
return nil
44+
}
45+
46+
func main() {
47+
relays := []string{"ws://161.97.129.166:10547"}
48+
sk := nostr.GeneratePrivateKey()
49+
pub, _ := nostr.GetPublicKey(sk)
50+
51+
// Create a subspace with all operations (basic + business)
52+
createEvent := nostr.NewSubspaceCreateEvent(
53+
"modelgraph",
54+
AllOps(), // Use combined operations string
55+
"energy>1000",
56+
"Desci AI Model collaboration subspace",
57+
"https://causality-graph.com/images/subspace.png",
58+
)
59+
createEvent.PubKey = pub
60+
createEvent.Sign(sk)
61+
62+
// Join a subspace
63+
joinEvent := nostr.NewSubspaceJoinEvent(createEvent.SubspaceID)
64+
joinEvent.PubKey = pub
65+
joinEvent.Sign(sk)
66+
67+
// Create a post operation (basic operation)
68+
postEvent := nostr.NewSubspaceOpEvent(createEvent.SubspaceID, nostr.OpPost)
69+
postEvent.PubKey = pub
70+
postEvent.SetContentType("markdown")
71+
postEvent.SetParent("parent-hash")
72+
postEvent.Content = "# Subspace update \n We have completed the model optimization!"
73+
postEvent.Sign(sk)
74+
75+
// Create a proposal (basic operation)
76+
proposeEvent := nostr.NewSubspaceOpEvent(createEvent.SubspaceID, nostr.OpPropose)
77+
proposeEvent.PubKey = pub
78+
proposeEvent.SetProposal("prop_001", "energy>2000")
79+
proposeEvent.Content = "Increase the energy requirement for subspace addition to 2000"
80+
proposeEvent.Sign(sk)
81+
82+
// Create a vote (basic operation)
83+
voteEvent := nostr.NewSubspaceOpEvent(createEvent.SubspaceID, nostr.OpVote)
84+
voteEvent.PubKey = pub
85+
voteEvent.SetVote("prop_001", "yes")
86+
voteEvent.Content = "Agree to increase the energy requirements"
87+
voteEvent.Sign(sk)
88+
89+
// Create an invite (basic operation)
90+
inviteEvent := nostr.NewSubspaceOpEvent(createEvent.SubspaceID, nostr.OpInvite)
91+
inviteEvent.PubKey = pub
92+
inviteEvent.SetInvite("<Charlie's ETH Address>", "energy>1000")
93+
inviteEvent.Content = "Invite Charlie join into Desci AI subspace"
94+
inviteEvent.Sign(sk)
95+
96+
// Create a model operation (business operation)
97+
modelEvent := nostr.NewSubspaceOpEvent(createEvent.SubspaceID, OpModel)
98+
modelEvent.PubKey = pub
99+
modelEvent.SetParent("parent-hash")
100+
modelEvent.SetContributions("base:0.1,data:0.6,algo:0.3")
101+
modelEvent.Content = "ipfs://bafy..."
102+
modelEvent.Sign(sk)
103+
104+
// Validate business operation
105+
if err := ValidateBusinessOp(modelEvent); err != nil {
106+
fmt.Printf("Invalid business operation: %v\n", err)
107+
return
108+
}
109+
110+
// publish the events to relays
111+
ctx := context.Background()
112+
for _, url := range relays {
113+
relay, err := nostr.RelayConnect(ctx, url)
114+
if err != nil {
115+
fmt.Println(err)
116+
continue
117+
}
118+
119+
// Publish all events
120+
events := []nostr.Event{
121+
createEvent.Event,
122+
joinEvent.Event,
123+
postEvent.Event,
124+
proposeEvent.Event,
125+
voteEvent.Event,
126+
inviteEvent.Event,
127+
modelEvent.Event,
128+
}
129+
130+
for _, ev := range events {
131+
if err := relay.Publish(ctx, ev); err != nil {
132+
fmt.Printf("failed to publish event to %s: %v\n", url, err)
133+
continue
134+
}
135+
fmt.Printf("published event %s to %s\n", ev.ID, url)
136+
}
137+
}
138+
relay, err := nostr.RelayConnect(ctx, relays[0])
139+
if err != nil {
140+
fmt.Println(err)
141+
return
142+
}
143+
var filter nostr.Filter
144+
filter = nostr.Filter{
145+
Kinds: []int{30300},
146+
// limit = 3, get the three most recent notes
147+
Limit: 3,
148+
}
149+
events, err := relay.QueryEvents(ctx, filter)
150+
if err != nil {
151+
fmt.Printf("failed to query events: %v\n", err)
152+
return
153+
}
154+
155+
for event := range events {
156+
fmt.Println("------")
157+
fmt.Printf("Content: %s\n", event)
158+
}
159+
}

0 commit comments

Comments
 (0)