/******************************************************************************* * (c) 2022 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ // THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.17; // MajUnsignedInt = 0 // MajSignedInt = 1 // MajByteString = 2 // MajTextString = 3 // MajArray = 4 // MajMap = 5 // MajTag = 6 // MajOther = 7 uint8 constant MajUnsignedInt = 0; uint8 constant MajSignedInt = 1; uint8 constant MajByteString = 2; uint8 constant MajTextString = 3; uint8 constant MajArray = 4; uint8 constant MajMap = 5; uint8 constant MajTag = 6; uint8 constant MajOther = 7; uint8 constant TagTypeBigNum = 2; uint8 constant TagTypeNegativeBigNum = 3; uint8 constant True_Type = 21; uint8 constant False_Type = 20; /// @notice This library is a set a functions that allows anyone to decode cbor encoded bytes /// @dev methods in this library try to read the data type indicated from cbor encoded data stored in bytes at a specific index /// @dev if it successes, methods will return the read value and the new index (intial index plus read bytes) /// @author Zondax AG library CBORDecoder { /// @notice check if next value on the cbor encoded data is null /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes function isNullNext(bytes memory cborData, uint byteIdx) internal pure returns (bool) { return cborData[byteIdx] == hex"f6"; } /// @notice attempt to read a bool value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return a bool decoded from input bytes and the byte index after moving past the value function readBool(bytes memory cborData, uint byteIdx) internal pure returns (bool, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajOther, "invalid maj (expected MajOther)"); assert(value == True_Type || value == False_Type); return (value != False_Type, byteIdx); } /// @notice attempt to read the length of a fixed array /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return length of the fixed array decoded from input bytes and the byte index after moving past the value function readFixedArray(bytes memory cborData, uint byteIdx) internal pure returns (uint, uint) { uint8 maj; uint len; (maj, len, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajArray, "invalid maj (expected MajArray)"); return (len, byteIdx); } /// @notice attempt to read an arbitrary length string value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return arbitrary length string decoded from input bytes and the byte index after moving past the value function readString(bytes memory cborData, uint byteIdx) internal pure returns (string memory, uint) { uint8 maj; uint len; (maj, len, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajTextString, "invalid maj (expected MajTextString)"); uint max_len = byteIdx + len; bytes memory slice = new bytes(len); uint slice_index = 0; for (uint256 i = byteIdx; i < max_len; i++) { slice[slice_index] = cborData[i]; slice_index++; } return (string(slice), byteIdx + len); } /// @notice attempt to read an arbitrary byte string value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return arbitrary byte string decoded from input bytes and the byte index after moving past the value function readBytes(bytes memory cborData, uint byteIdx) internal pure returns (bytes memory, uint) { uint8 maj; uint len; (maj, len, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajTag || maj == MajByteString, "invalid maj (expected MajTag or MajByteString)"); if (maj == MajTag) { (maj, len, byteIdx) = parseCborHeader(cborData, byteIdx); assert(maj == MajByteString); } uint max_len = byteIdx + len; bytes memory slice = new bytes(len); uint slice_index = 0; for (uint256 i = byteIdx; i < max_len; i++) { slice[slice_index] = cborData[i]; slice_index++; } return (slice, byteIdx + len); } /// @notice attempt to read a bytes32 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return a bytes32 decoded from input bytes and the byte index after moving past the value function readBytes32(bytes memory cborData, uint byteIdx) internal pure returns (bytes32, uint) { uint8 maj; uint len; (maj, len, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajByteString, "invalid maj (expected MajByteString)"); uint max_len = byteIdx + len; bytes memory slice = new bytes(32); uint slice_index = 32 - len; for (uint256 i = byteIdx; i < max_len; i++) { slice[slice_index] = cborData[i]; slice_index++; } return (bytes32(slice), byteIdx + len); } /// @notice attempt to read a uint256 value encoded per cbor specification /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an uint256 decoded from input bytes and the byte index after moving past the value function readUInt256(bytes memory cborData, uint byteIdx) internal pure returns (uint256, uint) { uint8 maj; uint256 value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajTag || maj == MajUnsignedInt, "invalid maj (expected MajTag or MajUnsignedInt)"); if (maj == MajTag) { require(value == TagTypeBigNum, "invalid tag (expected TagTypeBigNum)"); uint len; (maj, len, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajByteString, "invalid maj (expected MajByteString)"); require(cborData.length >= byteIdx + len, "slicing out of range"); assembly { value := mload(add(cborData, add(len, byteIdx))) } return (value, byteIdx + len); } return (value, byteIdx); } /// @notice attempt to read a int256 value encoded per cbor specification /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an int256 decoded from input bytes and the byte index after moving past the value function readInt256(bytes memory cborData, uint byteIdx) internal pure returns (int256, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajTag || maj == MajSignedInt, "invalid maj (expected MajTag or MajSignedInt)"); if (maj == MajTag) { assert(value == TagTypeNegativeBigNum); uint len; (maj, len, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajByteString, "invalid maj (expected MajByteString)"); require(cborData.length >= byteIdx + len, "slicing out of range"); assembly { value := mload(add(cborData, add(len, byteIdx))) } return (int256(value), byteIdx + len); } return (int256(value), byteIdx); } /// @notice attempt to read a uint64 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an uint64 decoded from input bytes and the byte index after moving past the value function readUInt64(bytes memory cborData, uint byteIdx) internal pure returns (uint64, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)"); return (uint64(value), byteIdx); } /// @notice attempt to read a uint32 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an uint32 decoded from input bytes and the byte index after moving past the value function readUInt32(bytes memory cborData, uint byteIdx) internal pure returns (uint32, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)"); return (uint32(value), byteIdx); } /// @notice attempt to read a uint16 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an uint16 decoded from input bytes and the byte index after moving past the value function readUInt16(bytes memory cborData, uint byteIdx) internal pure returns (uint16, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)"); return (uint16(value), byteIdx); } /// @notice attempt to read a uint8 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an uint8 decoded from input bytes and the byte index after moving past the value function readUInt8(bytes memory cborData, uint byteIdx) internal pure returns (uint8, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)"); return (uint8(value), byteIdx); } /// @notice attempt to read a int64 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an int64 decoded from input bytes and the byte index after moving past the value function readInt64(bytes memory cborData, uint byteIdx) internal pure returns (int64, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)"); return (int64(uint64(value)), byteIdx); } /// @notice attempt to read a int32 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an int32 decoded from input bytes and the byte index after moving past the value function readInt32(bytes memory cborData, uint byteIdx) internal pure returns (int32, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)"); return (int32(uint32(value)), byteIdx); } /// @notice attempt to read a int16 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an int16 decoded from input bytes and the byte index after moving past the value function readInt16(bytes memory cborData, uint byteIdx) internal pure returns (int16, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)"); return (int16(uint16(value)), byteIdx); } /// @notice attempt to read a int8 value /// @param cborData cbor encoded bytes to parse from /// @param byteIdx current position to read on the cbor encoded bytes /// @return an int8 decoded from input bytes and the byte index after moving past the value function readInt8(bytes memory cborData, uint byteIdx) internal pure returns (int8, uint) { uint8 maj; uint value; (maj, value, byteIdx) = parseCborHeader(cborData, byteIdx); require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)"); return (int8(uint8(value)), byteIdx); } /// @notice slice uint8 from bytes starting at a given index /// @param bs bytes to slice from /// @param start current position to slice from bytes /// @return uint8 sliced from bytes function sliceUInt8(bytes memory bs, uint start) internal pure returns (uint8) { require(bs.length >= start + 1, "slicing out of range"); return uint8(bs[start]); } /// @notice slice uint16 from bytes starting at a given index /// @param bs bytes to slice from /// @param start current position to slice from bytes /// @return uint16 sliced from bytes function sliceUInt16(bytes memory bs, uint start) internal pure returns (uint16) { require(bs.length >= start + 2, "slicing out of range"); bytes2 x; assembly { x := mload(add(bs, add(0x20, start))) } return uint16(x); } /// @notice slice uint32 from bytes starting at a given index /// @param bs bytes to slice from /// @param start current position to slice from bytes /// @return uint32 sliced from bytes function sliceUInt32(bytes memory bs, uint start) internal pure returns (uint32) { require(bs.length >= start + 4, "slicing out of range"); bytes4 x; assembly { x := mload(add(bs, add(0x20, start))) } return uint32(x); } /// @notice slice uint64 from bytes starting at a given index /// @param bs bytes to slice from /// @param start current position to slice from bytes /// @return uint64 sliced from bytes function sliceUInt64(bytes memory bs, uint start) internal pure returns (uint64) { require(bs.length >= start + 8, "slicing out of range"); bytes8 x; assembly { x := mload(add(bs, add(0x20, start))) } return uint64(x); } /// @notice Parse cbor header for major type and extra info. /// @param cbor cbor encoded bytes to parse from /// @param byteIndex current position to read on the cbor encoded bytes /// @return major type, extra info and the byte index after moving past header bytes function parseCborHeader(bytes memory cbor, uint byteIndex) internal pure returns (uint8, uint64, uint) { uint8 first = sliceUInt8(cbor, byteIndex); byteIndex += 1; uint8 maj = (first & 0xe0) >> 5; uint8 low = first & 0x1f; // We don't handle CBOR headers with extra > 27, i.e. no indefinite lengths require(low < 28, "cannot handle headers with extra > 27"); // extra is lower bits if (low < 24) { return (maj, low, byteIndex); } // extra in next byte if (low == 24) { uint8 next = sliceUInt8(cbor, byteIndex); byteIndex += 1; require(next >= 24, "invalid cbor"); // otherwise this is invalid cbor return (maj, next, byteIndex); } // extra in next 2 bytes if (low == 25) { uint16 extra16 = sliceUInt16(cbor, byteIndex); byteIndex += 2; return (maj, extra16, byteIndex); } // extra in next 4 bytes if (low == 26) { uint32 extra32 = sliceUInt32(cbor, byteIndex); byteIndex += 4; return (maj, extra32, byteIndex); } // extra in next 8 bytes assert(low == 27); uint64 extra64 = sliceUInt64(cbor, byteIndex); byteIndex += 8; return (maj, extra64, byteIndex); } }