Skip to content

SignMessage rpc#954

Open
bitcoin-coder-bob wants to merge 8 commits intomasterfrom
bob/SignMessage
Open

SignMessage rpc#954
bitcoin-coder-bob wants to merge 8 commits intomasterfrom
bob/SignMessage

Conversation

@bitcoin-coder-bob
Copy link
Collaborator

@bitcoin-coder-bob bitcoin-coder-bob commented Mar 4, 2026

requested by: #904 (review)

  • Proto: Added SignMessage RPC to SignerService with request/response messages
  • Generated files: Updated .pb.go, .pb.rgw.go, _grpc.pb.go, and OpenAPI spec
  • Port interface: Added SignMessage to SignerService interface
  • Infrastructure client: Implemented SignMessage on signerClient
  • Wallet types: Added SignMessage to WalletService interface
  • Wallet service: Implemented SignMessage using schnorr signing
  • gRPC handler: Added SignMessage handler to signerHandler
  • Tests: New test file with comprehensive SignMessage tests + fixture

Summary by CodeRabbit

  • New Features

    • Added message signing endpoint (POST /v1/sign-message) accessible via HTTP and gRPC; accepts hex-encoded messages and returns hex-encoded signatures. Input/output include validation (hex format, expected signature length) and clear errors for invalid payloads or missing signer key.
  • Tests

    • Added fixture-driven tests covering valid signings, edge cases (empty, large, fixed-size), key selection, and missing-signer error.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an end-to-end SignMessage flow: OpenAPI + protobuf RPC, signer client, core and wallet interfaces, gRPC handler, Schnorr signing implementation, and fixture-driven tests for valid and invalid cases.

Changes

Cohort / File(s) Summary
API Specs
api-spec/protobuf/signer/v1/service.proto, api-spec/openapi/swagger/signer/v1/service.openapi.json
Added SignMessage RPC and HTTP POST /v1/sign-message, plus SignMessageRequest/SignMessageResponse schemas and hex validation patterns.
Core Port Layer
internal/core/ports/signer.go
Added SignerService.SignMessage(ctx context.Context, message []byte) ([]byte, error) to the interface.
Infrastructure Client
internal/infrastructure/signer/client.go
Added signerClient.SignMessage to call RPC, hex-encode request and decode hex signature response; propagates RPC and hex decode errors.
Wallet Application
pkg/arkd-wallet/core/application/types.go, pkg/arkd-wallet/core/application/wallet/service.go
Added WalletService.SignMessage; hashes message with chainhash.HashB, signs with Schnorr using loaded signer key, returns serialized signature; errors if signer key not loaded.
gRPC Handler
pkg/arkd-wallet/interface/grpc/handlers/signer_handler.go
Added SignMessage handler: decodes hex message, returns InvalidArgument on decode failure, delegates to wallet.SignMessage, returns hex-encoded signature.
Tests & Fixtures
pkg/arkd-wallet/core/application/wallet/service_test.go, pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json
Added fixture-driven tests covering multiple valid message scenarios and an invalid missing-key case; includes new test fixtures file.

Sequence Diagram

sequenceDiagram
    participant Client as gRPC Client
    participant Handler as SignerHandler
    participant Wallet as Wallet Service
    participant Hasher as chainhash.HashB
    participant Signer as Signer Key

    Client->>Handler: SignMessage(hexMessage)
    Handler->>Handler: decode hex -> message
    Handler->>Wallet: SignMessage(ctx, message)
    Wallet->>Hasher: HashB(message)
    Hasher-->>Wallet: hash
    Wallet->>Signer: Schnorr.Sign(hash)
    Signer-->>Wallet: signature bytes
    Wallet-->>Handler: signature bytes
    Handler->>Handler: encode hex(signature)
    Handler-->>Client: SignMessageResponse{signature}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'SignMessage rpc' is concise and clearly summarizes the main change—adding a new SignMessage RPC to the SignerService across multiple layers of the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bob/SignMessage
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can enforce grammar and style rules using `languagetool`.

Configure the reviews.tools.languagetool setting to enable/disable rules and categories. Refer to the LanguageTool Community to learn more.

@bitcoin-coder-bob bitcoin-coder-bob marked this pull request as ready for review March 4, 2026 17:03
Copy link
Contributor

@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.

🧹 Nitpick comments (1)
pkg/arkd-wallet/core/application/wallet/service_test.go (1)

37-46: Mark test helper functions with t.Helper().

This improves assertion failure locations by attributing failures to callers instead of helper internals.

