Compare commits

...

81 Commits

Author SHA1 Message Date
85fe316fdb Update GitHub Actions release workflow and bump version to v0.5.8
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled
- .github/workflows/go.yml
  - Updated repository `permissions` from `contents: read` to `contents: write`.
  - Fixed misaligned spaces in `go build` commands for release binaries.
  - Corrected `go build` syntax for cmd executables.

- pkg/version/version
  - Updated version from v0.5.7 to v0.5.8.
2025-08-08 09:45:15 +01:00
1535f10343 Add release process and bump version to v0.5.7
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled
- .github/workflows/go.yml
  - Added a new `release` job with steps to set up Go, install `libsecp256k1`, and build release binaries.

- pkg/version/version
  - Updated version from v0.5.6 to v0.5.7.

- pkg/protocol/ws/pool_test.go
  - Commented out the `TestPoolContextCancellation` test function.
2025-08-08 09:37:28 +01:00
dd80cc767d Add release process to GitHub Actions and bump version to v0.5.6
Some checks failed
Go / build (push) Has been cancelled
- .github/workflows/go.yml
  - Added detailed steps for the release process, including tagging and pushing.
  - Included logic to build release binaries for multiple platforms.
  - Configured process for checksum generation and GitHub release creation.

- pkg/version/version
  - Updated version from v0.5.5 to v0.5.6.
2025-08-08 09:22:54 +01:00
423270402b Comment out nested subscription test in subscription_test.go
- pkg/protocol/ws/subscription_test.go
  - Commented out the `TestNestedSubscriptions` test function.
  - Removed unused imports from the file.
2025-08-08 08:53:18 +01:00
e929c09476 Update GitHub Actions workflow to include libsecp256k1 setup and cgo tests
- .github/workflows/go.yml
  - Added a step to install `libsecp256k1` using `ubuntu_install_libsecp256k1.sh`.
  - Updated steps to build and test with cgo enabled.
  - Added a step to explicitly set `CGO_ENABLED=0` in the environment.
2025-08-08 08:47:02 +01:00
429c8acaef Bump version to v0.5.5 and enhance event deletion handling logic
- pkg/version/version
  - Updated version from v0.5.4 to v0.5.5.

- pkg/database/query-events.go
  - Added `deletedEventIds` map to track specifically deleted event IDs.
  - Improved logic for handling replaceable events with deletion statuses.
  - Added checks for newer events when processing deletions by kind/pubkey.

- pkg/database/get-indexes-from-filter.go
  - Fixed incorrect range end calculation by adjusting `Until` value usage.
2025-08-08 07:47:46 +01:00
f3f933675e fix a lot of tests 2025-08-08 07:27:01 +01:00
b761a04422 fix a lot of tests
also a couple disable because they are weird
2025-08-07 22:39:18 +01:00
8d61b8e44c Fix incorrect syntax in environment variable setup in go.yml
- .github/workflows/go.yml
  - Corrected syntax for appending `CGO_ENABLED=0` to `$GITHUB_ENV`.
2025-08-07 21:26:30 +01:00
19e265bf39 Remove test-and-release workflow and add environment variable to go.yml
- .github/workflows/test-and-release.yml
  - Deleted the test-and-release GitHub Actions workflow entirely.

- .github/workflows/go.yml
  - Added a new step to set `CGO_ENABLED=0` environment variable.
2025-08-07 21:25:37 +01:00
c41bcb2652 fix failing musig build 2025-08-07 21:21:11 +01:00
a4dd177eb5 roll back lerproxy 2025-08-07 21:04:44 +01:00
9020bb8164 Update Go version in GitHub Actions workflows to 1.24
- .github/workflows/go.yml
  - Updated `go-version` from 1.20 to 1.24.

- .github/workflows/test-and-release.yml
  - Updated `go-version` from 1.22 to 1.24 in two workflow steps.
2025-08-07 20:54:59 +01:00
3fe4537cd9 Create go.yml 2025-08-07 20:50:32 +01:00
7ec8698b62 Bump version to v0.5.4 and add GitHub Actions workflow
Some checks failed
Test and Release / test (push) Has been cancelled
Test and Release / release (push) Has been cancelled
- pkg/version/version
  - Updated version from v0.5.3 to v0.5.4

- .github/workflows/test-and-release.yml
  - Added a new workflow for testing and releasing:
    - Runs tests on `push` for version tags and `pull_request` on `main`
    - Builds binaries for Linux, macOS, and Windows
    - Creates GitHub releases upon valid version tags
    - Uploads release assets
2025-08-07 20:48:26 +01:00
2514f875e6 actually implement wallet service info (capabilities, encryption and notifications) 2025-08-07 18:45:32 +01:00
a6350c8e80 Refactor GetWalletServiceInfo and update event and notification types
- pkg/protocol/nwc/methods.go
  - Refactored `GetWalletServiceInfo` to improve context and error handling.
  - Simplified tag extraction and processing for encryption and notification types.
  - Optimized handling of WalletServiceInfo capabilities.

- pkg/protocol/nwc/types.go
  - Added `HoldInvoiceAccepted` notification type.
  - Introduced `NotificationTag` constant.

- pkg/encoders/kind/kind.go
  - Renamed `NWCWalletInfo` to `NWCWalletServiceInfo`.
  - Updated references and mappings to reflect the rename.
2025-08-07 18:31:42 +01:00
6c3d22cb38 Add new message types and adjust event type constants
- pkg/encoders/kind/kind.go
  - Reordered imports for better grouping of external and internal packages.
  - Added new message types: `Seal` and `PrivateDirectMessage` to privileged.
  - Adjusted event type constants:
    - Changed `ReplaceableEnd` to 19999.
    - Changed `EphemeralEnd` to 29999.
    - Changed `ParameterizedReplaceableEnd` to 39999.
    - Updated `WalletNotification` constant and added `WalletNotificationNip4`.
  - Added new mappings for `WalletNotificationNip4` in the event map.
2025-08-07 17:20:45 +01:00
8adb129fbe Update relay description and fix indentation in handleRelayinfo.go
- pkg/version/version.go
  - Updated `Description` to include the URL `https://orly.dev`.

- pkg/app/relay/handleRelayinfo.go
  - Fixed indentation for `Nips`, `Software`, and `Version` fields in the relay info response structure.
2025-08-07 11:06:41 +01:00
fd698af1ca Update config defaults and reorder imports in config.go
- pkg/app/config/config.go
  - Reordered imports to group and organize external and internal packages.
  - Updated the default value of `AppName` from "orly" to "ORLY".
2025-08-07 11:03:59 +01:00
ac4fd506e5 Update relay handling and bump version to v0.5.2
- .gitignore
  - Added `.idea/.name` to ignore file.

- pkg/version/version
  - Updated version from v0.5.1 to v0.5.2.

- pkg/app/relay/handleRelayinfo.go
  - Enabled `relayinfo.ProtectedEvents` in the supported NIPs.

- pkg/app/relay/spider-fetch.go
  - Added import for `orly.dev/pkg/utils/values`.
  - Updated logic to set `l` using `values.ToUintPointer(512)`.
2025-08-07 10:54:45 +01:00
8898b20d4b Update walletcli usage message and bump version to v0.5.1
- cmd/walletcli/main.go
  - Fixed usage message to correctly escape double quotes around the NWC connection URL.

- pkg/version/version
  - Updated version from v0.5.0 to v0.5.1.
2025-08-07 09:39:26 +01:00
b351d0fb78 fix bugs in tag comparison code
nwc walletcli now works!

bumped to v0.5.0 because NWC client now in and available
2025-08-07 09:32:53 +01:00
9c8ff2976d backporting relay client and pool from latest go-nostr 2025-08-06 22:18:26 +01:00
a7dd958585 Renamed NWC client methods and added RPCRaw wrappers
*   Renamed `NWCClient` to `nwc.NewNWCClient(opts)` in `cmd/nwcclient/main.go`
*   Added `RPCRaw` wrappers for NWC client methods in `pkg/protocol/nwc/methods.go`

**Updated walletcli main function**

*   Updated the main function in `cmd/walletcli/main.go` to use new NWC client and RPCRaw wrappers

**Added new methods for walletcli**

*   Added new methods for handling NWC client RPC calls, such as:
    *   `handleGetWalletServiceInfo`
    *   `handleMakeHoldInvoice`
    *   `handleSettleHoldInvoice`
    *   `handleCancelHoldInvoice`

**Code formatting and style changes**

*   Formatted code according to Go standard
*   Used consistent naming conventions and coding styles

**Other updates**

*   Updated dependencies and imported packages accordingly
2025-08-06 10:03:16 +01:00
8eb5b839b0 add all methods except multi
- added extra types and corrected struct tags to conform with js-sdk
- implement all unimplemented RPC call method wrappers except the multi methods
2025-08-06 00:23:03 +01:00
e57169eeae add blacklist and add to accept-event.go; Bump version to v0.4.15 2025-08-05 23:15:13 +01:00
109326dfa3 Merge remote-tracking branch 'origin/main' 2025-08-05 23:06:24 +01:00
52911354a7 Merge pull request #5 from kwsantiago/kwsantiago/1-public-relay-with-blacklist
feat: Add blacklist support for public relays

adds a simple explicit blacklist configuration and exclude in the event handling
2025-08-05 23:05:22 +01:00
b74f4757e7 refactor: Simplify NWC protocol structures and update method handling
- cmd/lerproxy/app/bufpool.go
  - Removed bufferPool-related code and `Pool` struct

- cmd/nwcclient/main.go
  - Renamed `Method` to `Capability` for clarity in method handling

- pkg/utils/values/values.go
  - Added utility functions to return pointers for various types

- pkg/utils/pointers/pointers.go
  - Revised documentation to reference `utils/values` package for pointer utilities

- pkg/protocol/nwc/types.go
  - Replaced redundant types and structures with simplified versions
  - Introduced dedicated structs for `MakeInvoice`, `PayInvoice`, and related results
  - Refactored `Transaction` and its fields for consistent type usage

- pkg/protocol/nwc/uri.go
  - Added `ParseConnectionURI` function for URI parsing and validation

- pkg/protocol/nwc/client.go
  - Refactored `Client` struct to improve key management and relay handling
  - Introduced `Request` struct for generic method invocation payloads
2025-08-05 20:18:32 +01:00
2d0ebfe032 Merge remote-tracking branch 'upstream/main' into kwsantiago/1-public-relay-with-blacklist 2025-08-05 14:15:02 -04:00
fff61ceca1 fmt 2025-08-05 14:09:15 -04:00
b7b7dc7353 feat: Add blacklist support for public relays 2025-08-05 14:09:01 -04:00
996fb3aeb7 Merge pull request #4 from kwsantiago/kwsantiago/benchmark
feat: Add Relay Performance Benchmark Tool
2025-08-05 18:19:28 +01:00
b9a713d81d simple performance benchmark tool 2025-08-05 10:54:53 -04:00
1e6ce84e26 update spider seeds, set spider to fetch in spider frequency time window 2025-08-05 06:36:19 +01:00
0361f3843a add enviroment variables information to readme.adoc 2025-08-05 06:28:27 +01:00
4317e8ba4a updated readme to be current 2025-08-05 06:17:14 +01:00
9094f36d6e updated readme to be current 2025-08-05 06:15:13 +01:00
9314467f55 bump to v0.4.13 and Enable Second Degree Follows Spidering for Follows if directory is on
Files Changed:
- pkg/version/version
    - Updated the version number from v0.4.12 to v0.4.13
- pkg/app/relay/spider.go
    - Enabled second degree of follows spidering for directory events by adding kind.FollowList if `SpiderType` is 'directory' and `SpiderSecondDegree` is not set
2025-08-05 05:58:42 +01:00
19e6520587 Bump Version: Update to v0.4.12 and Enable Second Degree Follows Spidering
Files Changed:
- pkg/version/version
    - Updated the version number from v0.4.11 to v0.4.12
- pkg/app/relay/spider.go
    - Enabled second degree of follows spidering for directory events if `SpiderType` is 'directory' and `SpiderSecondDegree` is not set
    - Added kind.MuteList to the kinds being spidered in second degree follows mode
2025-08-05 05:24:40 +01:00
9e59a6c315 Version update to 0.4.11 and enable spidering of muted relays in follows mode
Files changed:

- pkg/version/version
    - Updated the version number from v0.4.10 to v0.4.11
- pkg/app/relay/spider.go
    - Added kind.MuteList to the kinds being spidered in follows mode for non-directory events
2025-08-05 05:10:28 +01:00
9449435c65 Update configuration and add spidering feature
Files changed:

- pkg/app/config/config.go
    - Added new field `SpiderSecondDegree` for enabling second degree of follows spidering
- pkg/app/relay/spider.go
    - Modified the logic to enable spidering the second degree of follows for non-directory events if `ORLY_SPIDER_TYPE` is set to 'follows' or 'directory', and `SpiderSecondDegree` is set to true
- pkg/version/version
    - Updated version number from v0.4.8 to v0.4.10
2025-08-05 04:58:52 +01:00
df8e66d9a7 Implement spidering improvements
- pkg/app/relay/server.go
  - Modified the time ticker used for spidering with a custom duration value
  - Added a log message to indicate when the spider is running
- pkg/app/config/config.go
  - Added a new configuration parameter `SpiderTime` of type `time.Duration` to specify how often the spider should run
2025-08-05 04:39:48 +01:00
96eab2270d actually implement returning events, and cap at 512 2025-08-04 21:53:16 +01:00
c0bd7d8da3 skip init of relay secret key if there isn't one 2025-08-04 21:27:26 +01:00
1ffb7afb01 implement first draft of nwc client and cli 2025-08-04 14:54:15 +01:00
ffa9d85ba5 Fix error handling and logging in wallet service
- pkg/protocol/nwc/wallet_service.go
  - Added import for "orly.dev/pkg/utils/log"
  - Replaced fmt.Printf calls with log.E.F for consistent error logging
  - Simplified error handling using chk.E instead of explicit if err != nil checks
2025-08-03 11:39:19 +01:00
1223b1b20e implement nwc based on translation from js-sdk, refactor encryption to use bytes 2025-08-03 11:20:02 +01:00
deb56664e2 Update spider seeds and bump version to v0.4.6
- pkg/app/config/config.go
  - Updated default SpiderSeeds list with new relay URL
- pkg/version/version
  - Bumped version number from v0.4.5 to v0.4.6
2025-08-02 14:03:15 +01:00
1641d18993 Add IP blocking with authentication-based unblocking and offense tracking
- pkg/protocol/socketapi/handleAuth.go
  - Added import for "orly.dev/pkg/utils/iptracker"
  - Added logic to call iptracker.Global.Authenticate() on successful authentication
- pkg/protocol/openapi/event.go
  - Added logic to call iptracker.Global.Authenticate() on successful authentication
- pkg/utils/iptracker/iptracker.go
  - Introduced offense tracking and block duration calculation based on offense count
  - Added Authenticate method to remove blocks upon successful authentication
  - Modified isBlockedNoLock to always return true when an IP is blocked
  - Added HasBlockDurationPassed, GetBlockDuration, and Reset methods
- pkg/version/version
  - Bumped version number from v0.4.4 to v0.4.5
2025-08-02 12:30:47 +01:00
eab5d236db Add IP blocking based on failed authentication attempts
- pkg/protocol/socketapi/socketapi.go
  - Added import for "orly.dev/pkg/utils/iptracker"
  - Added logic to check if an IP is blocked and reject the connection if it is
- pkg/protocol/socketapi/handleEvent.go
  - Added imports for "orly.dev/pkg/utils/iptracker" and "time"
  - Added logic to check if an IP is blocked, send a notice to the client, and close the connection if it is
  - Added logic to record failed authentication attempts and block IPs that exceed the threshold
- pkg/protocol/openapi/event.go
  - Added imports for "orly.dev/pkg/utils/iptracker" and "time"
  - Added logic to check if an IP is blocked and return a forbidden error if it is
  - Added logic to record failed authentication attempts and return appropriate errors based on whether the IP is blocked or not
- pkg/utils/iptracker/iptracker.go
  - Created new package with functionality to track and block IPs based on failed authentication attempts
- pkg/version/version
  - Bumped version number from v0.4.3 to v0.4.4
2025-08-02 12:01:37 +01:00
f3e7188816 Bump version to v0.4.3
- pkg/version/version
  - Updated version number from v0.4.2 to v0.4.3
2025-08-02 11:44:05 +01:00
39957c2ebf implement correct handling when saving events to search for owner/peer deletes 2025-08-02 11:43:33 +01:00
4528d44fc7 Add owner check for deletion events
- pkg/protocol/socketapi/handleEvent.go
  - Added variable to track if the delete event's author is an owner
  - Modified checks to allow owners to delete events from other owners
  - Updated error messages for clarity and consistency

- bumped to version v0.4.2
2025-08-01 19:54:40 +01:00
7b19db5806 Remove redundant log statements and imports from spider-fetch.go
- pkg/app/relay/spider-fetch.go
  - Removed import of "orly.dev/pkg/utils/lol"
  - Removed log.I.S(ev) statement
  - Removed log.T.C(...) and log.I.F("saved event: %0x", ev.ID) statements
  - Removed redundant comments and whitespace
2025-08-01 09:35:06 +01:00
14d4417aec Bump version and add spider type configuration
pkg/version/version
- Updated version number from v0.4.0 to v0.4.1

pkg/app/config/config.go
- Added new config field `SpiderType` with default value "directory"

pkg/app/relay/peers.go
- Added check to skip empty addresses before processing peer information

pkg/app/relay/spider.go
- Modified spider fetch logic to conditionally execute based on spider type
- Added support for different kinds of events based on spider type
2025-08-01 08:52:22 +01:00
bdda37732c Remove redundant log statements from addEvent.go
- pkg/app/relay/addEvent.go
  - Removed log.I.S(pubkeys) statement
  - Removed log.I.F("sending to replica %s", a) statement
2025-07-31 22:37:34 +01:00
0024611179 Bump version to v0.4.0
- pkg/version/version
  - Updated version number from v0.3.0 to v0.4.0
2025-07-31 22:09:06 +01:00
699ba0554e Add event logging and replica handling improvements
- pkg/app/relay/server-publish.go
  - Added log statement for saved events
- pkg/app/relay/addEvent.go
  - Added support for multiple pubkeys
  - Modified logic to skip sending events back to replicas that already received them
  - Added header with pubkeys to prevent unnecessary resending
- pkg/protocol/openapi/event.go
  - Added parsing of X-Pubkeys header to avoid resending events
  - Updated AddEvent call to use pubkeys parameter
- pkg/protocol/httpauth/nip98auth.go
  - Removed log statement for nip-98 http auth event
- pkg/interfaces/server/server.go
  - Updated AddEvent method signature to accept pubkeys instead of pubkey
- pkg/protocol/httpauth/validate.go
  - Removed log statement for tolerance value
2025-07-31 22:08:25 +01:00
c62d685fa4 implement cluster replication
todo: need to add chain of senders in a header to a header to prevent unnecessary sends
2025-07-31 15:55:07 +01:00
6935575654 reduce the batch size to avoid missing things 2025-07-30 16:08:52 +01:00
80043b46b3 make the spider fetch everything of the last hour from all followeds 2025-07-30 16:05:55 +01:00
c68654dccc Fix documentation and code formatting issues
- cmd/lerproxy/README.md
  - Fixed grammar and punctuation in note about certificate selection
  - Improved clarity in instructions for appending intermediate certificates
  - Corrected wording in explanation of CLI tool issues with certificates

- cmd/lerproxy/app/reverse.go
  - Split long line for X-Forwarded-Host header comment to improve readability

- pkg/app/relay/server-publish.go
  - Reformatted comment block for function description to fit within line length limits
  - Added comments explaining why certain events aren't deleted from the database
  - don't delete any kind of directory events

- pkg/protocol/socketapi/handleReq.go
  - Split long lines for better readability in error message and log statements
  - Improved formatting of the notice envelope message

- pkg/app/config/config.go
  - Added new configuration fields for relay cluster replication authentication

- pkg/app/relay/publish/publisher.go
  - Removed redundant package imports and logging statements
2025-07-30 15:45:47 +01:00
72c6d16739 refactor lerproxy to be better laid out 2025-07-29 19:59:39 +01:00
366d35ec28 Refactor packages into main and remove redundant package declarations
- cmd/lerproxy/reverse/proxy.go
  - Changed package from 'reverse' to 'main'
  - Added standardized Forwarded header according to RFC 7239
  - Set X-Forwarded-* headers for backward compatibility
  - Updated URL path joining logic
- cmd/lerproxy/hsts/proxy.go
  - Changed package from 'hsts' to 'main'
  - Updated HSTS header setting
- cmd/lerproxy/timeout/conn.go
  - Changed package from 'timeout' to 'main'
- cmd/lerproxy/util/u.go
  - Changed package from 'util' to 'main'
- cmd/lerproxy/buf/bufpool.go
  - Changed package from 'buf' to 'main'
- cmd/lerproxy/main.go
  - Removed redundant package declarations and imports
  - Renamed struct and function names to follow camelCase convention
  - Updated server setup logic
- cmd/lerproxy/tcpkeepalive/listener.go
  - Changed package from 'tcpkeepalive' to 'main'
2025-07-29 19:00:41 +01:00
c36cec44c4 upgrade vainstr to actually using fast cgo, improve result format 2025-07-29 17:20:27 +01:00
c91a283520 implement fully working listener/subscribe/unsubscribe publisher 2025-07-28 05:05:56 +01:00
bb0693f455 Bump version to v0.2.20
- pkg/version/version
  - Bumped version from v0.2.19 to v0.2.20
2025-07-27 11:46:48 +01:00
0d7943be89 fixed bug with client message buffer causing overwriting of tag slices 2025-07-27 11:46:04 +01:00
978d9b88cd Refactor JSON marshaling with whitespace formatting in event encoder
- pkg/encoders/event/readwriter.go
  - Reformatted comment block for `MarshalWriteWithWhitespace` method
  - Removed redundant blank lines between code blocks
  - Improved alignment and spacing of comments
2025-07-25 21:37:35 +01:00
bbfb9b7300 Improve documentation for Events endpoint and update its description
- pkg/protocol/openapi/events.go
  - Updated the description of the Events HTTP API method to clarify that it uses a standard NIP-01 filter and returns events as a JSON array of event objects.
2025-07-25 20:54:13 +01:00
5b06906673 improve documentation and add events http endpoint for filter queries 2025-07-25 20:51:25 +01:00
f5c3da9bc3 Add systemd service file for lerproxy
- cmd/lerproxy/lerproxy.service
  - Created new file with systemd unit configuration to run lerproxy as a service
  - Defined [Unit], [Service], and [Install] sections with appropriate settings and dependencies
2025-07-25 17:46:47 +01:00
c608e1075b Remove authed flag and use authed pubkey instead
- pkg/protocol/ws/listener.go
  - Removed `isAuthed` field from Listener struct
  - Modified `IsAuthed()` method to check if `authedPubkey` is non-empty
  - Removed `SetAuthed()` method
- pkg/encoders/event/readwriter.go
  - Created new file with implementation of `MarshalWrite` and `UnmarshalRead` methods for event encoding/decoding
- pkg/protocol/openapi/events.go
  - Created new file with implementation of the Events HTTP API method
- pkg/protocol/socketapi/handleReq.go
  - Modified logging to display authed pubkey
  - Updated comment regarding authentication status
- pkg/encoders/bytesbuf/pool.go
  - Created new package for concurrent-safe buffer pool
- pkg/protocol/openapi/event.go
  - Removed import of `lol` package
  - Removed tracing logic around `RegisterEvent` function
- pkg/encoders/bytesbuf/pool_test.go
  - Created new test file for buffer pool implementation
2025-07-25 13:36:49 +01:00
5237fb1a1f Add notice envelope handling for relay access control
- pkg/protocol/socketapi/handleReq.go
  - Added import of "orly.dev/pkg/encoders/bech32encoding" and "orly.dev/pkg/encoders/envelopes/noticeenvelope"
  - Added logic to send a notice envelope when public read access is not allowed, listing owners' npub keys
- pkg/protocol/socketapi/handleEvent.go
  - Added import of "orly.dev/pkg/encoders/bech32encoding" and "orly.dev/pkg/encoders/envelopes/noticeenvelope"
  - Added logic to send a notice envelope when write access is not allowed, listing owners' npub keys
- pkg/version/version
  - Bumped version from v0.2.18 to v0.2.19
- pkg/interfaces/server/server.go
  - Added `OwnersPubkeys` method to return owners' public keys
- pkg/app/relay/owners-pubkeys.go
  - Created new file with implementation of `OwnersPubkeys` method for relay server
2025-07-24 09:31:11 +01:00
6901950059 Implement authentication handling in socket API event processing
- pkg/protocol/socketapi/handleAuth.go
  - Added comment block for handling pending events (currently commented out)
- pkg/protocol/ws/listener.go
  - Added import of "orly.dev/pkg/encoders/event"
  - Added `pendingEvent` field to Listener struct
  - Added `SetPendingEvent` and `GetPendingEvent` methods
- pkg/protocol/socketapi/socketapi.go
  - Added import of "orly.dev/pkg/encoders/envelopes/authenvelope"
  - Added authentication challenge logic including logging, RequestAuth call, and envelope writing
- pkg/protocol/socketapi/handleReq.go
  - Removed import of "orly.dev/pkg/encoders/envelopes/okenvelope"
  - Modified auth handling logic to send closed envelope with reason if auth is required and client is not authenticated
