Add serve mode, fix binary tags, document CLI tools, improve Docker
Some checks failed
Go / build-and-release (push) Has been cancelled

- Add 'serve' subcommand for ephemeral RAM-based relay at /dev/shm with
  open ACL mode for testing and benchmarking
- Fix e-tag and p-tag decoding to use ValueHex()/ValueBinary() methods
  instead of Value() which returns raw bytes for binary-optimized storage
- Document all command-line tools in readme.adoc (relay-tester, benchmark,
  stresstest, blossomtest, aggregator, convert, FIND, policytest, etc.)
- Switch Docker images from Alpine to Debian for proper libsecp256k1
  Schnorr signature and ECDH support required by Nostr
- Upgrade Docker Go version from 1.21 to 1.25
- Add ramdisk mode (--ramdisk) to benchmark script for eliminating disk
  I/O bottlenecks in performance measurements
- Add docker-compose.ramdisk.yml for tmpfs-based benchmark volumes
- Add test coverage for privileged policy with binary-encoded p-tags
- Fix blossom test to expect 200 OK for anonymous uploads when auth is
  not required (RequireAuth=false with ACL mode 'none')
- Update follows ACL to handle both binary and hex p-tag formats
- Grant owner access to all users in serve mode via None ACL
- Add benchmark reports from multi-relay comparison run
- Update CLAUDE.md with binary tag handling documentation
- Bump version to v0.30.2
This commit is contained in:
2025-11-26 09:52:29 +00:00
parent f1ddad3318
commit fad39ec201
42 changed files with 2720 additions and 234 deletions

View File

@@ -313,7 +313,7 @@ func New(policyJSON []byte) (p *P, err error) {
// 2. Mentioned in a p-tag of the event
//
// Both ev.Pubkey and userPubkey must be binary ([]byte), not hex-encoded.
// P-tags are assumed to contain hex-encoded pubkeys that will be decoded.
// P-tags may be stored in either binary-optimized format (33 bytes) or hex format.
//
// This is the single source of truth for "parties_involved" / "privileged" checks.
func IsPartyInvolved(ev *event.E, userPubkey []byte) bool {
@@ -330,8 +330,8 @@ func IsPartyInvolved(ev *event.E, userPubkey []byte) bool {
// Check if user is in p tags
pTags := ev.Tags.GetAll([]byte("p"))
for _, pTag := range pTags {
// pTag.Value() returns hex-encoded string; decode to bytes for comparison
pt, err := hex.Dec(string(pTag.Value()))
// ValueHex() handles both binary and hex storage formats automatically
pt, err := hex.Dec(string(pTag.ValueHex()))
if err != nil {
// Skip malformed tags
continue

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"testing"
"git.mleku.dev/mleku/nostr/encoders/event"
"git.mleku.dev/mleku/nostr/encoders/hex"
)
@@ -241,3 +242,97 @@ func TestNoReadAllowNoPrivileged(t *testing.T) {
}
})
}
// TestPrivilegedWithBinaryEncodedPTags tests that privileged access works correctly
// when p-tags are stored in binary-optimized format (as happens after JSON unmarshaling).
// This is the real-world scenario where events come from network JSON.
func TestPrivilegedWithBinaryEncodedPTags(t *testing.T) {
_, alicePubkey := generateTestKeypair(t)
_, bobPubkey := generateTestKeypair(t)
_, charliePubkey := generateTestKeypair(t)
// Create policy with privileged flag
policyJSON := map[string]interface{}{
"rules": map[string]interface{}{
"4": map[string]interface{}{
"description": "DM - privileged only",
"privileged": true,
},
},
}
policyBytes, err := json.Marshal(policyJSON)
if err != nil {
t.Fatalf("Failed to marshal policy: %v", err)
}
policy, err := New(policyBytes)
if err != nil {
t.Fatalf("Failed to create policy: %v", err)
}
// Create event JSON with p-tag (simulating real network event)
// When this JSON is unmarshaled, the p-tag value will be converted to binary format
eventJSON := `{
"id": "0000000000000000000000000000000000000000000000000000000000000001",
"pubkey": "` + hex.Enc(alicePubkey) + `",
"created_at": 1234567890,
"kind": 4,
"tags": [["p", "` + hex.Enc(bobPubkey) + `"]],
"content": "Secret message",
"sig": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}`
var ev event.E
if err := json.Unmarshal([]byte(eventJSON), &ev); err != nil {
t.Fatalf("Failed to unmarshal event: %v", err)
}
// Verify the p-tag is stored in binary format
pTags := ev.Tags.GetAll([]byte("p"))
if len(pTags) == 0 {
t.Fatal("Event should have p-tag")
}
pTag := pTags[0]
binValue := pTag.ValueBinary()
t.Logf("P-tag Value() length: %d", len(pTag.Value()))
t.Logf("P-tag ValueBinary(): %v (len=%d)", binValue != nil, len(binValue))
if binValue == nil {
t.Log("Warning: P-tag is NOT in binary format (test may not exercise the binary code path)")
} else {
t.Log("P-tag IS in binary format - testing binary-encoded path")
}
// Test: Bob (in p-tag) should be able to read even with binary-encoded tag
t.Run("bob_binary_ptag_can_read", func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", &ev, bobPubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("BUG! Recipient (in binary-encoded p-tag) should be able to read privileged event")
}
})
// Test: Alice (author) should be able to read
t.Run("alice_author_can_read", func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", &ev, alicePubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !allowed {
t.Error("Author should be able to read their own privileged event")
}
})
// Test: Charlie (third party) should NOT be able to read
t.Run("charlie_denied", func(t *testing.T) {
allowed, err := policy.CheckPolicy("read", &ev, charliePubkey, "127.0.0.1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if allowed {
t.Error("Third party should NOT be able to read privileged event")
}
})
}