Skip to content

feat: implement BIP-340 Schnorr signing for secp256k1 - #545

Merged
xiaying-peng merged 3 commits into
masterfrom
jiayu/feat-schnorr-bip340
Jun 23, 2026
Merged

feat: implement BIP-340 Schnorr signing for secp256k1#545
xiaying-peng merged 3 commits into
masterfrom
jiayu/feat-schnorr-bip340

Conversation

@jiayupu-cb

@jiayupu-cb jiayupu-cb commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Fixes # .

Note on file count

99 of the 106 changed files are mechanical copyright-year updates (2025 → 2026) across types/, client/, and server/. These are auto-generated files — make check-gen runs addlicense inside Docker and stamps all regenerated files with the current year, so any PR opened in 2026 would include this diff. This is a pre-existing repo-wide issue that will be addressed separately.

Files worth reviewing:

  • keys/signer_secp256k1.go
  • keys/signer_secp256k1_test.go
  • asserter/construction.go
  • asserter/construction_test.go
  • go.mod / go.sum

Motivation

SchnorrBip340 ("schnorr_bip340") was already defined as a SignatureType
constant in the types module but was never implemented — Sign() and Verify()
both returned ErrSignUnsupportedSignatureType. This PR completes the
implementation so callers can actually use BIP-340 Schnorr signatures (used by
Bitcoin Taproot key-path spending) through the Mesh SDK.

Solution

keys/signer_secp256k1.go — Implement Sign and Verify for
types.SchnorrBip340 using github.com/btcsuite/btcd/btcec/v2/schnorr, which
is a spec-compliant BIP-340 implementation:

  • Sign: validates the message is exactly 32 bytes (BIP-340 requirement), then
    calls schnorr.Sign(privKey, msg) — handles key-parity negation and tagged-hash
    nonce derivation internally. Returns 64 bytes (bytes(R.x) || bytes(s)).
  • Verify: parses the 33-byte compressed public key via btcec.ParsePubKey,
    then verifies with schnorr.Signature.Verify, which lifts the key to its
    even-Y form per the BIP-340 spec.

asserter/construction.go — Add types.SchnorrBip340 to the
SignatureType() allowed-list so the asserter accepts it in payloads and
returned signatures.

go.mod — Bump github.com/coinbase/rosetta-sdk-go/types from v1.7.1
to v1.7.2, which renames the constant from SCHNORR_BIP340 to SchnorrBip340
for PascalCase consistency (see PR #546).

Tests added:

  • TestSignSecp256k1: sign with SchnorrBip340, assert 64-byte output.
  • TestVerifySecp256k1: BIP-340 round-trip (sign → verify).
  • TestSchnorrBip340_Bip341KeyPathVector: uses a tweaked private key and sighash
    from the BIP-341 wallet test vectors (keyPathSpending[0], txinIndex=4,
    hashType=0 / SIGHASH_DEFAULT). Checks (1) that our Sign produces a valid
    64-byte signature that self-verifies, and (2) that our Verify accepts the
    spec's expected witness bytes — confirming interoperability with external
    BIP-340 implementations.
  • TestSchnorrBip340_VerifyNegative: rejects garbage signature bytes, a
    signature over the wrong message, and a signature verified against the wrong
    public key.
  • TestSchnorrBip340_SignNegative: rejects messages that are not exactly 32
    bytes (5, 31, 33, 64 bytes tested).
  • asserter/construction_test.go: explicit asserter test case for
    types.SchnorrBip340 in TestSigningPayload.

Open questions

None.

@cb-heimdall

cb-heimdall commented Jun 23, 2026

Copy link
Copy Markdown

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

jiayupu-cb and others added 3 commits June 23, 2026 18:14
Revert the manual rename of SchnorrBip340 back to SCHNORR_BIP340 in
types/signature_type.go (auto-generated by OpenAPI Generator from spec
which produces SCREAMING_SNAKE_CASE). Update all call-sites in asserter
and keys packages accordingly. Run golines to reformat long lines so
check-gen passes.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
- Rename types.SCHNORR_BIP340 → types.SchnorrBip340 throughout asserter/
  and keys/ to match the PascalCase rename in types/v1.7.2
- Bump go.mod to github.com/coinbase/rosetta-sdk-go/types v1.7.2
- Add explicit 32-byte length check in SchnorrBip340 Sign(): BIP-340
  requires the message to be exactly 32 bytes; return a clear error
  rather than letting btcec fail silently
- Add TestSchnorrBip340_SignNegative covering wrong-length inputs

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@jiayupu-cb
jiayupu-cb force-pushed the jiayu/feat-schnorr-bip340 branch from b8c5001 to 3bacb06 Compare June 23, 2026 22:16
@xiaying-peng
xiaying-peng merged commit 6a2448b into master Jun 23, 2026
13 checks passed
@xiaying-peng
xiaying-peng deleted the jiayu/feat-schnorr-bip340 branch June 23, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants