Cryptography

Cryptography in Sui:

Agility

Dec 1, 2022

Joy Wang

6 min read

Our Cryptography in Sui blog post series highlights the technologies that maintain secure transactions and usability on the Sui network. This series helps developers understand Sui’s security infrastructure and how to develop safe decentralized applications.

We designed Sui with cryptographic agility in mind. The system supports multiple cryptography algorithms and primitives and can switch between them rapidly. Not only can developers choose best-of-breed cryptography for the system, they can implement the latest algorithms as they become available.

Sui defines its cryptography primitives, such as public key, signature, aggregated signature, and hash functions, under one unified type alias or enum wrapper that is shared across the entire repository. Making changes to these primitives affects all of an application’s components. Developers can quickly update application cryptography and be assured of uniform security.

Currently, Sui supports the following signing schemes for user transactions via the executing transaction endpoint:

  • Pure Ed25519

  • Secp256k1 ECDSA

User Account Keypair Implementation


Below is a demonstration of keypair representation in Sui. Extending to a new signing scheme is as easy as:

  1. Adding it to this enum

  2. Implementing the KeyPair trait defined in fastcrypto

pub enum SuiKeyPair { Ed25519SuiKeyPair(Ed25519KeyPair), Secp256k1SuiKeyPair(Secp256k1KeyPair), } pub enum Signature { Ed25519SuiSignature, Secp256k1SuiSignature, }

User signatures are serialized by extending an additional 1-byte flag where the flag identifies the associated signing scheme. Although we considered using Multiformats, a collection of protocols for self-describable data, its nature of variable flag length makes serialization problematic. Instead, we adopted a single byte zero-starting flag model. The signing scheme and its corresponding flag are defined below:

When a user submits a signed transaction, the following parameters are specified for transaction execution:

  • The BCS serialized transaction bytes in Base64

  • The signature scheme flag, which can be “ed25519” or “secp256k1”

  • The public key in Base64

  • The signature corresponding with its scheme in Base64

This code executes the signed transaction, returning the certificate and transaction effects if successful.

curl --location --request POST $SUI_RPC_HOST \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc": "2.0", "id": 1, "method": "sui_executeTransaction", "params": [ "{{tx_bytes}}", "{{sig_scheme}}", "{{signature}}", "{{pub_key}}", "{{request_type}}" ] }' | json_pp

The code snippet below shows how Sui’s Full Node assembles the API request fields into a serialized signature flag || signature || pubkey and performs verification check before executing.