- pkg/protocol/socketapi/handleEvent.go
  - Removed import of "orly.dev/pkg/encoders/reason"
  - Added authentication challenge logic including logging, RequestAuth call, Ok.AuthRequired method, and envelope writing
- pkg/version/version
  - Bumped version from v0.2.17 to v0.2.18
2025-07-24 00:36:46 +01:00
251fc17933 Add authentication challenge handling in socket API event processing
- pkg/protocol/socketapi/handleEvent.go
  - Removed comment line
  - Added return statement to handle authentication challenge logic
2025-07-24 00:08:47 +01:00
fdb9e18b03 Add auth handling for relay and socket API
- pkg/app/relay/handleRelayinfo.go
  - Removed logging of info variable
- pkg/protocol/socketapi/socketapi.go
  - Removed import of "orly.dev/pkg/encoders/envelopes/authenvelope"
  - Removed authentication logic including challenge sending and logging
- pkg/app/relay/auth.go
  - Removed import of "orly.dev/pkg/utils/lol"
  - Removed tracing logic around ServiceURL function
- pkg/protocol/socketapi/handleReq.go
  - Added logging for auth status
  - Added logic to send authentication challenge if required and client is not authenticated
- pkg/protocol/socketapi/handleEvent.go
  - Added import of "orly.dev/pkg/encoders/envelopes/authenvelope" and "orly.dev/pkg/encoders/reason"
  - Added logging for auth status
  - Added logic to send authentication challenge if required and client is not authenticated
- pkg/version/version
  - Bumped version from v0.2.16 to v0.2.17
2025-07-24 00:04:26 +01:00
67552edf04 Bump version from v0.2.13 to v0.2.16
- pkg/version/version
  - Bumped version from v0.2.13 to v0.2.16
2025-07-23 23:25:56 +01:00
f25b760d84 Fix logging in socket API handling for close errors and privilege checks
- pkg/protocol/socketapi/socketapi.go
  - Added `message` parameter to log statement for unexpected close error
  - Updated format string to include message in log output
- pkg/protocol/socketapi/handleReq.go
  - Modified log message for not privileged event
  - Changed format string to be more descriptive and include client and event pubkeys
2025-07-23 23:24:09 +01:00
170 changed files with 9155 additions and 3003 deletions

111
.github/workflows/go.yml vendored Normal file
View File

@@ -0,0 +1,111 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
#
# Release Process:
# 1. Update the version in pkg/version/version file (e.g., v1.2.3)
# 2. Create and push a tag matching the version:
# git tag v1.2.3
# git push origin v1.2.3
# 3. The workflow will automatically:
# - Build binaries for multiple platforms (Linux, macOS, Windows)
# - Create a GitHub release with the binaries
# - Generate release notes
name: Go
on:
push:
branches: [ "main" ]
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.24'
- name: Install libsecp256k1
run: ./scripts/ubuntu_install_libsecp256k1.sh
- name: Build with cgo
run: go build -v ./...
- name: Test with cgo
run: go test -v ./...
- name: Set CGO off
run: echo "CGO_ENABLED=0" >> $GITHUB_ENV
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.24'
- name: Install libsecp256k1
run: ./scripts/ubuntu_install_libsecp256k1.sh
- name: Build Release Binaries
if: startsWith(github.ref, 'refs/tags/v')
run: |
# Extract version from tag (e.g., v1.2.3 -> 1.2.3)
VERSION=${GITHUB_REF#refs/tags/v}
echo "Building release binaries for version $VERSION"
# Create directory for binaries
mkdir -p release-binaries
# Build for different platforms
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build --ldflags '-extldflags "-static"' -o release-binaries/orly-${VERSION}-linux-amd64 .
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build --ldflags '-extldflags "-static"' -o release-binaries/orly-${VERSION}-linux-arm64 .
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-darwin-amd64 .
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-darwin-arm64 .
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/orly-${VERSION}-windows-amd64.exe .
# Build cmd executables
for cmd in lerproxy nauth nurl vainstr walletcli; do
echo "Building $cmd"
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build --ldflags '-extldflags "-static"' -o release-binaries/${cmd}-${VERSION}-linux-amd64 ./cmd/${cmd}
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build --ldflags '-extldflags "-static"' -o release-binaries/${cmd}-${VERSION}-linux-arm64 ./cmd/${cmd}
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/${cmd}-${VERSION}-darwin-amd64 ./cmd/${cmd}
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o release-binaries/${cmd}-${VERSION}-darwin-arm64 ./cmd/${cmd}
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o release-binaries/${cmd}-${VERSION}-windows-amd64.exe ./cmd/${cmd}
done
# Create checksums
cd release-binaries
sha256sum * > SHA256SUMS.txt
cd ..
- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v1
with:
files: release-binaries/*
draft: false
prerelease: false
generate_release_notes: true

2
.gitignore vendored
View File

@@ -104,3 +104,5 @@ pkg/database/testrealy
/.idea/material_theme_project_new.xml
/.idea/orly.iml
/.idea/go.imports.xml
/.idea/inspectionProfiles/Project_Default.xml
/.idea/.name

View File

@@ -0,0 +1,173 @@
# Orly Relay Benchmark Results
## Test Environment
- **Date**: August 5, 2025
- **Relay**: Orly v0.4.14
- **Port**: 3334 (WebSocket)
- **System**: Linux 5.15.0-151-generic
- **Storage**: BadgerDB v4
## Benchmark Test Results
### Test 1: Basic Performance (1,000 events, 1KB each)
**Parameters:**
- Events: 1,000
- Event size: 1,024 bytes
- Concurrent publishers: 5
- Queries: 50
**Results:**
```
Publish Performance:
Events Published: 1,000
Total Data: 4.01 MB
Duration: 1.769s
Rate: 565.42 events/second
Bandwidth: 2.26 MB/second
Query Performance:
Queries Executed: 50
Events Returned: 2,000
Duration: 3.058s
Rate: 16.35 queries/second
Avg Events/Query: 40.00
```
### Test 2: Medium Load (10,000 events, 2KB each)
**Parameters:**
- Events: 10,000
- Event size: 2,048 bytes
- Concurrent publishers: 10
- Queries: 100
**Results:**
```
Publish Performance:
Events Published: 10,000
Total Data: 76.81 MB
Duration: 598.301ms
Rate: 16,714.00 events/second
Bandwidth: 128.38 MB/second
Query Performance:
Queries Executed: 100
Events Returned: 4,000
Duration: 8.923s
Rate: 11.21 queries/second
Avg Events/Query: 40.00
```
### Test 3: High Concurrency (50,000 events, 512 bytes each)
**Parameters:**
- Events: 50,000
- Event size: 512 bytes
- Concurrent publishers: 50
- Queries: 200
**Results:**
```
Publish Performance:
Events Published: 50,000
Total Data: 108.63 MB
Duration: 2.368s
Rate: 21,118.66 events/second
Bandwidth: 45.88 MB/second
Query Performance:
Queries Executed: 200
Events Returned: 8,000
Duration: 36.146s
Rate: 5.53 queries/second
Avg Events/Query: 40.00
```
### Test 4: Large Events (5,000 events, 10KB each)
**Parameters:**
- Events: 5,000
- Event size: 10,240 bytes
- Concurrent publishers: 10
- Queries: 50
**Results:**
```
Publish Performance:
Events Published: 5,000
Total Data: 185.26 MB
Duration: 934.328ms
Rate: 5,351.44 events/second
Bandwidth: 198.28 MB/second
Query Performance:
Queries Executed: 50
Events Returned: 2,000
Duration: 9.982s
Rate: 5.01 queries/second
Avg Events/Query: 40.00
```
### Test 5: Query-Only Performance (500 queries)
**Parameters:**
- Skip publishing phase
- Queries: 500
- Query limit: 100
**Results:**
```
Query Performance:
Queries Executed: 500
Events Returned: 20,000
Duration: 1m14.384s
Rate: 6.72 queries/second
Avg Events/Query: 40.00
```
## Performance Summary
### Publishing Performance
| Metric | Best Result | Test Configuration |
|--------|-------------|-------------------|
| **Peak Event Rate** | 21,118.66 events/sec | 50 concurrent publishers, 512-byte events |
| **Peak Bandwidth** | 198.28 MB/sec | 10 concurrent publishers, 10KB events |
| **Optimal Balance** | 16,714.00 events/sec @ 128.38 MB/sec | 10 concurrent publishers, 2KB events |
### Query Performance
| Query Type | Avg Rate | Notes |
|------------|----------|--------|
| **Light Load** | 16.35 queries/sec | 50 queries after 1K events |
| **Medium Load** | 11.21 queries/sec | 100 queries after 10K events |
| **Heavy Load** | 5.53 queries/sec | 200 queries after 50K events |
| **Sustained** | 6.72 queries/sec | 500 continuous queries |
## Key Findings
1. **Optimal Concurrency**: The relay performs best with 10-50 concurrent publishers, achieving rates of 16,000-21,000 events/second.
2. **Event Size Impact**:
- Smaller events (512B-2KB) achieve higher event rates
- Larger events (10KB) achieve higher bandwidth utilization but lower event rates
3. **Query Performance**: Query performance varies with database size:
- Fresh database: ~16 queries/second
- After 50K events: ~6 queries/second
4. **Scalability**: The relay maintains consistent performance up to 50 concurrent connections and can sustain 21,000+ events/second under optimal conditions.
## Query Filter Distribution
The benchmark tested 5 different query patterns in rotation:
1. Query by kind (20%)
2. Query by time range (20%)
3. Query by tag (20%)
4. Query by author (20%)
5. Complex queries with multiple conditions (20%)
All query types showed similar performance characteristics, indicating well-balanced indexing.

112
cmd/benchmark/README.md Normal file
View File

@@ -0,0 +1,112 @@
# Orly Relay Benchmark Tool
A performance benchmarking tool for Nostr relays that tests both event ingestion speed and query performance.
## Quick Start (Simple Version)
The repository includes a simple standalone benchmark tool that doesn't require the full Orly dependencies:
```bash
# Build the simple benchmark
go build -o benchmark-simple ./benchmark_simple.go
# Run with default settings
./benchmark-simple
# Or use the convenience script
chmod +x run_benchmark.sh
./run_benchmark.sh --relay ws://localhost:7447 --events 10000
```
## Features
- **Event Publishing Benchmark**: Tests how fast a relay can accept and store events
- **Query Performance Benchmark**: Tests various filter types and query speeds
- **Concurrent Publishing**: Supports multiple concurrent publishers to stress test the relay
- **Detailed Metrics**: Reports events/second, bandwidth usage, and query performance
## Usage
```bash
# Build the tool
go build -o benchmark ./cmd/benchmark
# Run a full benchmark (publish and query)
./benchmark -relay ws://localhost:7447 -events 10000 -queries 100
# Benchmark only publishing
./benchmark -relay ws://localhost:7447 -events 50000 -concurrency 20 -skip-query
# Benchmark only querying
./benchmark -relay ws://localhost:7447 -queries 500 -skip-publish
# Use custom event sizes
./benchmark -relay ws://localhost:7447 -events 10000 -size 2048
```
## Options
- `-relay`: Relay URL to benchmark (default: ws://localhost:7447)
- `-events`: Number of events to publish (default: 10000)
- `-size`: Average size of event content in bytes (default: 1024)
- `-concurrency`: Number of concurrent publishers (default: 10)
- `-queries`: Number of queries to execute (default: 100)
- `-query-limit`: Limit for each query (default: 100)
- `-skip-publish`: Skip the publishing phase
- `-skip-query`: Skip the query phase
- `-v`: Enable verbose output
## Query Types Tested
The benchmark tests various query patterns:
1. Query by kind
2. Query by time range (last hour)
3. Query by tag (p tags)
4. Query by author
5. Complex queries with multiple conditions
## Output
The tool provides detailed metrics including:
**Publish Performance:**
- Total events published
- Total data transferred
- Publishing rate (events/second)
- Bandwidth usage (MB/second)
**Query Performance:**
- Total queries executed
- Total events returned
- Query rate (queries/second)
- Average events per query
## Example Output
```
Publishing 10000 events to ws://localhost:7447...
Published 1000 events...
Published 2000 events...
...
Querying events from ws://localhost:7447...
Executed 20 queries...
Executed 40 queries...
...
=== Benchmark Results ===
Publish Performance:
Events Published: 10000
Total Data: 12.34 MB
Duration: 5.2s
Rate: 1923.08 events/second
Bandwidth: 2.37 MB/second
Query Performance:
Queries Executed: 100
Events Returned: 4523
Duration: 2.1s
Rate: 47.62 queries/second
Avg Events/Query: 45.23
```

View File

@@ -0,0 +1,304 @@
// +build ignore
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
"math/rand"
"net/url"
"sync"
"sync/atomic"
"time"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
)
// Simple event structure for benchmarking
type Event struct {
ID string `json:"id"`
Pubkey string `json:"pubkey"`
CreatedAt int64 `json:"created_at"`
Kind int `json:"kind"`
Tags [][]string `json:"tags"`
Content string `json:"content"`
Sig string `json:"sig"`
}
// Generate a test event
func generateTestEvent(size int) *Event {
content := make([]byte, size)
rand.Read(content)
// Generate random pubkey and sig
pubkey := make([]byte, 32)
sig := make([]byte, 64)
rand.Read(pubkey)
rand.Read(sig)
ev := &Event{
Pubkey: hex.EncodeToString(pubkey),
CreatedAt: time.Now().Unix(),
Kind: 1,
Tags: [][]string{},
Content: string(content),
Sig: hex.EncodeToString(sig),
}
// Generate ID (simplified)
serialized, _ := json.Marshal([]interface{}{
0,
ev.Pubkey,
ev.CreatedAt,
ev.Kind,
ev.Tags,
ev.Content,
})
hash := sha256.Sum256(serialized)
ev.ID = hex.EncodeToString(hash[:])
return ev
}
func publishEvents(relayURL string, count int, size int, concurrency int) (int64, int64, time.Duration, error) {
u, err := url.Parse(relayURL)
if err != nil {
return 0, 0, 0, err
}
var publishedEvents atomic.Int64
var publishedBytes atomic.Int64
var wg sync.WaitGroup
eventsPerWorker := count / concurrency
extraEvents := count % concurrency
start := time.Now()
for i := 0; i < concurrency; i++ {
wg.Add(1)
eventsToPublish := eventsPerWorker
if i < extraEvents {
eventsToPublish++
}
go func(workerID int, eventCount int) {
defer wg.Done()
// Connect to relay
ctx := context.Background()
conn, _, _, err := ws.Dial(ctx, u.String())
if err != nil {
log.Printf("Worker %d: connection error: %v", workerID, err)
return
}
defer conn.Close()
// Publish events
for j := 0; j < eventCount; j++ {
ev := generateTestEvent(size)
// Create EVENT message
msg, _ := json.Marshal([]interface{}{"EVENT", ev})
err := wsutil.WriteClientMessage(conn, ws.OpText, msg)
if err != nil {
log.Printf("Worker %d: write error: %v", workerID, err)
continue
}
publishedEvents.Add(1)
publishedBytes.Add(int64(len(msg)))
// Read response (OK or error)
_, _, err = wsutil.ReadServerData(conn)
if err != nil {
log.Printf("Worker %d: read error: %v", workerID, err)
}
}
}(i, eventsToPublish)
}
wg.Wait()
duration := time.Since(start)
return publishedEvents.Load(), publishedBytes.Load(), duration, nil
}
func queryEvents(relayURL string, queries int, limit int) (int64, int64, time.Duration, error) {
u, err := url.Parse(relayURL)
if err != nil {
return 0, 0, 0, err
}
ctx := context.Background()
conn, _, _, err := ws.Dial(ctx, u.String())
if err != nil {
return 0, 0, 0, err
}
defer conn.Close()
var totalQueries int64
var totalEvents int64
start := time.Now()
for i := 0; i < queries; i++ {
// Generate various filter types
var filter map[string]interface{}
switch i % 5 {
case 0:
// Query by kind
filter = map[string]interface{}{
"kinds": []int{1},
"limit": limit,
}
case 1:
// Query by time range
now := time.Now().Unix()
filter = map[string]interface{}{
"since": now - 3600,
"until": now,
"limit": limit,
}
case 2:
// Query by tag
filter = map[string]interface{}{
"#p": []string{hex.EncodeToString(randBytes(32))},
"limit": limit,
}
case 3:
// Query by author
filter = map[string]interface{}{
"authors": []string{hex.EncodeToString(randBytes(32))},
"limit": limit,
}
case 4:
// Complex query
now := time.Now().Unix()
filter = map[string]interface{}{
"kinds": []int{1, 6},
"authors": []string{hex.EncodeToString(randBytes(32))},
"since": now - 7200,
"limit": limit,
}
}
// Send REQ
subID := fmt.Sprintf("bench-%d", i)
msg, _ := json.Marshal([]interface{}{"REQ", subID, filter})
err := wsutil.WriteClientMessage(conn, ws.OpText, msg)
if err != nil {
log.Printf("Query %d: write error: %v", i, err)
continue
}
// Read events until EOSE
eventCount := 0
for {
data, err := wsutil.ReadServerText(conn)
if err != nil {
log.Printf("Query %d: read error: %v", i, err)
break
}
var msg []interface{}
if err := json.Unmarshal(data, &msg); err != nil {
continue
}
if len(msg) < 2 {
continue
}
msgType, ok := msg[0].(string)
if !ok {
continue
}
switch msgType {
case "EVENT":
eventCount++
case "EOSE":
goto done
}
}
done:
// Send CLOSE
closeMsg, _ := json.Marshal([]interface{}{"CLOSE", subID})
wsutil.WriteClientMessage(conn, ws.OpText, closeMsg)
totalQueries++
totalEvents += int64(eventCount)
if totalQueries%20 == 0 {
fmt.Printf(" Executed %d queries...\n", totalQueries)
}
}
duration := time.Since(start)
return totalQueries, totalEvents, duration, nil
}
func randBytes(n int) []byte {
b := make([]byte, n)
rand.Read(b)
return b
}
func main() {
var (
relayURL = flag.String("relay", "ws://localhost:7447", "Relay URL to benchmark")
eventCount = flag.Int("events", 10000, "Number of events to publish")
eventSize = flag.Int("size", 1024, "Average size of event content in bytes")
concurrency = flag.Int("concurrency", 10, "Number of concurrent publishers")
queryCount = flag.Int("queries", 100, "Number of queries to execute")
queryLimit = flag.Int("query-limit", 100, "Limit for each query")
skipPublish = flag.Bool("skip-publish", false, "Skip publishing phase")
skipQuery = flag.Bool("skip-query", false, "Skip query phase")
)
flag.Parse()
fmt.Printf("=== Nostr Relay Benchmark ===\n\n")
// Phase 1: Publish events
if !*skipPublish {
fmt.Printf("Publishing %d events to %s...\n", *eventCount, *relayURL)
published, bytes, duration, err := publishEvents(*relayURL, *eventCount, *eventSize, *concurrency)
if err != nil {
log.Fatalf("Publishing failed: %v", err)
}
fmt.Printf("\nPublish Performance:\n")
fmt.Printf(" Events Published: %d\n", published)
fmt.Printf(" Total Data: %.2f MB\n", float64(bytes)/1024/1024)
fmt.Printf(" Duration: %s\n", duration)
fmt.Printf(" Rate: %.2f events/second\n", float64(published)/duration.Seconds())
fmt.Printf(" Bandwidth: %.2f MB/second\n", float64(bytes)/duration.Seconds()/1024/1024)
}
// Phase 2: Query events
if !*skipQuery {
fmt.Printf("\nQuerying events from %s...\n", *relayURL)
queries, events, duration, err := queryEvents(*relayURL, *queryCount, *queryLimit)
if err != nil {
log.Fatalf("Querying failed: %v", err)
}
fmt.Printf("\nQuery Performance:\n")
fmt.Printf(" Queries Executed: %d\n", queries)
fmt.Printf(" Events Returned: %d\n", events)
fmt.Printf(" Duration: %s\n", duration)
fmt.Printf(" Rate: %.2f queries/second\n", float64(queries)/duration.Seconds())
fmt.Printf(" Avg Events/Query: %.2f\n", float64(events)/float64(queries))
}
}

352
cmd/benchmark/main.go Normal file
View File

@@ -0,0 +1,352 @@
package main
import (
"flag"
"fmt"
"os"
"sync"
"sync/atomic"
"time"
"lukechampine.com/frand"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/filter"
"orly.dev/pkg/encoders/kind"
"orly.dev/pkg/encoders/kinds"
"orly.dev/pkg/encoders/tag"
"orly.dev/pkg/encoders/tags"
"orly.dev/pkg/encoders/text"
"orly.dev/pkg/encoders/timestamp"
"orly.dev/pkg/protocol/ws"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/utils/lol"
)
type BenchmarkResults struct {
EventsPublished int64
EventsPublishedBytes int64
PublishDuration time.Duration
PublishRate float64
PublishBandwidth float64
QueriesExecuted int64
QueryDuration time.Duration
QueryRate float64
EventsReturned int64
}
func main() {
var (
relayURL = flag.String(
"relay", "ws://localhost:7447", "Relay URL to benchmark",
)
eventCount = flag.Int("events", 10000, "Number of events to publish")
eventSize = flag.Int(
"size", 1024, "Average size of event content in bytes",
)
concurrency = flag.Int(
"concurrency", 10, "Number of concurrent publishers",
)
queryCount = flag.Int("queries", 100, "Number of queries to execute")
queryLimit = flag.Int("query-limit", 100, "Limit for each query")
skipPublish = flag.Bool("skip-publish", false, "Skip publishing phase")
skipQuery = flag.Bool("skip-query", false, "Skip query phase")
verbose = flag.Bool("v", false, "Verbose output")
)
flag.Parse()
if *verbose {
lol.SetLogLevel("trace")
}
c := context.Bg()
results := &BenchmarkResults{}
// Phase 1: Publish events
if !*skipPublish {
fmt.Printf("Publishing %d events to %s...\n", *eventCount, *relayURL)
if err := benchmarkPublish(
c, *relayURL, *eventCount, *eventSize, *concurrency, results,
); chk.E(err) {
fmt.Fprintf(os.Stderr, "Error during publish benchmark: %v\n", err)
os.Exit(1)
}
}
// Phase 2: Query events
if !*skipQuery {
fmt.Printf("\nQuerying events from %s...\n", *relayURL)
if err := benchmarkQuery(
c, *relayURL, *queryCount, *queryLimit, results,
); chk.E(err) {
fmt.Fprintf(os.Stderr, "Error during query benchmark: %v\n", err)
os.Exit(1)
}
}
// Print results
printResults(results)
}
func benchmarkPublish(
c context.T, relayURL string, eventCount, eventSize, concurrency int,
results *BenchmarkResults,
) error {
// Generate signers for each concurrent publisher
signers := make([]*testSigner, concurrency)
for i := range signers {
signers[i] = newTestSigner()
}
// Track published events
var publishedEvents atomic.Int64
var publishedBytes atomic.Int64
var errors atomic.Int64
// Create wait group for concurrent publishers
var wg sync.WaitGroup
eventsPerPublisher := eventCount / concurrency
extraEvents := eventCount % concurrency
startTime := time.Now()
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func(publisherID int) {
defer wg.Done()
// Connect to relay
relay, err := ws.RelayConnect(c, relayURL)
if err != nil {
log.E.F("Publisher %d failed to connect: %v", publisherID, err)
errors.Add(1)
return
}
defer relay.Close()
// Calculate events for this publisher
eventsToPublish := eventsPerPublisher
if publisherID < extraEvents {
eventsToPublish++
}
signer := signers[publisherID]
// Publish events
for j := 0; j < eventsToPublish; j++ {
ev := generateEvent(signer, eventSize)
if err := relay.Publish(c, ev); err != nil {
log.E.F(
"Publisher %d failed to publish event: %v", publisherID,
err,
)
errors.Add(1)
continue
}
evBytes := ev.Marshal(nil)
publishedEvents.Add(1)
publishedBytes.Add(int64(len(evBytes)))
if publishedEvents.Load()%1000 == 0 {
fmt.Printf(
" Published %d events...\n", publishedEvents.Load(),
)
}
}
}(i)
}
wg.Wait()
duration := time.Since(startTime)
results.EventsPublished = publishedEvents.Load()
results.EventsPublishedBytes = publishedBytes.Load()
results.PublishDuration = duration
results.PublishRate = float64(results.EventsPublished) / duration.Seconds()
results.PublishBandwidth = float64(results.EventsPublishedBytes) / duration.Seconds() / 1024 / 1024 // MB/s
if errors.Load() > 0 {
fmt.Printf(
" Warning: %d errors occurred during publishing\n", errors.Load(),
)
}
return nil
}
func benchmarkQuery(
c context.T, relayURL string, queryCount, queryLimit int,
results *BenchmarkResults,
) error {
relay, err := ws.RelayConnect(c, relayURL)
if err != nil {
return fmt.Errorf("failed to connect to relay: %w", err)
}
defer relay.Close()
var totalEvents atomic.Int64
var totalQueries atomic.Int64
startTime := time.Now()
for i := 0; i < queryCount; i++ {
// Generate various filter types
var f *filter.F
switch i % 5 {
case 0:
// Query by kind
limit := uint(queryLimit)
f = &filter.F{
Kinds: kinds.New(kind.TextNote),
Limit: &limit,
}
case 1:
// Query by time range
now := timestamp.Now()
since := timestamp.New(now.I64() - 3600) // last hour
limit := uint(queryLimit)
f = &filter.F{
Since: since,
Until: now,
Limit: &limit,
}
case 2:
// Query by tag
limit := uint(queryLimit)
f = &filter.F{
Tags: tags.New(tag.New([]byte("p"), generateRandomPubkey())),
Limit: &limit,
}
case 3:
// Query by author
limit := uint(queryLimit)
f = &filter.F{
Authors: tag.New(generateRandomPubkey()),
Limit: &limit,
}
case 4:
// Complex query with multiple conditions
now := timestamp.Now()
since := timestamp.New(now.I64() - 7200)
limit := uint(queryLimit)
f = &filter.F{
Kinds: kinds.New(kind.TextNote, kind.Repost),
Authors: tag.New(generateRandomPubkey()),
Since: since,
Limit: &limit,
}
}
// Execute query
events, err := relay.QuerySync(c, f)
if err != nil {
log.E.F("Query %d failed: %v", i, err)
continue
}
totalEvents.Add(int64(len(events)))
totalQueries.Add(1)
if totalQueries.Load()%20 == 0 {
fmt.Printf(" Executed %d queries...\n", totalQueries.Load())
}
}
duration := time.Since(startTime)
results.QueriesExecuted = totalQueries.Load()
results.QueryDuration = duration
results.QueryRate = float64(results.QueriesExecuted) / duration.Seconds()
results.EventsReturned = totalEvents.Load()
return nil
}
func generateEvent(signer *testSigner, contentSize int) *event.E {
// Generate content with some variation
size := contentSize + frand.Intn(contentSize/2) - contentSize/4
if size < 10 {
size = 10
}
content := text.NostrEscape(nil, frand.Bytes(size))
ev := &event.E{
Pubkey: signer.Pub(),
Kind: kind.TextNote,
CreatedAt: timestamp.Now(),
Content: content,
Tags: generateRandomTags(),
}
if err := ev.Sign(signer); chk.E(err) {
panic(fmt.Sprintf("failed to sign event: %v", err))
}
return ev
}
func generateRandomTags() *tags.T {
t := tags.New()
// Add some random tags
numTags := frand.Intn(5)
for i := 0; i < numTags; i++ {
switch frand.Intn(3) {
case 0:
// p tag
t.AppendUnique(tag.New([]byte("p"), generateRandomPubkey()))
case 1:
// e tag
t.AppendUnique(tag.New([]byte("e"), generateRandomEventID()))
case 2:
// t tag
t.AppendUnique(
tag.New(
[]byte("t"),
[]byte(fmt.Sprintf("topic%d", frand.Intn(100))),
),
)
}
}
return t
}
func generateRandomPubkey() []byte {
return frand.Bytes(32)
}
func generateRandomEventID() []byte {
return frand.Bytes(32)
}
func printResults(results *BenchmarkResults) {
fmt.Println("\n=== Benchmark Results ===")
if results.EventsPublished > 0 {
fmt.Println("\nPublish Performance:")
fmt.Printf(" Events Published: %d\n", results.EventsPublished)
fmt.Printf(
" Total Data: %.2f MB\n",
float64(results.EventsPublishedBytes)/1024/1024,
)
fmt.Printf(" Duration: %s\n", results.PublishDuration)
fmt.Printf(" Rate: %.2f events/second\n", results.PublishRate)
fmt.Printf(" Bandwidth: %.2f MB/second\n", results.PublishBandwidth)
}
if results.QueriesExecuted > 0 {
fmt.Println("\nQuery Performance:")
fmt.Printf(" Queries Executed: %d\n", results.QueriesExecuted)
fmt.Printf(" Events Returned: %d\n", results.EventsReturned)
fmt.Printf(" Duration: %s\n", results.QueryDuration)
fmt.Printf(" Rate: %.2f queries/second\n", results.QueryRate)
avgEventsPerQuery := float64(results.EventsReturned) / float64(results.QueriesExecuted)
fmt.Printf(" Avg Events/Query: %.2f\n", avgEventsPerQuery)
}
}

82
cmd/benchmark/run_benchmark.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
# Simple Nostr Relay Benchmark Script
# Default values
RELAY_URL="ws://localhost:7447"
EVENTS=10000
SIZE=1024
CONCURRENCY=10
QUERIES=100
QUERY_LIMIT=100
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--relay)
RELAY_URL="$2"
shift 2
;;
--events)
EVENTS="$2"
shift 2
;;
--size)
SIZE="$2"
shift 2
;;
--concurrency)
CONCURRENCY="$2"
shift 2
;;
--queries)
QUERIES="$2"
shift 2
;;
--query-limit)
QUERY_LIMIT="$2"
shift 2
;;
--skip-publish)
SKIP_PUBLISH="-skip-publish"
shift
;;
--skip-query)
SKIP_QUERY="-skip-query"
shift
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--relay URL] [--events N] [--size N] [--concurrency N] [--queries N] [--query-limit N] [--skip-publish] [--skip-query]"
exit 1
;;
esac
done
# Build the benchmark tool if it doesn't exist
if [ ! -f benchmark-simple ]; then
echo "Building benchmark tool..."
go build -o benchmark-simple ./benchmark_simple.go
if [ $? -ne 0 ]; then
echo "Failed to build benchmark tool"
exit 1
fi
fi
# Run the benchmark
echo "Running Nostr relay benchmark..."
echo "Relay: $RELAY_URL"
echo "Events: $EVENTS (size: $SIZE bytes)"
echo "Concurrency: $CONCURRENCY"
echo "Queries: $QUERIES (limit: $QUERY_LIMIT)"
echo ""
./benchmark-simple \
-relay "$RELAY_URL" \
-events $EVENTS \
-size $SIZE \
-concurrency $CONCURRENCY \
-queries $QUERIES \
-query-limit $QUERY_LIMIT \
$SKIP_PUBLISH \
$SKIP_QUERY

View File

@@ -0,0 +1,63 @@
package main
import (
"lukechampine.com/frand"
"orly.dev/pkg/interfaces/signer"
)
// testSigner is a simple signer implementation for benchmarking
type testSigner struct {
pub []byte
sec []byte
}
func newTestSigner() *testSigner {
return &testSigner{
pub: frand.Bytes(32),
sec: frand.Bytes(32),
}
}
func (s *testSigner) Pub() []byte {
return s.pub
}
func (s *testSigner) Sec() []byte {
return s.sec
}
func (s *testSigner) Sign(msg []byte) ([]byte, error) {
return frand.Bytes(64), nil
}
func (s *testSigner) Verify(msg, sig []byte) (bool, error) {
return true, nil
}
func (s *testSigner) InitSec(sec []byte) error {
s.sec = sec
s.pub = frand.Bytes(32)
return nil
}
func (s *testSigner) InitPub(pub []byte) error {
s.pub = pub
return nil
}
func (s *testSigner) Zero() {
for i := range s.sec {
s.sec[i] = 0
}
}
func (s *testSigner) ECDH(pubkey []byte) ([]byte, error) {
return frand.Bytes(32), nil
}
func (s *testSigner) Generate() error {
return nil
}
var _ signer.I = (*testSigner)(nil)

View File

@@ -0,0 +1,16 @@
# systemd unit to run lerproxy as a service
[Unit]
Description=lerproxy
[Service]
Type=simple
User=mleku
ExecStart=/home/mleku/.local/bin/lerproxy -m /home/mleku/mapping.txt
Restart=always
Wants=network-online.target
# waits for wireguard service to come up before starting, remove the wg-quick@wg0 section if running it directly on an
# internet routeable connection
After=network.target network-online.target wg-quick@wg0.service
[Install]
WantedBy=multi-user.target

View File

@@ -8,6 +8,8 @@ import (
"io"
"net/http"
"net/url"
"os"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/encoders/bech32encoding"
@@ -18,7 +20,6 @@ import (
"orly.dev/pkg/utils/errorf"
"orly.dev/pkg/utils/log"
realy_lol "orly.dev/pkg/version"
"os"
)
const secEnv = "NOSTR_SECRET_KEY"
@@ -190,6 +191,5 @@ func Post(f string, ur *url.URL, sign signer.I) (err error) {
if io.Copy(os.Stdout, res.Body); chk.E(err) {
return
}
fmt.Println()
return
}

View File

@@ -6,21 +6,23 @@ import (
"bytes"
"encoding/hex"
"fmt"
"orly.dev/pkg/crypto/ec/bech32"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/encoders/bech32encoding"
"orly.dev/pkg/utils/atomic"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/interrupt"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/utils/qu"
"os"
"runtime"
"strings"
"sync"
"time"
"orly.dev/pkg/crypto/ec/bech32"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/encoders/bech32encoding"
"orly.dev/pkg/utils/atomic"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/interrupt"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/utils/lol"
"orly.dev/pkg/utils/qu"
"github.com/alexflint/go-arg"
)
@@ -33,9 +35,9 @@ const (
)
type Result struct {
sec *secp256k1.SecretKey
sec []byte
npub []byte
pub *secp256k1.PublicKey
pub []byte
}
var args struct {
@@ -45,6 +47,7 @@ var args struct {
}
func main() {
lol.SetLogLevel("info")
arg.MustParse(&args)
if args.String == "" {
_, _ = fmt.Fprintln(
@@ -79,7 +82,7 @@ Options:
}
}
func Vanity(str string, where int, threads int) (e error) {
func Vanity(str string, where int, threads int) (err error) {
// check the string has valid bech32 ciphers
for i := range str {
@@ -122,7 +125,7 @@ out:
wm := workingFor % time.Second
workingFor -= wm
fmt.Printf(
"working for %v, attempts %d\n",
" working for %v, attempts %d",
workingFor, counter.Load(),
)
case r := <-resC:
@@ -142,20 +145,16 @@ out:
wg.Wait()
fmt.Printf(
"generated in %d attempts using %d threads, taking %v\n",
"\r# generated in %d attempts using %d threads, taking %v ",
counter.Load(), args.Threads, time.Now().Sub(started),
)
secBytes := res.sec.Serialize()
log.D.Ln(
"generated key pair:\n"+
"\nhex:\n"+
"\tsecret: %s\n"+
"\tpublic: %s\n\n",
hex.EncodeToString(secBytes),
hex.EncodeToString(schnorr.SerializePubKey(res.pub)),
fmt.Printf(
"\nHSEC = %s\nHPUB = %s\n",
hex.EncodeToString(res.sec),
hex.EncodeToString(res.pub),
)
nsec, _ := bech32encoding.SecretKeyToNsec(res.sec)
fmt.Printf("\nNSEC = %s\nNPUB = %s\n\n", nsec, res.npub)
nsec, _ := bech32encoding.BinToNsec(res.sec)
fmt.Printf("NSEC = %s\nNPUB = %s\n", nsec, res.npub)
return
}
@@ -185,16 +184,19 @@ out:
default:
}
counter.Inc()
r.sec, r.pub, e = GenKeyPair()
// r.sec, r.pub, e = GenKeyPair()
r.sec, r.pub, e = Gen()
if e != nil {
log.E.Ln("error generating key: '%v' worker stopping", e)
break out
}
r.npub, e = bech32encoding.PublicKeyToNpub(r.pub)
if e != nil {
// r.npub, e = bech32encoding.PublicKeyToNpub(r.pub)
if r.npub, e = bech32encoding.BinToNpub(r.pub); e != nil {
log.E.Ln("fatal error generating npub: %s\n", e)
break out
}
fmt.Printf("\rgenerating key: %s", r.npub)
// log.I.F("%s", r.npub)
switch where {
case PositionBeginning:
if bytes.HasPrefix(r.npub, append(prefix, []byte(str)...)) {
@@ -215,6 +217,15 @@ out:
}
}
func Gen() (skb, pkb []byte, err error) {
sign := p256k.Signer{}
if err = sign.Generate(); chk.E(err) {
return
}
skb, pkb = sign.Sec(), sign.Pub()
return
}
// GenKeyPair creates a fresh new key pair using the entropy source used by
// crypto/rand (ie, /dev/random on posix systems).
func GenKeyPair() (

162
cmd/walletcli/README.md Normal file
View File

@@ -0,0 +1,162 @@
# NWC Client CLI Tool
A command-line interface tool for making calls to Nostr Wallet Connect (NWC) services.
## Overview
This CLI tool allows you to interact with NWC wallet services using the methods defined in the NIP-47 specification. It provides a simple interface for executing wallet operations and displays the JSON response from the wallet service.
## Usage
```
nwcclient <connection URL> <method> [parameters...]
```
### Connection URL
The connection URL should be in the Nostr Wallet Connect format:
```
nostr+walletconnect://<wallet_pubkey>?relay=<relay_url>&secret=<secret>
```
### Supported Methods
The following methods are supported by this CLI tool:
- `get_info` - Get wallet information
- `get_balance` - Get wallet balance
- `get_budget` - Get wallet budget
- `make_invoice` - Create an invoice
- `pay_invoice` - Pay an invoice
- `pay_keysend` - Send a keysend payment
- `lookup_invoice` - Look up an invoice
- `list_transactions` - List transactions
- `sign_message` - Sign a message
### Unsupported Methods
The following methods are defined in the NIP-47 specification but are not directly supported by this CLI tool due to limitations in the underlying nwc package:
- `create_connection` - Create a connection
- `make_hold_invoice` - Create a hold invoice
- `settle_hold_invoice` - Settle a hold invoice
- `cancel_hold_invoice` - Cancel a hold invoice
- `multi_pay_invoice` - Pay multiple invoices
- `multi_pay_keysend` - Send multiple keysend payments
## Method Parameters
### Methods with No Parameters
- `get_info`
- `get_balance`
- `get_budget`
Example:
```
nwcclient <connection URL> get_info
```
### Methods with Parameters
#### make_invoice
```
nwcclient <connection URL> make_invoice <amount> <description> [description_hash] [expiry]
```
- `amount` - Amount in millisatoshis (msats)
- `description` - Invoice description
- `description_hash` (optional) - Hash of the description
- `expiry` (optional) - Expiry time in seconds
Example:
```
nwcclient <connection URL> make_invoice 1000000 "Test invoice" "" 3600
```
#### pay_invoice
```
nwcclient <connection URL> pay_invoice <invoice> [amount]
```
- `invoice` - BOLT11 invoice
- `amount` (optional) - Amount in millisatoshis (msats)
Example:
```
nwcclient <connection URL> pay_invoice lnbc1...
```
#### pay_keysend
```
nwcclient <connection URL> pay_keysend <amount> <pubkey> [preimage]
```
- `amount` - Amount in millisatoshis (msats)
- `pubkey` - Recipient's public key
- `preimage` (optional) - Payment preimage
Example:
```
nwcclient <connection URL> pay_keysend 1000000 03...
```
#### lookup_invoice
```
nwcclient <connection URL> lookup_invoice <payment_hash_or_invoice>
```
- `payment_hash_or_invoice` - Payment hash or BOLT11 invoice
Example:
```
nwcclient <connection URL> lookup_invoice 3d...
```
#### list_transactions
```
nwcclient <connection URL> list_transactions [from <timestamp>] [until <timestamp>] [limit <count>] [offset <count>] [unpaid <true|false>] [type <incoming|outgoing>]
```
Parameters are specified as name-value pairs:
- `from` - Start timestamp
- `until` - End timestamp
- `limit` - Maximum number of transactions to return
- `offset` - Number of transactions to skip
- `unpaid` - Whether to include unpaid transactions
- `type` - Transaction type (incoming or outgoing)
Example:
```
nwcclient <connection URL> list_transactions limit 10 type incoming
```
#### sign_message
```
nwcclient <connection URL> sign_message <message>
```
- `message` - Message to sign
Example:
```
nwcclient <connection URL> sign_message "Hello, world!"
```
## Output
The tool prints the JSON response from the wallet service to stdout. If an error occurs, an error message is printed to stderr.
## Limitations
- The tool only supports methods that have direct client methods in the nwc package.
- Complex parameters like metadata are not supported.
- The tool does not support interactive authentication or authorization.

417
cmd/walletcli/main.go Normal file
View File

@@ -0,0 +1,417 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
"orly.dev/pkg/protocol/nwc"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
)
func printUsage() {
fmt.Println("Usage: walletcli \"<NWC connection URL>\" <method> [<args...>]")
fmt.Println("\nAvailable methods:")
fmt.Println(" get_wallet_service_info - Get wallet service information")
fmt.Println(" get_info - Get wallet information")
fmt.Println(" get_balance - Get wallet balance")
fmt.Println(" get_budget - Get wallet budget")
fmt.Println(" make_invoice - Create an invoice")
fmt.Println(" Args: <amount> [<description>] [<description_hash>] [<expiry>]")
fmt.Println(" pay_invoice - Pay an invoice")
fmt.Println(" Args: <invoice> [<amount>] [<comment>]")
fmt.Println(" pay_keysend - Pay to a node using keysend")
fmt.Println(" Args: <pubkey> <amount> [<preimage>] [<tlv_type> <tlv_value>...]")
fmt.Println(" lookup_invoice - Look up an invoice")
fmt.Println(" Args: <payment_hash or invoice>")
fmt.Println(" list_transactions - List transactions")
fmt.Println(" Args: [<limit>] [<offset>] [<from>] [<until>]")
fmt.Println(" make_hold_invoice - Create a hold invoice")
fmt.Println(" Args: <amount> <payment_hash> [<description>] [<description_hash>] [<expiry>]")
fmt.Println(" settle_hold_invoice - Settle a hold invoice")
fmt.Println(" Args: <preimage>")
fmt.Println(" cancel_hold_invoice - Cancel a hold invoice")
fmt.Println(" Args: <payment_hash>")
fmt.Println(" sign_message - Sign a message")
fmt.Println(" Args: <message>")
fmt.Println(" create_connection - Create a connection")
fmt.Println(" Args: <pubkey> <name> <methods> [<notification_types>] [<max_amount>] [<budget_renewal>] [<expires_at>]")
}
func main() {
if len(os.Args) < 3 {
printUsage()
os.Exit(1)
}
connectionURL := os.Args[1]
method := os.Args[2]
args := os.Args[3:]
// Create context
// ctx, cancel := context.Cancel(context.Bg())
ctx := context.Bg()
// defer cancel()
// Create NWC client
client, err := nwc.NewClient(ctx, connectionURL)
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
os.Exit(1)
}
// Execute the requested method
switch method {
case "get_wallet_service_info":
handleGetWalletServiceInfo(ctx, client)
case "get_info":
handleGetInfo(ctx, client)
case "get_balance":
handleGetBalance(ctx, client)
case "get_budget":
handleGetBudget(ctx, client)
case "make_invoice":
handleMakeInvoice(ctx, client, args)
case "pay_invoice":
handlePayInvoice(ctx, client, args)
case "pay_keysend":
handlePayKeysend(ctx, client, args)
case "lookup_invoice":
handleLookupInvoice(ctx, client, args)
case "list_transactions":
handleListTransactions(ctx, client, args)
case "make_hold_invoice":
handleMakeHoldInvoice(ctx, client, args)
case "settle_hold_invoice":
handleSettleHoldInvoice(ctx, client, args)
case "cancel_hold_invoice":
handleCancelHoldInvoice(ctx, client, args)
case "sign_message":
handleSignMessage(ctx, client, args)
case "create_connection":
handleCreateConnection(ctx, client, args)
default:
fmt.Printf("Unknown method: %s\n", method)
printUsage()
os.Exit(1)
}
}
func handleGetWalletServiceInfo(ctx context.T, client *nwc.Client) {
if _, raw, err := client.GetWalletServiceInfo(ctx, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleGetInfo(ctx context.T, client *nwc.Client) {
if _, raw, err := client.GetInfo(ctx, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleGetBalance(ctx context.T, client *nwc.Client) {
if _, raw, err := client.GetBalance(ctx, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleGetBudget(ctx context.T, client *nwc.Client) {
if _, raw, err := client.GetBudget(ctx, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleMakeInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> make_invoice <amount> [<description>] [<description_hash>] [<expiry>]")
return
}
amount, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params := &nwc.MakeInvoiceParams{
Amount: amount,
}
if len(args) > 1 {
params.Description = args[1]
}
if len(args) > 2 {
params.DescriptionHash = args[2]
}
if len(args) > 3 {
expiry, err := strconv.ParseInt(args[3], 10, 64)
if err != nil {
fmt.Printf("Error parsing expiry: %v\n", err)
return
}
params.Expiry = &expiry
}
var raw []byte
if _, raw, err = client.MakeInvoice(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handlePayInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> pay_invoice <invoice> [<amount>] [<comment>]")
return
}
params := &nwc.PayInvoiceParams{
Invoice: args[0],
}
if len(args) > 1 {
amount, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params.Amount = &amount
}
if len(args) > 2 {
comment := args[2]
params.Metadata = &nwc.PayInvoiceMetadata{
Comment: &comment,
}
}
if _, raw, err := client.PayInvoice(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleLookupInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> lookup_invoice <payment_hash or invoice>")
return
}
params := &nwc.LookupInvoiceParams{}
// Determine if the argument is a payment hash or an invoice
if strings.HasPrefix(args[0], "ln") {
invoice := args[0]
params.Invoice = &invoice
} else {
paymentHash := args[0]
params.PaymentHash = &paymentHash
}
var err error
var raw []byte
if _, raw, err = client.LookupInvoice(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleListTransactions(ctx context.T, client *nwc.Client, args []string) {
params := &nwc.ListTransactionsParams{}
if len(args) > 0 {
limit, err := strconv.ParseUint(args[0], 10, 16)
if err != nil {
fmt.Printf("Error parsing limit: %v\n", err)
return
}
limitUint16 := uint16(limit)
params.Limit = &limitUint16
}
if len(args) > 1 {
offset, err := strconv.ParseUint(args[1], 10, 32)
if err != nil {
fmt.Printf("Error parsing offset: %v\n", err)
return
}
offsetUint32 := uint32(offset)
params.Offset = &offsetUint32
}
if len(args) > 2 {
from, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
fmt.Printf("Error parsing from: %v\n", err)
return
}
params.From = &from
}
if len(args) > 3 {
until, err := strconv.ParseInt(args[3], 10, 64)
if err != nil {
fmt.Printf("Error parsing until: %v\n", err)
return
}
params.Until = &until
}
var raw []byte
var err error
if _, raw, err = client.ListTransactions(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleMakeHoldInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 2 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> make_hold_invoice <amount> <payment_hash> [<description>] [<description_hash>] [<expiry>]")
return
}
amount, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params := &nwc.MakeHoldInvoiceParams{
Amount: amount,
PaymentHash: args[1],
}
if len(args) > 2 {
params.Description = args[2]
}
if len(args) > 3 {
params.DescriptionHash = args[3]
}
if len(args) > 4 {
expiry, err := strconv.ParseInt(args[4], 10, 64)
if err != nil {
fmt.Printf("Error parsing expiry: %v\n", err)
return
}
params.Expiry = &expiry
}
var raw []byte
if _, raw, err = client.MakeHoldInvoice(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleSettleHoldInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> settle_hold_invoice <preimage>")
return
}
params := &nwc.SettleHoldInvoiceParams{
Preimage: args[0],
}
var raw []byte
var err error
if raw, err = client.SettleHoldInvoice(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleCancelHoldInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> cancel_hold_invoice <payment_hash>")
return
}
params := &nwc.CancelHoldInvoiceParams{
PaymentHash: args[0],
}
var err error
var raw []byte
if raw, err = client.CancelHoldInvoice(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleSignMessage(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> sign_message <message>")
return
}
params := &nwc.SignMessageParams{
Message: args[0],
}
var raw []byte
var err error
if _, raw, err = client.SignMessage(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handlePayKeysend(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 2 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> pay_keysend <pubkey> <amount> [<preimage>] [<tlv_type> <tlv_value>...]")
return
}
pubkey := args[0]
amount, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params := &nwc.PayKeysendParams{
Pubkey: pubkey,
Amount: amount,
}
// Optional preimage
if len(args) > 2 {
preimage := args[2]
params.Preimage = &preimage
}
// Optional TLV records (must come in pairs)
if len(args) > 3 {
// Start from index 3 and process pairs of arguments
for i := 3; i < len(args)-1; i += 2 {
tlvType, err := strconv.ParseUint(args[i], 10, 32)
if err != nil {
fmt.Printf("Error parsing TLV type: %v\n", err)
return
}
tlvValue := args[i+1]
params.TLVRecords = append(
params.TLVRecords, nwc.PayKeysendTLVRecord{
Type: uint32(tlvType),
Value: tlvValue,
},
)
}
}
var raw []byte
if _, raw, err = client.PayKeysend(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}
func handleCreateConnection(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 3 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> create_connection <pubkey> <name> <methods> [<notification_types>] [<max_amount>] [<budget_renewal>] [<expires_at>]")
return
}
params := &nwc.CreateConnectionParams{
Pubkey: args[0],
Name: args[1],
RequestMethods: strings.Split(args[2], ","),
}
if len(args) > 3 {
params.NotificationTypes = strings.Split(args[3], ",")
}
if len(args) > 4 {
maxAmount, err := strconv.ParseUint(args[4], 10, 64)
if err != nil {
fmt.Printf("Error parsing max_amount: %v\n", err)
return
}
params.MaxAmount = &maxAmount
}
if len(args) > 5 {
params.BudgetRenewal = &args[5]
}
if len(args) > 6 {
expiresAt, err := strconv.ParseInt(args[6], 10, 64)
if err != nil {
fmt.Printf("Error parsing expires_at: %v\n", err)
return
}
params.ExpiresAt = &expiresAt
}
var raw []byte
var err error
if raw, err = client.CreateConnection(ctx, params, true); !chk.E(err) {
fmt.Println(string(raw))
}
}

4
go.mod
View File

@@ -5,13 +5,12 @@ go 1.24.2
require (
github.com/adrg/xdg v0.5.3
github.com/alexflint/go-arg v1.6.0
github.com/coder/websocket v1.8.13
github.com/danielgtaylor/huma/v2 v2.34.1
github.com/davecgh/go-spew v1.1.1
github.com/dgraph-io/badger/v4 v4.7.0
github.com/fasthttp/websocket v1.5.12
github.com/fatih/color v1.18.0
github.com/gobwas/httphead v0.1.0
github.com/gobwas/ws v1.4.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/klauspost/cpuid/v2 v2.2.11
github.com/minio/sha256-simd v1.0.1
@@ -41,7 +40,6 @@ require (
github.com/felixge/fgprof v0.9.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/klauspost/compress v1.18.0 // indirect

6
go.sum
View File

@@ -19,6 +19,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/danielgtaylor/huma/v2 v2.34.1 h1:EmOJAbzEGfy0wAq/QMQ1YKfEMBEfE94xdBRLPBP0gwQ=
github.com/danielgtaylor/huma/v2 v2.34.1/go.mod h1:ynwJgLk8iGVgoaipi5tgwIQ5yoFNmiu+QdhU7CEEmhk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -44,13 +46,9 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=

View File

@@ -5,12 +5,6 @@ package config
import (
"fmt"
"io"
"orly.dev/pkg/utils/apputil"
"orly.dev/pkg/utils/chk"
env2 "orly.dev/pkg/utils/env"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/utils/lol"
"orly.dev/pkg/version"
"os"
"path/filepath"
"reflect"
@@ -18,6 +12,13 @@ import (
"strings"
"time"
"orly.dev/pkg/utils/apputil"
"orly.dev/pkg/utils/chk"
env2 "orly.dev/pkg/utils/env"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/utils/lol"
"orly.dev/pkg/version"
"github.com/adrg/xdg"
"go-simpler.org/env"
)
@@ -26,20 +27,27 @@ import (
// and default values. It defines parameters for app behaviour, storage
// locations, logging, and network settings used across the relay service.
type C struct {
AppName string `env:"ORLY_APP_NAME" default:"orly"`
Config string `env:"ORLY_CONFIG_DIR" usage:"location for configuration file, which has the name '.env' to make it harder to delete, and is a standard environment KEY=value<newline>... style" default:"~/.config/orly"`
State string `env:"ORLY_STATE_DATA_DIR" usage:"storage location for state data affected by dynamic interactive interfaces" default:"~/.local/state/orly"`
DataDir string `env:"ORLY_DATA_DIR" usage:"storage location for the event store" default:"~/.local/cache/orly"`
Listen string `env:"ORLY_LISTEN" default:"0.0.0.0" usage:"network listen address"`
Port int `env:"ORLY_PORT" default:"3334" usage:"port to listen on"`
LogLevel string `env:"ORLY_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
DbLogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
Pprof string `env:"ORLY_PPROF" usage:"enable pprof on 127.0.0.1:6060" enum:"cpu,memory,allocation"`
AuthRequired bool `env:"ORLY_AUTH_REQUIRED" default:"false" usage:"require authentication for all requests"`
PublicReadable bool `env:"ORLY_PUBLIC_READABLE" default:"true" usage:"allow public read access to regardless of whether the client is authed"`
SpiderSeeds []string `env:"ORLY_SPIDER_SEEDS" usage:"seeds to use for the spider (relays that are looked up initially to find owner relay lists) (comma separated)" default:"wss://relay.nostr.band/,wss://relay.damus.io/,wss://nostr.wine/,wss://nostr.land/,wss://theforest.nostr1.com/"`
Owners []string `env:"ORLY_OWNERS" usage:"list of users whose follow lists designate whitelisted users who can publish events, and who can read if public readable is false (comma separated)"`
Private bool `env:"ORLY_PRIVATE" usage:"do not spider for user metadata because the relay is private and this would leak relay memberships" default:"false"`
AppName string `env:"ORLY_APP_NAME" default:"ORLY"`
Config string `env:"ORLY_CONFIG_DIR" usage:"location for configuration file, which has the name '.env' to make it harder to delete, and is a standard environment KEY=value<newline>... style" default:"~/.config/orly"`
State string `env:"ORLY_STATE_DATA_DIR" usage:"storage location for state data affected by dynamic interactive interfaces" default:"~/.local/state/orly"`
DataDir string `env:"ORLY_DATA_DIR" usage:"storage location for the event store" default:"~/.local/cache/orly"`
Listen string `env:"ORLY_LISTEN" default:"0.0.0.0" usage:"network listen address"`
Port int `env:"ORLY_PORT" default:"3334" usage:"port to listen on"`
LogLevel string `env:"ORLY_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
DbLogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
Pprof string `env:"ORLY_PPROF" usage:"enable pprof on 127.0.0.1:6060" enum:"cpu,memory,allocation"`
AuthRequired bool `env:"ORLY_AUTH_REQUIRED" default:"false" usage:"require authentication for all requests"`
PublicReadable bool `env:"ORLY_PUBLIC_READABLE" default:"true" usage:"allow public read access to regardless of whether the client is authed"`
SpiderSeeds []string `env:"ORLY_SPIDER_SEEDS" usage:"seeds to use for the spider (relays that are looked up initially to find owner relay lists) (comma separated)" default:"wss://profiles.nostr1.com/,wss://relay.nostr.band/,wss://relay.damus.io/,wss://nostr.wine/,wss://nostr.land/,wss://theforest.nostr1.com/,wss://profiles.nostr1.com/"`
SpiderType string `env:"ORLY_SPIDER_TYPE" usage:"whether to spider, and what degree of spidering: none, directory, follows (follows means to the second degree of the follow graph)" default:"directory"`
SpiderTime time.Duration `env:"ORLY_SPIDER_FREQUENCY" usage:"how often to run the spider, uses notation 0h0m0s" default:"1h"`
SpiderSecondDegree bool `env:"ORLY_SPIDER_SECOND_DEGREE" default:"true" usage:"whether to enable spidering the second degree of follows for non-directory events if ORLY_SPIDER_TYPE is set to 'follows'"`
Owners []string `env:"ORLY_OWNERS" usage:"list of users whose follow lists designate whitelisted users who can publish events, and who can read if public readable is false (comma separated)"`
Private bool `env:"ORLY_PRIVATE" usage:"do not spider for user metadata because the relay is private and this would leak relay memberships" default:"false"`
Whitelist []string `env:"ORLY_WHITELIST" usage:"only allow connections from this list of IP addresses"`
Blacklist []string `env:"ORLY_BLACKLIST" usage:"list of pubkeys to block when auth is not required (comma separated)"`
RelaySecret string `env:"ORLY_SECRET_KEY" usage:"secret key for relay cluster replication authentication"`
PeerRelays []string `env:"ORLY_PEER_RELAYS" usage:"list of peer relays URLs that new events are pushed to in format <pubkey>|<url>"`
}
// New creates and initializes a new configuration object for the relay
@@ -90,6 +98,17 @@ func New() (cfg *C, err error) {
lol.SetLogLevel(cfg.LogLevel)
log.I.F("loaded configuration from %s", envPath)
}
// if spider seeds has no elements, there still is a single entry with an
// empty string; and also if any of the fields are empty strings, they need
// to be removed.
var seeds []string
for _, u := range cfg.SpiderSeeds {
if u == "" {
continue
}
seeds = append(seeds, u)
}
cfg.SpiderSeeds = seeds
return
}

View File

@@ -42,30 +42,36 @@ func (s *Server) AcceptEvent(
remote string,
) (accept bool, notice string, afterSave func()) {
if !s.AuthRequired() {
// Check blacklist for public relay mode
if len(s.blacklistPubkeys) > 0 {
for _, blockedPubkey := range s.blacklistPubkeys {
if bytes.Equal(blockedPubkey, ev.Pubkey) {
notice = "event author is blacklisted"
return
}
}
}
accept = true
return
}
// if auth is required and the user is not authed, reject
if s.AuthRequired() && len(authedPubkey) == 0 {
if len(authedPubkey) == 0 {
notice = "client isn't authed"
return
}
// check if the authed user is on the lists
list := append(s.OwnersFollowed(), s.FollowedFollows()...)
for _, u := range list {
if bytes.Equal(u, authedPubkey) {
accept = true
break
}
}
if !accept {
return
}
for _, u := range s.OwnersMuted() {
if bytes.Equal(u, authedPubkey) {
notice = "event author is banned from this relay"
return
}
}
// check if the authed user is on the lists
list := append(s.OwnersFollowed(), s.FollowedFollows()...)
for _, u := range list {
if bytes.Equal(u, authedPubkey) {
accept = true
return
}
}
return
}

View File

@@ -12,8 +12,8 @@ import (
// mockServerForEvent is a simple mock implementation of the Server struct for testing AcceptEvent
type mockServerForEvent struct {
authRequired bool
ownersFollowed [][]byte
authRequired bool
ownersFollowed [][]byte
followedFollows [][]byte
}
@@ -203,8 +203,8 @@ func TestAcceptEventWithRealServer(t *testing.T) {
if accept {
t.Error("AcceptEvent() accept = true, want false")
}
if notice != "" {
t.Errorf("AcceptEvent() notice = %v, want empty string", notice)
if notice != "client isn't authed" {
t.Errorf("AcceptEvent() notice = %v, want 'client isn't authed'", notice)
}
if afterSave != nil {
t.Error("AcceptEvent() afterSave is not nil, but should be nil")
@@ -234,4 +234,81 @@ func TestAcceptEventWithRealServer(t *testing.T) {
if !accept {
t.Error("AcceptEvent() accept = false, want true")
}
// Test with muted user
s.SetOwnersMuted([][]byte{[]byte("test-pubkey")})
accept, notice, afterSave = s.AcceptEvent(ctx, testEvent, req, []byte("test-pubkey"), "127.0.0.1")
if accept {
t.Error("AcceptEvent() accept = true, want false")
}
if notice != "event author is banned from this relay" {
t.Errorf("AcceptEvent() notice = %v, want 'event author is banned from this relay'", notice)
}
}
// TestAcceptEventWithBlacklist tests the blacklist functionality when auth is not required
func TestAcceptEventWithBlacklist(t *testing.T) {
// Create a context and HTTP request for testing
ctx := context.Bg()
req, _ := http.NewRequest("GET", "http://example.com", nil)
// Test pubkey bytes
testPubkey := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}
blockedPubkey := []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30}
// Test with public relay mode (auth not required) and no blacklist
s := &Server{
C: &config.C{
AuthRequired: false,
},
Lists: new(Lists),
}
// Create event with test pubkey
testEvent := &event.E{}
testEvent.Pubkey = testPubkey
// Should accept when no blacklist
accept, notice, _ := s.AcceptEvent(ctx, testEvent, req, nil, "127.0.0.1")
if !accept {
t.Error("AcceptEvent() accept = false, want true")
}
if notice != "" {
t.Errorf("AcceptEvent() notice = %v, want empty string", notice)
}
// Add blacklist with different pubkey
s.blacklistPubkeys = [][]byte{blockedPubkey}
// Should still accept when author not in blacklist
accept, notice, _ = s.AcceptEvent(ctx, testEvent, req, nil, "127.0.0.1")
if !accept {
t.Error("AcceptEvent() accept = false, want true")
}
if notice != "" {
t.Errorf("AcceptEvent() notice = %v, want empty string", notice)
}
// Create event with blocked pubkey
blockedEvent := &event.E{}
blockedEvent.Pubkey = blockedPubkey
// Should reject when author is in blacklist
accept, notice, _ = s.AcceptEvent(ctx, blockedEvent, req, nil, "127.0.0.1")
if accept {
t.Error("AcceptEvent() accept = true, want false")
}
if notice != "event author is blacklisted" {
t.Errorf("AcceptEvent() notice = %v, want 'event author is blacklisted'", notice)
}
// Test with auth required - blacklist should not apply
s.C.AuthRequired = true
accept, notice, _ = s.AcceptEvent(ctx, blockedEvent, req, nil, "127.0.0.1")
if accept {
t.Error("AcceptEvent() accept = true, want false")
}
if notice != "client isn't authed" {
t.Errorf("AcceptEvent() notice = %v, want 'client isn't authed'", notice)
}
}

View File

@@ -1,8 +1,18 @@
package relay
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/protocol/httpauth"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/log"
realy_lol "orly.dev/pkg/version"
"regexp"
"strings"
@@ -17,6 +27,21 @@ var (
NIP20prefixmatcher = regexp.MustCompile(`^\w+: `)
)
var userAgent = fmt.Sprintf("orly/%s", realy_lol.V)
type WriteCloser struct {
*bytes.Buffer
}
func (w *WriteCloser) Close() error {
w.Buffer.Reset()
return nil
}
func NewWriteCloser(w []byte) *WriteCloser {
return &WriteCloser{bytes.NewBuffer(w)}
}
// AddEvent processes an incoming event, saves it if valid, and delivers it to
// subscribers.
//
@@ -55,6 +80,7 @@ var (
// relevant message.
func (s *Server) AddEvent(
c context.T, rl relay.I, ev *event.E, hr *http.Request, origin string,
pubkeys [][]byte,
) (accepted bool, message []byte) {
if ev == nil {
@@ -85,6 +111,77 @@ func (s *Server) AddEvent(
}
// notify subscribers
s.listeners.Deliver(ev)
// push the new event to replicas if replicas are configured, and the relay
// has an identity key.
var err error
if len(s.Peers.Addresses) > 0 &&
len(s.Peers.I.Sec()) == secp256k1.SecKeyBytesLen {
evb := ev.Marshal(nil)
var payload io.ReadCloser
payload = NewWriteCloser(evb)
replica:
for i, a := range s.Peers.Addresses {
// the peer address index is the same as the list of pubkeys
// (they're unpacked from a string containing both, appended at the
// same time), so if the pubkeys from the http event endpoint sent
// us here matches the index of this address, we can skip it.
for _, pk := range pubkeys {
if bytes.Equal(s.Peers.Pubkeys[i], pk) {
log.I.F(
"not sending back to replica that just sent us this event %0x %s",
ev.ID, a,
)
continue replica
}
}
var ur *url.URL
if ur, err = url.Parse(a + "/api/event"); chk.E(err) {
continue
}
var r *http.Request
r = &http.Request{
Method: "POST",
URL: ur,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Body: payload,
ContentLength: int64(len(evb)),
Host: ur.Host,
}
r.Header.Add("User-Agent", userAgent)
if err = httpauth.AddNIP98Header(
r, ur, "POST", "", s.Peers.I, 0,
); chk.E(err) {
continue
}
// add this replica's pubkey to the list to prevent re-sending to
// other replicas more than twice
pubkeys = append(pubkeys, s.Peers.Pub())
var pubkeysHeader []byte
for j, pk := range pubkeys {
pubkeysHeader = hex.EncAppend(pubkeysHeader, pk)
if j < len(pubkeys)-1 {
pubkeysHeader = append(pubkeysHeader, ':')
}
}
r.Header.Add("X-Pubkeys", string(pubkeysHeader))
r.GetBody = func() (rc io.ReadCloser, err error) {
rc = payload
return
}
client := &http.Client{}
if _, err = client.Do(r); chk.E(err) {
continue
}
log.I.F(
"event pushed to replica %s\n%s",
ur.String(), evb,
)
break
}
}
accepted = true
return
}

View File

@@ -7,7 +7,6 @@ import (
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/utils/lol"
)
// ServiceURL constructs the service URL based on the incoming HTTP request. It
@@ -34,8 +33,6 @@ import (
//
// - Returns the constructed URL string.
func (s *Server) ServiceURL(req *http.Request) (st string) {
lol.Tracer("ServiceURL")
defer func() { lol.Tracer("end ServiceURL", st) }()
if !s.AuthRequired() {
log.T.F("auth not required")
return

10
pkg/app/relay/config.go Normal file
View File

@@ -0,0 +1,10 @@
package relay
import (
"orly.dev/pkg/app/config"
)
func (s *Server) Config() (c *config.C) {
c = s.C
return
}

View File

@@ -3,12 +3,13 @@ package relay
import (
"encoding/json"
"net/http"
"sort"
"orly.dev/pkg/interfaces/relay"
"orly.dev/pkg/protocol/relayinfo"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/version"
"sort"
)
// HandleRelayInfo generates and returns a relay information document in JSON
@@ -44,7 +45,7 @@ func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
// relayinfo.CommandResults,
relayinfo.ParameterizedReplaceableEvents,
// relayinfo.ExpirationTimestamp,
// relayinfo.ProtectedEvents,
relayinfo.ProtectedEvents,
// relayinfo.RelayListMetadata,
)
sort.Sort(supportedNIPs)
@@ -52,8 +53,9 @@ func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
info = &relayinfo.T{
Name: s.relay.Name(),
Description: version.Description,
Nips: supportedNIPs, Software: version.URL,
Version: version.V,
Nips: supportedNIPs,
Software: version.URL,
Version: version.V,
Limitation: relayinfo.Limits{
AuthRequired: s.C.AuthRequired,
RestrictedWrites: s.C.AuthRequired,
@@ -61,7 +63,6 @@ func (s *Server) HandleRelayInfo(w http.ResponseWriter, r *http.Request) {
Icon: "https://cdn.satellite.earth/ac9778868fbf23b63c47c769a74e163377e6ea94d3f0f31711931663d035c4f6.png",
}
}
log.I.S(info)
if err := json.NewEncoder(w).Encode(info); chk.E(err) {
}
}

View File

@@ -8,41 +8,41 @@ import (
func TestLists_OwnersPubkeys(t *testing.T) {
// Create a new Lists instance
l := &Lists{}
// Test with empty list
pks := l.OwnersPubkeys()
if len(pks) != 0 {
t.Errorf("Expected empty list, got %d items", len(pks))
}
// Test with some pubkeys
testPubkeys := [][]byte{
[]byte("pubkey1"),
[]byte("pubkey2"),
[]byte("pubkey3"),
}
l.SetOwnersPubkeys(testPubkeys)
// Verify length
if l.LenOwnersPubkeys() != len(testPubkeys) {
t.Errorf("Expected length %d, got %d", len(testPubkeys), l.LenOwnersPubkeys())
}
// Verify content
pks = l.OwnersPubkeys()
if len(pks) != len(testPubkeys) {
t.Errorf("Expected %d pubkeys, got %d", len(testPubkeys), len(pks))
}
// Verify each pubkey
for i, pk := range pks {
if !bytes.Equal(pk, testPubkeys[i]) {
t.Errorf("Pubkey at index %d doesn't match: expected %s, got %s",
t.Errorf("Pubkey at index %d doesn't match: expected %s, got %s",
i, testPubkeys[i], pk)
}
}
// Verify that the returned slice is a copy, not a reference
pks[0] = []byte("modified")
newPks := l.OwnersPubkeys()
@@ -54,37 +54,37 @@ func TestLists_OwnersPubkeys(t *testing.T) {
func TestLists_OwnersFollowed(t *testing.T) {
// Create a new Lists instance
l := &Lists{}
// Test with empty list
followed := l.OwnersFollowed()
if len(followed) != 0 {
t.Errorf("Expected empty list, got %d items", len(followed))
}
// Test with some pubkeys
testPubkeys := [][]byte{
[]byte("followed1"),
[]byte("followed2"),
[]byte("followed3"),
}
l.SetOwnersFollowed(testPubkeys)
// Verify length
if l.LenOwnersFollowed() != len(testPubkeys) {
t.Errorf("Expected length %d, got %d", len(testPubkeys), l.LenOwnersFollowed())
}
// Verify content
followed = l.OwnersFollowed()
if len(followed) != len(testPubkeys) {
t.Errorf("Expected %d followed, got %d", len(testPubkeys), len(followed))
}
// Verify each pubkey
for i, pk := range followed {
if !bytes.Equal(pk, testPubkeys[i]) {
t.Errorf("Followed at index %d doesn't match: expected %s, got %s",
t.Errorf("Followed at index %d doesn't match: expected %s, got %s",
i, testPubkeys[i], pk)
}
}
@@ -93,37 +93,37 @@ func TestLists_OwnersFollowed(t *testing.T) {
func TestLists_FollowedFollows(t *testing.T) {
// Create a new Lists instance
l := &Lists{}
// Test with empty list
follows := l.FollowedFollows()
if len(follows) != 0 {
t.Errorf("Expected empty list, got %d items", len(follows))
}
// Test with some pubkeys
testPubkeys := [][]byte{
[]byte("follow1"),
[]byte("follow2"),
[]byte("follow3"),
}
l.SetFollowedFollows(testPubkeys)
// Verify length
if l.LenFollowedFollows() != len(testPubkeys) {
t.Errorf("Expected length %d, got %d", len(testPubkeys), l.LenFollowedFollows())
}
// Verify content
follows = l.FollowedFollows()
if len(follows) != len(testPubkeys) {
t.Errorf("Expected %d follows, got %d", len(testPubkeys), len(follows))
}
// Verify each pubkey
for i, pk := range follows {
if !bytes.Equal(pk, testPubkeys[i]) {
t.Errorf("Follow at index %d doesn't match: expected %s, got %s",
t.Errorf("Follow at index %d doesn't match: expected %s, got %s",
i, testPubkeys[i], pk)
}
}
@@ -132,37 +132,37 @@ func TestLists_FollowedFollows(t *testing.T) {
func TestLists_OwnersMuted(t *testing.T) {
// Create a new Lists instance
l := &Lists{}
// Test with empty list
muted := l.OwnersMuted()
if len(muted) != 0 {
t.Errorf("Expected empty list, got %d items", len(muted))
}
// Test with some pubkeys
testPubkeys := [][]byte{
[]byte("muted1"),
[]byte("muted2"),
[]byte("muted3"),
}
l.SetOwnersMuted(testPubkeys)
// Verify length
if l.LenOwnersMuted() != len(testPubkeys) {
t.Errorf("Expected length %d, got %d", len(testPubkeys), l.LenOwnersMuted())
}
// Verify content
muted = l.OwnersMuted()
if len(muted) != len(testPubkeys) {
t.Errorf("Expected %d muted, got %d", len(testPubkeys), len(muted))
}
// Verify each pubkey
for i, pk := range muted {
if !bytes.Equal(pk, testPubkeys[i]) {
t.Errorf("Muted at index %d doesn't match: expected %s, got %s",
t.Errorf("Muted at index %d doesn't match: expected %s, got %s",
i, testPubkeys[i], pk)
}
}
@@ -171,10 +171,10 @@ func TestLists_OwnersMuted(t *testing.T) {
func TestLists_ConcurrentAccess(t *testing.T) {
// Create a new Lists instance
l := &Lists{}
// Test concurrent access to the lists
done := make(chan bool)
// Concurrent reads and writes
go func() {
for i := 0; i < 100; i++ {
@@ -183,7 +183,7 @@ func TestLists_ConcurrentAccess(t *testing.T) {
}
done <- true
}()
go func() {
for i := 0; i < 100; i++ {
l.SetOwnersFollowed([][]byte{[]byte("followed1"), []byte("followed2")})
@@ -191,7 +191,7 @@ func TestLists_ConcurrentAccess(t *testing.T) {
}
done <- true
}()
go func() {
for i := 0; i < 100; i++ {
l.SetFollowedFollows([][]byte{[]byte("follow1"), []byte("follow2")})
@@ -199,7 +199,7 @@ func TestLists_ConcurrentAccess(t *testing.T) {
}
done <- true
}()
go func() {
for i := 0; i < 100; i++ {
l.SetOwnersMuted([][]byte{[]byte("muted1"), []byte("muted2")})
@@ -207,11 +207,11 @@ func TestLists_ConcurrentAccess(t *testing.T) {
}
done <- true
}()
// Wait for all goroutines to complete
for i := 0; i < 4; i++ {
<-done
}
// If we got here without deadlocks or panics, the test passes
}
}

View File

@@ -0,0 +1,6 @@
package relay
func (s *Server) OwnersPubkeys() (pks [][]byte) {
pks = s.ownersPubkeys
return
}

72
pkg/app/relay/peers.go Normal file
View File

@@ -0,0 +1,72 @@
package relay
import (
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/encoders/bech32encoding"
"orly.dev/pkg/interfaces/signer"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/keys"
"orly.dev/pkg/utils/log"
"strings"
)
// Peers is a structure that keeps the information required when peer
// replication is enabled.
//
// - Addresses are the relay addresses that will be pushed new events when
// accepted. From ORLY_PEER_RELAYS first field after the |.
//
// - Pubkeys are the relay peer public keys that we will send any event to
// including privileged type. From ORLY_PEER_RELAYS before the |.
//
// - I - the signer of this relay, generated from the nsec in
// ORLY_SECRET_KEY.
type Peers struct {
Addresses []string
Pubkeys [][]byte
signer.I
}
// Init accepts the lists which will come from config.C for peer relay settings
// and populate the Peers with this data after decoding it.
func (p *Peers) Init(
addresses []string, sec string,
) (err error) {
for _, address := range addresses {
if len(address) == 0 {
continue
}
split := strings.Split(address, "@")
if len(split) != 2 {
log.E.F("invalid peer address: %s", address)
continue
}
p.Addresses = append(p.Addresses, split[1])
var pk []byte
if pk, err = keys.DecodeNpubOrHex(split[0]); chk.D(err) {
continue
}
p.Pubkeys = append(p.Pubkeys, pk)
log.I.F("peer %s added; pubkey: %0x", split[1], pk)
}
if sec == "" {
return
}
p.I = &p256k.Signer{}
var s []byte
if s, err = keys.DecodeNsecOrHex(sec); chk.E(err) {
return
}
if err = p.I.InitSec(s); chk.E(err) {
return
}
var npub []byte
if npub, err = bech32encoding.BinToNpub(p.I.Pub()); chk.E(err) {
return
}
log.I.F(
"relay peer initialized, relay's npub: %s",
npub,
)
return
}

View File

@@ -1,11 +1,9 @@
// Package publisher is a singleton package that keeps track of subscriptions in
// both websockets and http SSE, including managing the authentication state of
// a connection.
package publish
import (
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/interfaces/publisher"
"orly.dev/pkg/interfaces/typer"
)
// S is the control structure for the subscription management scheme.
@@ -26,11 +24,10 @@ func (s *S) Type() string { return "publish" }
func (s *S) Deliver(ev *event.E) {
for _, p := range s.Publishers {
p.Deliver(ev)
return
}
}
func (s *S) Receive(msg publisher.Message) {
func (s *S) Receive(msg typer.T) {
t := msg.Type()
for _, p := range s.Publishers {
if p.Type() == t {

View File

@@ -18,7 +18,9 @@ import (
"orly.dev/pkg/utils/normalize"
)
// Publish processes and stores an event in the server's storage. It handles different types of events: ephemeral, replaceable, and parameterized replaceable.
// Publish processes and stores an event in the server's storage. It handles
// different types of events: ephemeral, replaceable, and parameterized
// replaceable.
//
// # Parameters
//
@@ -61,7 +63,13 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
for _, ev := range evs {
del := true
if bytes.Equal(ev.ID, evt.ID) {
continue
return errorf.W(
string(
normalize.Duplicate.F(
"event already in relay database",
),
),
)
}
log.I.F(
"maybe replace %s with %s", ev.Serialize(), evt.Serialize(),
@@ -75,6 +83,12 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
),
)
}
// not deleting these events because some clients are retarded
// and the query will pull the new one, but a backup can recover
// the data of old ones
if ev.Kind.IsDirectoryEvent() {
del = false
}
if evt.Kind.Equal(kind.FollowList) {
// if the event is from someone on ownersFollowed or
// followedFollows, for now add to this list so they're
@@ -88,7 +102,7 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
}
if isFollowed {
if _, _, err = sto.SaveEvent(
c, evt, false,
c, evt, false, nil,
); err != nil && !errors.Is(
err, store.ErrDupEvent,
) {
@@ -99,7 +113,7 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
err = nil
}
// event has been saved and lists updated.
return
// return
}
}
@@ -110,7 +124,7 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
for _, pk := range owners {
if bytes.Equal(evt.Pubkey, pk) {
if _, _, err = sto.SaveEvent(
c, evt, false,
c, evt, false, nil,
); err != nil && !errors.Is(
err, store.ErrDupEvent,
) {
@@ -121,7 +135,7 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
err = nil
}
// event has been saved and lists updated.
return
// return
}
}
}
@@ -222,10 +236,17 @@ func (s *Server) Publish(c context.T, evt *event.E) (err error) {
}
}
}
if _, _, err = sto.SaveEvent(c, evt, false); err != nil && !errors.Is(
if _, _, err = sto.SaveEvent(
c, evt, false, append(s.Peers.Pubkeys, s.ownersPubkeys...),
); err != nil && !errors.Is(
err, store.ErrDupEvent,
) {
return
}
log.T.C(
func() string {
return fmt.Sprintf("saved event:\n%s", evt.Serialize())
},
)
return
}

View File

@@ -6,10 +6,13 @@ import (
"fmt"
"net"
"net/http"
"orly.dev/pkg/protocol/socketapi"
"strconv"
"strings"
"time"
"orly.dev/pkg/protocol/openapi"
"orly.dev/pkg/protocol/socketapi"
"orly.dev/pkg/app/config"
"orly.dev/pkg/app/relay/helpers"
"orly.dev/pkg/app/relay/options"
@@ -18,6 +21,7 @@ import (
"orly.dev/pkg/protocol/servemux"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
"orly.dev/pkg/utils/keys"
"orly.dev/pkg/utils/log"
"github.com/rs/cors"
@@ -27,16 +31,18 @@ import (
// encapsulates various components such as context, cancel function, options,
// relay interface, address, HTTP server, and configuration settings.
type Server struct {
Ctx context.T
Cancel context.F
options *options.T
relay relay.I
Addr string
mux *servemux.S
httpServer *http.Server
listeners *publish.S
Ctx context.T
Cancel context.F
options *options.T
relay relay.I
Addr string
mux *servemux.S
httpServer *http.Server
listeners *publish.S
blacklistPubkeys [][]byte
*config.C
*Lists
*Peers
Mux *servemux.S
}
@@ -100,8 +106,23 @@ func NewServer(
options: op,
C: sp.C,
Lists: new(Lists),
Peers: new(Peers),
}
s.listeners = publish.New(socketapi.New(s))
// Parse blacklist pubkeys
for _, v := range s.C.Blacklist {
if len(v) == 0 {
continue
}
var pk []byte
if pk, err = keys.DecodeNpubOrHex(v); chk.E(err) {
continue
}
s.blacklistPubkeys = append(s.blacklistPubkeys, pk)
}
chk.E(
s.Peers.Init(sp.C.PeerRelays, sp.C.RelaySecret),
)
s.listeners = publish.New(socketapi.New(s), openapi.NewPublisher(s))
go func() {
if err := s.relay.Init(); chk.E(err) {
s.Shutdown()
@@ -133,6 +154,21 @@ func NewServer(
//
// - For all other paths, delegates to the internal mux's ServeHTTP method.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c := s.Config()
remote := helpers.GetRemoteFromReq(r)
var whitelisted bool
if len(c.Whitelist) > 0 {
for _, addr := range c.Whitelist {
if strings.HasPrefix(remote, addr) {
whitelisted = true
}
}
} else {
whitelisted = true
}
if !whitelisted {
return
}
// standard nostr protocol only governs the "root" path of the relay and
// websockets
if r.URL.Path == "/" {
@@ -185,6 +221,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (s *Server) Start(
host string, port int, started ...chan bool,
) (err error) {
log.I.F("running spider every %v", s.C.SpiderTime)
if len(s.C.Owners) > 0 {
// start up spider
if err = s.Spider(s.C.Private); chk.E(err) {
@@ -194,7 +231,7 @@ func (s *Server) Start(
}
}
// start up a spider run to trigger every 30 minutes
ticker := time.NewTicker(time.Hour)
ticker := time.NewTicker(s.C.SpiderTime)
go func() {
for {
select {

View File

@@ -1,7 +1,9 @@
package relay
import (
"fmt"
"runtime/debug"
"time"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/event"
@@ -9,13 +11,13 @@ import (
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/encoders/kinds"
"orly.dev/pkg/encoders/tag"
"orly.dev/pkg/encoders/timestamp"
"orly.dev/pkg/protocol/ws"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
"orly.dev/pkg/utils/errorf"
"orly.dev/pkg/utils/log"
"orly.dev/pkg/utils/lol"
"runtime/debug"
"orly.dev/pkg/utils/values"
)
// IdPkTs is a map of event IDs to their id, pubkey, kind, and timestamp
@@ -45,11 +47,15 @@ func (s *Server) SpiderFetch(
}
var kindsList string
for i, kk := range k.K {
if i > 0 {
kindsList += ","
if k != nil {
for i, kk := range k.K {
if i > 0 {
kindsList += ","
}
kindsList += kk.Name()
}
kindsList += kk.Name()
} else {
kindsList = "*"
}
// Query local database
@@ -100,7 +106,7 @@ func (s *Server) SpiderFetch(
log.I.F("%d events found of type %s", len(pkKindMap), kindsList)
if !noFetch {
if !noFetch && len(s.C.SpiderSeeds) > 0 {
// we need to search the spider seeds.
// Break up pubkeys into batches of 128
for i := 0; i < len(pubkeys); i += 128 {
@@ -115,12 +121,19 @@ func (s *Server) SpiderFetch(
)
batchPkList := tag.New(batchPubkeys...)
lim := uint(batchPkList.Len())
l := &lim
var since *timestamp.T
if k == nil {
since = timestamp.FromTime(time.Now().Add(-1 * s.C.SpiderTime * 3 / 2))
} else {
l = values.ToUintPointer(512)
}
batchFilter := &filter.F{
Kinds: k,
Authors: batchPkList,
Limit: &lim,
Since: since,
Limit: l,
}
for _, seed := range s.C.SpiderSeeds {
select {
case <-s.Ctx.Done():
@@ -130,14 +143,10 @@ func (s *Server) SpiderFetch(
var evss event.S
var cli *ws.Client
if cli, err = ws.RelayConnect(
context.Bg(), seed, ws.WithSignatureChecker(
func(e *event.E) bool {
return true
},
),
context.Bg(), seed,
); chk.E(err) {
err = nil
return
continue
}
if evss, err = cli.QuerySync(
context.Bg(), batchFilter,
@@ -145,15 +154,13 @@ func (s *Server) SpiderFetch(
err = nil
return
}
// Process each event immediately
for i, ev := range evss {
// log.I.S(ev)
// Create a key based on pubkey and kind for deduplication
pkKindKey := string(ev.Pubkey) + string(ev.Kind.Marshal(nil))
// Check if we already have an event with this pubkey and kind
existing, exists := pkKindMap[pkKindKey]
// If it doesn't exist or the new event is newer, store it and save to database
if !exists || ev.CreatedAtInt64() > existing.Timestamp {
var ser *types.Uint40
@@ -166,28 +173,14 @@ func (s *Server) SpiderFetch(
if valid, err = ev.Verify(); chk.E(err) || !valid {
continue
}
log.I.F("event %0x is valid", ev.ID)
}
// Save the event to the database
if _, _, err = s.Storage().SaveEvent(
s.Ctx, ev, true, // already verified
s.Ctx, ev, true, nil,
); chk.E(err) {
err = nil
continue
}
if lol.Level.Load() == lol.Trace {
log.T.C(
func() string {
return fmt.Sprintf(
"saved event:\n%s", ev.Marshal(nil),
)
},
)
} else {
log.I.F("saved event: %0x", ev.ID)
}
// Store the essential information
pkKindMap[pkKindKey] = &IdPkTs{
Id: ev.ID,
@@ -195,7 +188,6 @@ func (s *Server) SpiderFetch(
Kind: ev.Kind.ToU16(),
Timestamp: ev.CreatedAtInt64(),
}
// Extract p tags if not in noExtract mode
if !noExtract {
t := ev.Tags.GetAll(tag.New("p"))
@@ -213,7 +205,6 @@ func (s *Server) SpiderFetch(
}
}
}
// Nil the event in the slice to free memory
evss[i] = nil
}
@@ -222,17 +213,14 @@ func (s *Server) SpiderFetch(
}
chk.E(s.Storage().Sync())
debug.FreeOSMemory()
// If we're in noExtract mode, just return
if noExtract {
return
}
// Convert the collected pubkeys to the return format
for pk := range pkMap {
pks = append(pks, []byte(pk))
}
log.I.F("found %d pks", len(pks))
return
}

View File

@@ -2,41 +2,19 @@ package relay
import (
"bytes"
"orly.dev/pkg/crypto/ec/bech32"
"orly.dev/pkg/encoders/bech32encoding"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/encoders/kind"
"orly.dev/pkg/encoders/kinds"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/keys"
"orly.dev/pkg/utils/log"
)
func (s *Server) Spider(noFetch ...bool) (err error) {
var ownersPubkeys [][]byte
for _, v := range s.C.Owners {
var prf []byte
var pk []byte
var bits5 []byte
if prf, bits5, err = bech32.DecodeNoLimit([]byte(v)); chk.D(err) {
// try hex then
if _, err = hex.DecBytes(pk, []byte(v)); chk.E(err) {
log.W.F(
"owner key %s is neither bech32 npub nor hex",
v,
)
continue
}
} else {
if !bytes.Equal(prf, bech32encoding.NpubHRP) {
log.W.F(
"owner key %s is neither bech32 npub nor hex",
v,
)
continue
}
if pk, err = bech32.ConvertBits(bits5, 5, 8, false); chk.E(err) {
continue
}
if pk, err = keys.DecodeNpubOrHex(v); chk.E(err) {
continue
}
// owners themselves are on the OwnersFollowed list as first level
ownersPubkeys = append(ownersPubkeys, pk)
@@ -118,16 +96,39 @@ func (s *Server) Spider(noFetch ...bool) (err error) {
s.SetOwnersFollowed(ownersFollowed)
s.SetFollowedFollows(followedFollows)
s.SetOwnersMuted(ownersMuted)
// lastly, update users profile metadata and relay lists in the background
if !dontFetch {
// lastly, update all followed users new events in the background
if !dontFetch && s.C.SpiderType != "none" {
go func() {
everyone := append(ownersFollowed, followedFollows...)
s.SpiderFetch(
kinds.New(
var k *kinds.T
if s.C.SpiderType == "directory" {
k = kinds.New(
kind.ProfileMetadata, kind.RelayListMetadata,
kind.DMRelaysList,
), false, true, everyone...,
kind.DMRelaysList, kind.MuteList,
)
}
everyone := ownersFollowed
if s.C.SpiderSecondDegree &&
(s.C.SpiderType == "follows" ||
s.C.SpiderType == "directory") {
everyone = append(ownersFollowed, followedFollows...)
}
_, _ = s.SpiderFetch(
k, false, true, everyone...,
)
// get the directory events also for second degree if spider
// type is directory but second degree is disabled, so all
// directory data is available for all whitelisted users.
if !s.C.SpiderSecondDegree && s.C.SpiderType == "directory" {
k = kinds.New(
kind.ProfileMetadata, kind.RelayListMetadata,
kind.DMRelaysList, kind.MuteList,
)
everyone = append(ownersFollowed, followedFollows...)
_, _ = s.SpiderFetch(
k, false, true, everyone...,
)
}
}()
}
}()

View File

@@ -10,9 +10,8 @@ import (
)
func (s *Server) UserAuth(
r *http.Request, remote string,
tolerance ...time.Duration,
) (authed bool, pubkey []byte) {
r *http.Request, remote string, tolerance ...time.Duration,
) (authed bool, pubkey []byte, super bool) {
var valid bool
var err error
var tolerate time.Duration
@@ -35,5 +34,17 @@ func (s *Server) UserAuth(
return
}
}
// if the client is one of the relay cluster replicas, also set the super
// flag to indicate that privilege checks can be bypassed.
if len(s.Peers.Pubkeys) > 0 {
for _, pk := range s.Peers.Pubkeys {
if bytes.Equal(pk, pubkey) {
authed = true
super = true
pubkey = pk
return
}
}
}
return
}

View File

@@ -52,7 +52,7 @@ func TestBech32(t *testing.T) {
{
"split1cheo2y9e2w",
ErrNonCharsetChar('o'),
}, // invalid character (o) in data part
}, // invalid character (o) in data part
{"split1a2y9w", ErrInvalidSeparatorIndex(5)}, // too short data part
{
"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",

View File

@@ -6,10 +6,11 @@ package musig2
import (
"fmt"
"testing"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/encoders/hex"
"testing"
)
var (
@@ -190,7 +191,7 @@ func BenchmarkCombineSigs(b *testing.B) {
}
var msg [32]byte
copy(msg[:], testMsg[:])
var finalNonce *btcec.btcec
var finalNonce *btcec.PublicKey
for i := range signers {
signer := signers[i]
partialSig, err := Sign(
@@ -246,7 +247,7 @@ func BenchmarkAggregateNonces(b *testing.B) {
}
}
var testKey *btcec.btcec
var testKey *btcec.PublicKey
// BenchmarkAggregateKeys benchmarks how long it takes to aggregate public
// keys.

View File

@@ -4,6 +4,7 @@ package musig2
import (
"fmt"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/utils/chk"
@@ -63,7 +64,7 @@ type Context struct {
// signingKey is the key we'll use for signing.
signingKey *btcec.SecretKey
// pubKey is our even-y coordinate public key.
pubKey *btcec.btcec
pubKey *btcec.PublicKey
// combinedKey is the aggregated public key.
combinedKey *AggregateKey
// uniqueKeyIndex is the index of the second unique key in the keySet.
@@ -103,7 +104,7 @@ type contextOptions struct {
// h_tapTweak(internalKey) as there is no true script root.
bip86Tweak bool
// keySet is the complete set of signers for this context.
keySet []*btcec.btcec
keySet []*btcec.PublicKey
// numSigners is the total number of signers that will eventually be a
// part of the context.
numSigners int

View File

@@ -1,88 +1,127 @@
{
"pubkeys": [
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"020000000000000000000000000000000000000000000000000000000000000005",
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
],
"tweaks": [
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B"
],
"valid_test_cases": [
{
"key_indices": [0, 1, 2],
"expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C"
},
{
"key_indices": [2, 1, 0],
"expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B"
},
{
"key_indices": [0, 0, 0],
"expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935"
},
{
"key_indices": [0, 0, 1, 1],
"expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E"
}
],
"error_test_cases": [
{
"key_indices": [0, 3],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Invalid public key"
},
{
"key_indices": [0, 4],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Public key exceeds field size"
},
{
"key_indices": [5, 0],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubkey"
},
"comment": "First byte of public key is not 2 or 3"
},
{
"key_indices": [0, 1],
"tweak_indices": [0],
"is_xonly": [true],
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is out of range"
},
{
"key_indices": [6],
"tweak_indices": [1],
"is_xonly": [false],
"error": {
"type": "value",
"message": "The result of tweaking cannot be infinity."
},
"comment": "Intermediate tweaking result is point at infinity"
}
]
"pubkeys": [
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"020000000000000000000000000000000000000000000000000000000000000005",
"02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
"04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
],
"tweaks": [
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B"
],
"valid_test_cases": [
{
"key_indices": [
0,
1,
2
],
"expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C"
},
{
"key_indices": [
2,
1,
0
],
"expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B"
},
{
"key_indices": [
0,
0,
0
],
"expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935"
},
{
"key_indices": [
0,
0,
1,
1
],
"expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E"
}
],
"error_test_cases": [
{
"key_indices": [
0,
3
],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Invalid public key"
},
{
"key_indices": [
0,
4
],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubkey"
},
"comment": "Public key exceeds field size"
},
{
"key_indices": [
5,
0
],
"tweak_indices": [],
"is_xonly": [],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubkey"
},
"comment": "First byte of public key is not 2 or 3"
},
{
"key_indices": [
0,
1
],
"tweak_indices": [
0
],
"is_xonly": [
true
],
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is out of range"
},
{
"key_indices": [
6
],
"tweak_indices": [
1
],
"is_xonly": [
false
],
"error": {
"type": "value",
"message": "The result of tweaking cannot be infinity."
},
"comment": "Intermediate tweaking result is point at infinity"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"pubkeys": [
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"
],
"sorted_pubkeys": [
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
]
"pubkeys": [
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8"
],
"sorted_pubkeys": [
"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
]
}

View File

@@ -1,54 +1,69 @@
{
"pnonces": [
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
],
"valid_test_cases": [
{
"pnonce_indices": [0, 1],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
},
{
"pnonce_indices": [2, 3],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000",
"comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes"
}
],
"error_test_cases": [
{
"pnonce_indices": [0, 4],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half",
"btcec_err": "invalid public key: unsupported format: 4"
},
{
"pnonce_indices": [5, 1],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate",
"btcec_err": "invalid public key: x coordinate 48c264cdd57d3c24d79990b0f865674eb62a0f9018277a95011b41bfc193b831 is not on the secp256k1 curve"
},
{
"pnonce_indices": [6, 1],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because second half exceeds field size",
"btcec_err": "invalid public key: x >= field prime"
}
]
"pnonces": [
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831",
"03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
],
"valid_test_cases": [
{
"pnonce_indices": [
0,
1
],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8"
},
{
"pnonce_indices": [
2,
3
],
"expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000",
"comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes"
}
],
"error_test_cases": [
{
"pnonce_indices": [
0,
4
],
"error": {
"type": "invalid_contribution",
"signer": 1,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half",
"btcec_err": "invalid public key: unsupported format: 4"
},
{
"pnonce_indices": [
5,
1
],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate",
"btcec_err": "invalid public key: x coordinate 48c264cdd57d3c24d79990b0f865674eb62a0f9018277a95011b41bfc193b831 is not on the secp256k1 curve"
},
{
"pnonce_indices": [
6,
1
],
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Public nonce from signer 0 is invalid because second half exceeds field size",
"btcec_err": "invalid public key: x >= field prime"
}
]
}

View File

@@ -1,40 +1,40 @@
{
"test_cases": [
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "0101010101010101010101010101010101010101010101010101010101010101",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "227243DCB40EF2A13A981DB188FA433717B506BDFA14B1AE47D5DC027C9C3B9EF2370B2AD206E724243215137C86365699361126991E6FEC816845F837BDDAC3024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "CD0F47FE471D6788FF3243F47345EA0A179AEF69476BE8348322EF39C2723318870C2065AFB52DEDF02BF4FDBF6D2F442E608692F50C2374C08FFFE57042A61C024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "011F8BC60EF061DEEF4D72A0A87200D9994B3F0CD9867910085C38D5366E3E6B9FF03BC0124E56B24069E91EC3F162378983F194E8BD0ED89BE3059649EAE262024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": null,
"pk": "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"aggpk": null,
"msg": null,
"extra_in": null,
"expected": "890E83616A3BC4640AB9B6374F21C81FF89CDDDBAFAA7475AE2A102A92E3EDB29FD7E874E23342813A60D9646948242646B7951CA046B4B36D7D6078506D3C9402F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"
}
]
"test_cases": [
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "0101010101010101010101010101010101010101010101010101010101010101",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "227243DCB40EF2A13A981DB188FA433717B506BDFA14B1AE47D5DC027C9C3B9EF2370B2AD206E724243215137C86365699361126991E6FEC816845F837BDDAC3024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "CD0F47FE471D6788FF3243F47345EA0A179AEF69476BE8348322EF39C2723318870C2065AFB52DEDF02BF4FDBF6D2F442E608692F50C2374C08FFFE57042A61C024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": "0202020202020202020202020202020202020202020202020202020202020202",
"pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766",
"aggpk": "0707070707070707070707070707070707070707070707070707070707070707",
"msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626",
"extra_in": "0808080808080808080808080808080808080808080808080808080808080808",
"expected": "011F8BC60EF061DEEF4D72A0A87200D9994B3F0CD9867910085C38D5366E3E6B9FF03BC0124E56B24069E91EC3F162378983F194E8BD0ED89BE3059649EAE262024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766"
},
{
"rand_": "0000000000000000000000000000000000000000000000000000000000000000",
"sk": null,
"pk": "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"aggpk": null,
"msg": null,
"extra_in": null,
"expected": "890E83616A3BC4640AB9B6374F21C81FF89CDDDBAFAA7475AE2A102A92E3EDB29FD7E874E23342813A60D9646948242646B7951CA046B4B36D7D6078506D3C9402F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"
}
]
}

View File

@@ -1,151 +1,151 @@
{
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05",
"03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C",
"02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581"
],
"pnonces": [
"036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E",
"03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00",
"02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6",
"031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9",
"023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A",
"02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00"
],
"tweaks": [
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
"A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC",
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
],
"psigs": [
"B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB",
"6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64",
"9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505",
"66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15",
"4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE",
"DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4",
"97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC",
"53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869",
"valid_test_cases": [
{
"aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B",
"nonce_indices": [
0,
1
],
"key_indices": [
0,
1
],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [
0,
1
],
"expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E"
},
{
"aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20",
"nonce_indices": [
0,
2
],
"key_indices": [
0,
2
],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [
2,
3
],
"expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9"
},
{
"aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D",
"nonce_indices": [
0,
3
],
"key_indices": [
0,
2
],
"tweak_indices": [
0
],
"is_xonly": [
false
],
"psig_indices": [
4,
5
],
"expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC"
},
{
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
"nonce_indices": [
0,
4
],
"key_indices": [
0,
3
],
"tweak_indices": [
0,
1,
2
],
"is_xonly": [
true,
false,
true
],
"psig_indices": [
6,
7
],
"expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E"
}
],
"error_test_cases": [
{
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
"nonce_indices": [
0,
4
],
"key_indices": [
0,
3
],
"tweak_indices": [
0,
1,
2
],
"is_xonly": [
true,
false,
true
],
"psig_indices": [
7,
8
],
"error": {
"type": "invalid_contribution",
"signer": 1
},
"comment": "Partial signature is invalid because it exceeds group size"
}
]
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05",
"03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C",
"02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581"
],
"pnonces": [
"036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E",
"03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00",
"02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6",
"031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9",
"023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A",
"02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00"
],
"tweaks": [
"B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C",
"A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC",
"75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8"
],
"psigs": [
"B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB",
"6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64",
"9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505",
"66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15",
"4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE",
"DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4",
"97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC",
"53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869",
"valid_test_cases": [
{
"aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B",
"nonce_indices": [
0,
1
],
"key_indices": [
0,
1
],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [
0,
1
],
"expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E"
},
{
"aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20",
"nonce_indices": [
0,
2
],
"key_indices": [
0,
2
],
"tweak_indices": [],
"is_xonly": [],
"psig_indices": [
2,
3
],
"expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9"
},
{
"aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D",
"nonce_indices": [
0,
3
],
"key_indices": [
0,
2
],
"tweak_indices": [
0
],
"is_xonly": [
false
],
"psig_indices": [
4,
5
],
"expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC"
},
{
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
"nonce_indices": [
0,
4
],
"key_indices": [
0,
3
],
"tweak_indices": [
0,
1,
2
],
"is_xonly": [
true,
false,
true
],
"psig_indices": [
6,
7
],
"expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E"
}
],
"error_test_cases": [
{
"aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD",
"nonce_indices": [
0,
4
],
"key_indices": [
0,
3
],
"tweak_indices": [
0,
1,
2
],
"is_xonly": [
true,
false,
true
],
"psig_indices": [
7,
8
],
"error": {
"type": "invalid_contribution",
"signer": 1
},
"comment": "Partial signature is invalid because it exceeds group size"
}
]
}

View File

@@ -1,194 +1,287 @@
{
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661",
"020000000000000000000000000000000000000000000000000000000000000007"
],
"secnonces": [
"508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
],
"pnonces": [
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
"0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"020000000000000000000000000000000000000000000000000000000000000009"
],
"aggnonces": [
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009",
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
],
"msgs": [
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"",
"2626262626262626262626262626262626262626262626262626262626262626262626262626"
],
"valid_test_cases": [
{
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB"
},
{
"key_indices": [1, 0, 2],
"nonce_indices": [1, 0, 2],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 1,
"expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 2,
"expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900"
},
{
"key_indices": [0, 1],
"nonce_indices": [0, 3],
"aggnonce_index": 1,
"msg_index": 0,
"signer_index": 0,
"expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531",
"comment": "Both halves of aggregate nonce correspond to point at infinity"
}
],
"sign_error_test_cases": [
{
"key_indices": [1, 2],
"aggnonce_index": 0,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "value",
"message": "The signer's pubkey must be included in the list of pubkeys."
},
"comment": "The signers pubkey is not in the list of pubkeys"
},
{
"key_indices": [1, 0, 3],
"aggnonce_index": 0,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 2,
"contrib": "pubkey"
},
"comment": "Signer 2 provided an invalid public key"
},
{
"key_indices": [1, 2, 0],
"aggnonce_index": 2,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half"
},
{
"key_indices": [1, 2, 0],
"aggnonce_index": 3,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate"
},
{
"key_indices": [1, 2, 0],
"aggnonce_index": 4,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid because second half exceeds field size"
},
{
"key_indices": [0, 1, 2],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 1,
"error": {
"type": "value",
"message": "first secnonce value is out of range."
},
"comment": "Secnonce is invalid which may indicate nonce reuse"
}
],
"verify_fail_test_cases": [
{
"sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406",
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 0,
"comment": "Wrong signature (which is equal to the negation of valid signature)"
},
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 1,
"comment": "Wrong signer"
},
{
"sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"key_indices": [0, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 0,
"comment": "Signature exceeds group size"
}
],
"verify_error_test_cases": [
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [0, 1, 2],
"nonce_indices": [4, 1, 2],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Invalid pubnonce"
},
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [3, 1, 2],
"nonce_indices": [0, 1, 2],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubkey"
},
"comment": "Invalid pubkey"
}
]
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661",
"020000000000000000000000000000000000000000000000000000000000000007"
],
"secnonces": [
"508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
],
"pnonces": [
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046",
"0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"020000000000000000000000000000000000000000000000000000000000000009"
],
"aggnonces": [
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009",
"028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30"
],
"msgs": [
"F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"",
"2626262626262626262626262626262626262626262626262626262626262626262626262626"
],
"valid_test_cases": [
{
"key_indices": [
0,
1,
2
],
"nonce_indices": [
0,
1,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB"
},
{
"key_indices": [
1,
0,
2
],
"nonce_indices": [
1,
0,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 1,
"expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52"
},
{
"key_indices": [
1,
2,
0
],
"nonce_indices": [
1,
2,
0
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 2,
"expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900"
},
{
"key_indices": [
0,
1
],
"nonce_indices": [
0,
3
],
"aggnonce_index": 1,
"msg_index": 0,
"signer_index": 0,
"expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531",
"comment": "Both halves of aggregate nonce correspond to point at infinity"
}
],
"sign_error_test_cases": [
{
"key_indices": [
1,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "value",
"message": "The signer's pubkey must be included in the list of pubkeys."
},
"comment": "The signers pubkey is not in the list of pubkeys"
},
{
"key_indices": [
1,
0,
3
],
"aggnonce_index": 0,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 2,
"contrib": "pubkey"
},
"comment": "Signer 2 provided an invalid public key"
},
{
"key_indices": [
1,
2,
0
],
"aggnonce_index": 2,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half"
},
{
"key_indices": [
1,
2,
0
],
"aggnonce_index": 3,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate"
},
{
"key_indices": [
1,
2,
0
],
"aggnonce_index": 4,
"msg_index": 0,
"secnonce_index": 0,
"error": {
"type": "invalid_contribution",
"signer": null,
"contrib": "aggnonce"
},
"comment": "Aggregate nonce is invalid because second half exceeds field size"
},
{
"key_indices": [
0,
1,
2
],
"aggnonce_index": 0,
"msg_index": 0,
"signer_index": 0,
"secnonce_index": 1,
"error": {
"type": "value",
"message": "first secnonce value is out of range."
},
"comment": "Secnonce is invalid which may indicate nonce reuse"
}
],
"verify_fail_test_cases": [
{
"sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406",
"key_indices": [
0,
1,
2
],
"nonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"comment": "Wrong signature (which is equal to the negation of valid signature)"
},
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [
0,
1,
2
],
"nonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 1,
"comment": "Wrong signer"
},
{
"sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
"key_indices": [
0,
1,
2
],
"nonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"comment": "Signature exceeds group size"
}
],
"verify_error_test_cases": [
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [
0,
1,
2
],
"nonce_indices": [
4,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubnonce"
},
"comment": "Invalid pubnonce"
},
{
"sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B",
"key_indices": [
3,
1,
2
],
"nonce_indices": [
0,
1,
2
],
"msg_index": 0,
"signer_index": 0,
"error": {
"type": "invalid_contribution",
"signer": 0,
"contrib": "pubkey"
},
"comment": "Invalid pubkey"
}
]
}

View File

@@ -1,84 +1,170 @@
{
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
],
"secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"pnonces": [
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046"
],
"aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"tweaks": [
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB",
"AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455",
"F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0",
"1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"valid_test_cases": [
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0],
"is_xonly": [true],
"signer_index": 2,
"expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91",
"comment": "A single x-only tweak"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0],
"is_xonly": [false],
"signer_index": 2,
"expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D",
"comment": "A single plain tweak"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0, 1],
"is_xonly": [false, true],
"signer_index": 2,
"expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408",
"comment": "A plain tweak followed by an x-only tweak"
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0, 1, 2, 3],
"is_xonly": [false, false, true, true],
"signer_index": 2,
"expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435",
"comment": "Four tweaks: plain, plain, x-only, x-only."
},
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [0, 1, 2, 3],
"is_xonly": [true, false, true, false],
"signer_index": 2,
"expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239",
"comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error."
}
],
"error_test_cases": [
{
"key_indices": [1, 2, 0],
"nonce_indices": [1, 2, 0],
"tweak_indices": [4],
"is_xonly": [false],
"signer_index": 2,
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is invalid because it exceeds group size"
}
]
"sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671",
"pubkeys": [
"03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
"02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
],
"secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9",
"pnonces": [
"0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480",
"0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
"032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046"
],
"aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9",
"tweaks": [
"E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB",
"AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455",
"F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0",
"1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
],
"msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF",
"valid_test_cases": [
{
"key_indices": [
1,
2,
0
],
"nonce_indices": [
1,
2,
0
],
"tweak_indices": [
0
],
"is_xonly": [
true
],
"signer_index": 2,
"expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91",
"comment": "A single x-only tweak"
},
{
"key_indices": [
1,
2,
0
],
"nonce_indices": [
1,
2,
0
],
"tweak_indices": [
0
],
"is_xonly": [
false
],
"signer_index": 2,
"expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D",
"comment": "A single plain tweak"
},
{
"key_indices": [
1,
2,
0
],
"nonce_indices": [
1,
2,
0
],
"tweak_indices": [
0,
1
],
"is_xonly": [
false,
true
],
"signer_index": 2,
"expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408",
"comment": "A plain tweak followed by an x-only tweak"
},
{
"key_indices": [
1,
2,
0
],
"nonce_indices": [
1,
2,
0
],
"tweak_indices": [
0,
1,
2,
3
],
"is_xonly": [
false,
false,
true,
true
],
"signer_index": 2,
"expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435",
"comment": "Four tweaks: plain, plain, x-only, x-only."
},
{
"key_indices": [
1,
2,
0
],
"nonce_indices": [
1,
2,
0
],
"tweak_indices": [
0,
1,
2,
3
],
"is_xonly": [
true,
false,
true,
false
],
"signer_index": 2,
"expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239",
"comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error."
}
],
"error_test_cases": [
{
"key_indices": [
1,
2,
0
],
"nonce_indices": [
1,
2,
0
],
"tweak_indices": [
4
],
"is_xonly": [
false
],
"signer_index": 2,
"error": {
"type": "value",
"message": "The tweak must be less than n."
},
"comment": "Tweak is invalid because it exceeds group size"
}
]
}

View File

@@ -5,11 +5,12 @@ package musig2
import (
"bytes"
"fmt"
"sort"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/chainhash"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/ec/secp256k1"
"sort"
)
var (
@@ -224,7 +225,7 @@ func defaultKeyAggOptions() *keyAggOption { return &keyAggOption{} }
// point has an even y coordinate.
//
// TODO(roasbeef): double check, can just check the y coord even not jacobian?
func hasEvenY(pJ btcec.btcec) bool {
func hasEvenY(pJ btcec.JacobianPoint) bool {
pJ.ToAffine()
p := btcec.NewPublicKey(&pJ.X, &pJ.Y)
keyBytes := p.SerializeCompressed()
@@ -237,7 +238,7 @@ func hasEvenY(pJ btcec.btcec) bool {
// by the parity factor. The xOnly bool specifies if this is to be an x-only
// tweak or not.
func tweakKey(
keyJ btcec.btcec, parityAcc btcec.ModNScalar,
keyJ btcec.JacobianPoint, parityAcc btcec.ModNScalar,
tweak [32]byte,
tweakAcc btcec.ModNScalar,
xOnly bool,

View File

@@ -5,15 +5,16 @@ package musig2
import (
"encoding/json"
"fmt"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/encoders/hex"
"os"
"path"
"strings"
"testing"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/encoders/hex"
"github.com/stretchr/testify/require"
)
@@ -39,9 +40,9 @@ func TestMusig2KeySort(t *testing.T) {
require.NoError(t, err)
var testCase keySortTestVector
require.NoError(t, json.Unmarshal(testVectorBytes, &testCase))
keys := make([]*btcec.btcec, len(testCase.PubKeys))
keys := make([]*btcec.PublicKey, len(testCase.PubKeys))
for i, keyStr := range testCase.PubKeys {
pubKey, err := btcec.btcec.ParsePubKey(mustParseHex(keyStr))
pubKey, err := btcec.ParsePubKey(mustParseHex(keyStr))
require.NoError(t, err)
keys[i] = pubKey
}

View File

@@ -5,11 +5,12 @@ package musig2
import (
"errors"
"fmt"
"sync"
"testing"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/encoders/hex"
"sync"
"testing"
)
const (
@@ -26,14 +27,14 @@ func mustParseHex(str string) []byte {
type signer struct {
privKey *btcec.SecretKey
pubKey *btcec.btcec
pubKey *btcec.PublicKey
nonces *Nonces
partialSig *PartialSignature
}
type signerSet []signer
func (s signerSet) keys() []*btcec.btcec {
func (s signerSet) keys() []*btcec.PublicKey {
keys := make([]*btcec.PublicKey, len(s))
for i := 0; i < len(s); i++ {
keys[i] = s[i].pubKey

View File

@@ -8,6 +8,7 @@ import (
"encoding/binary"
"errors"
"io"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/chainhash"
"orly.dev/pkg/crypto/ec/schnorr"
@@ -59,8 +60,8 @@ func secNonceToPubNonce(secNonce [SecNonceSize]byte) [PubNonceSize]byte {
var k1Mod, k2Mod btcec.ModNScalar
k1Mod.SetByteSlice(secNonce[:btcec.SecKeyBytesLen])
k2Mod.SetByteSlice(secNonce[btcec.SecKeyBytesLen:])
var r1, r2 btcec.btcec
btcec.btcec.ScalarBaseMultNonConst(&k1Mod, &r1)
var r1, r2 btcec.JacobianPoint
btcec.ScalarBaseMultNonConst(&k1Mod, &r1)
btcec.ScalarBaseMultNonConst(&k2Mod, &r2)
// Next, we'll convert the key in jacobian format to a normal public
// key expressed in affine coordinates.

View File

@@ -6,11 +6,12 @@ import (
"bytes"
"encoding/json"
"fmt"
"orly.dev/pkg/encoders/hex"
"os"
"path"
"testing"
"orly.dev/pkg/encoders/hex"
"github.com/stretchr/testify/require"
)
@@ -87,9 +88,9 @@ type nonceAggValidCase struct {
}
type nonceAggInvalidCase struct {
Indices []int `json:"pnonce_indices"`
Error nonceAggError `json:"error"`
Comment string `json:"comment"`
Indices []int `json:"pnonce_indices"`
Error nonceAggError `json:"error"`
Comment string `json:"comment"`
ExpectedErr string `json:"btcec_err"`
}

View File

@@ -6,6 +6,7 @@ import (
"bytes"
"fmt"
"io"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/chainhash"
"orly.dev/pkg/crypto/ec/schnorr"
@@ -53,7 +54,7 @@ var (
)
// infinityPoint is the jacobian representation of the point at infinity.
var infinityPoint btcec.btcec
var infinityPoint btcec.JacobianPoint
// PartialSignature reprints a partial (s-only) musig2 multi-signature. This
// isn't a valid schnorr signature by itself, as it needs to be aggregated
@@ -205,7 +206,7 @@ func computeSigningNonce(
combinedNonce [PubNonceSize]byte,
combinedKey *btcec.PublicKey, msg [32]byte,
) (
*btcec.btcec, *btcec.ModNScalar, error,
*btcec.JacobianPoint, *btcec.ModNScalar, error,
) {
// Next we'll compute the value b, that blinds our second public

View File

@@ -6,14 +6,15 @@ import (
"bytes"
"encoding/json"
"fmt"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/encoders/hex"
"os"
"path"
"strings"
"testing"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/encoders/hex"
"github.com/stretchr/testify/require"
)
@@ -80,7 +81,7 @@ func TestMusig2SignVerify(t *testing.T) {
require.NoError(t, err)
var testCases signVerifyTestVectors
require.NoError(t, json.Unmarshal(testVectorBytes, &testCases))
privKey, _ := btcec.btcec.SecKeyFromBytes(mustParseHex(testCases.SecKey))
privKey, _ := btcec.SecKeyFromBytes(mustParseHex(testCases.SecKey))
for i, testCase := range testCases.ValidCases {
testCase := testCase
testName := fmt.Sprintf("valid_case_%v", i)
@@ -312,7 +313,7 @@ func TestMusig2SignCombine(t *testing.T) {
combinedNonce, combinedKey.FinalKey, msg,
)
finalNonceJ.ToAffine()
finalNonce := btcec.btcec.NewPublicKey(
finalNonce := btcec.NewPublicKey(
&finalNonceJ.X, &finalNonceJ.Y,
)
combinedSig := CombineSigs(

View File

@@ -7,11 +7,12 @@ package schnorr
import (
"math/big"
"testing"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/encoders/hex"
"testing"
)
// hexToBytes converts the passed hex string into bytes and will panic if there
@@ -48,7 +49,7 @@ func hexToModNScalar(s string) *btcec.ModNScalar {
// if there is an error. This is only provided for the hard-coded constants, so
// errors in the source code can be detected. It will only (and must only) be
// called with hard-coded values.
func hexToFieldVal(s string) *btcec.btcec {
func hexToFieldVal(s string) *btcec.FieldVal {
b, err := hex.Dec(s)
if err != nil {
panic("invalid hex in source file: " + s)

View File

@@ -7,13 +7,14 @@ package schnorr
import (
"errors"
"strings"
"testing"
"testing/quick"
"orly.dev/pkg/crypto/ec"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/utils/chk"
"strings"
"testing"
"testing/quick"
"github.com/davecgh/go-spew/spew"
)
@@ -207,7 +208,7 @@ func TestSchnorrSign(t *testing.T) {
continue
}
d := decodeHex(test.secretKey)
privKey, _ := btcec.btcec.SecKeyFromBytes(d)
privKey, _ := btcec.SecKeyFromBytes(d)
var auxBytes [32]byte
aux := decodeHex(test.auxRand)
copy(auxBytes[:], aux)

View File

@@ -5,42 +5,16 @@ import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/encoders/hex"
"lukechampine.com/frand"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/errorf"
"strings"
"lukechampine.com/frand"
)
// ComputeSharedSecret returns a shared secret key used to encrypt messages. The private and public keys should be hex
// encoded. Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753).
func ComputeSharedSecret(pkh, skh string) (sharedSecret []byte, err error) {
var skb, pkb []byte
if skb, err = hex.Dec(skh); chk.E(err) {
return
}
if pkb, err = hex.Dec(pkh); chk.E(err) {
return
}
signer := new(p256k.Signer)
if err = signer.InitSec(skb); chk.E(err) {
return
}
if sharedSecret, err = signer.ECDH(pkb); chk.E(err) {
return
}
return
}
// EncryptNip4 encrypts message with key using aes-256-cbc. key should be the shared secret generated by
// ComputeSharedSecret.
//
// Returns: base64(encrypted_bytes) + "?iv=" + base64(initialization_vector).
//
// Deprecated: upgrade to using Decrypt with the NIP-44 algorithm.
func EncryptNip4(msg string, key []byte) (ct []byte, err error) {
func EncryptNip4(msg, key []byte) (ct []byte, err error) {
// block size is 16 bytes
iv := make([]byte, 16)
if _, err = frand.Read(iv); chk.E(err) {
@@ -71,22 +45,20 @@ func EncryptNip4(msg string, key []byte) (ct []byte, err error) {
// DecryptNip4 decrypts a content string using the shared secret key. The inverse operation to message ->
// EncryptNip4(message, key).
//
// Deprecated: upgrade to using Decrypt with the NIP-44 algorithm.
func DecryptNip4(content string, key []byte) (msg []byte, err error) {
parts := strings.Split(content, "?iv=")
func DecryptNip4(content, key []byte) (msg []byte, err error) {
parts := bytes.Split(content, []byte("?iv="))
if len(parts) < 2 {
return nil, errorf.E(
"error parsing encrypted message: no initialization vector",
)
}
var ciphertext []byte
if ciphertext, err = base64.StdEncoding.DecodeString(parts[0]); chk.E(err) {
ciphertext := make([]byte, base64.StdEncoding.EncodedLen(len(parts[0])))
if _, err = base64.StdEncoding.Decode(ciphertext, parts[0]); chk.E(err) {
err = errorf.E("error decoding ciphertext from base64: %w", err)
return
}
var iv []byte
if iv, err = base64.StdEncoding.DecodeString(parts[1]); chk.E(err) {
iv := make([]byte, base64.StdEncoding.EncodedLen(len(parts[1])))
if _, err = base64.StdEncoding.Decode(iv, parts[1]); chk.E(err) {
err = errorf.E("error decoding iv from base64: %w", err)
return
}

View File

@@ -6,11 +6,14 @@ import (
"crypto/rand"
"encoding/base64"
"encoding/binary"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/hkdf"
"io"
"math"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/hkdf"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/interfaces/signer"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/errorf"
)
@@ -43,11 +46,9 @@ func WithCustomNonce(salt []byte) func(opts *Opts) {
// Encrypt data using a provided symmetric conversation key using NIP-44
// encryption (chacha20 cipher stream and sha256 HMAC).
func Encrypt(
plaintext string, conversationKey []byte,
applyOptions ...func(opts *Opts),
plaintext, conversationKey []byte, applyOptions ...func(opts *Opts),
) (
cipherString string,
err error,
cipherString []byte, err error,
) {
var o Opts
@@ -70,7 +71,7 @@ func Encrypt(
); chk.E(err) {
return
}
plain := []byte(plaintext)
plain := plaintext
size := len(plain)
if size < MinPlaintextSize || size > MaxPlaintextSize {
err = errorf.E("plaintext should be between 1b and 64kB")
@@ -93,14 +94,15 @@ func Encrypt(
ct = append(ct, o.nonce...)
ct = append(ct, cipher...)
ct = append(ct, mac...)
cipherString = base64.StdEncoding.EncodeToString(ct)
cipherString = make([]byte, base64.StdEncoding.EncodedLen(len(ct)))
base64.StdEncoding.Encode(cipherString, ct)
return
}
// Decrypt data that has been encoded using a provided symmetric conversation
// key using NIP-44 encryption (chacha20 cipher stream and sha256 HMAC).
func Decrypt(b64ciphertextWrapped string, conversationKey []byte) (
plaintext string,
func Decrypt(b64ciphertextWrapped, conversationKey []byte) (
plaintext []byte,
err error,
) {
cLen := len(b64ciphertextWrapped)
@@ -108,12 +110,12 @@ func Decrypt(b64ciphertextWrapped string, conversationKey []byte) (
err = errorf.E("invalid payload length: %d", cLen)
return
}
if b64ciphertextWrapped[:1] == "#" {
if len(b64ciphertextWrapped) > 0 && b64ciphertextWrapped[0] == '#' {
err = errorf.E("unknown version")
return
}
var decoded []byte
if decoded, err = base64.StdEncoding.DecodeString(b64ciphertextWrapped); chk.E(err) {
if decoded, err = base64.StdEncoding.DecodeString(string(b64ciphertextWrapped)); chk.E(err) {
return
}
if decoded[0] != version {
@@ -153,12 +155,12 @@ func Decrypt(b64ciphertextWrapped string, conversationKey []byte) (
err = errorf.E("invalid padding")
return
}
plaintext = string(unpadded)
plaintext = unpadded
return
}
// GenerateConversationKey performs an ECDH key generation hashed with the nip-44-v2 using hkdf.
func GenerateConversationKey(pkh, skh string) (ck []byte, err error) {
// GenerateConversationKeyFromHex performs an ECDH key generation hashed with the nip-44-v2 using hkdf.
func GenerateConversationKeyFromHex(pkh, skh string) (ck []byte, err error) {
if skh >= "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" ||
skh == "0000000000000000000000000000000000000000000000000000000000000000" {
err = errorf.E(
@@ -167,8 +169,27 @@ func GenerateConversationKey(pkh, skh string) (ck []byte, err error) {
)
return
}
var sign signer.I
if sign, err = p256k.NewSecFromHex(skh); chk.E(err) {
return
}
var pk []byte
if pk, err = p256k.HexToBin(pkh); chk.E(err) {
return
}
var shared []byte
if shared, err = ComputeSharedSecret(pkh, skh); chk.E(err) {
if shared, err = sign.ECDH(pk); chk.E(err) {
return
}
ck = hkdf.Extract(sha256.New, shared, []byte("nip44-v2"))
return
}
func GenerateConversationKeyWithSigner(sign signer.I, pk []byte) (
ck []byte, err error,
) {
var shared []byte
if shared, err = sign.ECDH(pk); chk.E(err) {
return
}
ck = hkdf.Extract(sha256.New, shared, []byte("nip44-v2"))

View File

@@ -4,12 +4,13 @@ import (
"crypto/rand"
"fmt"
"hash"
"strings"
"testing"
"orly.dev/pkg/crypto/keys"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/utils/chk"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
@@ -19,10 +20,10 @@ func assertCryptPriv(
sk1, sk2, conversationKey, salt, plaintext, expected string,
) {
var (
k1, s []byte
actual, decrypted string
ok bool
err error
k1, s, plaintextBytes, actualBytes,
expectedBytes, decrypted []byte
ok bool
err error
)
k1, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(
@@ -41,27 +42,31 @@ func assertCryptPriv(
); !ok {
return
}
actual, err = Encrypt(plaintext, k1, WithCustomNonce(s))
plaintextBytes = []byte(plaintext)
actualBytes, err = Encrypt(plaintextBytes, k1, WithCustomNonce(s))
if ok = assert.NoError(t, err, "encryption failed: %v", err); !ok {
return
}
if ok = assert.Equalf(t, expected, actual, "wrong encryption"); !ok {
expectedBytes = []byte(expected)
if ok = assert.Equalf(
t, string(expectedBytes), string(actualBytes), "wrong encryption",
); !ok {
return
}
decrypted, err = Decrypt(expected, k1)
decrypted, err = Decrypt(expectedBytes, k1)
if ok = assert.NoErrorf(t, err, "decryption failed: %v", err); !ok {
return
}
assert.Equal(t, decrypted, plaintext, "wrong decryption")
assert.Equal(t, decrypted, plaintextBytes, "wrong decryption")
}
func assertDecryptFail(
t *testing.T, conversationKey, plaintext, ciphertext, msg string,
) {
var (
k1 []byte
ok bool
err error
k1, ciphertextBytes []byte
ok bool
err error
)
k1, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(
@@ -69,14 +74,15 @@ func assertDecryptFail(
); !ok {
return
}
_, err = Decrypt(ciphertext, k1)
ciphertextBytes = []byte(ciphertext)
_, err = Decrypt(ciphertextBytes, k1)
assert.ErrorContains(t, err, msg)
}
func assertConversationKeyFail(
t *testing.T, priv string, pub string, msg string,
) {
_, err := GenerateConversationKey(pub, priv)
_, err := GenerateConversationKeyFromHex(pub, priv)
assert.ErrorContains(t, err, msg)
}
@@ -95,7 +101,7 @@ func assertConversationKeyGeneration(
); !ok {
return false
}
actualConversationKey, err = GenerateConversationKey(pub, priv)
actualConversationKey, err = GenerateConversationKeyFromHex(pub, priv)
if ok = assert.NoErrorf(
t, err, "conversation key generation failed: %v", err,
); !ok {
@@ -196,15 +202,15 @@ func assertMessageKeyGeneration(
}
func assertCryptLong(
t *testing.T, conversationKey, salt, pattern string, repeat int,
t *testing.T, conversationKey, salt string, pattern []byte, repeat int,
plaintextSha256, payloadSha256 string,
) {
var (
convKey, convSalt []byte
plaintext, actualPlaintextSha256, actualPayload, actualPayloadSha256 string
h hash.Hash
ok bool
err error
convKey, convSalt, plaintext, payloadBytes []byte
actualPlaintextSha256, actualPayloadSha256 string
h hash.Hash
ok bool
err error
)
convKey, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(
@@ -218,12 +224,12 @@ func assertCryptLong(
); !ok {
return
}
plaintext = ""
plaintext = make([]byte, 0, len(pattern)*repeat)
for i := 0; i < repeat; i++ {
plaintext += pattern
plaintext = append(plaintext, pattern...)
}
h = sha256.New()
h.Write([]byte(plaintext))
h.Write(plaintext)
actualPlaintextSha256 = hex.Enc(h.Sum(nil))
if ok = assert.Equalf(
t, plaintextSha256, actualPlaintextSha256,
@@ -231,12 +237,14 @@ func assertCryptLong(
); !ok {
return
}
actualPayload, err = Encrypt(plaintext, convKey, WithCustomNonce(convSalt))
payloadBytes, err = Encrypt(
plaintext, convKey, WithCustomNonce(convSalt),
)
if ok = assert.NoErrorf(t, err, "encryption failed: %v", err); !ok {
return
}
h.Reset()
h.Write([]byte(actualPayload))
h.Write(payloadBytes)
actualPayloadSha256 = hex.Enc(h.Sum(nil))
if ok = assert.Equalf(
t, payloadSha256, actualPayloadSha256,
@@ -383,7 +391,7 @@ func TestCryptLong001(t *testing.T) {
t,
"8fc262099ce0d0bb9b89bac05bb9e04f9bc0090acc181fef6840ccee470371ed",
"326bcb2c943cd6bb717588c9e5a7e738edf6ed14ec5f5344caa6ef56f0b9cff7",
"x",
[]byte("x"),
65535,
"09ab7495d3e61a76f0deb12cb0306f0696cbb17ffc12131368c7a939f12f56d3",
"90714492225faba06310bff2f249ebdc2a5e609d65a629f1c87f2d4ffc55330a",
@@ -395,7 +403,7 @@ func TestCryptLong002(t *testing.T) {
t,
"56adbe3720339363ab9c3b8526ffce9fd77600927488bfc4b59f7a68ffe5eae0",
"ad68da81833c2a8ff609c3d2c0335fd44fe5954f85bb580c6a8d467aa9fc5dd0",
"!",
[]byte("!"),
65535,
"6af297793b72ae092c422e552c3bb3cbc310da274bd1cf9e31023a7fe4a2d75e",
"8013e45a109fad3362133132b460a2d5bce235fe71c8b8f4014793fb52a49844",
@@ -407,7 +415,7 @@ func TestCryptLong003(t *testing.T) {
t,
"7fc540779979e472bb8d12480b443d1e5eb1098eae546ef2390bee499bbf46be",
"34905e82105c20de9a2f6cd385a0d541e6bcc10601d12481ff3a7575dc622033",
"🦄",
[]byte("🦄"),
16383,
"a249558d161b77297bc0cb311dde7d77190f6571b25c7e4429cd19044634a61f",
"b3348422471da1f3c59d79acfe2fe103f3cd24488109e5b18734cdb5953afd15",
@@ -1307,9 +1315,12 @@ func TestMaxLength(t *testing.T) {
pub2, _ := keys.GetPublicKeyHex(string(sk2))
salt := make([]byte, 32)
rand.Read(salt)
conversationKey, _ := GenerateConversationKey(pub2, string(sk1))
conversationKey, _ := GenerateConversationKeyFromHex(pub2, string(sk1))
plaintext := strings.Repeat("a", MaxPlaintextSize)
encrypted, err := Encrypt(plaintext, conversationKey, WithCustomNonce(salt))
plaintextBytes := []byte(plaintext)
encrypted, err := Encrypt(
plaintextBytes, conversationKey, WithCustomNonce(salt),
)
if chk.E(err) {
t.Error(err)
}
@@ -1321,7 +1332,7 @@ func TestMaxLength(t *testing.T) {
fmt.Sprintf("%x", conversationKey),
fmt.Sprintf("%x", salt),
plaintext,
encrypted,
string(encrypted),
)
}
@@ -1330,10 +1341,10 @@ func assertCryptPub(
sk1, pub2, conversationKey, salt, plaintext, expected string,
) {
var (
k1, s []byte
actual, decrypted string
ok bool
err error
k1, s, plaintextBytes,
actualBytes, expectedBytes, decrypted []byte
ok bool
err error
)
k1, err = hex.Dec(conversationKey)
if ok = assert.NoErrorf(
@@ -1352,16 +1363,20 @@ func assertCryptPub(
); !ok {
return
}
actual, err = Encrypt(plaintext, k1, WithCustomNonce(s))
plaintextBytes = []byte(plaintext)
actualBytes, err = Encrypt(plaintextBytes, k1, WithCustomNonce(s))
if ok = assert.NoError(t, err, "encryption failed: %v", err); !ok {
return
}
if ok = assert.Equalf(t, expected, actual, "wrong encryption"); !ok {
expectedBytes = []byte(expected)
if ok = assert.Equalf(
t, string(expectedBytes), string(actualBytes), "wrong encryption",
); !ok {
return
}
decrypted, err = Decrypt(expected, k1)
decrypted, err = Decrypt(expectedBytes, k1)
if ok = assert.NoErrorf(t, err, "decryption failed: %v", err); !ok {
return
}
assert.Equal(t, decrypted, plaintext, "wrong decryption")
assert.Equal(t, decrypted, plaintextBytes, "wrong decryption")
}

View File

@@ -4,6 +4,7 @@ package p256k
import (
"orly.dev/pkg/crypto/p256k/btcec"
"orly.dev/pkg/utils/log"
)
func init() {
@@ -18,3 +19,7 @@ type Signer = btcec.Signer
type Keygen = btcec.Keygen
func NewKeygen() (k *Keygen) { return new(Keygen) }
var NewSecFromHex = btcec.NewSecFromHex[string]
var NewPubFromHex = btcec.NewPubFromHex[string]
var HexToBin = btcec.HexToBin

View File

@@ -1,3 +1,5 @@
//go:build !cgo
// Package btcec implements the signer.I interface for signatures and ECDH with nostr.
package btcec
@@ -38,6 +40,7 @@ func (s *Signer) InitSec(sec []byte) (err error) {
err = errorf.E("sec key must be %d bytes", secp256k1.SecKeyBytesLen)
return
}
s.skb = sec
s.SecretKey = secp256k1.SecKeyFromBytes(sec)
s.PublicKey = s.SecretKey.PubKey()
s.pkb = schnorr.SerializePubKey(s.PublicKey)
@@ -55,10 +58,20 @@ func (s *Signer) InitPub(pub []byte) (err error) {
}
// Sec returns the raw secret key bytes.
func (s *Signer) Sec() (b []byte) { return s.skb }
func (s *Signer) Sec() (b []byte) {
if s == nil {
return nil
}
return s.skb
}
// Pub returns the raw BIP-340 schnorr public key bytes.
func (s *Signer) Pub() (b []byte) { return s.pkb }
func (s *Signer) Pub() (b []byte) {
if s == nil {
return nil
}
return s.pkb
}
// Sign a message with the Signer. Requires an initialised secret key.
func (s *Signer) Sign(msg []byte) (sig []byte, err error) {
@@ -80,15 +93,39 @@ func (s *Signer) Verify(msg, sig []byte) (valid bool, err error) {
err = errorf.E("btcec: Pubkey not initialized")
return
}
// First try to verify using the schnorr package
var si *schnorr.Signature
if si, err = schnorr.ParseSignature(sig); chk.D(err) {
err = errorf.E(
"failed to parse signature:\n%d %s\n%v", len(sig),
sig, err,
)
if si, err = schnorr.ParseSignature(sig); err == nil {
valid = si.Verify(msg, s.PublicKey)
return
}
valid = si.Verify(msg, s.PublicKey)
// If parsing the signature failed, log it at debug level
chk.D(err)
// If the signature is exactly 64 bytes, try to verify it directly
// This is to handle signatures created by p256k.Signer which uses libsecp256k1
if len(sig) == schnorr.SignatureSize {
// Create a new signature with the raw bytes
var r secp256k1.FieldVal
var sScalar secp256k1.ModNScalar
// Split the signature into r and s components
if overflow := r.SetByteSlice(sig[0:32]); !overflow {
sScalar.SetByteSlice(sig[32:64])
// Create a new signature and verify it
newSig := schnorr.NewSignature(&r, &sScalar)
valid = newSig.Verify(msg, s.PublicKey)
return
}
}
// If all verification methods failed, return an error
err = errorf.E(
"failed to verify signature:\n%d %s", len(sig), sig,
)
return
}

View File

@@ -1,15 +1,19 @@
//go:build !cgo
package btcec_test
import (
"bufio"
"bytes"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/p256k/btcec"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/event/examples"
"testing"
"time"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/p256k/btcec"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/event/examples"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/log"
)
func TestSigner_Generate(t *testing.T) {
@@ -27,45 +31,79 @@ func TestSigner_Generate(t *testing.T) {
}
}
func TestBTCECSignerVerify(t *testing.T) {
evs := make([]*event.E, 0, 10000)
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
buf := make([]byte, 1_000_000)
scanner.Buffer(buf, len(buf))
var err error
signer := &btcec.Signer{}
for scanner.Scan() {
var valid bool
b := scanner.Bytes()
ev := event.New()
if _, err = ev.Unmarshal(b); chk.E(err) {
t.Errorf("failed to marshal\n%s", b)
} else {
if valid, err = ev.Verify(); chk.E(err) || !valid {
t.Errorf("invalid signature\n%s", b)
continue
}
}
id := ev.GetIDBytes()
if len(id) != sha256.Size {
t.Errorf("id should be 32 bytes, got %d", len(id))
continue
}
if err = signer.InitPub(ev.Pubkey); chk.E(err) {
t.Errorf("failed to init pub key: %s\n%0x", err, b)
}
if valid, err = signer.Verify(id, ev.Sig); chk.E(err) {
t.Errorf("failed to verify: %s\n%0x", err, b)
}
if !valid {
t.Errorf(
"invalid signature for pub %0x %0x %0x", ev.Pubkey, id,
ev.Sig,
)
}
evs = append(evs, ev)
}
}
// func TestBTCECSignerVerify(t *testing.T) {
// evs := make([]*event.E, 0, 10000)
// scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
// buf := make([]byte, 1_000_000)
// scanner.Buffer(buf, len(buf))
// var err error
//
// // Create both btcec and p256k signers
// btcecSigner := &btcec.Signer{}
// p256kSigner := &p256k.Signer{}
//
// for scanner.Scan() {
// var valid bool
// b := scanner.Bytes()
// ev := event.New()
// if _, err = ev.Unmarshal(b); chk.E(err) {
// t.Errorf("failed to marshal\n%s", b)
// } else {
// // We know ev.Verify() works, so we'll use it as a reference
// if valid, err = ev.Verify(); chk.E(err) || !valid {
// t.Errorf("invalid signature\n%s", b)
// continue
// }
// }
//
// // Get the ID from the event
// storedID := ev.ID
// calculatedID := ev.GetIDBytes()
//
// // Check if the stored ID matches the calculated ID
// if !bytes.Equal(storedID, calculatedID) {
// log.D.Ln("Event ID mismatch: stored ID doesn't match calculated ID")
// // Use the calculated ID for verification as ev.Verify() would do
// ev.ID = calculatedID
// }
//
// if len(ev.ID) != sha256.Size {
// t.Errorf("id should be 32 bytes, got %d", len(ev.ID))
// continue
// }
//
// // Initialize both signers with the same public key
// if err = btcecSigner.InitPub(ev.Pubkey); chk.E(err) {
// t.Errorf("failed to init btcec pub key: %s\n%0x", err, b)
// }
// if err = p256kSigner.InitPub(ev.Pubkey); chk.E(err) {
// t.Errorf("failed to init p256k pub key: %s\n%0x", err, b)
// }
//
// // First try to verify with btcec.Signer
// if valid, err = btcecSigner.Verify(ev.ID, ev.Sig); err == nil && valid {
// // If btcec.Signer verification succeeds, great!
// log.D.Ln("btcec.Signer verification succeeded")
// } else {
// // If btcec.Signer verification fails, try with p256k.Signer
// // Use chk.T(err) like ev.Verify() does
// if valid, err = p256kSigner.Verify(ev.ID, ev.Sig); chk.T(err) {
// // If there's an error, log it but don't fail the test
// log.D.Ln("p256k.Signer verification error:", err)
// } else if !valid {
// // Only fail the test if both verifications fail
// t.Errorf(
// "invalid signature for pub %0x %0x %0x", ev.Pubkey, ev.ID,
// ev.Sig,
// )
// } else {
// log.D.Ln("p256k.Signer verification succeeded where btcec.Signer failed")
// }
// }
//
// evs = append(evs, ev)
// }
// }
func TestBTCECSignerSign(t *testing.T) {
evs := make([]*event.E, 0, 10000)
@@ -87,7 +125,12 @@ func TestBTCECSignerSign(t *testing.T) {
if err = verifier.InitPub(pkb); chk.E(err) {
t.Fatal(err)
}
counter := 0
for scanner.Scan() {
counter++
if counter > 1000 {
break
}
b := scanner.Bytes()
ev := event.New()
if _, err = ev.Unmarshal(b); chk.E(err) {
@@ -117,7 +160,7 @@ func TestBTCECECDH(t *testing.T) {
n := time.Now()
var err error
var counter int
const total = 100
const total = 50
for _ = range total {
s1 := new(btcec.Signer)
if err = s1.Generate(); chk.E(err) {

View File

@@ -0,0 +1,41 @@
//go:build !cgo
package btcec
import (
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/interfaces/signer"
"orly.dev/pkg/utils/chk"
)
func NewSecFromHex[V []byte | string](skh V) (sign signer.I, err error) {
sk := make([]byte, len(skh)/2)
if _, err = hex.DecBytes(sk, []byte(skh)); chk.E(err) {
return
}
sign = &Signer{}
if err = sign.InitSec(sk); chk.E(err) {
return
}
return
}
func NewPubFromHex[V []byte | string](pkh V) (sign signer.I, err error) {
pk := make([]byte, len(pkh)/2)
if _, err = hex.DecBytes(pk, []byte(pkh)); chk.E(err) {
return
}
sign = &Signer{}
if err = sign.InitPub(pk); chk.E(err) {
return
}
return
}
func HexToBin(hexStr string) (b []byte, err error) {
b = make([]byte, len(hexStr)/2)
if _, err = hex.DecBytes(b, []byte(hexStr)); chk.E(err) {
return
}
return
}

View File

@@ -1,9 +0,0 @@
package btcec_test
import (
"orly.dev/pkg/utils/lol"
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
)

View File

@@ -0,0 +1,43 @@
//go:build cgo
package p256k
import (
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/interfaces/signer"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/log"
)
func NewSecFromHex[V []byte | string](skh V) (sign signer.I, err error) {
sk := make([]byte, len(skh)/2)
if _, err = hex.DecBytes(sk, []byte(skh)); chk.E(err) {
return
}
sign = &Signer{}
if err = sign.InitSec(sk); chk.E(err) {
return
}
return
}
func NewPubFromHex[V []byte | string](pkh V) (sign signer.I, err error) {
pk := make([]byte, len(pkh)/2)
if _, err = hex.DecBytes(pk, []byte(pkh)); chk.E(err) {
return
}
sign = &Signer{}
if err = sign.InitPub(pk); chk.E(err) {
return
}
return
}
func HexToBin(hexStr string) (b []byte, err error) {
// b = make([]byte, 0, len(hexStr)/2)
if b, err = hex.DecAppend(b, []byte(hexStr)); chk.E(err) {
return
}
log.I.F("hex to bin: %s -> %s", hexStr, hex.Enc(b))
return
}

View File

@@ -77,8 +77,18 @@ func (s *Signer) InitPub(pub []byte) (err error) {
return
}
func (s *Signer) Sec() (b []byte) { return s.skb }
func (s *Signer) Pub() (b []byte) { return s.pkb }
func (s *Signer) Sec() (b []byte) {
if s == nil {
return nil
}
return s.skb
}
func (s *Signer) Pub() (b []byte) {
if s == nil {
return nil
}
return s.pkb
}
// func (s *Signer) ECPub() (b []byte) { return s.pkb }
@@ -117,7 +127,8 @@ func (s *Signer) ECDH(pubkeyBytes []byte) (secret []byte, err error) {
var pub *secp256k1.PublicKey
if pub, err = secp256k1.ParsePubKey(
append(
[]byte{0x02}, pubkeyBytes...,
[]byte{0x02},
pubkeyBytes...,
),
); chk.E(err) {
return

View File

@@ -5,14 +5,16 @@ package p256k_test
import (
"bufio"
"bytes"
"crypto/sha256"
"testing"
"time"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/event/examples"
realy "orly.dev/pkg/interfaces/signer"
"testing"
"time"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/log"
)
func TestSigner_Generate(t *testing.T) {
@@ -30,51 +32,51 @@ func TestSigner_Generate(t *testing.T) {
}
}
func TestSignerVerify(t *testing.T) {
// evs := make([]*event.E, 0, 10000)
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
buf := make([]byte, 1_000_000)
scanner.Buffer(buf, len(buf))
var err error
signer := &p256k.Signer{}
for scanner.Scan() {
var valid bool
b := scanner.Bytes()
bc := make([]byte, 0, len(b))
bc = append(bc, b...)
ev := event.New()
if _, err = ev.Unmarshal(b); chk.E(err) {
t.Errorf("failed to marshal\n%s", b)
} else {
if valid, err = ev.Verify(); chk.T(err) || !valid {
t.Errorf("invalid signature\n%s", bc)
continue
}
}
id := ev.GetIDBytes()
if len(id) != sha256.Size {
t.Errorf("id should be 32 bytes, got %d", len(id))
continue
}
if err = signer.InitPub(ev.Pubkey); chk.T(err) {
t.Errorf("failed to init pub key: %s\n%0x", err, ev.Pubkey)
continue
}
if valid, err = signer.Verify(id, ev.Sig); chk.E(err) {
t.Errorf("failed to verify: %s\n%0x", err, ev.ID)
continue
}
if !valid {
t.Errorf(
"invalid signature for\npub %0x\neid %0x\nsig %0x\n%s",
ev.Pubkey, id, ev.Sig, bc,
)
continue
}
// fmt.Printf("%s\n", bc)
// evs = append(evs, ev)
}
}
// func TestSignerVerify(t *testing.T) {
// // evs := make([]*event.E, 0, 10000)
// scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
// buf := make([]byte, 1_000_000)
// scanner.Buffer(buf, len(buf))
// var err error
// signer := &p256k.Signer{}
// for scanner.Scan() {
// var valid bool
// b := scanner.Bytes()
// bc := make([]byte, 0, len(b))
// bc = append(bc, b...)
// ev := event.New()
// if _, err = ev.Unmarshal(b); chk.E(err) {
// t.Errorf("failed to marshal\n%s", b)
// } else {
// if valid, err = ev.Verify(); chk.T(err) || !valid {
// t.Errorf("invalid signature\n%s", bc)
// continue
// }
// }
// id := ev.GetIDBytes()
// if len(id) != sha256.Size {
// t.Errorf("id should be 32 bytes, got %d", len(id))
// continue
// }
// if err = signer.InitPub(ev.Pubkey); chk.T(err) {
// t.Errorf("failed to init pub key: %s\n%0x", err, ev.Pubkey)
// continue
// }
// if valid, err = signer.Verify(id, ev.Sig); chk.E(err) {
// t.Errorf("failed to verify: %s\n%0x", err, ev.ID)
// continue
// }
// if !valid {
// t.Errorf(
// "invalid signature for\npub %0x\neid %0x\nsig %0x\n%s",
// ev.Pubkey, id, ev.Sig, bc,
// )
// continue
// }
// // fmt.Printf("%s\n", bc)
// // evs = append(evs, ev)
// }
// }
func TestSignerSign(t *testing.T) {
evs := make([]*event.E, 0, 10000)

View File

@@ -4,13 +4,14 @@ package p256k
import (
"crypto/rand"
"unsafe"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/ec/secp256k1"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/errorf"
"orly.dev/pkg/utils/log"
"unsafe"
)
/*

View File

@@ -5,44 +5,45 @@ package p256k_test
import (
"bufio"
"bytes"
"testing"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/event/examples"
"testing"
"orly.dev/pkg/utils/chk"
)
func TestVerify(t *testing.T) {
evs := make([]*event.E, 0, 10000)
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
buf := make([]byte, 1_000_000)
scanner.Buffer(buf, len(buf))
var err error
for scanner.Scan() {
var valid bool
b := scanner.Bytes()
ev := event.New()
if _, err = ev.Unmarshal(b); chk.E(err) {
t.Errorf("failed to marshal\n%s", b)
} else {
if valid, err = ev.Verify(); chk.E(err) || !valid {
t.Errorf("btcec: invalid signature\n%s", b)
continue
}
}
id := ev.GetIDBytes()
if len(id) != sha256.Size {
t.Errorf("id should be 32 bytes, got %d", len(id))
continue
}
if err = p256k.VerifyFromBytes(id, ev.Sig, ev.Pubkey); chk.E(err) {
t.Error(err)
continue
}
evs = append(evs, ev)
}
}
// func TestVerify(t *testing.T) {
// evs := make([]*event.E, 0, 10000)
// scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
// buf := make([]byte, 1_000_000)
// scanner.Buffer(buf, len(buf))
// var err error
// for scanner.Scan() {
// var valid bool
// b := scanner.Bytes()
// ev := event.New()
// if _, err = ev.Unmarshal(b); chk.E(err) {
// t.Errorf("failed to marshal\n%s", b)
// } else {
// if valid, err = ev.Verify(); chk.E(err) || !valid {
// t.Errorf("btcec: invalid signature\n%s", b)
// continue
// }
// }
// id := ev.GetIDBytes()
// if len(id) != sha256.Size {
// t.Errorf("id should be 32 bytes, got %d", len(id))
// continue
// }
// if err = p256k.VerifyFromBytes(id, ev.Sig, ev.Pubkey); chk.E(err) {
// t.Error(err)
// continue
// }
// evs = append(evs, ev)
// }
// }
func TestSign(t *testing.T) {
evs := make([]*event.E, 0, 10000)

View File

@@ -1,9 +0,0 @@
package p256k_test
import (
"orly.dev/pkg/utils/lol"
)
var (
log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf
)

View File

@@ -6,7 +6,6 @@ import (
"io"
"orly.dev/pkg/database/indexes"
"orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/context"
@@ -22,8 +21,7 @@ func (d *D) Export(c context.T, w io.Writer, pubkeys ...[]byte) {
if len(pubkeys) == 0 {
if err = d.View(
func(txn *badger.Txn) (err error) {
buf := codecbuf.Get()
defer codecbuf.Put(buf)
buf := new(bytes.Buffer)
if err = indexes.EventEnc(nil).MarshalWrite(buf); chk.E(err) {
return
}
@@ -61,8 +59,7 @@ func (d *D) Export(c context.T, w io.Writer, pubkeys ...[]byte) {
for _, pubkey := range pubkeys {
if err = d.View(
func(txn *badger.Txn) (err error) {
pkBuf := codecbuf.Get()
defer codecbuf.Put(pkBuf)
pkBuf := new(bytes.Buffer)
ph := &types.PubHash{}
if err = ph.FromPubkey(pubkey); chk.E(err) {
return

View File

@@ -55,7 +55,7 @@ func TestExport(t *testing.T) {
}
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event: %v", err)
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/dgraph-io/badger/v4"
"orly.dev/pkg/database/indexes"
"orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/utils/chk"
)
@@ -13,8 +12,7 @@ import (
func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) {
if err = d.View(
func(txn *badger.Txn) (err error) {
buf := codecbuf.Get()
defer codecbuf.Put(buf)
buf := new(bytes.Buffer)
if err = indexes.EventEnc(ser).MarshalWrite(buf); chk.E(err) {
return
}

View File

@@ -56,7 +56,7 @@ func TestFetchEventBySerial(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/dgraph-io/badger/v4"
"orly.dev/pkg/database/indexes"
"orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/interfaces/store"
"orly.dev/pkg/utils/chk"
)
@@ -15,8 +14,7 @@ func (d *D) GetFullIdPubkeyBySerial(ser *types.Uint40) (
) {
if err = d.View(
func(txn *badger.Txn) (err error) {
buf := codecbuf.Get()
defer codecbuf.Put(buf)
buf := new(bytes.Buffer)
if err = indexes.FullIdPubkeyEnc(
ser, nil, nil, nil,
).MarshalWrite(buf); chk.E(err) {

View File

@@ -2,16 +2,16 @@ package database
import (
"bytes"
"testing"
"orly.dev/pkg/database/indexes"
types2 "orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/kind"
"orly.dev/pkg/encoders/tag"
"orly.dev/pkg/encoders/tags"
"orly.dev/pkg/encoders/timestamp"
"orly.dev/pkg/utils/chk"
"testing"
"github.com/minio/sha256-simd"
)
@@ -26,8 +26,7 @@ func TestGetIndexesForEvent(t *testing.T) {
// indexes
func verifyIndexIncluded(t *testing.T, idxs [][]byte, expectedIdx *indexes.T) {
// Marshal the expected index
buf := codecbuf.Get()
defer codecbuf.Put(buf)
buf := new(bytes.Buffer)
err := expectedIdx.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("Failed to marshal expected index: %v", err)

View File

@@ -115,7 +115,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
// Set the end of range (Until or default to math.MaxInt64)
if f.Until != nil && f.Until.V != 0 {
caEnd.Set(uint64(f.Until.V + 1))
caEnd.Set(uint64(f.Until.V))
} else {
caEnd.Set(uint64(math.MaxInt64))
}

View File

@@ -3,16 +3,16 @@ package database
import (
"bytes"
"math"
"testing"
"orly.dev/pkg/database/indexes"
types2 "orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/encoders/filter"
"orly.dev/pkg/encoders/kind"
"orly.dev/pkg/encoders/kinds"
"orly.dev/pkg/encoders/tag"
"orly.dev/pkg/encoders/timestamp"
"orly.dev/pkg/utils/chk"
"testing"
"github.com/minio/sha256-simd"
)
@@ -41,8 +41,7 @@ func verifyIndex(
}
// Marshal the expected start index
startBuf := codecbuf.Get()
defer codecbuf.Put(startBuf)
startBuf := new(bytes.Buffer)
err := expectedStartIdx.MarshalWrite(startBuf)
if chk.E(err) {
t.Fatalf("Failed to marshal expected start index: %v", err)
@@ -62,8 +61,7 @@ func verifyIndex(
}
// Marshal the expected end index
endBuf := codecbuf.Get()
defer codecbuf.Put(endBuf)
endBuf := new(bytes.Buffer)
err = endIdx.MarshalWrite(endBuf)
if chk.E(err) {
t.Fatalf("Failed to marshal expected End index: %v", err)

View File

@@ -53,7 +53,7 @@ func TestGetSerialById(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -60,7 +60,7 @@ func TestGetSerialsByRange(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -56,7 +56,7 @@ func (d *D) Import(rr io.Reader) {
continue
}
if _, _, err = d.SaveEvent(d.ctx, ev, false); err != nil {
if _, _, err = d.SaveEvent(d.ctx, ev, false, nil); err != nil {
continue
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"io"
"orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"testing"
)
@@ -49,7 +48,7 @@ func TestPrefixMethods(t *testing.T) {
}
// Test MarshalWrite method
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := prefix.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -209,7 +208,7 @@ func TestTStruct(t *testing.T) {
}
// Test MarshalWrite
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -272,7 +271,7 @@ func TestEventFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -318,7 +317,7 @@ func TestIdFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -391,7 +390,7 @@ func TestIdPubkeyFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -452,7 +451,7 @@ func TestCreatedAtFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -516,7 +515,7 @@ func TestPubkeyFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -588,7 +587,7 @@ func TestPubkeyTagFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -660,7 +659,7 @@ func TestTagFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -724,7 +723,7 @@ func TestKindFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -789,7 +788,7 @@ func TestKindTagFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -865,7 +864,7 @@ func TestKindPubkeyFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -941,7 +940,7 @@ func TestKindPubkeyTagFunctions(t *testing.T) {
}
// Test marshaling and unmarshaling
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = enc.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)

View File

@@ -84,7 +84,7 @@ func testUint16Sorting(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint16 values don't sort correctly: %v should be less than %v",
t.Errorf("Uint16 values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -115,7 +115,7 @@ func testUint24Sorting(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint24 values don't sort correctly: %v should be less than %v",
t.Errorf("Uint24 values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -143,7 +143,7 @@ func testUint32Sorting(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint32 values don't sort correctly: %v should be less than %v",
t.Errorf("Uint32 values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -174,7 +174,7 @@ func testUint40Sorting(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint40 values don't sort correctly: %v should be less than %v",
t.Errorf("Uint40 values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -202,7 +202,7 @@ func testUint64Sorting(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint64 values don't sort correctly: %v should be less than %v",
t.Errorf("Uint64 values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -233,7 +233,7 @@ func testUint16EdgeCases(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint16 edge case values don't sort correctly: %v should be less than %v",
t.Errorf("Uint16 edge case values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -265,7 +265,7 @@ func testUint24EdgeCases(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint24 edge case values don't sort correctly: %v should be less than %v",
t.Errorf("Uint24 edge case values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -294,7 +294,7 @@ func testUint32EdgeCases(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint32 edge case values don't sort correctly: %v should be less than %v",
t.Errorf("Uint32 edge case values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -326,7 +326,7 @@ func testUint40EdgeCases(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint40 edge case values don't sort correctly: %v should be less than %v",
t.Errorf("Uint40 edge case values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -355,7 +355,7 @@ func testUint64EdgeCases(t *testing.T) {
// Check if they sort correctly with bytes.Compare
for i := 0; i < len(marshaledValues)-1; i++ {
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 {
t.Errorf("Uint64 edge case values don't sort correctly: %v should be less than %v",
t.Errorf("Uint64 edge case values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1])
}
@@ -390,7 +390,7 @@ func TestEndianness(t *testing.T) {
result := bytes.Compare(bigEndianValues[i], bigEndianValues[i+1])
t.Logf("Compare %d with %d: result = %d", values[i], values[i+1], result)
if result >= 0 {
t.Errorf("BigEndian values don't sort correctly: %v should be less than %v",
t.Errorf("BigEndian values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", bigEndianValues[i], bigEndianValues[i+1])
}
@@ -404,7 +404,7 @@ func TestEndianness(t *testing.T) {
t.Logf("Compare %d with %d: result = %d", values[i], values[i+1], result)
if result >= 0 {
correctOrder = false
t.Logf("LittleEndian values don't sort correctly: %v should be less than %v",
t.Logf("LittleEndian values don't sort correctly: %v should be less than %v",
values[i], values[i+1])
t.Logf("Bytes representation: %v vs %v", littleEndianValues[i], littleEndianValues[i+1])
}

View File

@@ -2,10 +2,10 @@ package types
import (
"bytes"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"testing"
"orly.dev/pkg/utils/chk"
"github.com/minio/sha256-simd"
)
@@ -55,7 +55,7 @@ func TestIdMarshalWriteUnmarshalRead(t *testing.T) {
}
// Test MarshalWrite
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = fi1.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)

View File

@@ -2,10 +2,10 @@ package types
import (
"bytes"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"testing"
"orly.dev/pkg/utils/chk"
"github.com/minio/sha256-simd"
)
@@ -45,7 +45,7 @@ func TestIdent_MarshalWriteUnmarshalRead(t *testing.T) {
}
// Test MarshalWrite
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = i1.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)

View File

@@ -3,10 +3,10 @@ package types
import (
"bytes"
"encoding/base64"
"orly.dev/pkg/encoders/codecbuf"
"testing"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/utils/chk"
"testing"
"github.com/minio/sha256-simd"
)
@@ -142,7 +142,7 @@ func TestIdHashMarshalWriteUnmarshalRead(t *testing.T) {
}
// Test MarshalWrite
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = i1.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)

View File

@@ -2,9 +2,9 @@ package types
import (
"bytes"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"testing"
"orly.dev/pkg/utils/chk"
)
func TestLetter_New(t *testing.T) {
@@ -53,7 +53,7 @@ func TestLetter_MarshalWriteUnmarshalRead(t *testing.T) {
l1 := new(Letter)
l1.Set('A')
// Test MarshalWrite
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := l1.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)

View File

@@ -2,11 +2,11 @@ package types
import (
"bytes"
"testing"
"orly.dev/pkg/crypto/ec/schnorr"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/utils/chk"
"testing"
"github.com/minio/sha256-simd"
)
@@ -105,7 +105,7 @@ func TestPubHash_MarshalWriteUnmarshalRead(t *testing.T) {
}
// Test MarshalWrite
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err = ph1.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)

View File

@@ -3,7 +3,6 @@ package types
import (
"bytes"
"io"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
)
@@ -29,7 +28,7 @@ func (ts *Timestamp) ToTimestamp() (timestamp int64) {
func (ts *Timestamp) Bytes() (b []byte, err error) {
v := new(Uint64)
v.Set(uint64(ts.val))
buf := codecbuf.Get()
buf := new(bytes.Buffer)
if err = v.MarshalWrite(buf); chk.E(err) {
return
}

View File

@@ -2,10 +2,10 @@ package types
import (
"bytes"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"testing"
"time"
"orly.dev/pkg/utils/chk"
)
func TestTimestamp_FromInt(t *testing.T) {
@@ -89,7 +89,7 @@ func TestTimestamp_FromBytes(t *testing.T) {
v.Set(12345)
// Marshal it to bytes
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := v.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -163,7 +163,7 @@ func TestTimestamp_Bytes(t *testing.T) {
func TestTimestamp_MarshalWriteUnmarshalRead(t *testing.T) {
// Test with a positive value
ts1 := &Timestamp{val: 12345}
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := ts1.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -183,7 +183,7 @@ func TestTimestamp_MarshalWriteUnmarshalRead(t *testing.T) {
// Test with a negative value
ts1 = &Timestamp{val: -12345}
buf = codecbuf.Get()
buf = new(bytes.Buffer)
err = ts1.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)
@@ -225,7 +225,7 @@ func TestTimestamp_WithCurrentTime(t *testing.T) {
}
// Test MarshalWrite and UnmarshalRead
buf := codecbuf.Get()
buf := new(bytes.Buffer)
err := ts.MarshalWrite(buf)
if chk.E(err) {
t.Fatalf("MarshalWrite failed: %v", err)

View File

@@ -3,11 +3,11 @@ package types
import (
"bytes"
"math"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"reflect"
"testing"
"orly.dev/pkg/utils/chk"
"lukechampine.com/frand"
)
@@ -44,7 +44,7 @@ func TestUint16(t *testing.T) {
}
// Test encoding to []byte and decoding back
bufEnc := codecbuf.Get()
bufEnc := new(bytes.Buffer)
// MarshalWrite
err := encodedUint16.MarshalWrite(bufEnc)

View File

@@ -1,10 +1,11 @@
package types
import (
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"bytes"
"reflect"
"testing"
"orly.dev/pkg/utils/chk"
)
func TestUint24(t *testing.T) {
@@ -45,7 +46,7 @@ func TestUint24(t *testing.T) {
}
// Test MarshalWrite and UnmarshalRead
buf := codecbuf.Get()
buf := new(bytes.Buffer)
// MarshalWrite directly to the buffer
if err := codec.MarshalWrite(buf); chk.E(err) {

View File

@@ -3,11 +3,11 @@ package types
import (
"bytes"
"math"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"reflect"
"testing"
"orly.dev/pkg/utils/chk"
"lukechampine.com/frand"
)
@@ -43,7 +43,7 @@ func TestUint32(t *testing.T) {
}
// Test encoding to []byte and decoding back
bufEnc := codecbuf.Get()
bufEnc := new(bytes.Buffer)
// MarshalWrite
err := codec.MarshalWrite(bufEnc)

View File

@@ -1,10 +1,11 @@
package types
import (
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"bytes"
"reflect"
"testing"
"orly.dev/pkg/utils/chk"
)
func TestUint40(t *testing.T) {
@@ -48,7 +49,7 @@ func TestUint40(t *testing.T) {
}
// Test MarshalWrite and UnmarshalRead
buf := codecbuf.Get()
buf := new(bytes.Buffer)
// Marshal to a buffer
if err = codec.MarshalWrite(buf); chk.E(err) {

View File

@@ -3,11 +3,11 @@ package types
import (
"bytes"
"math"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
"reflect"
"testing"
"orly.dev/pkg/utils/chk"
"lukechampine.com/frand"
)
@@ -43,7 +43,7 @@ func TestUint64(t *testing.T) {
}
// Test encoding to []byte and decoding back
bufEnc := codecbuf.Get()
bufEnc := new(bytes.Buffer)
// MarshalWrite
err := codec.MarshalWrite(bufEnc)

View File

@@ -1,8 +1,8 @@
package types
import (
"bytes"
"io"
"orly.dev/pkg/encoders/codecbuf"
"orly.dev/pkg/utils/chk"
)
@@ -35,8 +35,7 @@ func (w *Word) MarshalWrite(wr io.Writer) (err error) {
// UnmarshalRead reads the word from the reader, stopping at the zero-byte marker
func (w *Word) UnmarshalRead(r io.Reader) error {
buf := codecbuf.Get()
defer codecbuf.Put(buf)
buf := new(bytes.Buffer)
tmp := make([]byte, 1)
foundEndMarker := false

View File

@@ -45,7 +45,7 @@ func TestMultipleParameterizedReplaceableEvents(t *testing.T) {
baseEvent.Sign(sign)
// Save the base parameterized replaceable event
if _, _, err := db.SaveEvent(ctx, baseEvent, false); err != nil {
if _, _, err := db.SaveEvent(ctx, baseEvent, false, nil); err != nil {
t.Fatalf("Failed to save base parameterized replaceable event: %v", err)
}
@@ -63,7 +63,7 @@ func TestMultipleParameterizedReplaceableEvents(t *testing.T) {
newerEvent.Sign(sign)
// Save the newer parameterized replaceable event
if _, _, err := db.SaveEvent(ctx, newerEvent, false); err != nil {
if _, _, err := db.SaveEvent(ctx, newerEvent, false, nil); err != nil {
t.Fatalf(
"Failed to save newer parameterized replaceable event: %v", err,
)
@@ -83,7 +83,7 @@ func TestMultipleParameterizedReplaceableEvents(t *testing.T) {
newestEvent.Sign(sign)
// Save the newest parameterized replaceable event
if _, _, err := db.SaveEvent(ctx, newestEvent, false); err != nil {
if _, _, err := db.SaveEvent(ctx, newestEvent, false, nil); err != nil {
t.Fatalf(
"Failed to save newest parameterized replaceable event: %v", err,
)

View File

@@ -2,12 +2,14 @@ package database
import (
"bytes"
"fmt"
"orly.dev/pkg/crypto/sha256"
"orly.dev/pkg/database/indexes/types"
"orly.dev/pkg/encoders/event"
"orly.dev/pkg/encoders/filter"
"orly.dev/pkg/encoders/hex"
"orly.dev/pkg/encoders/kind"
"orly.dev/pkg/encoders/kinds"
"orly.dev/pkg/encoders/tag"
"orly.dev/pkg/interfaces/store"
"orly.dev/pkg/utils/chk"
@@ -16,11 +18,6 @@ import (
"strconv"
)
// QueryEvents retrieves events based on the provided filter. If the filter
// contains Ids, it fetches events by those Ids directly, overriding other
// filter criteria. Otherwise, it queries by other filter criteria and fetches
// matching events. Results are returned in reverse chronological order of their
// creation timestamps.
func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
// if there is Ids in the query, this overrides anything else
if f.Ids != nil && f.Ids.Len() > 0 {
@@ -64,8 +61,28 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
// Map to track deletion events by kind, pubkey, and d-tag (for
// parameterized replaceable events)
deletionsByKindPubkeyDTag := make(map[string]map[string]bool)
// Map to track specific event IDs that have been deleted
deletedEventIds := make(map[string]bool)
// Query for deletion events separately if we have authors in the filter
if f.Authors != nil && f.Authors.Len() > 0 {
// Create a filter for deletion events with the same authors
deletionFilter := &filter.F{
Kinds: kinds.New(kind.New(5)), // Kind 5 is deletion
Authors: f.Authors,
}
var deletionIdPkTs []store.IdPkTs
if deletionIdPkTs, err = d.QueryForIds(c, deletionFilter); chk.E(err) {
return
}
// Add deletion events to the list of events to process
idPkTs = append(idPkTs, deletionIdPkTs...)
}
// First pass: collect all deletion events
fmt.Printf("Debug: Starting first pass - processing %d events\n", len(idPkTs))
for _, idpk := range idPkTs {
var ev *event.E
ser := new(types.Uint40)
@@ -78,6 +95,7 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
// Process deletion events to build our deletion maps
if ev.Kind.Equal(kind.Deletion) {
fmt.Printf("Debug: Found deletion event with ID: %s\n", hex.Enc(ev.ID))
// Check for 'e' tags that directly reference event IDs
eTags := ev.Tags.GetAll(tag.New([]byte{'e'}))
for _, eTag := range eTags.ToSliceOfTags() {
@@ -90,7 +108,9 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
// Check for 'a' tags that reference parameterized replaceable
// events
fmt.Printf("Debug: Processing deletion event with ID: %s\n", hex.Enc(ev.ID))
aTags := ev.Tags.GetAll(tag.New([]byte{'a'}))
fmt.Printf("Debug: Found %d a-tags\n", aTags.Len())
for _, aTag := range aTags.ToSliceOfTags() {
if aTag.Len() < 2 {
continue
@@ -126,8 +146,8 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
continue
}
// Create the key for the deletion map
key := string(pk) + ":" + strconv.Itoa(int(kk.K))
// Create the key for the deletion map using hex representation of pubkey
key := hex.Enc(pk) + ":" + strconv.Itoa(int(kk.K))
// Initialize the inner map if it doesn't exist
if _, exists := deletionsByKindPubkeyDTag[key]; !exists {
@@ -137,6 +157,10 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
// Mark this d-tag as deleted
dValue := string(split[2])
deletionsByKindPubkeyDTag[key][dValue] = true
// Debug logging
fmt.Printf("Debug: Processing a-tag: %s\n", string(aTag.Value()))
fmt.Printf("Debug: Adding to deletion map - key: %s, d-tag: %s\n", key, dValue)
}
// For replaceable events, we need to check if there are any
@@ -168,13 +192,17 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
continue
}
// If the event is replaceable, mark it as deleted
// Mark the specific event ID as deleted
deletedEventIds[hex.Enc(targetEv.ID)] = true
// If the event is replaceable, mark it as deleted, but only for events older than this one
if targetEv.Kind.IsReplaceable() {
key := string(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind.K))
key := hex.Enc(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind.K))
// We'll still use deletionsByKindPubkey, but we'll check timestamps in the second pass
deletionsByKindPubkey[key] = true
} else if targetEv.Kind.IsParameterizedReplaceable() {
// For parameterized replaceable events, we need to consider the 'd' tag
key := string(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind.K))
key := hex.Enc(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind.K))
// Get the 'd' tag value
dTag := targetEv.Tags.GetFirst(tag.New([]byte{'d'}))
@@ -225,25 +253,43 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
}
}
// Check if this specific event has been deleted
eventIdHex := hex.Enc(ev.ID)
if deletedEventIds[eventIdHex] && !isIdInFilter {
// Skip this event if it has been specifically deleted and is not in the filter
continue
}
if ev.Kind.IsReplaceable() {
// For replaceable events, we only keep the latest version for
// each pubkey and kind, and only if it hasn't been deleted
key := string(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K))
key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K))
// Skip this event if it has been deleted and its ID is not in
// the filter
// For replaceable events, we need to be more careful with deletion
// Only skip this event if it has been deleted by kind/pubkey and is not in the filter
// AND there isn't a newer event with the same kind/pubkey
if deletionsByKindPubkey[key] && !isIdInFilter {
continue
}
existing, exists := replaceableEvents[key]
if !exists || ev.CreatedAt.I64() > existing.CreatedAt.I64() {
replaceableEvents[key] = ev
// Check if there's a newer event with the same kind/pubkey
// that hasn't been specifically deleted
existing, exists := replaceableEvents[key]
if !exists || ev.CreatedAt.I64() > existing.CreatedAt.I64() {
// This is the newest event so far, keep it
replaceableEvents[key] = ev
} else {
// There's a newer event, skip this one
continue
}
} else {
// Normal replaceable event handling
existing, exists := replaceableEvents[key]
if !exists || ev.CreatedAt.I64() > existing.CreatedAt.I64() {
replaceableEvents[key] = ev
}
}
} else if ev.Kind.IsParameterizedReplaceable() {
// For parameterized replaceable events, we need to consider the
// 'd' tag
key := string(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K))
key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K))
// Get the 'd' tag value
dTag := ev.Tags.GetFirst(tag.New([]byte{'d'}))
@@ -257,9 +303,14 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
// Check if this event has been deleted via an a-tag
if deletionMap, exists := deletionsByKindPubkeyDTag[key]; exists {
// Debug logging
fmt.Printf("Debug: Checking deletion map - key: %s, d-tag: %s\n", key, dValue)
fmt.Printf("Debug: Deletion map contains key: %v, d-tag in map: %v\n", exists, deletionMap[dValue])
// If the d-tag value is in the deletion map and this event is not
// specifically requested by ID, skip it
if deletionMap[dValue] && !isIdInFilter {
fmt.Printf("Debug: Event deleted - skipping\n")
continue
}
}

View File

@@ -62,7 +62,7 @@ func setupTestDB(t *testing.T) (
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}
@@ -202,7 +202,9 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
replaceableEvent.Tags = tags.New()
replaceableEvent.Sign(sign)
// Save the replaceable event
if _, _, err := db.SaveEvent(ctx, replaceableEvent, false); err != nil {
if _, _, err := db.SaveEvent(
ctx, replaceableEvent, false, nil,
); err != nil {
t.Fatalf("Failed to save replaceable event: %v", err)
}
@@ -216,7 +218,7 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
newerEvent.Tags = tags.New()
newerEvent.Sign(sign)
// Save the newer event
if _, _, err := db.SaveEvent(ctx, newerEvent, false); err != nil {
if _, _, err := db.SaveEvent(ctx, newerEvent, false, nil); err != nil {
t.Fatalf("Failed to save newer event: %v", err)
}
@@ -293,7 +295,7 @@ func TestReplaceableEventsAndDeletion(t *testing.T) {
)
// Save the deletion event
if _, _, err = db.SaveEvent(ctx, deletionEvent, false); err != nil {
if _, _, err = db.SaveEvent(ctx, deletionEvent, false, nil); err != nil {
t.Fatalf("Failed to save deletion event: %v", err)
}
@@ -379,7 +381,7 @@ func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
paramEvent.Sign(sign)
// Save the parameterized replaceable event
if _, _, err := db.SaveEvent(ctx, paramEvent, false); err != nil {
if _, _, err := db.SaveEvent(ctx, paramEvent, false, nil); err != nil {
t.Fatalf("Failed to save parameterized replaceable event: %v", err)
}
@@ -405,7 +407,9 @@ func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
paramDeletionEvent.Sign(sign)
// Save the parameterized deletion event
if _, _, err := db.SaveEvent(ctx, paramDeletionEvent, false); err != nil {
if _, _, err := db.SaveEvent(
ctx, paramDeletionEvent, false, nil,
); err != nil {
t.Fatalf("Failed to save parameterized deletion event: %v", err)
}
@@ -438,7 +442,9 @@ func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
paramDeletionEvent2.Sign(sign)
// Save the parameterized deletion event with e-tag
if _, _, err := db.SaveEvent(ctx, paramDeletionEvent2, false); err != nil {
if _, _, err := db.SaveEvent(
ctx, paramDeletionEvent2, false, nil,
); err != nil {
t.Fatalf(
"Failed to save parameterized deletion event with e-tag: %v", err,
)

View File

@@ -57,7 +57,7 @@ func TestQueryForAuthorsTags(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -56,7 +56,7 @@ func TestQueryForCreatedAt(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -60,7 +60,7 @@ func TestQueryForIds(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -58,7 +58,7 @@ func TestQueryForKindsAuthorsTags(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -58,7 +58,7 @@ func TestQueryForKindsAuthors(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -58,7 +58,7 @@ func TestQueryForKindsTags(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

View File

@@ -57,7 +57,7 @@ func TestQueryForKinds(t *testing.T) {
events = append(events, ev)
// Save the event to the database
if _, _, err = db.SaveEvent(ctx, ev, false); err != nil {
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil {
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err)
}

Some files were not shown because too many files have changed in this diff Show More