Open matevz opened 3 weeks ago
CBOR encoding the encrypted calldata packet may be a little annoying, but there are many examples of CBOR encoding in Sapphire contracts library.
It may be worth extracting the CBOR related functions into a helper library, although I'd be wary of modifying or refactoring of the existing functions.
Made a quick sketch so far, will test it
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import { HMAC_sha512_256 } from './HMAC_sha512_256.sol';
import { Subcall } from '@oasisprotocol/sapphire-contracts/contracts/Subcall.sol';
import { Sapphire } from '@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol';
library EncryptedTx {
function internal_deriveSharedSecret(
bytes32 peerPublicKey,
Sapphire.Curve25519SecretKey mySecretKey
)
internal view
returns (bytes32)
{
return HMAC_sha512_256(
"MRAE_Box_Deoxys-II-256-128",
abi.encodePacked(
Sapphire.deriveSymmetricKey(
Sapphire.Curve25519PublicKey.wrap(peerPublicKey),
mySecretKey)));
}
error CBORBytesTooLong();
function _cborEncodeBytes(bytes memory in_bytes)
internal pure
returns (bytes memory out_cbor)
{
/*
0x40..0x57 byte string (0x00..0x17 bytes follow)
0x58 byte string (one-byte uint8_t for n, and then n bytes follow)
0x59 byte string (two-byte uint16_t for n, and then n bytes follow)
0x5a byte string (four-byte uint32_t for n, and then n bytes follow)
0x5b byte string (eight-byte uint64_t for n, and then n bytes follow)
*/
if( in_bytes.length <= 0x17 ) {
return abi.encodePacked(uint8(0x40 + 0x17), in_bytes);
}
if( in_bytes.length <= 0xFF ) {
return abi.encodePacked(uint8(0x58), uint8(in_bytes.length), in_bytes);
}
if( in_bytes.length <= 0xFFFF ) {
return abi.encodePacked(uint8(0x59), uint16(in_bytes.length), in_bytes);
}
// We assume Solidity won't be encoding anything larger than 64kb
revert CBORBytesTooLong();
}
function _cborEncodeUint(uint256 value) public pure returns (bytes memory) {
if (value < 24) {
return abi.encodePacked(uint8(value));
} else if (value <= type(uint8).max) {
return abi.encodePacked(uint8(24), uint8(value));
} else if (value <= type(uint16).max) {
return abi.encodePacked(uint8(25), uint16(value));
} else if (value <= type(uint32).max) {
return abi.encodePacked(uint8(26), uint32(value));
} else {
return abi.encodePacked(uint8(27), uint64(value));
}
}
function encryptCalldata(bytes memory in_data)
internal
returns (bytes memory out_encrypted)
{
(uint256 epoch, Subcall.CallDataPublicKey memory cdpk) = Subcall.coreCallDataPublicKey();
(Sapphire.Curve25519PublicKey myPublic, Sapphire.Curve25519SecretKey mySecret) = Sapphire.generateCurve25519KeyPair("");
bytes32 sharedSecret = internal_deriveSharedSecret(cdpk.key, mySecret);
bytes memory plaintextEnvelope = abi.encodePacked(
hex"a1", // map(1)
hex"64", // text(4)
"body", // "body"
_cborEncodeBytes(in_data));
bytes15 nonce = bytes15(Sapphire.randomBytes(32, ""));
bytes memory encryptedInner = Sapphire.encrypt(sharedSecret, nonce, plaintextEnvelope, "");
return abi.encodePacked(
hex"a2", // map(2)
hex"64", "body", // text(4) "body"
hex"a4", // map(4)
hex"62", "pk", // text(2) "pk"
hex"5820", myPublic, // bytes(32) pubkey
hex"64", "data", // text(4) "data"
encryptedInner, // bytes(n) inner
hex"65", "nonce", // text(4) "nonce"
hex"580f", nonce, // bytes(15) nonce
hex"65", "epoch", // text(4) "epoch"
_cborEncodeUint(epoch),
hex"66", "format", // text(6) "format"
hex"01" // unsigned(1)
);
}
}
The current EIP155Signer expects arbitrary calldata, e.g. https://docs.oasis.io/dapp/sapphire/gasless#gasless-proxy-contract.
Add a helper to Sapphire.sol or other contract for on-chain Deoxys-II encryption of the call data prior to signing the transaction. See how to fetch the ephemeral public key here.
Update the Gasless chapter with the new encrypted version of the tx.