Skip to main content

Symmetric Encryption

Sapphire.encrypt and Sapphire.decrypt wrap the Deoxys-II authenticated symmetric encryption algorithm. Use them when you need to encrypt data with a caller-supplied key — for example, to pass encrypted blobs across contract boundaries, or to store user-specific ciphertext.

For "the contract keeps its own secret", you generally don't need these — encrypted contract storage handles that automatically.

Signatures​

function encrypt(
bytes32 key,
bytes32 nonce,
bytes memory plaintext,
bytes memory additionalData
) internal view returns (bytes memory);

function decrypt(
bytes32 key,
bytes32 nonce,
bytes memory ciphertext,
bytes memory additionalData
) internal view returns (bytes memory);
  • key — 32-byte symmetric key.
  • nonce — 32-byte unique value per encryption. Must not repeat with the same key.
  • plaintext / ciphertext — the data to encrypt or decrypt.
  • additionalData — authenticated but not encrypted. Typically a context string like "contract:0xabc...".

decrypt reverts if the ciphertext or additional data has been tampered with (Deoxys-II is authenticated encryption).

Basic usage​

import "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol";

contract Vault {
bytes32 private symmetricKey;

function initialize() external {
symmetricKey = bytes32(Sapphire.randomBytes(32, ""));
}

function store(bytes calldata plaintext) external view returns (bytes memory) {
bytes32 nonce = bytes32(Sapphire.randomBytes(32, ""));
bytes memory ct = Sapphire.encrypt(symmetricKey, nonce, plaintext, "vault");
return abi.encodePacked(nonce, ct); // include nonce for later decrypt
}

function retrieve(bytes calldata blob) external view returns (bytes memory) {
bytes32 nonce = bytes32(blob[:32]);
return Sapphire.decrypt(symmetricKey, nonce, blob[32:], "vault");
}
}

When to use explicit encryption​

When automatic storage encryption is enough: just declare private state variables. The runtime encrypts / decrypts on every SSTORE / SLOAD. No explicit precompile calls needed.

When explicit encryption is right:

  • Handing ciphertext to another contract. The other contract can hold the blob, and only your contract (with the key) can decrypt.
  • Persistent ciphertext across contract redeploys. Since automatic storage keys are per-contract-address, redeploying loses access. Explicit encryption with an app-managed key survives.
  • Passing ciphertext off-chain. Emit an encrypted blob in an event that the caller can decrypt off-chain.
  • Encrypting for a specific recipient. Combine with Key Derivation to encrypt-to-user.

Nonce management​

Never reuse a nonce with the same key. Doing so breaks Deoxys-II's security guarantees.

Two safe patterns:

  1. Random nonces. Generate 32 random bytes for each encryption. Collision probability is astronomically low.

    bytes32 nonce = bytes32(Sapphire.randomBytes(32, ""));
    bytes memory ct = Sapphire.encrypt(key, nonce, plaintext, "");
  2. Counter nonces. Maintain a per-key counter, increment on each encryption. Cheaper (no randomness call) but requires state.

    uint256 counter;
    function encryptWith(bytes memory plaintext) internal returns (bytes memory) {
    counter++;
    bytes32 nonce = bytes32(counter);
    return Sapphire.encrypt(key, nonce, plaintext, "");
    }

Store or transmit the nonce alongside the ciphertext — decryption needs both.

additionalData for domain separation​

Use additionalData to bind ciphertext to a specific context. For example, encryptions produced by contract A should not decrypt in contract B, even if the key leaks:

bytes memory ct = Sapphire.encrypt(key, nonce, plaintext, abi.encodePacked(address(this)));

An attacker with the key still cannot successfully decrypt against a different additionalData value.

Common pitfalls​

"Decryption always reverts." You changed the additionalData between encrypt and decrypt. Both sides must use exactly the same value.

"Ciphertext is much larger than plaintext." Expected. Deoxys-II adds a 16-byte authentication tag. Ciphertext length = plaintext length + 16 bytes.

"I want to encrypt with a public key." Use Key Derivation to derive a shared secret from X25519 keypairs, then encrypt with that as the symmetric key. Sapphire does not directly expose public-key encryption; the hybrid approach (X25519 + Deoxys-II) is standard.

Next steps​