async fn execute_transaction( &self, tx_bytes: Base64, sig_scheme: SignatureScheme, signature: Base64, pub_key: Base64, request_type: ExecuteTransactionRequestType, ) -> RpcResult<SuiExecuteTransactionResponse> { let data = TransactionData::from_signable_bytes(&tx_bytes.to_vec().map_err(|e| anyhow!(e))?)?; let flag = vec![sig_scheme.flag()]; let signature = crypto::Signature::from_bytes( &[ &*flag, &*signature.to_vec().map_err(|e| anyhow!(e))?, &pub_key.to_vec().map_err(|e| anyhow!(e))?, ] .concat(), ) .map_err(|e| anyhow!(e))?; let txn = Transaction::new(SenderSignedData::new(data, signature)); let txn_digest = *txn.digest(); let transaction_orchestrator = self.transaction_orchestrator.clone(); let response = spawn_monitored_task!(transaction_orchestrator.execute_transaction( ExecuteTransactionRequest { transaction: txn, request_type, } )) .await .map_err(|e| anyhow!(e))? // for JoinError .map_err(|e| anyhow!(e))?; // For Sui transaction execution error (SuiResult<ExecuteTransactionResponse>) SuiExecuteTransactionResponse::from_execute_transaction_response( response, txn_digest, self.module_cache.as_ref(), ) .map_err(jsonrpsee_core::Error::from) }


Why we support different signature schemes

ECDSA using the secp256k1 elliptic curve is widely adopted by Bitcoin, Ethereum, and other cryptocurrencies. Users may prefer this signing scheme because they want to leverage existing wallets and custodian key management tools, such as threshold signatures and native multisig. In addition, it has better compatibility with cloud infrastructure and hardware security modules, while it supports recovering the public key from the message and the signature payload.

Meanwhile, Ed25519, a more modern signing scheme, features deterministic fast signing and simplified math. We chose Ed25519 for the reference Sui Wallet, although Typescript SDK supports both signature schemes.

With Sui’s ability to support different signature schemes, it would take minimal effort in the near future to add such schemes as ECDSA using the secp256r1 (also known as NIST-P256) curve, which has native mobile enclave support (a commonly requested feature by the community), or in the far future post-quantum cryptography.

This flexible signature scheme support also enables us to benchmark the Sui system against an insecure empty signing scheme. For a fast execution system like Sui, where parallelized design signing and verifying also occur at the transaction level instead of just the block layer, cryptographic agility provides us insights on how much overhead cryptography operations introduce to the system. These benchmark results already gave us great insights on identifying bottlenecks and optimizations.

Authority Keypairs

The Authority on Sui (collection of validators) holds three distinctive keypairs:

  • Protocol keypair

  • Account keypair

  • Network keypair

Now let’s dive into how they are used and why certain signing schemes are chosen.

Protocol Keypair

The Protocol keypair 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 two-thirds threshold, Sui executes the transaction. We currently chose the BLS12381 scheme for its fast verification on aggregated signatures for a given number of authorities. In particular, we decided to use the minSig BLS mode, according to which 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, we optimize on minimum signature size.

Note that with the BLS scheme, one can aggregate independent signatures resulting in a single BLS signature payload. We also accompany 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 one BLS_sig payload, which in turn has significant network cost benefits resulting in compressed transaction certificates independently on the validators set size.

The key material type aliases are centralized in a single location used by the entire repository. In fact, we recently switched Sui from Ed25519 to BLS12381 for protocol keys by merely changing the alias (with some trivial serialization modifications on the aggregated signature)!

pub type AuthorityKeyPair = BLS12381KeyPair; pub type AuthorityPublicKey = BLS12381PublicKey; pub type AuthorityPrivateKey = BLS12381PrivateKey; pub type AuthoritySignature = BLS12381Signature; pub type AggregateAuthoritySignature = BLS12381AggregateSignature;

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. The proof is signed by the protocol key over a message of domain kosk || protocol public key || sui address. Unlike most standards, our proof of knowledge scheme commits to the address as well, which offers an extra protection against adversarial reuse of a validator’s BLS key from another malicious validator.

An aggregated signature is useful in two scenarios:

  1. When the quorum driver forms a CertifiedTransaction from SignedTransaction returned by multiple authorities.

  2. When the authorities form a SignedCheckpointSummary, each authority signs over the checkpoint content.

Account Keypair

The account that the authority uses to receive payments on staking rewards is secured by the account keypair. We use Ed25519 as the signing scheme.

Network Keypair

The private key is used to perform the TLS handshake required by QUIC for Narwhal primary and its worker network interface. The public key is used for validator peer ID. Ed25519 is used as the signing scheme.

Hashing and Encoding Agility

At the moment, Sui’s default hash function is sha3256 and we are running benchmarks to compare against sha256 and the blake2/blake3 family. To support encoding agility, Base64 and Hex are defined with an encoding trait in fastcrypto, as a wrapper around base64ct::Base64 and hex with its customized serialization and validations. Notably, the base64ct crate has been chosen instead of the most popular base64 Rust crate, because a) it is constant time and b) mangled encodings are explicitly rejected to prevent malleability attacks when decoding. Note that members of our research team recently reported a surprising malleability issue in the majority of base64 decoder libraries, winning the Best Poster Award in AsiaCCS 2022, one of the major conferences in the cryptography and security spaces.

The code snippet below shows how the wrapper struct is implemented in fastcrypto:

pub struct Base64(String); impl Encoding for Base64 { fn decode(s: &str) -> Result<Vec<u8>, anyhow::Error> { base64ct::Base64::decode_vec(s).map_err(|e| anyhow!(e)) } fn encode<T: AsRef<[u8]>>(data: T) -> String { base64ct::Base64::encode_string(data.as_ref()) } }

Future-proofing Through Agility

With cryptographic agility in keypairs, signature, and hash functions, Sui is agile on library selections, underlying signature schemes, encodings and hash functions. Not only does this allow Sui to be quickly upgraded in the event of a library vulnerability or post-quantum breakage on a particular scheme, it also lets us benchmark the entire system against a selection of cryptography primitives as parameters.

Learn more about Sui

Build with us!

