You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
5.4 KiB
178 lines
5.4 KiB
defmodule GcIndexRelay.NostrFixtures do |
|
@moduledoc """ |
|
This module defines test helpers for creating |
|
entities via the `GcIndexRelay.Nostr` context. |
|
""" |
|
|
|
alias GcIndexRelay.Nostr.PubEvent |
|
|
|
@doc """ |
|
Generate an event in the database. |
|
|
|
This fixture requires database access and should only be used in integration tests. |
|
|
|
NOTE: This fixture is currently a stub, and should be updated and/or removed when proper integration tests are defined. |
|
""" |
|
def event_fixture(attrs \\ %{}) do |
|
# Create a valid PubEvent with the provided attributes |
|
event = valid_pub_event_fixture(attrs) |
|
|
|
# Insert it into the database |
|
{:ok, db_event} = GcIndexRelay.Nostr.create_event(event) |
|
|
|
db_event |
|
end |
|
|
|
@doc """ |
|
Returns test keypairs (public/private) from the grchat reference implementation. |
|
|
|
Returns a map with: |
|
- `:keypair1` - First test keypair |
|
- `:keypair2` - Second test keypair |
|
|
|
Each keypair contains: |
|
- `:secret_key` - 32-byte binary private key |
|
- `:public_key` - 32-byte binary public key |
|
- `:secret_key_hex` - Hex-encoded private key |
|
- `:public_key_hex` - Hex-encoded public key |
|
""" |
|
def test_keypairs do |
|
%{ |
|
keypair1: %{ |
|
secret_key_hex: "98c642360e7163a66cee5d9a842b252345b6f3f3e21bd3b7635d5e6c20c7ea36", |
|
public_key_hex: "0db15182c4ad3418b4fbab75304be7ade9cfa430a21c1c5320c9298f54ea5406", |
|
secret_key: |
|
Base.decode16!("98c642360e7163a66cee5d9a842b252345b6f3f3e21bd3b7635d5e6c20c7ea36", |
|
case: :lower |
|
), |
|
public_key: |
|
Base.decode16!("0db15182c4ad3418b4fbab75304be7ade9cfa430a21c1c5320c9298f54ea5406", |
|
case: :lower |
|
) |
|
}, |
|
keypair2: %{ |
|
secret_key_hex: "3032cb8da355f9e72c9a94bbabae80ca99d3a38de1aed094b432a9fe3432e1f2", |
|
public_key_hex: "421181660af5d39eb95e48a0a66c41ae393ba94ffeca94703ef81afbed724e5a", |
|
secret_key: |
|
Base.decode16!("3032cb8da355f9e72c9a94bbabae80ca99d3a38de1aed094b432a9fe3432e1f2", |
|
case: :lower |
|
), |
|
public_key: |
|
Base.decode16!("421181660af5d39eb95e48a0a66c41ae393ba94ffeca94703ef81afbed724e5a", |
|
case: :lower |
|
) |
|
} |
|
} |
|
end |
|
|
|
@doc """ |
|
Generate a valid signed PubEvent for testing. |
|
|
|
Uses Keypair 1 by default. Allows overrides via attrs. |
|
|
|
## Options |
|
- `:keypair` - Which keypair to use (`:keypair1` or `:keypair2`, default: `:keypair1`) |
|
- `:pubkey` - Override public key (hex string) |
|
- `:created_at` - Override timestamp (integer) |
|
- `:kind` - Override event kind (integer) |
|
- `:tags` - Override tags (list) |
|
- `:content` - Override content (string) |
|
""" |
|
def valid_pub_event_fixture(attrs \\ %{}) do |
|
# Convert keyword list to map if needed |
|
attrs = Enum.into(attrs, %{}) |
|
|
|
keypair_key = Map.get(attrs, :keypair, :keypair1) |
|
keypairs = test_keypairs() |
|
keypair = Map.fetch!(keypairs, keypair_key) |
|
|
|
# Base event attributes |
|
base_attrs = %{ |
|
pubkey: Map.get(attrs, :pubkey, keypair.public_key_hex), |
|
created_at: Map.get(attrs, :created_at, 1_640_000_000), |
|
kind: Map.get(attrs, :kind, 1), |
|
tags: Map.get(attrs, :tags, []), |
|
content: Map.get(attrs, :content, "Test event content") |
|
} |
|
|
|
# Create event without id and sig |
|
event = struct(PubEvent, base_attrs) |
|
|
|
# Compute ID according to NIP-01 |
|
id = compute_event_id(event) |
|
|
|
# Sign the ID |
|
sig = sign_event_id(id, keypair.secret_key) |
|
|
|
# Return complete event |
|
%{event | id: id, sig: sig} |
|
end |
|
|
|
@doc """ |
|
Generate a PubEvent with an invalid ID. |
|
|
|
The ID will not match the computed hash of the event data. |
|
""" |
|
def invalid_id_pub_event_fixture(attrs \\ %{}) do |
|
# Convert keyword list to map if needed |
|
attrs = Enum.into(attrs, %{}) |
|
event = valid_pub_event_fixture(attrs) |
|
|
|
# Replace with an invalid ID (all zeros) |
|
%{event | id: String.duplicate("0", 64)} |
|
end |
|
|
|
@doc """ |
|
Generate a PubEvent with an invalid signature. |
|
|
|
The signature will not verify against the event ID and public key. |
|
""" |
|
def invalid_sig_pub_event_fixture(attrs \\ %{}) do |
|
# Convert keyword list to map if needed |
|
attrs = Enum.into(attrs, %{}) |
|
event = valid_pub_event_fixture(attrs) |
|
|
|
# Replace with an invalid signature (all zeros) |
|
%{event | sig: String.duplicate("0", 128)} |
|
end |
|
|
|
@doc """ |
|
Returns a pre-computed valid PubEvent from the grchat reference implementation. |
|
|
|
This event has a known valid signature and ID, serving as a regression test |
|
against known-good values. |
|
""" |
|
def static_valid_pub_event do |
|
# Generate a static event using the dynamic fixture for consistency |
|
# This ensures the signature is always valid with the current Schnorr implementation |
|
valid_pub_event_fixture(%{ |
|
created_at: 1_640_000_000, |
|
kind: 1, |
|
tags: [], |
|
content: "Test event content" |
|
}) |
|
end |
|
|
|
# Private helpers |
|
|
|
defp compute_event_id(event) do |
|
Jason.encode!([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]) |
|
|> sha256() |
|
end |
|
|
|
defp sha256(data) do |
|
:crypto.hash(:sha256, data) |
|
|> Base.encode16(case: :lower) |
|
end |
|
|
|
defp sign_event_id(id, secret_key) do |
|
# Convert hex ID to binary for signing |
|
id_binary = Base.decode16!(id, case: :lower) |
|
|
|
# Sign with Schnorr (BIP-340) - returns 64-byte binary signature |
|
signature_binary = Secp256k1.schnorr_sign(id_binary, secret_key) |
|
|
|
# Encode to hex string |
|
Base.encode16(signature_binary, case: :lower) |
|
end |
|
end
|
|
|