Add serve mode, fix binary tags, document CLI tools, improve Docker
Some checks failed
Go / build-and-release (push) Has been cancelled
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:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user