We’re excited about how the future unfolds and invite creators and builders to join us.

Our Cryptography in Sui blog post series highlights the technologies that maintain secure transactions and usability on the Sui network. This series helps developers understand Sui’s security infrastructure and how to develop safe decentralized applications.

We designed Sui with cryptographic agility in mind. The system supports multiple cryptography algorithms and primitives and can switch between them rapidly. Not only can developers choose best-of-breed cryptography for the system, they can implement the latest algorithms as they become available.

Sui defines its cryptography primitives, such as public key, signature, aggregated signature, and hash functions, under one unified type alias or enum wrapper that is shared across the entire repository. Making changes to these primitives affects all of an application’s components. Developers can quickly update application cryptography and be assured of uniform security.

Currently, Sui supports the following signing schemes for user transactions via the executing transaction endpoint:

  • Pure Ed25519

  • Secp256k1 ECDSA

User Account Keypair Implementation


Below is a demonstration of keypair representation in Sui. Extending to a new signing scheme is as easy as:

  1. Adding it to this enum

  2. Implementing the KeyPair trait defined in fastcrypto

pub enum SuiKeyPair { Ed25519SuiKeyPair(Ed25519KeyPair), Secp256k1SuiKeyPair(Secp256k1KeyPair), } pub enum Signature { Ed25519SuiSignature, Secp256k1SuiSignature, }

User signatures are serialized by extending an additional 1-byte flag where the flag identifies the associated signing scheme. Although we considered using Multiformats, a collection of protocols for self-describable data, its nature of variable flag length makes serialization problematic. Instead, we adopted a single byte zero-starting flag model. The signing scheme and its corresponding flag are defined below:

When a user submits a signed transaction, the following parameters are specified for transaction execution:

  • The BCS serialized transaction bytes in Base64

  • The signature scheme flag, which can be “ed25519” or “secp256k1”

  • The public key in Base64

  • The signature corresponding with its scheme in Base64

This code executes the signed transaction, returning the certificate and transaction effects if successful.

curl --location --request POST $SUI_RPC_HOST \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc": "2.0", "id": 1, "method": "sui_executeTransaction", "params": [ "{{tx_bytes}}", "{{sig_scheme}}", "{{signature}}", "{{pub_key}}", "{{request_type}}" ] }' | json_pp

The code snippet below shows how Sui’s Full Node assembles the API request fields into a serialized signature flag || signature || pubkey and performs verification check before executing.

