Skip to content

Conversation

Gmin2
Copy link
Contributor

@Gmin2 Gmin2 commented Jul 8, 2025

❯ ./target/debug/near message sign-nep413 \
  --nonce "$NONCE" \
  --recipient "myapp.com" \
  --signer-account-id "your_account.near" \
  utf8 "Hello, NEAR" \
  sign-with-plaintext-private-key "$SECRET_KEY"


{
  "accountId": "your_account.near",
  "publicKey": "ed25519:3mxVindpjHiDVMBNXEJa8PvBpqENpPjUhGtGPRX7xzkn",
  "signature": "ed25519:22mexufQVRT1TApheUee1YXcHhXGSygtEWBiH6obENdq37Jd4XArU9ec1rwjLmukRShK8WYBbWvBJ1Z3ev5tqdXy"
}

Fixes #497

@github-project-automation github-project-automation bot moved this to NEW❗ in DevTools Jul 8, 2025
@Gmin2 Gmin2 marked this pull request as draft July 8, 2025 17:14
@Gmin2 Gmin2 marked this pull request as ready for review July 9, 2025 08:50
@Gmin2 Gmin2 requested a review from frol July 9, 2025 08:51
Copy link
Collaborator

@frol frol left a comment

Choose a reason for hiding this comment

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

@Gmin2 This is a great progress!

Were you able to test all the signing flows end-to-end? Which Operating System do you have? (I will also do it on my side, but I'd like you to check it first unless you don't have Ledger device or system keychain)

@race-of-sloths invite

Comment on lines 52 to 54
#[strum_discriminants(strum(message = "message - Sign an arbitrary message (NEP-413)"))]
/// Sign an arbitrary message (NEP-413)
Message(self::message::MessageCommand),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's move it up - between "transaction" and "config"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 9fb58cd

Comment on lines 12 to 23
pub struct SignNep413 {
#[interactive_clap(long)]
/// A 32-byte nonce as a base64-encoded string:
nonce: crate::types::base64_bytes::Base64Bytes,
#[interactive_clap(long)]
/// The recipient of the message (e.g. "alice.near" or "myapp.com"):
recipient: String,
#[interactive_clap(long)]
/// Which account to sign the message with:
signer_account_id: crate::types::account_id::AccountId,
#[interactive_clap(subcommand)]
message_type: self::message_type::MessageType,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Well, the proposed design in #497 was following the general style of named arguments used everywhere else in the CLI and this implementation uses --long parameters, which are generally only used for optional parameters.

  1. Let's use named args (you'll have to split it into intermediate structs)
  2. Let's move signer_account_id after the message (you will only be able to do that after you introduce the intermediate structs from step (1)) and name it sign-as to align with how it is named when you sign a transaction

Copy link
Collaborator

@frol frol Jul 10, 2025

Choose a reason for hiding this comment

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

I mean, here is the proposal in #497 that I liked:

near message sign-nep413
    <"utf8" | "base64"> "<MESSAGE>" \
    nonce "<BASE64_NONCE>" recipient "<RECIPIENT>" \
    sign-as "<SIGNER_ACCOUNT_ID>" \
    sign-with-keychain

This is the implemented:

near message sign-nep413 \
  --nonce "$BASE64_NONCE" \
  --recipient "myapp.com" \
  --signer-account-id "your_account.near" \
  utf8 "Hello, NEAR" \
  sign-with-plaintext-private-key "$SECRET_KEY"
  1. The message is the most important thing and it is the first we want to prompt
  2. nonce and recipient are not optional, so they should have no --
  3. The signer account must be followed by the signing options and should be named "sign-as" to match the other CLI commands

Copy link
Contributor Author

@Gmin2 Gmin2 Jul 11, 2025

Choose a reason for hiding this comment

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

refactored in 9fb58cd

Comment on lines 38 to 46
let nonce_bytes = scope.nonce.as_bytes();
if nonce_bytes.len() != 32 {
return Err(color_eyre::eyre::eyre!(
"Invalid nonce length: expected 32 bytes, got {}",
nonce_bytes.len()
));
}
let mut nonce = [0u8; 32];
nonce.copy_from_slice(nonce_bytes);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's create a new wrapper type, so it can validate the input during the interactive input and not crash the app later on:

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done in 9fb58cd

@race-of-sloths
Copy link

race-of-sloths commented Jul 10, 2025

@Gmin2 Thank you for your contribution! Your pull request is now a part of the Race of Sloths!
New Sloth joined the Race! Welcome!

Shows inviting banner with latest news.

Shows profile picture for the author of the PR

Current status: executed
Reviewer Score
@frol 13

Your contribution is much appreciated with a final score of 13!
You have received 150 (130 base + 10 weekly bonus + 10 monthly bonus) Sloth points for this contribution

@frol received 25 Sloth Points for reviewing and scoring this pull request.

Congratulations @Gmin2! Your PR was highly scored and you completed another monthly streak! To keep your monthly streak make another pull request next month and get 8+ score for it

What is the Race of Sloths

Race of Sloths is a friendly competition where you can participate in challenges and compete with other open-source contributors within your normal workflow

For contributors:

  • Tag @race-of-sloths inside your pull requests
  • Wait for the maintainer to review and score your pull request
  • Check out your position in the Leaderboard
  • Keep weekly and monthly streaks to reach higher positions
  • Boast your contributions with a dynamic picture of your Profile

For maintainers:

  • Score pull requests that participate in the Race of Sloths and receive a reward
  • Engage contributors with fair scoring and fast responses so they keep their streaks
  • Promote the Race to the point where the Race starts promoting you
  • Grow the community of your contributors

Feel free to check our website for additional details!

Bot commands
  • For contributors
    • Include a PR: @race-of-sloths include to enter the Race with your PR
  • For maintainers:
    • Invite contributor @race-of-sloths invite to invite the contributor to participate in a race or include it, if it's already a runner.
    • Assign points: @race-of-sloths score [1/2/3/5/8/13] to award points based on your assessment.
    • Reject this PR: @race-of-sloths exclude to send this PR back to the drawing board.
    • Exclude repo: @race-of-sloths pause to stop bot activity in this repo until @race-of-sloths unpause command is called

@Gmin2
Copy link
Contributor Author

Gmin2 commented Jul 11, 2025

Were you able to test all the signing flows end-to-end? Which Operating System do you have? (I will also do it on my side, but I'd like you to check it first unless you don't have Ledger device or system keychain)

hey tested all of it just not tested the ledger one , i am on ubuntu

@race-of-sloths

@frol frol requested a review from Copilot July 11, 2025 15:53
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for NEP-413 offchain message signing via a new message sign-nep413 CLI command.

  • Introduces a Nonce32 type for handling 32-byte nonces encoded as base64
  • Implements the sign_nep413_payload function with Borsh serialization and hash prefixing
  • Adds interactive CLI flows for signing messages with seed phrases, private keys, keychains, Ledger devices, and access key files

Reviewed Changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/types/nonce32_bytes.rs Adds Nonce32 for base64-encoded 32-byte nonces
src/types/mod.rs Exposes nonce32_bytes module
src/commands/mod.rs Registers the top-level message subcommand
src/commands/message/mod.rs Defines MessageCommand and MessageActions with sign-nep413
src/commands/message/sign_nep413/mod.rs Implements NEP413Payload, SignedMessage, and sign_nep413_payload
src/commands/message/sign_nep413/nonce/mod.rs Adds interactive step to collect a Nonce32
src/commands/message/sign_nep413/recipient/mod.rs Adds interactive step to collect the message recipient
src/commands/message/sign_nep413/message_type/mod.rs Defines MessageType enum for UTF-8 or base64 input
src/commands/message/sign_nep413/message_type/utf8.rs Handles plain UTF-8 message input
src/commands/message/sign_nep413/message_type/base64.rs Handles base64 message input
src/commands/message/sign_nep413/signer/mod.rs Adds SignAs step to choose signer account
src/commands/message/sign_nep413/signature_options/mod.rs Defines SignWith enum for signing methods
src/commands/message/sign_nep413/signature_options/sign_with_seed_phrase.rs Implements signing via seed phrase
src/commands/message/sign_nep413/signature_options/sign_with_private_key.rs Implements signing via plaintext private key
src/commands/message/sign_nep413/signature_options/sign_with_access_key_file.rs Implements signing via access key file
src/commands/message/sign_nep413/signature_options/sign_with_keychain.rs Implements signing via OS keychain
src/commands/message/sign_nep413/signature_options/sign_with_legacy_keychain.rs Implements signing via legacy keychain
src/commands/message/sign_nep413/signature_options/sign_with_ledger.rs Implements signing via Ledger Nano device
src/commands/account/mod.rs Makes export_account public for keychain integration
Cargo.toml Adds borsh dependency for payload serialization
Comments suppressed due to low confidence (9)

src/types/nonce32_bytes.rs:1

  • Consider adding unit tests for Nonce32::from_str and Display to verify correct base64 parsing/formatting and error conditions.
#[derive(Debug, Clone, Default)]

src/commands/message/sign_nep413/signature_options/sign_with_seed_phrase.rs:34

  • Missing import for serde_json. Add use serde_json; (or the specific functions) at the top to avoid compile errors.
        println!("{}", serde_json::to_string_pretty(&signed_message)?);

src/commands/message/sign_nep413/signature_options/sign_with_private_key.rs:26

  • Missing import for serde_json. Add use serde_json; at the top to fix the unresolved name.
        println!("{}", serde_json::to_string_pretty(&signed_message)?);

src/commands/message/sign_nep413/signature_options/sign_with_legacy_keychain.rs:40

  • Missing import for serde_json. Add use serde_json; to ensure to_string_pretty is in scope.
                    println!("{}", serde_json::to_string_pretty(&signed_message)?);

src/commands/message/sign_nep413/signature_options/sign_with_keychain.rs:31

  • Missing import for serde_json. Add use serde_json; so the JSON serialization call compiles.
                    println!("{}", serde_json::to_string_pretty(&signed_message)?);

src/commands/message/sign_nep413/signature_options/sign_with_access_key_file.rs:36

  • Missing import for serde_json. Add use serde_json; at the top to resolve this symbol.
        println!("{}", serde_json::to_string_pretty(&signed_message)?);

src/commands/message/sign_nep413/signature_options/sign_with_ledger.rs:54

  • Missing import for serde_json. Include use serde_json; to fix the unresolved call.
        println!("{}", serde_json::to_string_pretty(&signed_message)?);

src/commands/message/sign_nep413/signature_options/sign_with_seed_phrase.rs:7

  • [nitpick] The seed phrase is sensitive and will be echoed in the terminal. Consider marking this input as secret (e.g., hidden/password input) to avoid exposing it on-screen.
    master_seed_phrase: String,

src/commands/message/sign_nep413/signature_options/sign_with_private_key.rs:6

  • [nitpick] The private key will be entered in plaintext. Consider hiding the input (password style) to prevent it from being displayed in the terminal.
    pub private_key: crate::types::secret_key::SecretKey,

.to_bytes(),
));

std::thread::sleep(std::time::Duration::from_secs(1));
Copy link
Preview

Copilot AI Jul 11, 2025

Choose a reason for hiding this comment

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

[nitpick] Sleeping for a fixed duration may slow interactive use and is brittle. Consider polling or using an event to detect when the Ledger app is ready instead of an arbitrary sleep.

Suggested change
std::thread::sleep(std::time::Duration::from_secs(1));
wait_for_ledger_ready(std::time::Duration::from_secs(10))?;

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

@frol frol left a comment

Choose a reason for hiding this comment

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

@race-of-sloths score 13

@frol frol enabled auto-merge (squash) July 11, 2025 16:08
@frol frol merged commit 5a59b6c into near:main Jul 11, 2025
8 checks passed
@github-project-automation github-project-automation bot moved this from NEW❗ to Shipped 🚀 in DevTools Jul 11, 2025
@frol frol mentioned this pull request Jul 11, 2025
frol added a commit that referenced this pull request Jul 21, 2025
## 🤖 New release

* `near-cli-rs`: 0.21.0 -> 0.22.0 (⚠ API breaking changes)

### ⚠ `near-cli-rs` breaking changes

```text
--- failure enum_no_repr_variant_discriminant_changed: enum variant had its discriminant change value ---

Description:
The enum's variant had its discriminant value change. This breaks downstream code that used its value via a numeric cast like `as isize`.
        ref: https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.41.0/src/lints/enum_no_repr_variant_discriminant_changed.ron

Failed in:
  variant TopLevelCommandDiscriminants::Config 5 -> 6 in /tmp/.tmphokuMN/near-cli-rs/src/commands/mod.rs:50
  variant TopLevelCommandDiscriminants::Extensions 6 -> 7 in /tmp/.tmphokuMN/near-cli-rs/src/commands/mod.rs:54
  variant TopLevelCommandDiscriminants::Config 5 -> 6 in /tmp/.tmphokuMN/near-cli-rs/src/commands/mod.rs:50
  variant TopLevelCommandDiscriminants::Extensions 6 -> 7 in /tmp/.tmphokuMN/near-cli-rs/src/commands/mod.rs:54

--- failure enum_variant_added: enum variant added on exhaustive enum ---

Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.41.0/src/lints/enum_variant_added.ron

Failed in:
  variant CliTopLevelCommand:Message in /tmp/.tmphokuMN/near-cli-rs/src/commands/mod.rs:15
  variant TopLevelCommandDiscriminants:Message in /tmp/.tmphokuMN/near-cli-rs/src/commands/mod.rs:45
  variant TopLevelCommandDiscriminants:Message in /tmp/.tmphokuMN/near-cli-rs/src/commands/mod.rs:45
```

<details><summary><i><b>Changelog</b></i></summary><p>

<blockquote>

##
[0.22.0](v0.21.0...v0.22.0)
- 2025-07-21

### Added

- New "message sign-nep413" command - NEP-413 offchain messages signing
([#507](#507))

### Fixed

- Gracefully handle (Started, NotStarted) transaction status
([#510](#510))

### Other

- Removed dj8yfo from CODEOWNERS
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Shipped 🚀
Development

Successfully merging this pull request may close these issues.

[Task] Sign NEP-413 off-chain messages
3 participants