Skip to content

feat: add SignArbitrary and ADR-036 signature verification - #45

Merged
jinoosss merged 3 commits into
mainfrom
feat/add-signarbitrary
Oct 15, 2026
Merged

jinoosss merged 3 commits into
mainfrom
feat/add-signarbitrary

Conversation

@jinoosss

Copy link
Copy Markdown
Member

Summary

Adds SignArbitrary support to the SDK, matching the window.adena.SignArbitrary API on the latest adena-wallet main, plus a shared ADR-036 signature verifier usable by any dApp or backend.

Changes

signArbitrary

  • New AdenaSDK.signArbitrary({ signer, data }) method, WalletProvider.signArbitrary, and signArbitrary method helper.
  • AdenaWalletProvider: forwards to window.adena.SignArbitrary. Returns UNSUPPORTED_TYPE when the installed extension predates the API (SignArbitrary is typed as optional on AdenaWallet).
  • GnoWalletProvider (and GnoSocialWalletProvider): signs locally with the TM2 wallet signer, binding the current network's chain id. Mirrors the extension's rules: INVALID_FORMAT for empty or >4096-byte data, ACCOUNT_MISMATCH for a signer other than the wallet address.
  • Adds SIGN_ARBITRARY to WalletResponseSuccessType / WalletResponseExecuteType and WalletMessageInfo. Without the success entry, mapResponseByAdenaResponse throws on the extension's SIGN_ARBITRARY success response. This also unblocks the wallet-side TODOs waiting for the SDK to ship this entry.

ADR-036 verification utilities (core/utils/arbitrary.utils.ts)

  • verifyArbitrarySignature(proof, { signer?, chainId?, data? }) resolves true only if:

    • the public key is /tm.PubKeySecp256k1 and derives the signer address (bech32 prefix taken from the signer),
    • the signature covers the canonical ADR-036 document, rebuilt from (chainId, signer, data) (any extra message, non-zero fee/sequence, or memo fails),
    • the signature is a valid low-S Secp256k1 signature, matching tm2/Cosmos SDK malleability rules,
    • every provided expectation matches.

    It never throws; malformed input resolves to false.

  • parseArbitrarySignDoc, makeArbitrarySignDoc, getArbitrarySignBytes are exported as well.

  • Works with standard Cosmos ADR-036 proofs (chainId: ''), not only Adena's chain-bound variant.

Types

  • ARBITRARY_MSG_TYPE, MAX_ARBITRARY_MESSAGE_BYTES, ArbitraryMessage, ArbitrarySignature, SignedArbitraryData, SignArbitraryOptions, SignArbitraryResponse.
  • Raw extension types SignArbitraryParams / AdenaSignArbitrary; the raw response is re-exported as AdenaSignArbitraryResponse following the existing disambiguation pattern in src/index.ts.

Dependencies

  • Adds @cosmjs/amino, @cosmjs/crypto, @cosmjs/encoding at 0.39.0 as direct dependencies. They are the same versions @gnolang/tm2-js-client already pulls in.

Docs

  • Documents signArbitrary and the ADR-036 verification helpers in both the root and package READMEs, including replay-protection guidance (pin signer, chainId, and a nonce-bearing data).

Notes

  • Adena deviates from ADR-036 by putting the connected network's chain id in chain_id instead of '', so a proof cannot be replayed across networks. The SDK follows the wallet's format.
  • fromBech32 is called with an explicit BIP-173 limit (90): the default Infinity is rejected by the @scure/base version bundled with @cosmjs/encoding@0.39.0.

Testing

  • arbitrary.utils.test.ts:
    • sign bytes match the payload pinned in adena-module's spec and are byte-identical to @cosmjs/amino serializeSignDoc
    • round-trip verification, including the empty chain id case
    • rejection cases: mismatched expectations, tampered or non-canonical documents, a foreign key, high-S malleated signatures, a wrong key type, malformed input
  • sign-arbitrary.test.ts: method delegation for success, failure, reject, and throw.
  • gno-wallet-sign-arbitrary.test.ts: the local provider produces verifiable proofs, plus the ACCOUNT_MISMATCH, INVALID_FORMAT, and NOT_CONNECTED paths.
  • adena-sign-arbitrary.test.ts: maps the extension's response shape, passes failures through, and returns UNSUPPORTED_TYPE on older extensions.
  • yarn test (139 passed), yarn lint, yarn build, tsc --noEmit all pass.

@notJoon notJoon left a comment

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.

LGTM overall, I left some suggestions and feel free to apply them. thank you!

@aronpark1007 aronpark1007 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified the fix in ac7132d. The supplied document must now match the rebuilt canonical sign bytes, and all five originally reported tampering cases are rejected while retaining the original signature. The regression tests cover these cases.

Verification on this revision: all 146 tests across 38 suites pass; lint, tsc --noEmit, and build pass. A separate smoke run through the built SDK with GnoWalletProvider confirmed that normal and BOM-prefixed messages verify, and all five document mutations fail both with and without expected fields.

No additional blocking findings from the review. Resolving my canonical-document verification thread and approving.

@jinoosss
jinoosss merged commit 338096d into main Oct 15, 2026
3 checks passed
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.

3 participants