Skip to content

Commit

Permalink
Merge pull request #440 from oasisprotocol/CedarMist/1-of-many-PRs-fo…
Browse files Browse the repository at this point in the history
…r-matevz-CBOR-refactor

contracts: move CBOR related encoding & decoding into separate module
  • Loading branch information
CedarMist authored Oct 28, 2024
2 parents 5b37986 + 7914b1f commit be334e2
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 287 deletions.
186 changes: 186 additions & 0 deletions contracts/contracts/CBOR.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.0;

/// While parsing CBOR map, unexpected key
error CBOR_InvalidKey();

/// While parsing CBOR map, length is invalid, or other parse error
error CBOR_InvalidMap();

/// While parsing CBOR structure, data length was unexpected
error CBOR_InvalidLength(uint256);

/// Value cannot be parsed as a uint
error CBOR_InvalidUintPrefix(uint8);

/// CBOR spec supports, 1, 2, 4 & 8 byte uints. Caused by parse error.
error CBOR_InvalidUintSize(uint8);

/// CBOR parsed value is out of expected range
error CBOR_Error_ValueOutOfRange(uint256 value, uint256 maxValue);

/// Buffer too short to parse expected value
error CBOR_Error_BufferOverrun(uint256 len, uint256 offset, uint256 need);

/// @notice Byte array is too long
/// @dev Solidity has 256bit length prefixes for byte arrays. We decided for
/// a reasonable cutoff point (64kb), so at most a 2-byte uint describing
/// the array length.
error CBOR_Error_BytesTooLong(uint256 byteLength);

/// @dev we don't follow bignum tagged encoding
/// See: https://www.rfc-editor.org/rfc/rfc8949.html#section-3.4.3
function encodeUint(uint256 value) 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 if (value <= type(uint64).max) {
return abi.encodePacked(uint8(27), uint64(value));
} else if (value <= type(uint128).max) {
return abi.encodePacked(uint8(0x50), uint128(value));
}
return abi.encodePacked(uint8(0x58), uint256(32), value);
}

function encodeBytes(bytes memory in_bytes)
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 + in_bytes.length), 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 CBOR_Error_BytesTooLong(in_bytes.length);
}

function parseMapStart(bytes memory in_data, uint256 in_offset)
pure
returns (uint256 n_entries, uint256 out_offset)
{
uint256 b = uint256(uint8(in_data[in_offset]));
if (b < 0xa0 || b > 0xb7) {
revert CBOR_InvalidMap();
}

n_entries = b - 0xa0;
out_offset = in_offset + 1;
}

function parseUint(bytes memory result, uint256 offset)
pure
returns (uint256 newOffset, uint256 value)
{
uint8 prefix = uint8(result[offset]);
uint256 len;

if (prefix <= 0x17) {
return (offset + 1, prefix);
}
// Byte array(uint256), parsed as a big-endian integer.
else if (prefix == 0x58) {
len = uint8(result[++offset]);
offset++;
}
// Byte array, parsed as a big-endian integer.
else if (prefix & 0x40 == 0x40) {
len = uint8(result[offset++]) ^ 0x40;
}
// Unsigned integer, CBOR encoded.
else if (prefix & 0x10 == 0x10) {
if (prefix == 0x18) {
len = 1;
} else if (prefix == 0x19) {
len = 2;
} else if (prefix == 0x1a) {
len = 4;
} else if (prefix == 0x1b) {
len = 8;
} else {
// Falls outside of the CBOR spec, tagged as uint, but unsupported
revert CBOR_InvalidUintSize(prefix);
}
offset += 1;
}
// Unknown...
else {
revert CBOR_InvalidUintPrefix(prefix);
}

// Exceeds what can be represented by a 256bit word
if (len > 0x20) revert CBOR_InvalidLength(len);

if (offset + len > result.length)
revert CBOR_Error_BufferOverrun(result.length, offset, len);

// Load 32 bytes from the buffer at the given offset
assembly {
value := mload(add(add(0x20, result), offset))
}

// Then shift the value right, until it matches the required bit-width
value = value >> (256 - (len * 8));

newOffset = offset + len;
}

function parseUint64(bytes memory result, uint256 offset)
pure
returns (uint256 newOffset, uint64 value)
{
uint256 tmp;

(newOffset, tmp) = parseUint(result, offset);

if (tmp > type(uint64).max)
revert CBOR_Error_ValueOutOfRange(tmp, type(uint64).max);

value = uint64(tmp);
}

function parseUint128(bytes memory result, uint256 offset)
pure
returns (uint256 newOffset, uint128 value)
{
uint256 tmp;

(newOffset, tmp) = parseUint(result, offset);

if (tmp > type(uint128).max)
revert CBOR_Error_ValueOutOfRange(tmp, type(uint128).max);

value = uint128(tmp);
}

function parseKey(bytes memory result, uint256 offset)
pure
returns (uint256 newOffset, bytes32 keyDigest)
{
if (result[offset] & 0x60 != 0x60) revert CBOR_InvalidKey();

uint8 len = uint8(result[offset++]) ^ 0x60;

assembly {
keyDigest := keccak256(add(add(0x20, result), offset), len)
}

newOffset = offset + len;
}
Loading

0 comments on commit be334e2

Please sign in to comment.