{"action":"created","comment":{"url":"https://api.github.com/repos/google/certificate-transparency/pulls/comments/44951864","id":44951864,"diff_hunk":"@@ -0,0 +1,243 @@\n+#ifndef CERT_TRANS_MERKLETREE_SPARSE_MERKLE_TREE_H\n+#define CERT_TRANS_MERKLETREE_SPARSE_MERKLE_TREE_H\n+\n+#include <bitset>\n+#include <glog/logging.h>\n+#include <stddef.h>\n+#include <string>\n+#include <unordered_map>\n+#include <vector>\n+\n+#include \"merkletree/merkle_tree_interface.h\"\n+#include \"merkletree/tree_hasher.h\"\n+\n+class SerialHasher;\n+\n+const int kDigestSizeBits = 256;\n+\n+\n+// TODO(alcutter): compare using bitset and BIGNUM.\n+typedef std::bitset<kDigestSizeBits> Path;\n+\n+\n+// Creates a Path from the bits passed in.\n+Path PathFromBytes(const std::string& bytes);\n+\n+\n+// Calculates the set of \"null\" hashes:\n+// ...H(H(H(\"\")||H(\"\"))||H(\"\")||(H(\"\"))||...)...\n+//\n+// Visible out here because it's useful for testing too.\n+std::vector<std::string> CalculateNullHashes(const TreeHasher& hasher);\n+\n+// Implementation of a Sparse Merkle Tree.\n+//\n+// The design is inspired by the tree described in\n+// http://www.links.org/files/RevocationTransparency.pdf), but with some\n+// of tweaks, most notably:\n+//   1) Leaf values are hashed before being incorporated into the tree.\n+//   2) Similar to the way it works in the CT MerkleTree, hashes are domain\n+//      separated by prefixing the preimage with \\x00 for leaves, and \\x01 for\n+//      internal nodes.  (This prevents generation of maps with identical root\n+//      hashes by manipulating leaf data between neighbouring nodes.)\n+//\n+//\n+// These mean that level 2 nodes are of the form:\n+//      H(\\x01||H(\\0x00||valueL)||H(\\0x00||valueR))\n+// and so on.\n+//\n+// Nodes are addressed by a Path, which is a bit-string of the same length as\n+// the output of the hashing fuction used.  This string describes a path down\n+// from the root to a leaf, with the 0-bits indicating the path takes the\n+// left-hand child branch, and 1-bits the right. e.g:\n+//        Root\n+//        /  \\\n+//      0/    \\1\n+//      /      \\\n+//     i0      i3\n+//   0/ \\1   0/  \\1\n+//   /   \\   /    \\\n+//  l0  l1  l2    l3\n+//\n+//  The paths to the 4 leaves would then be:\n+//  l0: \"00\"\n+//  l1: \"01\"\n+//  l2: \"10\"\n+//  l3: \"11\"\n+//\n+// To help with memory consumption, leaves inserted into the tree are stored\n+// at the first unused node along their path.  An example is given below:\n+//\n+// * Empty tree:\n+//      Root\n+//\n+// * Add \"10\" = \"hi\":\n+// Since the tree is empty, the first bit of the added path is sufficient to\n+// identify a unique prefix, so the leaf is stored as the \"1\" entry\n+// immediately below the root.\n+//              Root\n+//                |\n+//                |_______1\n+//                       p:\"10\"\n+//                       v:\"hi\"\n+//\n+// * Add \"11\" = \"to\":\n+// The first bit of the added path is not enough to provide a unique\n+// prefix so the leaf node currently occupying the \"1\" node immediately below\n+// the root must be pushed down a level, resulting in:\n+//              Root\n+//                |\n+//                |_______1\n+//                        |\n+//                        |\n+//                  0_____|_____1\n+//                  |           |\n+//                p:\"10\"      p:\"11\"\n+//                v:\"hi\"      v:\"to\"\n+//\n+// (In the case where paths are longer and multiple bits of the prefix collide,\n+// the existing node is repeated pushed down a level until a unique prefix is\n+// found.)\n+//\n+// * Add \"00\" = \"aa\":\n+// The first bit of the added path is unique, and so the resulting tree is:\n+//              Root\n+//                |\n+//        0_______|_______1\n+//        |               |\n+//      p:\"00\"            |\n+//      v:\"aa\"      0_____|_____1\n+//                  |           |\n+//                p:\"10\"      p:\"11\"\n+//                v:\"hi\"      v:\"to\"\n+//\n+// * Calculating the root hash\n+// Calculating the root of the tree is similar to a regular MerkleTree, but is\n+// optimised by cribbing the value of \"missing\" nodes from a simple cache. This\n+// removes the need to calculate the vast majority of nodes from scratch.\n+//\n+// TODO(alcutter): LOTS!\n+//\n+// This class is thread-compatible, but not thread-safe.\n+class SparseMerkleTree {\n+ public:\n+  // The constructor takes a pointer to some concrete hash function\n+  // instantiation of the SerialHasher abstract class.\n+  // Takes ownership of the hasher.\n+  explicit SparseMerkleTree(SerialHasher* hasher);\n+\n+  // Length of a node (i.e., a hash), in bytes.\n+  virtual size_t NodeSize() const {\n+    return treehasher_.DigestSize();\n+  };\n+\n+  // Number of non-default leaves in the tree.\n+  virtual size_t LeafCount() const {\n+    LOG(FATAL) << \"Not implemented.\";\n+  }\n+\n+  // The |leaf|th leaf hash in the tree. Indexing starts from 1.\n+  std::string LeafHash(size_t leaf) const {\n+    if (leaf == 0 || leaf > LeafCount())\n+      return std::string();\n+    return Node(0, leaf - 1);\n+  }\n+\n+  // Return the leaf hash, but do not append the data to the tree.\n+  virtual std::string LeafHash(const std::string& data) const {\n+    return treehasher_.HashLeaf(data);\n+  }\n+\n+  // Add a new leaf to the hash tree. Stores the hash of the leaf data in the\n+  // tree structure at path |Hash(key)|, does not store the data itself.\n+  //\n+  // @param data Binary input blob\n+  // @param path Binary path of node to set.\n+  virtual void SetLeafByHash(const std::string& key, const std::string& data);\n+\n+  // Add a new leaf to the hash tree. Stores the hash of the leaf data in the\n+  // tree structure, does not store the data itself.\n+  //\n+  // @param data Binary input blob\n+  // @param path Binary path of node to set.\n+  virtual void SetLeaf(const Path& path, const std::string& data);\n+\n+  // Get the current root of the tree.\n+  // Update the root to reflect the current shape of the tree,\n+  // and return the tree digest.\n+  //\n+  // Returns the hash of an empty string if the tree has no leaves\n+  // (and hence, no root).\n+  virtual std::string CurrentRoot();\n+\n+  // Get the Merkle path from leaf to root.\n+  //\n+  // Returns a vector of node hashes, ordered by levels from leaf to root.\n+  // The first element is the sibling of the leaf hash, and the last element\n+  // is one below the root.\n+  // Returns an empty vector if the tree is not large enough\n+  // or the leaf index is 0.\n+  //\n+  // @param leaf the index of the leaf the path is for.\n+  std::vector<std::string> PathToCurrentRoot(size_t leaf);\n+\n+  std::string Dump() const;\n+\n+ private:\n+  // WARNING WARNING WARNING\n+  // 64 < 256 !\n+  // WARNING WARNING WARNING\n+  // TODO(alcutter): BIGNUM probably.\n+  typedef uint64_t IndexType;","path":"cpp/merkletree/sparse_merkle_tree.h","position":189,"original_position":191,"commit_id":"7ed352f18b94d072a2115e26ced430551e188575","original_commit_id":"5cbf593f0ca8fa06d17ccd92d5d0adbe21b30f34","user":{"login":"pphaneuf","id":40400,"avatar_url":"https://avatars.githubusercontent.com/u/40400?v=3","gravatar_id":"","url":"https://api.github.com/users/pphaneuf","html_url":"https://github.com/pphaneuf","followers_url":"https://api.github.com/users/pphaneuf/followers","following_url":"https://api.github.com/users/pphaneuf/following{/other_user}","gists_url":"https://api.github.com/users/pphaneuf/gists{/gist_id}","starred_url":"https://api.github.com/users/pphaneuf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pphaneuf/subscriptions","organizations_url":"https://api.github.com/users/pphaneuf/orgs","repos_url":"https://api.github.com/users/pphaneuf/repos","events_url":"https://api.github.com/users/pphaneuf/events{/privacy}","received_events_url":"https://api.github.com/users/pphaneuf/received_events","type":"User","site_admin":false},"body":"I know it's going away and everything, but it'd be great if you explained that type a bit better, like how for example it's right-shifted to have the useless bits be the most significants (it's not a truncated path, which is sort of what I expected).","created_at":"2015-11-16T17:11:58Z","updated_at":"2015-11-16T17:11:58Z","html_url":"https://github.com/google/certificate-transparency/pull/1033#discussion_r44951864","pull_request_url":"https://api.github.com/repos/google/certificate-transparency/pulls/1033","_links":{"self":{"href":"https://api.github.com/repos/google/certificate-transparency/pulls/comments/44951864"},"html":{"href":"https://github.com/google/certificate-transparency/pull/1033#discussion_r44951864"},"pull_request":{"href":"https://api.github.com/repos/google/certificate-transparency/pulls/1033"}}},"pull_request":{"url":"https://api.github.com/repos/google/certificate-transparency/pulls/1033","id":49952197,"html_url":"https://github.com/google/certificate-transparency/pull/1033","diff_url":"https://github.com/google/certificate-transparency/pull/1033.diff","patch_url":"https://github.com/google/certificate-transparency/pull/1033.patch","issue_url":"https://api.github.com/repos/google/certificate-transparency/issues/1033","number":1033,"state":"open","locked":false,"title":"Add a simple first cut of a SparseMerkleTree.","user":{"login":"AlCutter","id":7648032,"avatar_url":"https://avatars.githubusercontent.com/u/7648032?v=3","gravatar_id":"","url":"https://api.github.com/users/AlCutter","html_url":"https://github.com/AlCutter","followers_url":"https://api.github.com/users/AlCutter/followers","following_url":"https://api.github.com/users/AlCutter/following{/other_user}","gists_url":"https://api.github.com/users/AlCutter/gists{/gist_id}","starred_url":"https://api.github.com/users/AlCutter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlCutter/subscriptions","organizations_url":"https://api.github.com/users/AlCutter/orgs","repos_url":"https://api.github.com/users/AlCutter/repos","events_url":"https://api.github.com/users/AlCutter/events{/privacy}","received_events_url":"https://api.github.com/users/AlCutter/received_events","type":"User","site_admin":false},"body":"Very early first cut of a C++ sparse merkle tree / map implementation.\r\nInspired by the [revocation transparency](http://www.links.org/files/RevocationTransparency.pdf) paper, but with some differences (as noted in the header) for security and to be useful as a general purpose verifiable map.\r\n\r\nLots left to do, just trying to break it up into digestible chunks.","created_at":"2015-11-06T13:17:23Z","updated_at":"2015-11-16T17:11:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a47b9927aff9503d834126db3975337ffa9cbef5","assignee":{"login":"pphaneuf","id":40400,"avatar_url":"https://avatars.githubusercontent.com/u/40400?v=3","gravatar_id":"","url":"https://api.github.com/users/pphaneuf","html_url":"https://github.com/pphaneuf","followers_url":"https://api.github.com/users/pphaneuf/followers","following_url":"https://api.github.com/users/pphaneuf/following{/other_user}","gists_url":"https://api.github.com/users/pphaneuf/gists{/gist_id}","starred_url":"https://api.github.com/users/pphaneuf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pphaneuf/subscriptions","organizations_url":"https://api.github.com/users/pphaneuf/orgs","repos_url":"https://api.github.com/users/pphaneuf/repos","events_url":"https://api.github.com/users/pphaneuf/events{/privacy}","received_events_url":"https://api.github.com/users/pphaneuf/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/google/certificate-transparency/pulls/1033/commits","review_comments_url":"https://api.github.com/repos/google/certificate-transparency/pulls/1033/comments","review_comment_url":"https://api.github.com/repos/google/certificate-transparency/pulls/comments{/number}","comments_url":"https://api.github.com/repos/google/certificate-transparency/issues/1033/comments","statuses_url":"https://api.github.com/repos/google/certificate-transparency/statuses/7ed352f18b94d072a2115e26ced430551e188575","head":{"label":"AlCutter:sparse_merkle_tree","ref":"sparse_merkle_tree","sha":"7ed352f18b94d072a2115e26ced430551e188575","user":{"login":"AlCutter","id":7648032,"avatar_url":"https://avatars.githubusercontent.com/u/7648032?v=3","gravatar_id":"","url":"https://api.github.com/users/AlCutter","html_url":"https://github.com/AlCutter","followers_url":"https://api.github.com/users/AlCutter/followers","following_url":"https://api.github.com/users/AlCutter/following{/other_user}","gists_url":"https://api.github.com/users/AlCutter/gists{/gist_id}","starred_url":"https://api.github.com/users/AlCutter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlCutter/subscriptions","organizations_url":"https://api.github.com/users/AlCutter/orgs","repos_url":"https://api.github.com/users/AlCutter/repos","events_url":"https://api.github.com/users/AlCutter/events{/privacy}","received_events_url":"https://api.github.com/users/AlCutter/received_events","type":"User","site_admin":false},"repo":{"id":23961092,"name":"certificate-transparency","full_name":"AlCutter/certificate-transparency","owner":{"login":"AlCutter","id":7648032,"avatar_url":"https://avatars.githubusercontent.com/u/7648032?v=3","gravatar_id":"","url":"https://api.github.com/users/AlCutter","html_url":"https://github.com/AlCutter","followers_url":"https://api.github.com/users/AlCutter/followers","following_url":"https://api.github.com/users/AlCutter/following{/other_user}","gists_url":"https://api.github.com/users/AlCutter/gists{/gist_id}","starred_url":"https://api.github.com/users/AlCutter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AlCutter/subscriptions","organizations_url":"https://api.github.com/users/AlCutter/orgs","repos_url":"https://api.github.com/users/AlCutter/repos","events_url":"https://api.github.com/users/AlCutter/events{/privacy}","received_events_url":"https://api.github.com/users/AlCutter/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/AlCutter/certificate-transparency","description":"Auditing for TLS certificates.","fork":true,"url":"https://api.github.com/repos/AlCutter/certificate-transparency","forks_url":"https://api.github.com/repos/AlCutter/certificate-transparency/forks","keys_url":"https://api.github.com/repos/AlCutter/certificate-transparency/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AlCutter/certificate-transparency/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AlCutter/certificate-transparency/teams","hooks_url":"https://api.github.com/repos/AlCutter/certificate-transparency/hooks","issue_events_url":"https://api.github.com/repos/AlCutter/certificate-transparency/issues/events{/number}","events_url":"https://api.github.com/repos/AlCutter/certificate-transparency/events","assignees_url":"https://api.github.com/repos/AlCutter/certificate-transparency/assignees{/user}","branches_url":"https://api.github.com/repos/AlCutter/certificate-transparency/branches{/branch}","tags_url":"https://api.github.com/repos/AlCutter/certificate-transparency/tags","blobs_url":"https://api.github.com/repos/AlCutter/certificate-transparency/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AlCutter/certificate-transparency/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AlCutter/certificate-transparency/git/refs{/sha}","trees_url":"https://api.github.com/repos/AlCutter/certificate-transparency/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AlCutter/certificate-transparency/statuses/{sha}","languages_url":"https://api.github.com/repos/AlCutter/certificate-transparency/languages","stargazers_url":"https://api.github.com/repos/AlCutter/certificate-transparency/stargazers","contributors_url":"https://api.github.com/repos/AlCutter/certificate-transparency/contributors","subscribers_url":"https://api.github.com/repos/AlCutter/certificate-transparency/subscribers","subscription_url":"https://api.github.com/repos/AlCutter/certificate-transparency/subscription","commits_url":"https://api.github.com/repos/AlCutter/certificate-transparency/commits{/sha}","git_commits_url":"https://api.github.com/repos/AlCutter/certificate-transparency/git/commits{/sha}","comments_url":"https://api.github.com/repos/AlCutter/certificate-transparency/comments{/number}","issue_comment_url":"https://api.github.com/repos/AlCutter/certificate-transparency/issues/comments{/number}","contents_url":"https://api.github.com/repos/AlCutter/certificate-transparency/contents/{+path}","compare_url":"https://api.github.com/repos/AlCutter/certificate-transparency/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AlCutter/certificate-transparency/merges","archive_url":"https://api.github.com/repos/AlCutter/certificate-transparency/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AlCutter/certificate-transparency/downloads","issues_url":"https://api.github.com/repos/AlCutter/certificate-transparency/issues{/number}","pulls_url":"https://api.github.com/repos/AlCutter/certificate-transparency/pulls{/number}","milestones_url":"https://api.github.com/repos/AlCutter/certificate-transparency/milestones{/number}","notifications_url":"https://api.github.com/repos/AlCutter/certificate-transparency/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AlCutter/certificate-transparency/labels{/name}","releases_url":"https://api.github.com/repos/AlCutter/certificate-transparency/releases{/id}","created_at":"2014-09-12T13:11:02Z","updated_at":"2015-05-07T15:41:29Z","pushed_at":"2015-11-16T16:03:11Z","git_url":"git://github.com/AlCutter/certificate-transparency.git","ssh_url":"git@github.com:AlCutter/certificate-transparency.git","clone_url":"https://github.com/AlCutter/certificate-transparency.git","svn_url":"https://github.com/AlCutter/certificate-transparency","homepage":"http://www.certificate-transparency.org/","size":22490,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"google:master","ref":"master","sha":"080bdeda12ab09fa32d80ede49c547f6d8757b11","user":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=3","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":19989605,"name":"certificate-transparency","full_name":"google/certificate-transparency","owner":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=3","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/google/certificate-transparency","description":"Auditing for TLS certificates.","fork":false,"url":"https://api.github.com/repos/google/certificate-transparency","forks_url":"https://api.github.com/repos/google/certificate-transparency/forks","keys_url":"https://api.github.com/repos/google/certificate-transparency/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/certificate-transparency/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/certificate-transparency/teams","hooks_url":"https://api.github.com/repos/google/certificate-transparency/hooks","issue_events_url":"https://api.github.com/repos/google/certificate-transparency/issues/events{/number}","events_url":"https://api.github.com/repos/google/certificate-transparency/events","assignees_url":"https://api.github.com/repos/google/certificate-transparency/assignees{/user}","branches_url":"https://api.github.com/repos/google/certificate-transparency/branches{/branch}","tags_url":"https://api.github.com/repos/google/certificate-transparency/tags","blobs_url":"https://api.github.com/repos/google/certificate-transparency/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/certificate-transparency/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/certificate-transparency/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/certificate-transparency/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/certificate-transparency/statuses/{sha}","languages_url":"https://api.github.com/repos/google/certificate-transparency/languages","stargazers_url":"https://api.github.com/repos/google/certificate-transparency/stargazers","contributors_url":"https://api.github.com/repos/google/certificate-transparency/contributors","subscribers_url":"https://api.github.com/repos/google/certificate-transparency/subscribers","subscription_url":"https://api.github.com/repos/google/certificate-transparency/subscription","commits_url":"https://api.github.com/repos/google/certificate-transparency/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/certificate-transparency/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/certificate-transparency/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/certificate-transparency/issues/comments{/number}","contents_url":"https://api.github.com/repos/google/certificate-transparency/contents/{+path}","compare_url":"https://api.github.com/repos/google/certificate-transparency/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/certificate-transparency/merges","archive_url":"https://api.github.com/repos/google/certificate-transparency/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/certificate-transparency/downloads","issues_url":"https://api.github.com/repos/google/certificate-transparency/issues{/number}","pulls_url":"https://api.github.com/repos/google/certificate-transparency/pulls{/number}","milestones_url":"https://api.github.com/repos/google/certificate-transparency/milestones{/number}","notifications_url":"https://api.github.com/repos/google/certificate-transparency/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/certificate-transparency/labels{/name}","releases_url":"https://api.github.com/repos/google/certificate-transparency/releases{/id}","created_at":"2014-05-20T17:03:47Z","updated_at":"2015-11-14T00:13:58Z","pushed_at":"2015-11-16T16:03:11Z","git_url":"git://github.com/google/certificate-transparency.git","ssh_url":"git@github.com:google/certificate-transparency.git","clone_url":"https://github.com/google/certificate-transparency.git","svn_url":"https://github.com/google/certificate-transparency","homepage":"http://www.certificate-transparency.org/","size":27510,"stargazers_count":190,"watchers_count":190,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":71,"mirror_url":null,"open_issues_count":64,"forks":71,"open_issues":64,"watchers":190,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/google/certificate-transparency/pulls/1033"},"html":{"href":"https://github.com/google/certificate-transparency/pull/1033"},"issue":{"href":"https://api.github.com/repos/google/certificate-transparency/issues/1033"},"comments":{"href":"https://api.github.com/repos/google/certificate-transparency/issues/1033/comments"},"review_comments":{"href":"https://api.github.com/repos/google/certificate-transparency/pulls/1033/comments"},"review_comment":{"href":"https://api.github.com/repos/google/certificate-transparency/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/google/certificate-transparency/pulls/1033/commits"},"statuses":{"href":"https://api.github.com/repos/google/certificate-transparency/statuses/7ed352f18b94d072a2115e26ced430551e188575"}}}}