How to..
Identify User Wallets

Identify User Wallets

User-Identification in accomplished by signing a message with the private key of a user. The signature includes information about the signing address, making it possible to identify and verify a user address.

Connex provides a Signing-Service (opens in a new tab) aimed to solve this in combination with Sync(2) in a user friendly way.

For the user this is a perfect way for identification since signing does not create a transaction or any traces on the blockchain.



The signing service prompts the user to sign a payload. The payload can be anything and is presented to the user as the message to sign.

  const certificate = await connex.vendor
    .sign("cert", {
      purpose: "identification",
      payload: {
        type: "text",
        content: "content so sign"

The returned signed certificate anatomy is like this:

  "annex": {
    "domain": "",
    "signer": "0x2f3da21ad07657ad6608d251e8f3d3fe7e57ea0e",
    "timestamp": 1665558056
  "signature": "0x7fe7389f2dd15e8e4c1511539e626bd8f4d4d006a769e4b0243c6333a1d6571724126f597c212033a939ba19c212bac17b1bedc9b6f69dca547962af5a72a02901"

annex.signer includes the wanted user address and the timestamp the time of signing.

In the web this can be used instantly. For backend communication it needs to be verified on the backend side.

If only the signing service is used in the application, the vendor can be used directly without defining a node to connect to:

// main, test or genesis ID if it's private network
const vendor = new Connex.Vendor('main')


In none-UI environments the same can be accomplished using the wallets private key directly and thor-devkit (opens in a new tab):

import { Certificate, secp256k1, blake2b256 } from 'thor-devkit'
const certificate = {
    purpose: 'identification',
    payload: {
        type: 'text',
        content: 'content to sign'
    domain: 'localhost',
    timestamp: Math.floor(+(new Date())/1000),
    signer: wallet.address
const jsonStr = Certificate.encode(certificate)
const signature = secp256k1.sign(blake2b256(jsonStr), privateKey)
certificate.signature = `0x${signature.toString('hex')}`



thor-devkit (opens in a new tab) provides the necessary functionality to verify the signed message.

The verification requires three important parts

  1. The signed message (object that was passed for signing)
  2. The signer information (annex)
  3. And the signature

The verification will throw if it fails, otherwise it will silently work without a return value:

  const certificate = {
    purpose: "identification",
    payload: {
      type: "text",
      content: "content so sign"
  const message = await connex.vendor.sign("cert", certificate).request();
  try {
      signature: message.signature
  catch (err) {
    // signing failed, err.message contains more details


In addition to the identification also agreement is a valid purpose. It can be used to formally agree to terms and archive the result.

To prevent someone from re-using a signed certificate, using a custom payload can ensure the signature is for the right purpose.


An example project signing a message and verifying it is available as Sandbox here:

Screen recording