密钥派生(X25519)
Sapphire.deriveSymmetricKey 实现 X25519 Diffie-Hellman 密钥交换。给定交换两端来的公钥和私钥,它返回一个 32 字节共享对称密钥。与对称加密组合构造 encrypt-to-user 或 encrypt-to-contract 模式。
签名
function deriveSymmetricKey(bytes32 publicKey, bytes32 privateKey)
internal view returns (bytes32);
publicKey——对方的 X25519 公钥。privateKey——你的 X25519 私钥。
返回一个 32 字节对称密钥,双方可以各自独立计算:
- Alice 算
derive(Bob.publicKey, Alice.privateKey) - Bob 算
derive(Alice.publicKey, Bob.privateKey) - 两者得到相同结果
基本用法
import "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol";
contract EncryptToUser {
function encryptFor(
bytes32 userPublicKey,
bytes32 myPrivateKey,
bytes memory plaintext
) external view returns (bytes32 nonce, bytes memory ciphertext) {
bytes32 sharedKey = Sapphire.deriveSymmetricKey(userPublicKey, myPrivateKey);
nonce = bytes32(Sapphire.randomBytes(32, ""));
ciphertext = Sapphire.encrypt(sharedKey, nonce, plaintext, "");
}
}
只有目标接收方能算出 sharedKey(他有 myPublicKey 和自己的私钥)。其他任何人——包括 enclave 自身——都不保留这个密钥。
完整 encrypt-to-user 示例
contract PrivateMessages {
// 用户注册自己的 X25519 公钥
mapping(address => bytes32) public userPublicKeys;
mapping(address => bytes[]) private inboxes;
function registerKey(bytes32 pk) external {
userPublicKeys[msg.sender] = pk;
}
function sendMessage(address to, bytes32 senderPrivateKey, bytes calldata message) external {
bytes32 sharedKey = Sapphire.deriveSymmetricKey(
userPublicKeys[to],
senderPrivateKey
);
bytes32 nonce = bytes32(Sapphire.randomBytes(32, ""));
bytes memory ct = Sapphire.encrypt(sharedKey, nonce, message, "PrivateMessages");
// 存信封:nonce || ciphertext,加发送方公钥便于接收方解密
bytes32 senderPk = derivePublicFromPrivate(senderPrivateKey); // 自定义辅助
inboxes[to].push(abi.encodePacked(senderPk, nonce, ct));
}
}
链下,接收方(用自己的 X25519 私钥)算出相同的共享密钥并解密。
X25519 vs 存对称密钥的选择
用 X25519(deriveSymmetricKey):
- 需要加密给特定接收方(他持有自己的私钥)。
- 用户间发消息,合约只中继密文。
- 链下服务需要解密。
用存储的对称密钥:
- 合约是密钥的唯一持有者与使用者。
- 同一密钥下会多次解密。
- 无链下接收方。
临时 vs 持久密钥
临时 X25519 密钥——每会话生成后丢弃。提供前向保密:今天的会话密钥泄漏不影响过去或未来的会话。这就是 OXN 加密交易信封的工作方式。
持久 X25519 密钥——存在合约存储或用户钱包里。简单,但密钥泄漏会暴露所有历史加密到它的密文。用户身份需要稳定时用持久密钥。
常见模式
握手模式:
function beginSession(bytes32 clientEphemeralPk) external view returns (bytes32 shared) {
return Sapphire.deriveSymmetricKey(
clientEphemeralPk,
contractPrivateKey
);
}
Encrypt-then-emit(事件):
event Message(address indexed to, bytes32 nonce, bytes ciphertext);
function sendMessage(...) external {
bytes32 shared = Sapphire.deriveSymmetricKey(userPublicKeys[to], msg.sender);
// ...
emit Message(to, nonce, ciphertext);
}
记住:OXN 上事件内容按调用者会话加密。消息进了区块,但 topic 和 data 对观察者不透明。