async fn execute_transaction( &self, tx_bytes: Base64, sig_scheme: SignatureScheme, signature: Base64, pub_key: Base64, request_type: ExecuteTransactionRequestType, ) -> RpcResult<SuiExecuteTransactionResponse> { let data = TransactionData::from_signable_bytes(&tx_bytes.to_vec().map_err(|e| anyhow!(e))?)?; let flag = vec![sig_scheme.flag()]; let signature = crypto::Signature::from_bytes( &[ &*flag, &*signature.to_vec().map_err(|e| anyhow!(e))?, &pub_key.to_vec().map_err(|e| anyhow!(e))?, ] .concat(), ) .map_err(|e| anyhow!(e))?; let txn = Transaction::new(SenderSignedData::new(data, signature)); let txn_digest = *txn.digest(); let transaction_orchestrator = self.transaction_orchestrator.clone(); let response = spawn_monitored_task!(transaction_orchestrator.execute_transaction( ExecuteTransactionRequest { transaction: txn, request_type, } )) .await .map_err(|e| anyhow!(e))? // for JoinError .map_err(|e| anyhow!(e))?; // For Sui transaction execution error (SuiResult<ExecuteTransactionResponse>) SuiExecuteTransactionResponse::from_execute_transaction_response( response, txn_digest, self.module_cache.as_ref(), ) .map_err(jsonrpsee_core::Error::from) }


Why we support different signature schemes

ECDSA using the secp256k1 elliptic curve is widely adopted by Bitcoin, Ethereum, and other cryptocurrencies. Users may prefer this signing scheme because they want to leverage existing wallets and custodian key management tools, such as threshold signatures and native multisig. In addition, it has better compatibility with cloud infrastructure and hardware security modules, while it supports recovering the public key from the message and the signature payload.

Meanwhile, Ed25519, a more modern signing scheme, features deterministic fast signing and simplified math. We chose Ed25519 for the reference Sui Wallet, although Typescript SDK supports both signature schemes.

With Sui’s ability to support different signature schemes, it would take minimal effort in the near future to add such schemes as ECDSA using the secp256r1 (also known as NIST-P256) curve, which has native mobile enclave support (a commonly requested feature by the community), or in the far future post-quantum cryptography.

This flexible signature scheme support also enables us to benchmark the Sui system against an insecure empty signing scheme. For a fast execution system like Sui, where parallelized design signing and verifying also occur at the transaction level instead of just the block layer, cryptographic agility provides us insights on how much overhead cryptography operations introduce to the system. These benchmark results already gave us great insights on identifying bottlenecks and optimizations.

Authority Keypairs

The Authority on Sui (collection of validators) holds three distinctive keypairs:

  • Protocol keypair

  • Account keypair

  • Network keypair

Now let’s dive into how they are used and why certain signing schemes are chosen.

Protocol Keypair

The Protocol keypair 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 two-thirds threshold, Sui executes the transaction. We currently chose the BLS12381 scheme for its fast verification on aggregated signatures for a given number of authorities. In particular, we decided to use the minSig BLS mode, according to which 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, we optimize on minimum signature size.

Note that with the BLS scheme, one can aggregate independent signatures resulting in a single BLS signature payload. We also accompany 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 one BLS_sig payload, which in turn has significant network cost benefits resulting in compressed transaction certificates independently on the validators set size.

The key material type aliases are centralized in a single location used by the entire repository. In fact, we recently switched Sui from Ed25519 to BLS12381 for protocol keys by merely changing the alias (with some trivial serialization modifications on the aggregated signature)!

pub type AuthorityKeyPair = BLS12381KeyPair; pub type AuthorityPublicKey = BLS12381PublicKey; pub type AuthorityPrivateKey = BLS12381PrivateKey; pub type AuthoritySignature = BLS12381Signature; pub type AggregateAuthoritySignature = BLS12381AggregateSignature;

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. The proof is signed by the protocol key over a message of domain kosk || protocol public key || sui address. Unlike most standards, our proof of knowledge scheme commits to the address as well, which offers an extra protection against adversarial reuse of a validator’s BLS key from another malicious validator.

An aggregated signature is useful in two scenarios:

  1. When the quorum driver forms a CertifiedTransaction from SignedTransaction returned by multiple authorities.

  2. When the authorities form a SignedCheckpointSummary, each authority signs over the checkpoint content.

Account Keypair

The account that the authority uses to receive payments on staking rewards is secured by the account keypair. We use Ed25519 as the signing scheme.

Network Keypair

The private key is used to perform the TLS handshake required by QUIC for Narwhal primary and its worker network interface. The public key is used for validator peer ID. Ed25519 is used as the signing scheme.

Hashing and Encoding Agility

At the moment, Sui’s default hash function is sha3256 and we are running benchmarks to compare against sha256 and the blake2/blake3 family. To support encoding agility, Base64 and Hex are defined with an encoding trait in fastcrypto, as a wrapper around base64ct::Base64 and hex with its customized serialization and validations. Notably, the base64ct crate has been chosen instead of the most popular base64 Rust crate, because a) it is constant time and b) mangled encodings are explicitly rejected to prevent malleability attacks when decoding. Note that members of our research team recently reported a surprising malleability issue in the majority of base64 decoder libraries, winning the Best Poster Award in AsiaCCS 2022, one of the major conferences in the cryptography and security spaces.

The code snippet below shows how the wrapper struct is implemented in fastcrypto:

pub struct Base64(String); impl Encoding for Base64 { fn decode(s: &str) -> Result<Vec<u8>, anyhow::Error> { base64ct::Base64::decode_vec(s).map_err(|e| anyhow!(e)) } fn encode<T: AsRef<[u8]>>(data: T) -> String { base64ct::Base64::encode_string(data.as_ref()) } }

Future-proofing Through Agility

With cryptographic agility in keypairs, signature, and hash functions, Sui is agile on library selections, underlying signature schemes, encodings and hash functions. Not only does this allow Sui to be quickly upgraded in the event of a library vulnerability or post-quantum breakage on a particular scheme, it also lets us benchmark the entire system against a selection of cryptography primitives as parameters.

Learn more about Sui

Build with us!

We’re excited about how the future unfolds and invite creators and builders to join us.

Read Next