跳到主要内容

密钥派生(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 对观察者不透明。

下一步