♻️ Suggested patch
 func loadServiceFixtures(t *testing.T) *serviceFixtures {
+	t.Helper()
 	data, err := os.ReadFile("fixtures/service_fixtures.json")
 	require.NoError(t, err)
@@
 func getTestKey(t *testing.T, fixtures *serviceFixtures, keyName string) *btcec.PrivateKey {
+	t.Helper()
 	for _, k := range fixtures.SignMessageTests.TestKeys {

Also applies to: 48-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/arkd-wallet/core/application/wallet/service_test.go` around lines 37 -
46, Add t.Helper() at the start of test helper functions so failures attribute
to the caller; specifically, update loadServiceFixtures(t *testing.T) to call
t.Helper() as the first statement and do the same for the other helper function
defined around lines 48-59 (the other helper's name in the diff) so all
assertions inside those helpers point to the test that invoked them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/arkd-wallet/core/application/wallet/service_test.go`:
- Around line 37-46: Add t.Helper() at the start of test helper functions so
failures attribute to the caller; specifically, update loadServiceFixtures(t
*testing.T) to call t.Helper() as the first statement and do the same for the
other helper function defined around lines 48-59 (the other helper's name in the
diff) so all assertions inside those helpers point to the test that invoked
them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62b4605c-d0f2-4235-badb-cbb21d5856e9

📥 Commits

Reviewing files that changed from the base of the PR and between 8615412 and cafe919.

⛔ Files ignored due to path filters (3)
  • api-spec/protobuf/gen/signer/v1/service.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • api-spec/protobuf/gen/signer/v1/service.pb.rgw.go is excluded by !**/gen/**
  • api-spec/protobuf/gen/signer/v1/service_grpc.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (9)
  • api-spec/openapi/swagger/signer/v1/service.openapi.json
  • api-spec/protobuf/signer/v1/service.proto
  • internal/core/ports/signer.go
  • internal/infrastructure/signer/client.go
  • pkg/arkd-wallet/core/application/types.go
  • pkg/arkd-wallet/core/application/wallet/fixtures/service_fixtures.json
  • pkg/arkd-wallet/core/application/wallet/service.go
  • pkg/arkd-wallet/core/application/wallet/service_test.go
  • pkg/arkd-wallet/interface/grpc/handlers/signer_handler.go

Copy link

@IsaqueFranklin IsaqueFranklin left a comment

Choose a reason for hiding this comment

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

Overall it looks very solid!

I ran the unit tests provided and validated the changes on my machine, specifically TestSignMessage and TestSignMessage_ConsistentWithSchnorrVerify and they passed just fine.

Sorry for the nits 😅

cACK.

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json (1)

46-51: Consider removing key_name from invalid fixtures (currently unused).

In pkg/arkd-wallet/core/application/wallet/service_test.go:102-120, invalid cases do not consume tc.KeyName; keeping it here can imply behavior that is not actually tested.

♻️ Minimal cleanup diff
--- a/pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json
+++ b/pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json
@@
     {
       "name": "no_signer_key",
       "message_hex": "48656c6c6f20576f726c64",
-      "key_name": "",
       "expected_error": "signer key not loaded"
     }
--- a/pkg/arkd-wallet/core/application/wallet/service_test.go
+++ b/pkg/arkd-wallet/core/application/wallet/service_test.go
@@
 type signMessageInvalidCase struct {
     Name          string `json:"name"`
     MessageHex    string `json:"message_hex"`
-    KeyName       string `json:"key_name"`
     ExpectedError string `json:"expected_error"`
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json`
around lines 46 - 51, The invalid fixtures include an unused "key_name" field
(e.g., the "no_signer_key" entry in the "invalid" array of
signmessage_fixtures.json) while the test code in service_test.go does not use
tc.KeyName; remove the "key_name" property from all entries in the "invalid"
array of signmessage_fixtures.json (or alternatively update the tests to
actually consume tc.KeyName), ensuring the fixture shape matches what the test
expects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json`:
- Around line 46-51: The invalid fixtures include an unused "key_name" field
(e.g., the "no_signer_key" entry in the "invalid" array of
signmessage_fixtures.json) while the test code in service_test.go does not use
tc.KeyName; remove the "key_name" property from all entries in the "invalid"
array of signmessage_fixtures.json (or alternatively update the tests to
actually consume tc.KeyName), ensuring the fixture shape matches what the test
expects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0fb86777-da94-46d0-8efa-28ac6c77b952

📥 Commits

Reviewing files that changed from the base of the PR and between cafe919 and ebe18da.

📒 Files selected for processing (2)
  • pkg/arkd-wallet/core/application/wallet/service_test.go
  • pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/arkd-wallet/core/application/wallet/service_test.go

Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api-spec/openapi/swagger/signer/v1/service.openapi.json`:
- Around line 227-244: The OpenAPI schemas for SignMessageRequest and
SignMessageResponse currently allow any string but the handler expects
hex-encoded input/output; update the SignMessageRequest.message and
SignMessageResponse.signature properties to declare they are hex strings by
adding a clear description ("hex-encoded string"), a pattern constraint (e.g.
regex for hex characters), and an example, so clients know to send/expect hex;
refer to SignMessageRequest and SignMessageResponse (and the signer handler that
decodes/encodes hex) when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 359650cf-201b-41a9-899d-1d8ec13e52ce

📥 Commits

Reviewing files that changed from the base of the PR and between ebe18da and 31e5b44.

⛔ Files ignored due to path filters (2)
  • api-spec/protobuf/gen/ark/v1/indexer.pb.rgw.go is excluded by !**/gen/**
  • api-spec/protobuf/gen/signer/v1/service.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (4)
  • api-spec/openapi/swagger/signer/v1/service.openapi.json
  • api-spec/protobuf/signer/v1/service.proto
  • internal/infrastructure/signer/client.go
  • pkg/arkd-wallet/interface/grpc/handlers/signer_handler.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • api-spec/protobuf/signer/v1/service.proto

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
api-spec/protobuf/signer/v1/service.proto (1)

66-67: Clarify raw message vs pre-hashed input in the request comment.

Line 66 currently documents encoding but not input semantics. Please state whether clients send raw message bytes or a pre-hash to avoid signing mismatches across implementations.

✏️ Suggested proto comment tweak
 message SignMessageRequest {
-  // hex-encoded message to sign
+  // hex-encoded raw message bytes to sign (not a pre-hashed digest)
   string message = 1;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api-spec/protobuf/signer/v1/service.proto` around lines 66 - 67, Update the
comment on the protobuf field "message" in service.proto to explicitly state
whether clients must send the raw message bytes or a pre-computed hash/digest
and to describe the exact encoding and hashing expectations; e.g., clarify that
"message" is a hex-encoded representation of raw message bytes (or alternatively
a hex-encoded digest), state which hashing algorithm (e.g., SHA-256) clients
should use if a pre-hash is required, and note whether the service will hash the
input before signing or expects the provided digest to be signed as-is so
implementers avoid mismatches when calling the signer API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@api-spec/protobuf/signer/v1/service.proto`:
- Around line 66-67: Update the comment on the protobuf field "message" in
service.proto to explicitly state whether clients must send the raw message
bytes or a pre-computed hash/digest and to describe the exact encoding and
hashing expectations; e.g., clarify that "message" is a hex-encoded
representation of raw message bytes (or alternatively a hex-encoded digest),
state which hashing algorithm (e.g., SHA-256) clients should use if a pre-hash
is required, and note whether the service will hash the input before signing or
expects the provided digest to be signed as-is so implementers avoid mismatches
when calling the signer API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce55538a-ee2f-49cf-831f-527cc6fc8dd2

📥 Commits

Reviewing files that changed from the base of the PR and between 31e5b44 and db3e733.

⛔ Files ignored due to path filters (2)
  • api-spec/protobuf/gen/ark/v1/indexer.pb.rgw.go is excluded by !**/gen/**
  • api-spec/protobuf/gen/signer/v1/service.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (2)
  • api-spec/openapi/swagger/signer/v1/service.openapi.json
  • api-spec/protobuf/signer/v1/service.proto
🚧 Files skipped from review as they are similar to previous changes (1)
  • api-spec/openapi/swagger/signer/v1/service.openapi.json

@arkanaai
Copy link
Contributor

arkanaai bot commented Mar 16, 2026

👋 Hey @bitcoin-coder-bob@louisinger requested changes on this PR on March 14th. Just a nudge to check in on the review feedback when you get a chance!

Copy link
Contributor

@arkanaai arkanaai bot left a comment

Choose a reason for hiding this comment

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

🔍 Arkana Review — SignMessage RPC

Summary

Adds a new SignMessage RPC to the signer service that signs arbitrary messages with the operator's Schnorr key. Exposes via gRPC and REST (POST /v1/sign-message).

Security

⚠️ Access control on signer service endpoint: The POST /v1/sign-message endpoint accepts any hex-encoded message and signs it with the operator's private key. This is fine if the signer service is only accessible internally (operator-to-signer communication), but could be dangerous if any part of the API surface is exposed to untrusted callers. Worth confirming the signer service is not reachable from client-facing ports.

✅ Message hashing: The implementation correctly hashes the message with chainhash.HashB(message) (double-SHA256) before passing to schnorr.Sign. This prevents signing raw transaction sighashes that an attacker might try to sneak through — the double-SHA256 wrapping ensures the signed digest can't be confused with a transaction signature.

ℹ️ No message length limit: The protobuf pattern ^(?:[0-9a-fA-F]{2})*$ validates hex format but doesn't limit length. For a signing endpoint, this is generally fine since the message is hashed anyway, but a max length would be a sensible defense-in-depth measure against abuse.

Implementation

  • Proto definition, generated code, Go handler, and wallet implementation all look correct and consistent.
  • Test fixtures cover basic cases including empty message, various key lengths, and the no-key error path.
  • Schnorr signature size validation in the response (^[0-9a-fA-F]{128}$, min/max 128 chars = 64 bytes) is correct.

Nit

  • service.proto — missing trailing newline at EOF.

Overall clean and well-structured. The main question is ensuring the signer service's network exposure is limited to trusted internal callers.

Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api-spec/protobuf/signer/v1/service.proto`:
- Around line 65-77: The SignMessage client currently hex-decodes the signature
returned by the server (in SignMessage in
internal/infrastructure/signer/client.go) but does not validate the decoded byte
length against the proto contract; after hex decoding the string, add a runtime
check that the decoded signature length == 64 bytes (proto enforces 128 hex
chars => 64 bytes) and return a clear error if not, ensuring any malformed or
short/long signatures are rejected before further use.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c19a4e0-591b-40a3-aea3-f95f3aa57080

📥 Commits

Reviewing files that changed from the base of the PR and between db3e733 and 343a8ae.

⛔ Files ignored due to path filters (2)
  • api-spec/protobuf/gen/ark/v1/indexer.pb.rgw.go is excluded by !**/gen/**
  • api-spec/protobuf/gen/signer/v1/service.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (4)
  • api-spec/openapi/swagger/signer/v1/service.openapi.json
  • api-spec/protobuf/signer/v1/service.proto
  • pkg/arkd-wallet/core/application/wallet/service_test.go
  • pkg/arkd-wallet/core/application/wallet/testdata/signmessage_fixtures.json

@arkanaai
Copy link
Contributor

arkanaai bot commented Mar 16, 2026

🔍 Arkana Review — SignMessage RPC

Overview

Adds SignMessage RPC to SignerService: accepts hex-encoded message, returns hex-encoded Schnorr signature. Clean implementation across proto, gRPC handler, wallet service, and tests.

Security Notes

1. Signing oracle risk — message domain separation (Medium)
The implementation signs chainhash.HashB(message) (SHA-256d) with no domain separator or prefix. This is the same hash function used for Bitcoin transaction signing. A malicious caller could potentially craft a message whose SHA-256d hash collides with a sighash, tricking the signer into producing a valid transaction signature via this RPC.

Consider adding a tagged hash or domain-separated prefix (e.g. "ark-signmessage:" prefix, or BIP-340 tagged hash with "ArkMessage" tag) to ensure signatures produced by SignMessage can never be valid for transaction signing contexts.

2. No auth/ACL check mentioned
The endpoint is registered alongside SignTransaction and SignTransactionTapscript. Verify that existing macaroon auth interceptors cover this new endpoint.

3. Empty message signing
The proto regex ^(?:[0-9a-fA-F]{2})*$ allows empty strings. Signing an empty message is technically valid but unusual — consider whether this should be rejected.

Code Quality

  • Proto validation patterns look correct (hex input, 128-char hex signature output)
  • Test fixtures cover edge cases well (empty, 32-byte, 36-byte auth token, large, different key)
  • Missing newline at EOF in signer_handler.go (minor)

Cross-Repo Impact

  • SDKs (ts-sdk, rust-sdk, dart-sdk, go-sdk) will need client bindings for this new RPC

Verdict

Implementation is clean and well-tested. The domain separation concern is the most important item — without it, this RPC becomes a generic signing oracle over the operator key.

Copy link
Contributor

@arkanaai arkanaai bot left a comment

Choose a reason for hiding this comment

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

🔍 Arkana Review — arkd#954

Summary: Adds a SignMessage RPC to the SignerService. Accepts a hex-encoded message, signs it with Schnorr (via chainhash.HashB then schnorr.Sign), and returns the hex-encoded 64-byte signature.

🔒 Security — Key observations

  1. Signing scheme: The message is hashed with chainhash.HashB (double SHA-256) before being passed to schnorr.Sign. This means the RPC signs SHA256(SHA256(message)), which is Bitcoin-standard. Good — no risk of signing raw transaction hashes that could be replayed as actual transactions, since the hash domain is distinct.

  2. No authentication boundary visible in this diff — the SignerService is wired through the macaroon auth interceptor (visible in the interceptor chain in interceptor.go), so this RPC inherits the existing auth requirements. Anyone with a valid macaroon can call SignMessage. This is appropriate since they can already call SignTransaction.

  3. No message length limit on the input — the proto schema only validates hex format (^(?:[0-9a-fA-F]{2})*$), not maximum length. Since chainhash.HashB handles arbitrary input, this is functionally safe, but a very large payload could be used for memory exhaustion. Consider adding a max_length constraint on the request field (e.g., 1MB).

  4. Missing signer key not loaded gRPC status mapping — in wallet/service.go, when SignerKey is nil, the error is a plain fmt.Errorf("signer key not loaded"). The gRPC handler in signer_handler.go returns this raw error. It should probably be wrapped in status.Errorf(codes.FailedPrecondition, ...) for consistency with other handler methods.

✅ What looks good

  • Signature validation is thorough — the infrastructure client (signerClient) checks both hex decode success and exact 64-byte length on the response.
  • Test coverage — fixture-driven tests cover normal signing, empty message, 32-byte message, large message, different keys, and missing signer key. Verification round-trips through schnorr.ParseSignature + Verify.
  • Proto definition is clean — input/output patterns are properly constrained in the OpenAPI spec.
  • gRPC handler hex-decode error properly returns codes.InvalidArgument.

🟡 Nits

  1. Missing newline at end of signer_handler.go and service.proto — will cause diff noise on next edit.
  2. The OpenAPI spec title is empty in options.openapi.json — appears to be from the new options.proto file in #960, not this PR. Worth checking if that's intentional.

Cross-repo impact

SDK parity: The SignMessage RPC is new. All SDKs (ts-sdk, go-sdk, rust-sdk, swift-sdk) will need to add the corresponding client method. Consider tracking this with an umbrella issue.

Verdict: Solid implementation. The two security nits (input length limit, gRPC status wrapping) are non-blocking but worth addressing.

Copy link
Contributor

@arkanaai arkanaai bot left a comment

Choose a reason for hiding this comment

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

🔍 Arkana Review — Post-Update (1 new commit)

New commit: d5c29636 — "check decoded signature is 64 bytes in SignMessage client"

✅ Good — Client-Side Signature Length Validation

The new check in internal/infrastructure/signer/client.go:

if len(sig) != 64 {
    return nil, fmt.Errorf("invalid signature length: expected 64 bytes, got %d", len(sig))
}

This is good defensive coding. Schnorr signatures are always 64 bytes, and validating this on the client side catches malformed responses from the signer service early, before they propagate further into the signing pipeline.

This complements the OpenAPI spec validation (^[0-9a-fA-F]{128}$ with min/max length 128) which enforces it at the HTTP layer. The Go client check catches issues in the gRPC path too.

Previous Review Notes Still Apply

The overall SignMessage RPC looks clean:

  • Message is hex-encoded, validated with ^(?:[0-9a-fA-F]{2})*$ regex
  • Signing uses chainhash.HashB(message) (SHA-256 hash) then schnorr.Sign — standard Schnorr signing
  • Tests cover valid signing, edge cases (empty/large messages), key selection, and missing-signer error
  • No key material is leaked in responses

No blocking issues. The PR is in good shape after this fix.

@arkanaai
Copy link
Contributor

arkanaai bot commented Mar 17, 2026

🔍 Arkana Review — Post-Update (1 new commit)

New commit: 49e2b82 — "ErrSignerDisabled usage"

✅ Good cleanup

Replaces the inline fmt.Errorf("signer key not loaded") with the existing ErrSignerDisabled sentinel error. The test fixture expectation is updated to match ("signer not enabled").

This is the right call — using sentinel errors enables callers to reliably match on error type (e.g., errors.Is(err, ErrSignerDisabled)) instead of fragile string comparison. Consistent with how other wallet service errors are handled.

No concerns

No behavioral change beyond the error message wording.

@louisinger louisinger requested a review from altafan March 17, 2026 08:36
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.

4 participants