Skip to content

Add DKG Round 2 parsing, NIP-46 support, fix USB device paths#27

Merged
kwsantiago merged 1 commit intomainfrom
feat/dkg-nip46-usb-fixes
Jan 6, 2026
Merged

Add DKG Round 2 parsing, NIP-46 support, fix USB device paths#27
kwsantiago merged 1 commit intomainfrom
feat/dkg-nip46-usb-fixes

Conversation

@kwsantiago
Copy link
Contributor

@kwsantiago kwsantiago commented Jan 6, 2026

Summary by CodeRabbit

  • New Features

    • DKG Round2 event handling and NIP46 connect/request support added.
    • RPC method to retrieve share metadata (pubkey, index, threshold, participants) added.
  • Configuration

    • Default serial device path changed to /dev/ttyACM0 across scripts, tests, and integrations.
  • Documentation

    • JSON-RPC API docs updated to include the new share-info method.

✏️ Tip: You can customize this high-level summary in your review settings.

@kwsantiago kwsantiago self-assigned this Jan 6, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Warning

Rate limit exceeded

@kwsantiago has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between dbecfaf and 0a40eff.

📒 Files selected for processing (18)
  • README.md
  • main/frost.c
  • main/frost.h
  • main/frost_coordinator.c
  • main/frost_coordinator.h
  • main/frost_signer.c
  • main/frost_signer.h
  • main/main.c
  • main/nostr_frost.c
  • main/nostr_frost.h
  • main/protocol.c
  • main/protocol.h
  • scripts/monitor_serial.py
  • scripts/test_all_rpc.py
  • scripts/test_dkg_full.py
  • scripts/test_dkg_relay.py
  • test/hardware/test_hardware.py
  • test/integration/test_full_flow.sh

Walkthrough

Adds DKG Round2 and NIP46 parsing/handling and callbacks; introduces nip46 request/response types and helpers; adds get_share_info RPC backed by a new frost_state participants field; updates default serial device paths in scripts/tests and adjusts related test expectations.

Changes

Cohort / File(s) Summary
DKG Round2 & NIP46 — coordinator
main/frost_coordinator.h, main/frost_coordinator.c
New callback typedefs frost_dkg_round2_cb, frost_nip46_request_cb; frost_coordinator_callbacks_t gains on_dkg_round2 and on_nip46_request; websocket dispatch extended to parse DKG_ROUND2 and NIP46 connect events and call new callbacks.
DKG Round2 & NIP46 — parsing & crypto
main/nostr_frost.h, main/nostr_frost.c
Added types nip46_request_t, nip46_response_t and functions frost_parse_dkg_round2_event, frost_parse_nip46_event, frost_create_nip46_response, frost_nip46_request_free — decrypting, parsing, constructing responses, and cleanup.
Share info RPC & state
main/frost.h, main/frost.c, main/frost_signer.h, main/frost_signer.c, main/main.c
Added uint16_t participants to frost_state_t and initialized in frost_init; new API frost_get_share_info added and wired to an RPC case in main.c.
Protocol enum & docs
main/protocol.h, main/protocol.c, README.md
New enum member RPC_METHOD_GET_SHARE_INFO, parsing support for "get_share_info", and README entry documenting the get_share_info JSON‑RPC method.
Serial device path updates & tests
scripts/monitor_serial.py, scripts/test_all_rpc.py, scripts/test_dkg_full.py, scripts/test_dkg_relay.py, test/hardware/test_hardware.py, test/integration/test_full_flow.sh
Default serial device changed from /dev/ttyUSB0 to /dev/ttyACM0 across scripts/tests; test_all_rpc.py adjusted to expect/handle commitment semantics and uses a fixed session_id in frost_sign tests.

Sequence Diagram(s)

sequenceDiagram
    participant WS as WebSocket Handler
    participant Parser as Event Parser
    participant Crypto as Crypto Module
    participant App as Application Callback

    rect rgb(240,248,255)
    Note over WS,App: DKG Round2 flow (new)
    WS->>Parser: receive event (DKG_ROUND2)
    Parser->>Crypto: derive shared secret / decrypt (our_privkey + sender_pubkey)
    Crypto-->>Parser: decrypted JSON payload
    Parser->>Parser: extract group, sender/recipient indices, share → populate frost_dkg_round2_t
    Parser-->>WS: parsed round2 struct
    WS->>App: on_dkg_round2(round2, ctx)
    App-->>WS: return
    end

    rect rgb(255,249,240)
    Note over WS,App: NIP46 connect flow (new)
    WS->>Parser: receive event (NIP46_CONNECT)
    Parser->>Crypto: decrypt content (our_privkey + sender_pubkey)
    Crypto-->>Parser: decrypted JSON payload
    Parser->>Parser: extract id, method, params, sender_pubkey → populate nip46_request_t
    Parser-->>WS: parsed request
    WS->>App: on_nip46_request(request, ctx)
    App-->>WS: return
    WS->>Parser: frost_nip46_request_free(request)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
New rounds hop in the midnight code,
NIP46 whispers on the node,
Serial cords change their nightly track,
Shares now show who’s at the back,
I nibble bytes and leave a happy snack.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes in the PR: DKG Round 2 parsing functionality, NIP-46 support implementation, and USB device path fixes across multiple test scripts.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kwsantiago kwsantiago force-pushed the feat/dkg-nip46-usb-fixes branch from d53007e to 5d317fa Compare January 6, 2026 02:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Fix all issues with AI Agents 🤖
In @main/nostr_frost.c:
- Around line 1397-1407: Ensure "error" and "result" are treated as mutually
exclusive: if response->error is present add only the error to content_obj and
skip adding response->result; otherwise perform the existing response->result
handling (parse into result_json and add it or fall back to a string). Update
the block handling response->result/response->error to check response->error
first (or explicitly guard adding result with !response->error) and use the
existing result_json/content_obj logic unchanged when allowed.

In @scripts/test_all_rpc.py:
- Around line 80-83: The print on the failure branch uses an f-string with no
placeholders; change the unnecessary f-string print(f"    FAIL: no commitment in
response") to a plain string print("    FAIL: no commitment in response") in
scripts/test_all_rpc.py (the failure branch after the "if 'commitment' in
result" check) so the code uses a normal string literal.
🧹 Nitpick comments (3)
main/frost_signer.c (1)

190-207: Consider extracting common logic to reduce duplication.

The function shares significant logic with frost_get_pubkey (lines 172-188):

  • Loading state (lines 191-195 vs 173-177)
  • Converting pubkey to hex (lines 197-198 vs 179-180)
  • Cleanup pattern (line 206 vs 187)

Consider extracting a helper function that loads state and returns the populated structure, then have both functions format their specific JSON responses.

Additionally, bytes_to_hex (line 102) is duplicated across multiple files (frost_coordinator.c:72-77, frost_dkg.c:45-50). Consider moving it to a shared utility module.

main/nostr_frost.c (2)

1020-1032: Potential confusion from dual sources of index fields.

The function reads sender_index and recipient_index from both:

  1. Tags (lines 1028-1032): participant_indexsender_index, recipient_indexrecipient_index
  2. Inner JSON content (lines 1057-1064): sender_index and recipient_index fields

If both sources provide values and they differ, the inner JSON values silently override the tag values. This could lead to confusion or inconsistencies.

Consider either:

  • Validating that both sources match when both are present, or
  • Documenting which source is authoritative

Also applies to: 1057-1064


1053-1056: Consider validating hex string length before conversion.

The share field is converted to encrypted_share (48 bytes), but there's no validation that the hex string has the expected length (96 characters for 48 bytes) before calling hex_to_bytes.

While hex_to_bytes likely handles this gracefully, explicit validation would make the code more robust and provide clearer error handling.

🔎 Proposed validation
             cJSON *share = cJSON_GetObjectItem(inner, "share");
             if (share && cJSON_IsString(share)) {
+                if (strlen(share->valuestring) != 96) {
+                    cJSON_Delete(inner);
+                    free(decrypted);
+                    cJSON_Delete(root);
+                    return -4;
+                }
                 hex_to_bytes(share->valuestring, round2->encrypted_share, 48);
             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d53007e and 5d317fa.

📒 Files selected for processing (18)
  • README.md
  • main/frost.c
  • main/frost.h
  • main/frost_coordinator.c
  • main/frost_coordinator.h
  • main/frost_signer.c
  • main/frost_signer.h
  • main/main.c
  • main/nostr_frost.c
  • main/nostr_frost.h
  • main/protocol.c
  • main/protocol.h
  • scripts/monitor_serial.py
  • scripts/test_all_rpc.py
  • scripts/test_dkg_full.py
  • scripts/test_dkg_relay.py
  • test/hardware/test_hardware.py
  • test/integration/test_full_flow.sh
✅ Files skipped from review due to trivial changes (1)
  • scripts/test_dkg_full.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • test/integration/test_full_flow.sh
  • main/frost_coordinator.c
  • main/nostr_frost.h
  • scripts/test_dkg_relay.py
  • test/hardware/test_hardware.py
🧰 Additional context used
🧬 Code graph analysis (4)
main/frost_signer.c (3)
main/protocol.c (2)
  • protocol_error (147-154)
  • protocol_success (138-145)
main/frost_coordinator.c (1)
  • bytes_to_hex (73-78)
main/frost.c (1)
  • frost_free (161-170)
main/main.c (1)
main/frost_signer.c (1)
  • frost_get_share_info (190-207)
main/frost_signer.h (1)
main/frost_signer.c (1)
  • frost_get_share_info (190-207)
main/nostr_frost.c (2)
main/frost_dkg.c (2)
  • hex_to_bytes (35-44)
  • bytes_to_hex (46-51)
main/frost_coordinator.c (1)
  • bytes_to_hex (73-78)
🪛 Ruff (0.14.10)
scripts/test_all_rpc.py

83-83: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (16)
scripts/monitor_serial.py (1)

7-7: LGTM! Device path update aligns with ESP32 USB CDC.

The change from /dev/ttyUSB0 to /dev/ttyACM0 correctly reflects ESP32 boards using native USB CDC, and users can still override via --device if needed.

scripts/test_all_rpc.py (4)

94-96: API contract change handled correctly.

The change to use a hardcoded session_id instead of extracting it from commit_result correctly reflects the updated API where session_id is now an explicit input parameter rather than being derived from the commitment response. The commit_result is still appropriately used to control test flow.


127-164: Well-structured test suite.

The test execution flow is logical, with proper sequencing of operations (import → commit → sign → delete) and comprehensive result tracking. The test suite effectively validates the updated RPC interface.


7-7: Confirm the device path matches your target hardware configuration.

The change from /dev/ttyUSB0 to /dev/ttyACM0 indicates the ESP32-S3 is configured to use CDC ACM (native USB serial) instead of a USB-to-serial adapter. Ensure this aligns with your hardware setup, as the correct device path depends on your ESP32-S3's firmware configuration and USB mode.


74-74: Session_id reuse is acceptable for this test. RFC 9591 (FROST protocol) does not mandate session_id uniqueness—it is an implementation detail. Hardcoding test values is a standard practice, and the actual hardware implementation properly generates unique session IDs when needed (see test/hardware/test_hardware.py).

main/frost.h (1)

14-14: LGTM!

The new participants field is well-positioned and uses a consistent type with other fields in the struct.

main/frost.c (1)

135-135: LGTM!

The initialization of the participants field follows the same pattern as other fields and correctly assigns the value from max_participants extracted from the serialized share data.

main/frost_coordinator.h (1)

24-25: LGTM!

The new callback typedefs and struct members follow the established pattern and are well-integrated with the existing callback structure. The naming is clear and consistent.

Also applies to: 31-32

main/nostr_frost.c (3)

1435-1441: LGTM!

The cleanup function properly frees the allocated params string and resets the associated fields. Consistent with the pattern used in frost_sign_request_free.


1345-1349: No action needed—NIP46 requires encryption per specification.

NIP-46 (Nostr Connect) events must have encrypted content per the specification ("the event content MUST be an encrypted JSON-RPC-like payload"). The strict error return when decryption fails is correct and differs intentionally from frost_parse_dkg_round1_event, which lacks this encryption requirement.


1046-1049: Inconsistent decryption handling compared to frost_parse_dkg_round1_event.

Both functions always encrypt their messages during creation, but they differ during parsing. frost_parse_dkg_round1_event (line 881) falls back to unencrypted content if decryption fails, while frost_parse_dkg_round2_event (lines 1046-1049) returns error -3 immediately on decryption failure.

If DKG Round2 messages must always be encrypted per protocol (unlike Round1), this is correct. Otherwise, clarify whether Round2 should also support the fallback pattern for consistency.

README.md (1)

176-176: LGTM! Documentation aligns with implementation.

The new RPC method documentation is clear and accurately describes the functionality implemented in frost_signer.c.

main/main.c (1)

90-92: LGTM! Handler follows established pattern.

The new case for RPC_METHOD_GET_SHARE_INFO correctly routes to frost_get_share_info and follows the same pattern as the existing GET_SHARE_PUBKEY handler.

main/protocol.c (1)

9-9: LGTM! Method parsing is correct and consistent.

The new method string parsing for "get_share_info" follows the established pattern and is logically positioned with other share-related methods.

main/frost_signer.h (1)

10-10: LGTM! Function signature is appropriate and consistent.

The new function declaration for frost_get_share_info matches the pattern of frost_get_pubkey and has an appropriate signature for the RPC handler use case.

main/protocol.h (1)

27-27: LGTM! Enum member properly positioned.

The new RPC_METHOD_GET_SHARE_INFO enum member is logically placed after GET_SHARE_PUBKEY, maintaining grouping of related methods while preserving RPC_METHOD_UNKNOWN as the sentinel value.

@kwsantiago kwsantiago force-pushed the feat/dkg-nip46-usb-fixes branch from 5d317fa to ddc49eb Compare January 6, 2026 02:34
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Fix all issues with AI Agents 🤖
In @main/nostr_frost.c:
- Around line 1397-1406: The response handling block (checking response->error
and response->result) lacks a validation for the case when both are NULL; add a
guard after constructing content_obj to detect if both response->error and
response->result are NULL and produce a valid NIP-46 error (e.g., set "error" in
content_obj to a descriptive message or a standard NIP-46 error string) so every
response contains either "error" or "result"; reference symbols: response,
response->error, response->result, and content_obj.
🧹 Nitpick comments (1)
main/frost_signer.c (1)

190-207: LGTM! Function correctly retrieves and formats share info.

The implementation properly loads the frost state, formats the response JSON, and cleans up resources. Buffer size (192 bytes) is adequate for the JSON payload.

💡 Optional: Consider extracting common load-and-format logic

Lines 191-198 duplicate logic from frost_get_pubkey (lines 173-180). If more similar accessors are added, consider extracting a helper:

static int load_and_get_pubkey_hex(const char *group, frost_state_t *state, char *pubkey_hex) {
    if (load_frost_state(state, group) != 0) {
        return -1;
    }
    bytes_to_hex(state->group_pubkey, sizeof(state->group_pubkey), pubkey_hex);
    return 0;
}

This would centralize the load-convert pattern used by both frost_get_pubkey and frost_get_share_info.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d317fa and ddc49eb.

📒 Files selected for processing (18)
  • README.md
  • main/frost.c
  • main/frost.h
  • main/frost_coordinator.c
  • main/frost_coordinator.h
  • main/frost_signer.c
  • main/frost_signer.h
  • main/main.c
  • main/nostr_frost.c
  • main/nostr_frost.h
  • main/protocol.c
  • main/protocol.h
  • scripts/monitor_serial.py
  • scripts/test_all_rpc.py
  • scripts/test_dkg_full.py
  • scripts/test_dkg_relay.py
  • test/hardware/test_hardware.py
  • test/integration/test_full_flow.sh
🚧 Files skipped from review as they are similar to previous changes (6)
  • test/integration/test_full_flow.sh
  • main/frost.h
  • scripts/test_dkg_full.py
  • main/frost_coordinator.h
  • main/protocol.h
  • main/frost.c
🧰 Additional context used
🧬 Code graph analysis (5)
main/frost_coordinator.c (1)
main/nostr_frost.c (3)
  • frost_parse_dkg_round2_event (991-1074)
  • frost_parse_nip46_event (1324-1376)
  • frost_nip46_request_free (1434-1440)
main/frost_signer.h (1)
main/frost_signer.c (1)
  • frost_get_share_info (190-207)
main/nostr_frost.c (2)
main/frost_dkg.c (2)
  • hex_to_bytes (35-44)
  • bytes_to_hex (46-51)
main/frost_coordinator.c (1)
  • bytes_to_hex (73-78)
main/main.c (1)
main/frost_signer.c (1)
  • frost_get_share_info (190-207)
main/frost_signer.c (2)
main/protocol.c (2)
  • protocol_error (147-154)
  • protocol_success (138-145)
main/frost_coordinator.c (1)
  • bytes_to_hex (73-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (17)
scripts/monitor_serial.py (1)

7-7: LGTM!

The default device path update to /dev/ttyACM0 is appropriate for ESP32-S3's native USB CDC interface, which enumerates as an ACM device rather than a USB-to-serial adapter.

test/hardware/test_hardware.py (1)

8-8: LGTM!

The default device path update is consistent with the other scripts in this PR while preserving the environment variable override for flexibility.

scripts/test_dkg_relay.py (2)

23-23: LGTM!

Device path update is consistent with the broader PR changes.


174-177: LGTM!

The example commands correctly reflect the updated device path, keeping documentation aligned with the code.

scripts/test_all_rpc.py (3)

7-7: LGTM!

Device path update is consistent with other files in this PR.


70-86: LGTM with a note on the test approach.

The test now:

  1. Passes a fixed session_id to frost_commit (rather than receiving one back)
  2. Validates for commitment in the response

Using a fixed session ID for testing is reasonable since this is a single-device test, and the same ID is correctly reused in test_frost_sign. The validation change reflects the updated RPC contract where the device returns a commitment for the caller-provided session.


94-98: LGTM!

The fixed session_id matches what was passed to frost_commit, ensuring test consistency. The commit_result parameter is still used to gate the test execution (line 90-92), which is appropriate.

main/nostr_frost.h (2)

140-143: LGTM!

The new DKG Round2 and NIP46 parsing APIs follow consistent naming conventions and integrate well with the existing FROST protocol functions.

Also applies to: 178-185


100-112: This comment is incorrect—there is no asymmetric memory management.

frost_create_nip46_response is a serialization function that takes a const nip46_response_t *response and only reads the result and error fields. It never allocates these fields. The library doesn't own nip46_response_t instances; callers create and own them. The absence of frost_nip46_response_free is correct and intentional.

By contrast, frost_nip46_request_free is needed because nip46_request_t is parsed and allocated by the library, which dynamically allocates request->params. The asymmetry you observed is actually the proper separation of memory ownership: types allocated by the library have free functions; types provided by callers do not.

Likely an incorrect or invalid review comment.

main/main.c (1)

90-92: LGTM!

The new RPC_METHOD_GET_SHARE_INFO case follows the established pattern of delegating to a frost_signer function, consistent with the GET_SHARE_PUBKEY handler above it.

main/protocol.c (1)

9-9: LGTM!

The new method parsing entry for "get_share_info" follows the established pattern and maintains consistency with the surrounding code.

main/frost_signer.h (1)

10-10: LGTM!

The new frost_get_share_info function declaration follows the established pattern of other frost_signer functions, with consistent signature style and parameter naming.

README.md (1)

176-176: LGTM!

The documentation accurately describes the new get_share_info endpoint and aligns with the implementation that returns pubkey, index, threshold, and participants metadata.

main/frost_coordinator.c (1)

134-144: LGTM! DKG Round2 and NIP46 event handling properly integrated.

The new event handlers follow the established pattern and correctly manage resources:

  • DKG Round2 parsing invokes the callback without additional cleanup (no dynamic allocations in frost_dkg_round2_t)
  • NIP46 parsing appropriately frees the request after the callback (nip46_request_t.params is heap-allocated)

Both handlers verify callback existence before parsing, preventing unnecessary work.

main/nostr_frost.c (3)

991-1074: LGTM! DKG Round2 parsing follows established patterns.

The function correctly:

  • Validates event kind
  • Extracts group_id, sender_index, and recipient_index from tags
  • Decrypts content and parses the inner JSON payload
  • Populates the frost_dkg_round2_t structure
  • Cleans up allocated resources

The strict decryption requirement (returning -3 on failure) is appropriate for DKG security.


1324-1376: LGTM! NIP46 request parsing correctly handles dynamic allocation.

The function properly:

  • Validates the NIP46 event kind
  • Decrypts the content using the sender's pubkey
  • Extracts id, method, and params from the decrypted JSON
  • Allocates and stores params as a string (freed later by frost_nip46_request_free)

Resource management is correct: decrypted is freed, and the caller is responsible for freeing request->params.


1434-1440: LGTM! Resource cleanup correctly frees NIP46 request params.

The function safely frees the dynamically allocated params field and resets the pointer and length. It's idempotent and follows the same pattern as frost_sign_request_free.

@kwsantiago kwsantiago force-pushed the feat/dkg-nip46-usb-fixes branch from ddc49eb to dbecfaf Compare January 6, 2026 02:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Fix all issues with AI Agents 🤖
In @main/nostr_frost.c:
- Around line 1351-1372: The code currently treats a failed
cJSON_Parse(decrypted) as success and leaves request partially initialized;
after calling cJSON_Parse and getting a NULL inner, free decrypted, clean up any
partially set fields on request (or zero them) and return an error status
instead of proceeding to the success path; specifically update the block around
cJSON_Parse/decrypted to check if inner == NULL, call free(decrypted),
cJSON_Delete if needed, ensure request->params (and params_len) and any other
fields are left in a predictable state, and propagate an error return from the
enclosing function so callers cannot use a partially populated request.
- Around line 1046-1049: frost_parse_dkg_round2_event currently rejects events
when decryption fails, but frost_create_dkg_round2_event silently falls back to
plaintext ("{}") on nip44_encrypt_content failure; make these symmetric by
changing frost_create_dkg_round2_event to treat nip44_encrypt_content failure as
a hard error (propagate/return an error code instead of inserting "{}"), remove
the plaintext fallback, and ensure error codes match the parser (use the same -3
or a consistent error) so Round2 events cannot be created as plaintext while the
parser requires decryption.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddc49eb and dbecfaf.

📒 Files selected for processing (18)
  • README.md
  • main/frost.c
  • main/frost.h
  • main/frost_coordinator.c
  • main/frost_coordinator.h
  • main/frost_signer.c
  • main/frost_signer.h
  • main/main.c
  • main/nostr_frost.c
  • main/nostr_frost.h
  • main/protocol.c
  • main/protocol.h
  • scripts/monitor_serial.py
  • scripts/test_all_rpc.py
  • scripts/test_dkg_full.py
  • scripts/test_dkg_relay.py
  • test/hardware/test_hardware.py
  • test/integration/test_full_flow.sh
🚧 Files skipped from review as they are similar to previous changes (13)
  • main/protocol.c
  • README.md
  • scripts/test_all_rpc.py
  • main/frost_signer.c
  • main/frost.c
  • main/frost_coordinator.c
  • test/integration/test_full_flow.sh
  • test/hardware/test_hardware.py
  • main/frost_signer.h
  • scripts/test_dkg_relay.py
  • main/frost.h
  • scripts/monitor_serial.py
  • main/frost_coordinator.h
🧰 Additional context used
🧬 Code graph analysis (2)
main/nostr_frost.h (1)
main/nostr_frost.c (3)
  • frost_parse_nip46_event (1324-1376)
  • frost_create_nip46_response (1378-1434)
  • frost_nip46_request_free (1436-1442)
main/main.c (1)
main/frost_signer.c (1)
  • frost_get_share_info (190-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (8)
scripts/test_dkg_full.py (1)

15-15: LGTM! Device path updated correctly.

The change from /dev/ttyUSB0 to /dev/ttyACM0 reflects the correct USB device type for the hardware being tested (USB CDC ACM devices vs. USB-to-serial converters). This aligns with the PR's objective to fix USB device paths and is part of a broader standardization effort across test scripts.

main/protocol.h (1)

27-27: LGTM!

The new enum member is properly positioned and aligns with the related RPC dispatch logic added in main.c.

main/nostr_frost.h (2)

100-112: LGTM!

The NIP-46 request and response structures are well-designed. The nip46_request_t struct has a corresponding cleanup function (frost_nip46_request_free), which is essential for the dynamically allocated params field.

Note: nip46_response_t pointers (result, error) are caller-owned and should be freed by the caller after use. Consider documenting this ownership contract in comments if not already documented elsewhere.


140-143: LGTM!

The function prototypes follow existing conventions and provide complete coverage for DKG Round2 and NIP-46 operations, including proper cleanup functions.

Also applies to: 178-185

main/main.c (1)

90-92: LGTM!

The new RPC method handler follows the established pattern and correctly delegates to frost_get_share_info.

main/nostr_frost.c (3)

1397-1408: LGTM!

The error/result handling correctly addresses the concerns from past review comments:

  1. Mutual exclusivity is enforced via the if-else-if-else structure
  2. The fallback error (line 1407) ensures that a valid NIP-46 response is always created, even when both error and result are NULL

The implementation aligns with NIP-46 semantics requiring either error or result in every response.


1410-1434: LGTM!

The event creation, encryption, signing, and serialization flow is correctly implemented with proper error handling and resource cleanup at each step.


1436-1442: LGTM!

The cleanup function follows best practices: null-checks the request pointer, frees dynamically allocated memory, and resets the pointer and length to prevent use-after-free.

@kwsantiago kwsantiago force-pushed the feat/dkg-nip46-usb-fixes branch from dbecfaf to 0a40eff Compare January 6, 2026 02:59
@kwsantiago kwsantiago merged commit 04610c4 into main Jan 6, 2026
3 checks passed
@kwsantiago kwsantiago deleted the feat/dkg-nip46-usb-fixes branch January 6, 2026 03:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant