Skip to content

Commit

Permalink
Refactor Cbor decoding
Browse files Browse the repository at this point in the history
Move NodePtr => Asn1Ptr into Asn1Decode.sol
  • Loading branch information
mdehoog committed Dec 3, 2024
1 parent fa8d93d commit c024d9e
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 215 deletions.
62 changes: 45 additions & 17 deletions src/Asn1Decode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,49 @@ pragma solidity ^0.8.15;

// adapted from https://github.com/JonahGroendal/asn1-decode/tree/master

import {NodePtr, LibNodePtr} from "./NodePtr.sol";
import {LibBytes} from "./LibBytes.sol";

type Asn1Ptr is uint256;

library LibAsn1Ptr {
using LibAsn1Ptr for Asn1Ptr;

// First byte index of the header
function header(Asn1Ptr self) internal pure returns (uint256) {
return uint80(Asn1Ptr.unwrap(self));
}

// First byte index of the content
function content(Asn1Ptr self) internal pure returns (uint256) {
return uint80(Asn1Ptr.unwrap(self) >> 80);
}

// Content length
function length(Asn1Ptr self) internal pure returns (uint256) {
return uint80(Asn1Ptr.unwrap(self) >> 160);
}

// Total length (header length + content length)
function totalLength(Asn1Ptr self) internal pure returns (uint256) {
return self.length() + self.content() - self.header();
}

// Pack 3 uint80s into a uint256
function toAsn1Ptr(uint256 _header, uint256 _content, uint256 _length) internal pure returns (Asn1Ptr) {
return Asn1Ptr.wrap(_header | _content << 80 | _length << 160);
}
}

library Asn1Decode {
using LibNodePtr for NodePtr;
using LibAsn1Ptr for Asn1Ptr;
using LibBytes for bytes;

bytes1 public constant NULL_VALUE = 0xF6;

/*
* @dev Get the root node. First step in traversing an ASN1 structure
* @param der The DER-encoded ASN1 structure
* @return A pointer to the outermost node
*/
function root(bytes memory der) internal pure returns (NodePtr) {
function root(bytes memory der) internal pure returns (Asn1Ptr) {
return readNodeLength(der, 0);
}

Expand All @@ -48,7 +76,7 @@ library Asn1Decode {
* @param ptr Pointer to the current node
* @return A pointer to the child root node
*/
function rootOf(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) {
function rootOf(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
return readNodeLength(der, ptr.content());
}

Expand All @@ -58,7 +86,7 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return A pointer to the next sibling node
*/
function nextSiblingOf(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) {
function nextSiblingOf(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
return readNodeLength(der, ptr.content() + ptr.length());
}

Expand All @@ -68,7 +96,7 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return A pointer to the first child node
*/
function firstChildOf(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) {
function firstChildOf(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
require(der[ptr.header()] & 0x20 == 0x20, "Not a constructed type");
return readNodeLength(der, ptr.content());
}
Expand All @@ -79,11 +107,11 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return A pointer to a bitstring
*/
function bitstring(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) {
function bitstring(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
require(der[ptr.header()] == 0x03, "Not type BIT STRING");
// Only 00 padded bitstr can be converted to bytestr!
require(der[ptr.content()] == 0x00, "Non-0-padded BIT STRING");
return LibNodePtr.toNodePtr(ptr.header(), ptr.content() + 1, ptr.length() - 1);
return LibAsn1Ptr.toAsn1Ptr(ptr.header(), ptr.content() + 1, ptr.length() - 1);
}

/*
Expand All @@ -92,7 +120,7 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return A bitstring encoded in a uint256
*/
function bitstringUintAt(bytes memory der, NodePtr ptr) internal pure returns (uint256) {
function bitstringUintAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) {
require(der[ptr.header()] == 0x03, "Not type BIT STRING");
uint256 len = ptr.length() - 1;
return uint256(readBytesN(der, ptr.content() + 1, len) >> ((32 - len) * 8));
Expand All @@ -104,7 +132,7 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return A pointer to a octet string
*/
function octetString(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) {
function octetString(bytes memory der, Asn1Ptr ptr) internal pure returns (Asn1Ptr) {
require(der[ptr.header()] == 0x04, "Not type OCTET STRING");
return readNodeLength(der, ptr.content());
}
Expand All @@ -115,7 +143,7 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return Uint value of node
*/
function uintAt(bytes memory der, NodePtr ptr) internal pure returns (uint256) {
function uintAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) {
require(der[ptr.header()] == 0x02, "Not type INTEGER");
require(der[ptr.content()] & 0x80 == 0, "Not positive");
uint256 len = ptr.length();
Expand All @@ -128,7 +156,7 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return 384-bit uint encoded in uint128 and uint256
*/
function uint384At(bytes memory der, NodePtr ptr) internal pure returns (uint128, uint256) {
function uint384At(bytes memory der, Asn1Ptr ptr) internal pure returns (uint128, uint256) {
require(der[ptr.header()] == 0x02, "Not type INTEGER");
require(der[ptr.content()] & 0x80 == 0, "Not positive");
uint256 valueLength = ptr.length();
Expand All @@ -149,7 +177,7 @@ library Asn1Decode {
* @param ptr Points to the indices of the current node
* @return UNIX timestamp (seconds since 1970/01/01)
*/
function timestampAt(bytes memory der, NodePtr ptr) internal pure returns (uint256) {
function timestampAt(bytes memory der, Asn1Ptr ptr) internal pure returns (uint256) {
uint16 _years;
uint256 offset = ptr.content();
uint256 length = ptr.length();
Expand All @@ -170,7 +198,7 @@ library Asn1Decode {
return timestampFromDateTime(_years, _months, _days, _hours, _mins, _secs);
}

function readNodeLength(bytes memory der, uint256 ix) private pure returns (NodePtr) {
function readNodeLength(bytes memory der, uint256 ix) private pure returns (Asn1Ptr) {
uint256 length;
uint80 ixFirstContentByte;
if ((der[ix + 1] & 0x80) == 0) {
Expand All @@ -187,7 +215,7 @@ library Asn1Decode {
}
ixFirstContentByte = uint80(ix + 2 + lengthbytesLength);
}
return LibNodePtr.toNodePtr(ix, ixFirstContentByte, uint80(length));
return LibAsn1Ptr.toAsn1Ptr(ix, ixFirstContentByte, uint80(length));
}

function readBytesN(bytes memory self, uint256 idx, uint256 len) private pure returns (bytes32 ret) {
Expand Down
116 changes: 116 additions & 0 deletions src/CborDecode.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {LibBytes} from "./LibBytes.sol";

type CborElement is uint256;

library LibCborElement {
// Cbor element type
function cborType(CborElement self) internal pure returns (uint8) {
return uint8(CborElement.unwrap(self));
}

// First byte index of the content
function start(CborElement self) internal pure returns (uint256) {
return uint80(CborElement.unwrap(self) >> 80);
}

// First byte index of the next element (exclusive end of content)
function end(CborElement self) internal pure returns (uint256) {
return start(self) + length(self);
}

// Content length (0 for non-string types)
function length(CborElement self) internal pure returns (uint256) {
uint8 _type = cborType(self);
if (_type == 0x40 || _type == 0x60) {
// length is non-zero only for byte strings and text strings
return value(self);
}
return 0;
}

// Value of the element (length for string/map/array types, value for others)
function value(CborElement self) internal pure returns (uint64) {
return uint64(CborElement.unwrap(self) >> 160);
}

// Returns true if the element is null
function isNull(CborElement self) internal pure returns (bool) {
return cborType(self) == 0xF6;
}

// Pack 3 uint80s into a uint256
function toCborElement(uint256 _type, uint256 _start, uint256 _length) internal pure returns (CborElement) {
return CborElement.wrap(_type | _start << 80 | _length << 160);
}
}

library CborDecode {
using LibBytes for bytes;
using LibCborElement for CborElement;

// Calculate the keccak256 hash of the given cbor element
function keccak(bytes memory cbor, CborElement ptr) internal pure returns (bytes32) {
return cbor.keccak(ptr.start(), ptr.length());
}

// Take a slice of the given cbor element
function slice(bytes memory cbor, CborElement ptr) internal pure returns (bytes memory) {
return cbor.slice(ptr.start(), ptr.length());
}

function byteStringAt(bytes memory cbor, uint256 ix) internal pure returns (CborElement) {
return elementAt(cbor, ix, 0x40, true);
}

function nextByteString(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), 0x40, true);
}

function nextByteStringOrNull(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), 0x40, false);
}

function nextTextString(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), 0x60, true);
}

function nextPositiveInt(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), 0x00, true);
}

function mapAt(bytes memory cbor, uint256 ix) internal pure returns (CborElement) {
return elementAt(cbor, ix, 0xa0, true);
}

function nextMap(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return mapAt(cbor, ptr.end());
}

function nextArray(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), 0x80, true);
}

function elementAt(bytes memory cbor, uint256 ix, uint8 expectedType, bool required) internal pure returns (CborElement) {
uint8 _type = uint8(cbor[ix] & 0xe0);
uint8 ai = uint8(cbor[ix] & 0x1f);
if (_type == 0xe0) {
require(!required || ai != 22, "null value for required element");
// primitive type, retain the additional information
return LibCborElement.toCborElement(_type | ai, ix + 1, 0);
}
require(_type == expectedType, "unexpected type");
if (ai == 24) {
return LibCborElement.toCborElement(_type, ix + 2, uint8(cbor[ix + 1]));
} else if (ai == 25) {
return LibCborElement.toCborElement(_type, ix + 3, cbor.readUint16(ix + 1));
} else if (ai == 26) {
return LibCborElement.toCborElement(_type, ix + 5, cbor.readUint32(ix + 1));
} else if (ai == 27) {
return LibCborElement.toCborElement(_type, ix + 9, cbor.readUint64(ix + 1));
}
return LibCborElement.toCborElement(_type, ix + 1, ai);
}
}
Loading

0 comments on commit c024d9e

Please sign in to comment.