

Sign an arbitrary message with the user's wallet and verify the signature later — useful for **off-chain authentication** ("Sign-In with Wallet"), **proving address ownership**, or **creating verifiable attestations**. The API is identical regardless of whether the user connected a CKB, EVM, BTC, JoyID, Nostr, or Doge wallet.

## What you'll get [#what-youll-get]

After this guide you'll be able to:

* **Sign a message** with any connected wallet (one unified call)
* **Verify a signature** statically — no connected wallet required
* Understand the `Signature` object and how `signType` enables **cross-chain verification**

<Callout type="info">
  All examples assume you already have a connected `signer`. If you don't, see [Connect Wallets](/docs/guides/connect-wallets) first.
</Callout>

## The `Signature` type [#the-signature-type]

`signer.signMessage` returns a `Signature` object defined in `packages/core/src/signer/signer/index.ts`:

```typescript
class Signature {
  signature: string;  // the raw signature hex
  identity: string;   // signer identity (usually their address)
  signType: SignerSignType;
}
```

The `signType` field tells verifiers which cryptographic scheme was used, enabling scheme-specific verification without the caller needing to know the wallet type ahead of time.

## `SignerSignType` enum [#signersigntype-enum]

| Value                         | Wallet / scheme                         |
| ----------------------------- | --------------------------------------- |
| `SignerSignType.CkbSecp256k1` | CKB native secp256k1 wallets            |
| `SignerSignType.EvmPersonal`  | EVM wallets (MetaMask, OKX EVM, etc.)   |
| `SignerSignType.BtcEcdsa`     | Bitcoin wallets (UniSat, OKX BTC, etc.) |
| `SignerSignType.JoyId`        | JoyID passkey wallet                    |
| `SignerSignType.NostrEvent`   | Nostr clients                           |
| `SignerSignType.DogeEcdsa`    | Dogecoin wallets                        |
| `SignerSignType.Unknown`      | Unknown / unsupported type              |

## Sign a message [#sign-a-message]

One call — works with every wallet type CCC supports:

```typescript
import { ccc } from "@ckb-ccc/ccc";

const message = "Hello world";
const signature = await signer.signMessage(message);

console.log(signature.signature);  // "0x..." — raw signature bytes as hex
console.log(signature.identity);   // signer's address or public key
console.log(signature.signType);   // e.g. "CkbSecp256k1", "EvmPersonal", "BtcEcdsa"
```

`signMessage` accepts either a plain `string` or a `BytesLike` (`Uint8Array` / hex string), so you can sign raw bytes when needed.

## Verify a signature [#verify-a-signature]

Verification is a **static method** — you do not need a connected wallet. CCC dispatches to the correct cryptographic scheme automatically based on `signature.signType`:

```typescript
const isValid = await ccc.Signer.verifyMessage(message, signature);
// true — message + signature match

const isFail = await ccc.Signer.verifyMessage("Wrong message", signature);
// false — message doesn't match
```

This means your backend can verify signatures from *any* CCC-supported wallet without knowing which wallet the user used.

## Full sign + verify example [#full-sign--verify-example]

End-to-end example from `packages/examples/src/sign.ts`. It handles the playground's default `SignerCkbPublicKey` (which cannot sign messages) by substituting a private-key signer for demonstration:

```typescript
import { ccc } from "@ckb-ccc/ccc";
import { client, signer as playgroundSigner } from "@ckb-ccc/playground";

// The default playground signer cannot sign messages.
// In a real app the connected wallet signer is always usable.
const signer: ccc.Signer =
  playgroundSigner instanceof ccc.SignerCkbPublicKey
    ? new ccc.SignerCkbPrivateKey(client, "01".repeat(32))
    : playgroundSigner;

const message = "Hello world";

// Sign
const signature = await signer.signMessage(message);
console.log(signature);

// Verify — passes
console.log(
  `Verification should pass: ${await ccc.Signer.verifyMessage(message, signature)}`,
);

// Verify — fails with wrong message
console.log(
  `Verification should fail: ${await ccc.Signer.verifyMessage("Wrong message", signature)}`,
);
```

## Instance-level verification (identity check) [#instance-level-verification-identity-check]

**Use this when:** you not only want to verify the signature is valid, but also confirm it was produced by the *currently connected* wallet. The instance method additionally checks that `signature.identity` matches the signer:

```typescript
// Returns false if the signature was produced by a different signer
const ok = await signer.verifyMessage(message, signature);
```

<Callout type="info">
  **Static vs. instance verification:** `ccc.Signer.verifyMessage()` (static) verifies any `Signature` regardless of who produced it. `signer.verifyMessage()` (instance) additionally enforces that `signature.identity` matches the current signer's identity.
</Callout>

## Sign raw bytes [#sign-raw-bytes]

**Use this when:** you need to sign arbitrary binary data (e.g. a hash, a serialized protobuf, or a binary payload):

```typescript
const bytes = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
const signature = await signer.signMessage(bytes);

// Hex strings also work:
const sigFromHex = await signer.signMessage("0xdeadbeef");
```

## Troubleshooting [#troubleshooting]

**`signer.signMessage` throws "not implemented" or "unsupported"**
Some signer types (e.g. `SignerCkbPublicKey`) represent a read-only public key with no signing capability. This happens in the CCC playground default signer. In production apps with real wallet connections, `signMessage` always works.

**Verification returns `false` even though I signed the same message**
Make sure you're passing the *exact* same `message` value (including encoding). If you signed a `string`, verify with the same `string` — not a `Uint8Array` version of it, and vice versa.

**I need to verify on a backend that doesn't have CCC**
The `Signature` object is JSON-serializable. Send it to your backend, install `@ckb-ccc/shell`, and call `ccc.Signer.verifyMessage(message, signature)` there.

## Next steps [#next-steps]

* [Compose transactions](/docs/guides/compose-transactions) — send CKB or tokens on-chain.
* [Node.js backend](/docs/guides/node-js-backend) — verify signatures and send transactions from the server.


---

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ckbccc.com/llms.txt
> Use this file to discover all available pages before exploring further.
