Skip to content

wallet: add sign-msg and verify-msg commands (WalletConnect scheme)#4188

Draft
Copilot wants to merge 6 commits into
masterfrom
copilot/add-wallet-sign-msg-command
Draft

wallet: add sign-msg and verify-msg commands (WalletConnect scheme)#4188
Copilot wants to merge 6 commits into
masterfrom
copilot/add-wallet-sign-msg-command

Conversation

Copilot AI commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

NeoGo had no way to produce or verify message signatures compatible with Neon/NeoLine and the NeoFS WalletConnect scheme, making it impossible to interoperate with signatures from other Neo wallets.

Changes

  • pkg/crypto/keys/walletconnect.go — core primitives:

    • (*PrivateKey).SignWalletConnect(data []byte) ([]byte, error) — signs using the WalletConnect scheme; returns 80 bytes (64-byte ECDSA sig + 16-byte random salt)
    • (*PublicKey).VerifyWalletConnect(data, signature []byte) bool — verifies an 80-byte WalletConnect signature
    • Hash computed by streaming the canonical payload directly into sha256.New() (no intermediate buffer): 0x01 0x00 0x01 0xf0 | VarUint(len(hex(salt)+base64(msg))) | hex(salt) | base64(msg) | 0x00 0x00
  • cli/wallet/message.go — two new wallet subcommands:

    • wallet sign-msg — signs an arbitrary message and prints the hex-encoded 80-byte signature (64-byte ECDSA + 16-byte salt)
    • wallet verify-msg — verifies against a public key or wallet address

Usage

# Sign (cleartext, hex, or base64 input)
neo-go wallet sign-msg -w wallet.json -a NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP "Hello, Neo"
# Output:
deadbeef...a1b2c3d4e5f6...  # 80 bytes hex: 64-byte ECDSA sig + 16-byte salt

# Verify (no wallet needed if public key is known)
neo-go wallet verify-msg \
  --public-key 03... \
  --signature deadbeef...a1b2c3d4e5f6... \
  "Hello, Neo"

Both commands accept --hex and --base64 flags to handle binary messages. verify-msg also accepts --address with a wallet for address-based lookup, extracting the public key from the verification script without requiring private key decryption.

The signing payload format is identical to the neofs-sdk-go SignerWalletConnect implementation, ensuring cross-wallet compatibility.

Original prompt

This section details on the original issue you should resolve

<issue_title>wallet sign-msg</issue_title>
<issue_description>## Is your feature request related to a problem? Please describe.
I'm always frustrated when I can't reproduce signatures routinely made by Neon/Neoline/etc in NeoGo.

Describe the solution you'd like

Add some way (wallet sign-msg command or something else) to produce signatures like in NeoFS "WalletConnect" scheme that is widely known for years and available in various wallets. The message can be cleartext or hex/base64, wallet shouldn't encode it.

Describe alternatives you've considered

No alternatives, some compatible thing has to exist.

Additional context

https://pkg.go.dev/github.com/nspcc-dev/neofs-sdk-go@v1.0.0-rc.16/crypto/ecdsa#SignerWalletConnect
neo-project/neo#4286
neo-project/neo-node#924
neo-project/proposals#213
neo-project/proposals#210
</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits March 10, 2026 10:49
…g scheme

Co-authored-by: roman-khimov <22092804+roman-khimov@users.noreply.github.com>
…word in test

Co-authored-by: roman-khimov <22092804+roman-khimov@users.noreply.github.com>
Copilot AI changed the title [WIP] Add wallet sign-msg command for signature production wallet: add sign-msg and verify-msg commands (WalletConnect scheme) Mar 10, 2026
@codecov

codecov Bot commented Mar 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.28767% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.35%. Comparing base (88586b7) to head (fd9a1fd).

Files with missing lines Patch % Lines
cli/wallet/message.go 61.05% 24 Missing and 13 partials ⚠️
pkg/crypto/keys/walletconnect.go 93.93% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4188      +/-   ##
==========================================
- Coverage   83.40%   83.35%   -0.06%     
==========================================
  Files         357      359       +2     
  Lines       43760    43906     +146     
==========================================
+ Hits        36499    36598      +99     
- Misses       5464     5496      +32     
- Partials     1797     1812      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread pkg/crypto/keys/walletconnect.go Outdated
// RFC 6979 ECDSA with SHA-256. The returned signature is 64 bytes of ECDSA
// signature followed by WalletConnectSaltLen bytes of salt (80 bytes total).
func (p *PrivateKey) SignWalletConnect(data []byte) ([]byte, error) {
b64 := make([]byte, base64.StdEncoding.EncodedLen(len(data)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Avoid this and other allocations, we only need a hash of this data, so io.Writer of hash.Hash can be leveraged to avoid buffering and copying things around.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 3681226. Replaced the intermediate payload buffer with a streaming approach: sha256.New() is used as an io.Writer, and hex.NewEncoder/base64.NewEncoder write directly into the hash — no buffer allocation needed.

Comment thread pkg/crypto/keys/walletconnect.go Outdated
return false
}
b64 := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(b64, data)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here.

Comment thread cli/wallet/message.go Outdated
"github.com/urfave/cli/v2"
)

// signMessageResult contains the output of the sign-msg command.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not needed. The output is just a signature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in commit 3681226. sign-msg now just prints the hex-encoded 80-byte signature (ECDSA + salt) directly.

Comment thread cli/wallet/message.go Outdated
}

sig := sigWithSalt[:keys.SignatureLen]
salt := sigWithSalt[keys.SignatureLen:]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why splitting it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 3681226. No longer splitting — SignWalletConnect returns the full 80-byte combined value which is passed directly to output/verify without intermediate slicing.

Comment thread cli/wallet/message.go Outdated
return cli.Exit(err, 1)
}

saltHex := ctx.String("salt")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Single parameter, please.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 3681226. Removed the --salt flag entirely. --signature now takes the full 80-byte hex value (64-byte ECDSA + 16-byte salt) as produced by sign-msg.

@roman-khimov

Copy link
Copy Markdown
Member

@copilot, please fix comments from previous review

Co-authored-by: roman-khimov <22092804+roman-khimov@users.noreply.github.com>
@roman-khimov

Copy link
Copy Markdown
Member

@copilot try again

Copilot AI and others added 2 commits March 11, 2026 14:50
…ature param

Co-authored-by: roman-khimov <22092804+roman-khimov@users.noreply.github.com>
…test

Co-authored-by: roman-khimov <22092804+roman-khimov@users.noreply.github.com>

Copilot AI commented Mar 11, 2026

Copy link
Copy Markdown
Contributor Author

@copilot try again

Done in fc96671. All review feedback addressed: streaming hash (no allocation), plain hex signature output, single --signature parameter combining ECDSA bytes and salt.

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.

wallet sign-msg

2 participants