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:
-
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, ""); -
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
- Key Derivation — X25519 for encrypt-to-user patterns
- Encrypted Contract Storage — the automatic alternative
- Signatures — sign, verify, keypair generation