Skip to main content

Offline Signing

Offline signing is the process of signing transactions using a device that is not connected to a Sui network or using a wallet implemented in a different programming language that doesn't rely on Sui Keystore. To implement offline signing:

  1. Serialize the data for signing.

  2. Place the serialized data in a location, such as the wallet of your choice, or tools in other programming languages, and produce a signature with the corresponding public key.

  3. Sign the data.

  4. Execute the signed transaction.

Serialize data

You must serialize transaction data following Binary Canonical Serialization (BCS). It is supported in other languages.

The following example demonstrates how to serialize data for a transfer using the Sui CLI. This returns serialized transaction data in Base64. Submit the raw transaction to execute as tx_bytes.

tip

Beginning with the Sui v1.24.1 release, the --gas-budget option is no longer required for CLI commands.

$ sui client ptb --transfer-objects "[gas]" @<SUI-ADDRESS> --gas-coin @<COIN-OBJECT-ID> --gas-budget <GAS-AMOUNT> --serialize-unsigned-transaction

The console responds with the resulting <TX_BYTES> value.

tip

All other CLI commands that craft a transaction (such as sui client publish and sui client call) also accept the --serialize-unsigned-transaction flag and you can use it in the same way.

Sign the serialized data

You can sign the data using the device and programming language of your choice. Sui accepts signatures for pure Ed25519, ECDSA Secp256k1, ECDSA Secp256r1, and native multisig. To learn more about the requirements of the signatures, see Sui Signatures.

This example uses the sui keytool command to sign, using the Ed25519 key corresponding to the provided address stored in sui.keystore. This command outputs the signature, the public key, and the flag encoded in Base64. This command is backed by fastcrypto.

sui keytool sign --address <SUI-ADDRESS> --data <TX_BYTES>

You receive the following response:

Signer address: <SUI-ADDRESS>
Raw tx_bytes to execute: <TX_BYTES>
Intent: Intent { scope: TransactionData, version: V0, app_id: Sui }
Raw intent message: <INTENT-MESSAGE>
Digest to sign: <DIGEST>
Serialized signature (`flag || sig || pk` in Base64): <SERIALIZED-SIGNATURE>

To ensure the signature produced offline meets Sui validity rules for testing purposes, you can import the mnemonics to sui.keystore using sui keytool import. You can then sign with it using sui keytool sign and compare the signature results. Additionally, you can find test vectors in ~/sui/sdk/typescript/test/e2e/raw-signer.test.ts.

To verify a signature against the cryptography library backing Sui when debugging, see sigs-cli.

Execute the signed transaction

After you obtain the serialized signature, you can submit it using the execution transaction command. This command takes --tx-bytes as the raw transaction bytes to execute (see output of the previous sui client ptb command) and the serialized signature (Base64 encoded flag || sig || pk, see output of sui keytool sign). This executes the signed transaction and returns the certificate and transaction effects if successful.

$ sui client execute-signed-tx --tx-bytes <TX_BYTES> --signatures <SERIALIZED-SIGNATURE>

You receive the following response:

----- Certificate ----
Transaction Hash: <TRANSACTION-ID>
Transaction Signature: <SIGNATURE>
Signed Authorities Bitmap: RoaringBitmap<[0, 1, 3]>
Transaction Kind : Transfer SUI
Recipient : <SUI-ADDRESS>
Amount: Full Balance

----- Transaction Effects ----
Status : Success
Mutated Objects:
- ID: <OBJECT_ID> , Owner: Account Address ( <SUI-ADDRESS> )

Alternative: sign with Sui Keystore and execute transaction

Alternatively, you can use the active key in Sui Keystore to sign and output a Base64-encoded sender signed data with flag --serialize-signed-transaction.

$ sui client ptb --transfer-objects "[gas]" @<SUI-ADDRESS> --gas-coin @<COIN-OBJECT-ID> --gas-budget <GAS-AMOUNT> --serialize-signed-transaction

The console responds with the resulting <SIGNED-TX-BYTES> value.

After you obtain the signed transaction bytes, you can submit it using the execute-combined-signed-tx command. This command takes --signed-tx-bytes as the signed transaction bytes to execute (see output of the previous sui client ptb command). This executes the signed transaction and returns the certificate and transaction effects if successful.

$ sui client execute-combined-signed-tx --signed-tx-bytes <SIGNED-TX-BYTES>

Manual signing (TEE/HSM)

If you sign transactions in a TEE, HSM, or any environment without the Sui SDK, implement the following pipeline manually:

  1. Serialize the TransactionData to BCS bytes.
  2. Prepend the 3-byte intent prefix: [0, 0, 0] for transactions.
  3. Hash the prefixed bytes with Blake2b-256 (producing 32 bytes).
  4. Sign the hash with your Ed25519 private key (producing a 64-byte signature).
  5. Encode the signature as flag_byte || signature_bytes || public_key_bytes, then Base64 encode the result.

The flag byte indicates the signature scheme: 0x00 for Ed25519, 0x01 for Secp256k1, 0x02 for Secp256r1.

Pseudocode

intent_prefix = bytes([0, 0, 0])
message = intent_prefix + bcs_serialize(transaction_data)
digest = blake2b256(message)
signature = ed25519_sign(private_key, digest)
encoded = base64(bytes([0x00]) + signature + public_key)

Intent prefix reference

Intent scopePrefix bytesUsed by
Transaction[0, 0, 0]User transaction signing.
Personal message[3, 0, 0]signPersonalMessage API.
Proof of possession[5, 0, 0]Validator registration.

The prefix exists for domain separation: it prevents a signature over a personal message from being reused as a transaction signature.