跳到主要内容

对称加密

Sapphire.encryptSapphire.decrypt 封装 Deoxys-II 认证对称加密算法。当你需要用调用者提供的密钥加密数据时用它们——例如跨合约边界传递加密 blob,或存储用户特定的密文。

对于"合约保存自己的秘密",一般不需要这些——加密合约存储自动处理。

签名

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 字节对称密钥。
  • nonce——每次加密唯一的 32 字节值。同一 key 下不得重复
  • plaintext / ciphertext——待加密或解密的数据。
  • additionalData——经认证但不加密。通常是像 "contract:0xabc..." 的上下文字符串。

如果密文或 additional data 被篡改,decrypt 会 revert(Deoxys-II 是认证加密)。

基本用法

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); // 带上 nonce 供后续 decrypt
}

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

什么时候用显式加密

自动存储加密够用时: 直接声明 private 状态变量。运行时在每次 SSTORE / SLOAD 加密 / 解密。无需显式调用预编译。

显式加密合适的场景:

  • 把密文交给另一个合约。 另一个合约存放 blob,只有你的合约(持密钥)能解密。
  • 跨合约重部署的持久密文。 因为自动存储密钥按合约地址派生,重部署后就失去访问。用应用管理的密钥做显式加密能存活。
  • 把密文送到链下。 在事件里 emit 加密 blob,让调用者在链下解密。
  • 加密给特定接收方。 结合密钥派生做 encrypt-to-user。

Nonce 管理

同一 key 下永远不要重用 nonce。 这样会打破 Deoxys-II 的安全保证。

两种安全模式:

  1. 随机 nonce。 每次加密生成 32 字节随机值。碰撞概率天文数字级低。

    bytes32 nonce = bytes32(Sapphire.randomBytes(32, ""));
    bytes memory ct = Sapphire.encrypt(key, nonce, plaintext, "");
  2. 计数器 nonce。 每个 key 维护一个计数器,每次加密递增。更便宜(无需 randomness 调用)但要状态。

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

Nonce 要与密文一起存储或传输——解密两者都要。

additionalData 做 domain separation

additionalData 把密文绑定到特定上下文。例如合约 A 产生的加密,即便 key 泄漏也不应该在合约 B 里能解密:

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

即便攻击者拿到 key,用不同 additionalData 值也无法成功解密。

常见陷阱

"解密总是 revert。" encrypt 与 decrypt 之间改了 additionalData。两边必须完全一致。

"密文比明文大很多。" 预期。Deoxys-II 加 16 字节的认证 tag。密文长度 = 明文长度 + 16 字节。

"我想用公钥加密。"密钥派生从 X25519 密钥对派生共享密钥,然后用它作为对称密钥加密。Sapphire 不直接暴露公钥加密;混合方案(X25519 + Deoxys-II)是标准做法。

下一步