Signing and Sending Transactions
Transaction authentication on Sui involves three core concepts: cryptographic keys that you control, addresses that identify accounts onchain, and signatures that prove ownership. Sui implements these concepts using widely accepted wallet specifications and multiple signature schemes.
Keys and addresses​
Sui adheres to widely accepted wallet specifications in the cryptocurrency industry, including BIP-32 and its variation SLIP-0010, BIP-44, and BIP-39, to facilitate key management for users. Sui supports pure Ed25519, ECDSA Secp256k1, ECDSA Secp256r1, and multisig for signed transactions.
Key derivation schemes​
For managing wallets that support the Ed25519 (EdDSA) signing scheme, Sui follows SLIP-0010, which enforces wallets to always derive child private keys from parent private keys using the hardened key path.
Sui follows BIP-32 for managing wallets that support the ECDSA Secp256k1 and ECDSA Secp256r1 signing schemes.
BIP-32 defines the hierarchical deterministic wallet structure to logically associate a set of keys. Grouping keys in this manner reduces the overhead of keeping track of a large number of private keys. This method also allows custodians to issue distinct managed addresses for each user account under one source of control. Using BIP-32 decouples the private key derivation from the public key derivation, enabling the watch-only wallet use case, where a chain of public keys and its addresses can be derived, while the private key can be kept offline for signing.
Key derivation paths​
BIP-44 further defines the 5 levels of the derivation path with their exact meanings:
In this structure, the slashes indicate levels in the hierarchy.
The purpose level distinguishes different signing schemes:
-
44for Ed25519 -
54for ECDSA Secp256k1 -
74for Secp256r1
BIP-49 and BIP-84, for example, are used to identify script types in Bitcoin. Sui chose 54 to indicate ECDSA Secp256k1 because there is no existing BIP under 54, avoiding confusion with any Bitcoin standard.
The coin_type value is managed with a repository of all other cryptocurrencies. Both signature schemes use Sui's registered coin_type: 784.
The account level is used for logically separating user accounts and creating specific account categories.
Account-based currencies define only the first 3 levels, while UTXO-based currencies add change and address level definitions. Because Sui's object-oriented data model is neither UTXO or account-based (it combines both), it employs all 5 levels for maximum compatibility.
| Scheme | Path | Comments |
|---|---|---|
| Ed25519 | m/44'/784'/{account}'/{change}'/{address}' | Each level of the derivation path is hardened. |
| ECDSA Secp256k1 | m/54'/784'/{account}'/{change}/{address} | The first 3 levels are hardened. |
| ECDSA Secp256r1 | m/74'/784'/{account}'/{change}/{address} | The first 3 levels are hardened. |
Mnemonics support​
After Sui defines the deterministic way to derive the master key from a seed, BIP-39 is introduced to make the seed more human-readable and memorable using mnemonics. Sui accepts 12, 15, 18, 21, and 24 words from the BIP-39 word list that is properly checksummed, corresponding to 128, 160, 192, 224, and 256 bits of entropy.
Address format​
For deriving a 32-byte Sui address, Sui hashes the signature scheme flag 1-byte concatenated with public key bytes using the BLAKE2b (256 bits output) hashing function. Sui addresses currently support pure Ed25519, Secp256k1, Secp256r1, and multisig with corresponding flag bytes of 0x00, 0x01, 0x02, and 0x03, respectively.
Signatures​
When a user submits a signed transaction, a serialized signature and serialized transaction data is submitted. The serialized transaction data is the Binary Canonical Serialization serialized bytes of the struct TransactionData and the serialized signature is defined as a concatenation of bytes of flag || sig || pk.
Signature structure​
A Sui signature consists of three components concatenated together:
-
flag: A 1-byte representation corresponding to the signature scheme. -
sig: The compressed bytes representation of the signature (not DER encoding). -
pk: The bytes representation of the public key corresponding to the signature.
Supported signature schemes​
The following table lists each signing scheme and its corresponding flag, signature format, and public key format:
| Scheme | Flag | Signature Format | Public Key Format |
|---|---|---|---|
| Pure Ed25519 | 0x00 | Compressed, 64 bytes | Compressed, 32 bytes |
| ECDSA Secp256k1 | 0x01 | Non-recoverable, compressed, 64 bytes | Compressed, 33 bytes |
| ECDSA Secp256r1 | 0x02 | Non-recoverable, compressed, 64 bytes | Compressed, 33 bytes |
| multisig | 0x03 | BCS serialized all signatures, size varies | BCS serialized all participating public keys, size varies |
| zkLogin | 0x05 | BCS serialized zkLogin inputs, max epoch and ephemeral signature, size varies | Concatenation of iss length, iss bytes, address seed padded to 32-bytes, size varies |
| passkey | 0x06 | BCS serialized passkey inputs (authenticatorData, clientDataJson, userSignature), size varies | Compressed, 33 bytes |
Signature requirements​
The signature must commit to the hash of the intent message of the transaction data, which you can construct by appending the 3-byte intent before the BCS serialized transaction data. To learn more on what an intent is and how to construct an intent message, see Intent Signing.
When invoking the signing API, you must first hash the intent message of the transaction data to 32 bytes using Blake2b. This external hashing is distinct from the hashing performed inside the signing API. To be compatible with existing standards and hardware secure modules (HSMs), the signing algorithms perform additional hashing internally. For ECDSA Secp256k1 and Secp256r1, you must use SHA-2 SHA256 as the internal hash function. For pure Ed25519, you must use SHA-512.
ECDSA signature requirements​
An accepted ECDSA Secp256k1 and Secp256r1 signature must follow:
-
The internal hash used by ECDSA must be SHA256 SHA-2 hash of the transaction data. Sui uses SHA256 because it is supported by Apple, HSMs, and it is widely adopted by Bitcoin.
-
The signature must be of length 64 bytes in the form of
[r, s]where the first 32 bytes arer, the second 32 bytes ares. -
The
rvalue can be between0x1and0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364140(inclusive). -
The
svalue must be in the lower half of the curve order. If the signature is too high, convert it to a lowersaccording to BIP-0062 with the corresponding curve orders usingorder - s. For Secp256k1, the curve order is0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. For Secp256r1, the curve order is0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551defined in Standards for Efficient Cryptography. -
Ideally, the signature must be generated with deterministic nonce according to RFC6979.
Ed25519 signature requirements​
An accepted pure Ed25519 signature must follow:
-
The signature must be produced according to RFC 8032. The internal hash used is SHA-512.
-
The signature must be valid according to ZIP215.
Special signature schemes​
For more information on advanced signature schemes:
-
zkLogin: See zkLogin for details on zero-knowledge login signatures.
-
Passkey: See SIP-8 for passkey implementation details.
-
Multisig: See Multisig for multi-signature transactions.
-
Offline Signing: See Offline Signing for concrete examples of signing transactions offline.
Authority signatures​
Sui's collection of validators holds 3 distinctive key pairs for different purposes.
Protocol key pair​
The protocol key pair provides authority signatures on user-signed transactions if they are verified. When a stake of the authorities that provide signatures on user transactions passes the required 2/3 threshold, Sui executes the transaction. Sui uses the BLS12381 scheme for its fast verification on aggregated signatures for a given number of authorities. In particular, Sui uses the minSig BLS mode, where each individual public key is 96 bytes, while the signature is 48 bytes. The latter is important as typically validators register their keys once at the beginning of each epoch and then they continuously sign transactions; thus, Sui optimizes on minimum signature size.
As with the BLS scheme, you can aggregate independent signatures resulting in a single BLS signature payload. Sui also accompanies the aggregated signature with a bitmap to denote which of the validators signed. This effectively reduces the authorities signature size from (2f + 1) × BLS_sig size to just 1 BLS_sig payload. This has significant network cost benefits resulting in compressed transaction certificates independently on the validators set size.
To counter potential rogue key attacks on BLS12381 aggregated signatures, proof of knowledge of the secret key (KOSK) is used during authority registration. When an authority requests to be added to the validator set, a proof of possession is submitted and verified. See Intent Signing on how to create a proof of possession. Unlike most standards, the Sui proof of knowledge scheme commits to the address as well, which offers an extra protection against adversarial reuse of a validator BLS key from another malicious validator.
Account key pair​
The account that the authority uses to receive payments on staking rewards is secured by the account key pair. Sui uses pure Ed25519 as the signing scheme.
Network key pair​
The private key is used to perform the TLS handshake required for consensus networking. The public key is used for validator identity. Pure Ed25519 is used as the scheme.
See more authority key toolings in Validator Tool.
Examples​
The Sui CLI tool and the Sui SDKs provide a flexible interface to sign transactions with various signing schemes.
- CLI
- TypeScript
$ sui keytool import "TEST_MNEMONIC" ed25519 "m/44'/784'/0'/0'/0'"
$ sui client new-address ed25519 "m/44'/784'/0'/0'/0'"
const keypair = Ed25519Keypair.deriveKeypair(TEST_MNEMONIC, `m/44'/784'/0'/0'/0'`);
const address = keypair.getPublicKey().toSuiAddress();
See more test vectors for pure Ed25519 or ECDSA Secp256k1.
Workflow​
The following high-level process describes the overall workflow for constructing, signing, and executing an onchain transaction:
-
Construct the transaction data by creating a
Transactionwhere multiple commands are chained. See Building Programmable Transaction Blocks for more information. -
The SDK's built-in gas estimation and coin selection picks the gas coin.
-
Sign the transaction to generate a signature.
-
Submit the
Transactionand its signature for onchain execution.
If you are using the address balances feature, the feature automatically removes the need to select or merge gas coins entirely.
If you want to use a specific gas coin, first find the gas coin object ID to be used to pay for gas and explicitly use that in the PTB. If there is no gas coin object, use the splitCoin transaction to create a gas coin object. The split coin transaction should be the first transaction call in the PTB.
Process multiple transactions from the same address​
The Sui SDK offers transaction executors to help process multiple transactions from the same address.
SerialTransactionExecutor​
Use the SerialTransactionExecutor when you process transactions one after another. The executor grabs all the coins from the sender and combines them into a single coin that is used for all transactions.
Using the SerialTransactionExecutor prevents SequenceNumber errors by handling the versioning of object inputs across a PTB.
ParallelTransactionExecutor​
Use the ParallelTransactionExecutor when you want to process transactions with the same sender at the same time. This class creates a pool of gas coins that it manages to ensure parallel transactions don't equivocate those coins. The class tracks objects used across transactions and orders their processing so that the object inputs are also not equivocated.
Examples of signing transactions​
The following examples demonstrate how to sign and execute transactions using Rust, TypeScript, or the Sui CLI.
- TypeScript
- Rust
- Sui CLI
There are various ways to instantiate a key pair and to derive its public key and Sui address using the Sui TypeScript SDK.
import { fromHex } from '@mysten/bcs';
import { type Keypair } from '@mysten/sui/cryptography';
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { Secp256k1Keypair } from '@mysten/sui/keypairs/secp256k1';
import { Secp256r1Keypair } from '@mysten/sui/keypairs/secp256r1';
import { Transaction } from '@mysten/sui/transactions';
const kp_rand_0 = new Ed25519Keypair();
const kp_rand_1 = new Secp256k1Keypair();
const kp_rand_2 = new Secp256r1Keypair();
const kp_import_0 = Ed25519Keypair.fromSecretKey(
fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'),
);
const kp_import_1 = Secp256k1Keypair.fromSecretKey(
fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'),
);
const kp_import_2 = Secp256r1Keypair.fromSecretKey(
fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'),
);
// $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, for example, "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/develop/transactions/transaction-auth/auth-overview) for more.
const kp_derive_0 = Ed25519Keypair.deriveKeypair('$MNEMONICS');
const kp_derive_1 = Secp256k1Keypair.deriveKeypair('$MNEMONICS');
const kp_derive_2 = Secp256r1Keypair.deriveKeypair('$MNEMONICS');
const kp_derive_with_path_0 = Ed25519Keypair.deriveKeypair('$MNEMONICS', "m/44'/784'/1'/0'/0'");
const kp_derive_with_path_1 = Secp256k1Keypair.deriveKeypair('$MNEMONICS', "m/54'/784'/1'/0/0");
const kp_derive_with_path_2 = Secp256r1Keypair.deriveKeypair('$MNEMONICS', "m/74'/784'/1'/0/0");
// replace `kp_rand_0` with the variable names above.
const pk = kp_rand_0.getPublicKey();
const sender = pk.toSuiAddress();
// create an example transaction.
const txb = new Transaction();
txb.setSender(sender);
txb.setGasPrice(5);
txb.setGasBudget(100);
const bytes = await txb.build();
const serializedSignature = (await keypair.signTransaction(bytes)).signature;
// verify the signature locally
expect(await keypair.getPublicKey().verifyTransaction(bytes, serializedSignature)).toEqual(true);
// define sui client for the desired network.
const client = new SuiGrpcClient({
baseUrl: 'https://fullnode.testnet.sui.io:443',
network: 'testnet',
});
// execute transaction.
let res = await client.executeTransaction({
transaction: bytes,
signatures: [serializedSignature],
});
console.log(res);
The full code example below can be found under crates/sui-sdk.
There are various ways to instantiate a SuiKeyPair and to derive its public key and Sui address using the Sui Rust SDK.
// deterministically generate a key pair, testing only, do not use for mainnet, use the next section to randomly generate a key pair instead.
let skp_determ_0 =
SuiKeyPair::Ed25519(Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])));
let _skp_determ_1 =
SuiKeyPair::Secp256k1(Secp256k1KeyPair::generate(&mut StdRng::from_seed([0; 32])));
let _skp_determ_2 =
SuiKeyPair::Secp256r1(Secp256r1KeyPair::generate(&mut StdRng::from_seed([0; 32])));
// randomly generate a key pair.
let _skp_rand_0 = SuiKeyPair::Ed25519(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
let _skp_rand_1 = SuiKeyPair::Secp256k1(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
let _skp_rand_2 = SuiKeyPair::Secp256r1(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
// import a key pair from a base64 encoded 32-byte `private key`.
let _skp_import_no_flag_0 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
let _skp_import_no_flag_1 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
let _skp_import_no_flag_2 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
// import a key pair from a base64 encoded 33-byte `flag || private key`. The signature scheme is determined by the flag.
let _skp_import_with_flag_0 =
SuiKeyPair::decode_base64("ANRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
let _skp_import_with_flag_1 =
SuiKeyPair::decode_base64("AdRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
let _skp_import_with_flag_2 =
SuiKeyPair::decode_base64("AtRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
// replace `skp_determ_0` with the variable names above
let pk = skp_determ_0.public();
let sender = SuiAddress::from(&pk);
Next, sign transaction data constructed using an example programmable transaction with default gas coin, gas budget, and gas price. See Building Programmable Transaction Blocks for more information.
// construct an example programmable transaction.
let pt = {
let mut builder = ProgrammableTransactionBuilder::new();
builder.pay_sui(vec![sender], vec![1])?;
builder.finish()
};
let gas_budget = 5_000_000;
let gas_price = sui_client.read_api().get_reference_gas_price().await?;
// create the transaction data that will be sent to the network.
let tx_data = TransactionData::new_programmable(
sender,
vec![gas_coin.object_ref()],
pt,
gas_budget,
gas_price,
);
Commit a signature to the Blake2b hash digest of the intent message (intent || bcs bytes of tx_data).
// derive the digest that the key pair should sign on, that is, the blake2b hash of `intent || tx_data`.
let intent_msg = IntentMessage::new(Intent::sui_transaction(), tx_data);
let raw_tx = bcs::to_bytes(&intent_msg).expect("bcs should not fail");
let mut hasher = sui_types::crypto::DefaultHash::default();
hasher.update(raw_tx.clone());
let digest = hasher.finalize().digest;
// use SuiKeyPair to sign the digest.
let sui_sig = skp_determ_0.sign(&digest);
// if you would like to verify the signature locally before submission, use this function. if it fails to verify locally, the transaction will fail to execute in Sui.
let res = sui_sig.verify_secure(
&intent_msg,
sender,
sui_types::crypto::SignatureScheme::ED25519,
);
assert!(res.is_ok());
Finally, submit the transaction with the signature.
let transaction_response = sui_client
.quorum_driver_api()
.execute_transaction_block(
sui_types::transaction::Transaction::from_generic_sig_data(
intent_msg.value,
Intent::sui_transaction(),
vec![GenericSignature::Signature(sui_sig)],
),
SuiTransactionBlockResponseOptions::default(),
None,
)
.await?;
When using the Sui CLI for the first time, it creates a local file in ~/.sui/keystore on your machine with a list of private keys (encoded as Base64 encoded flag || 32-byte-private-key). You can use any key to sign transactions by specifying its address. Use sui keytool list to see a list of addresses.
There are 3 ways to initialize a key:
# generate randomly.
sui client new-address ed25519
sui client new-address secp256k1
sui client new-address secp256r1
# import the 32-byte private key to keystore.
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" ed25519
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" secp256k1
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" secp256r1
# import the mnemonics (recovery phrase) with derivation path to keystore.
# $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, for example, "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/develop/transactions/transaction-auth/auth-overview) for more.
sui keytool import "$MNEMONICS" ed25519
sui keytool import "$MNEMONICS" secp256k1
sui keytool import "$MNEMONICS" secp256r1
Create a transfer transaction in the CLI. Set the $SUI_ADDRESS to the one corresponding to the key pair used to sign. $GAS_COIN_ID refers to the object ID that is owned by the sender to be used as gas. $GAS_BUDGET refers to the budget used to execute transaction. Then sign with the private key corresponding to the sender address. $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, for example, "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to Keys and Addresses for more.
Beginning with the Sui v1.24.1 release, the --gas-budget option is no longer required for CLI commands.
$ sui client gas
$ sui client transfer-sui --to $SUI_ADDRESS --sui-coin-object-id $GAS_COIN_ID --gas-budget $GAS_BUDGET --serialize-unsigned-transaction
$ sui keytool sign --address $SUI_ADDRESS --data $TX_BYTES
$ sui client execute-signed-tx --tx-bytes $TX_BYTES --signatures $SERIALIZED_SIGNATURE