Change log leaf encoding am: b611df2423 am: d67b07f1c6

Change-Id: I0bfccd26e2df669f793896d1f7f51bfebd3ebee0
diff --git a/Android.bp b/Android.bp
index 19ea97e..6a80a62 100644
--- a/Android.bp
+++ b/Android.bp
@@ -94,7 +94,6 @@
     name: "aftl_proto",
     srcs: [
         "proto/api.proto",
-        "proto/aftl.proto",
         "proto/crypto/sigpb/sigpb.proto",
         "proto/crypto/keyspb/keyspb.proto",
         "proto/trillian.proto",
diff --git a/aftltool.py b/aftltool.py
index 192ad9b..613e83b 100755
--- a/aftltool.py
+++ b/aftltool.py
@@ -24,10 +24,11 @@
 #
 """Command-line tool for AFTL support for Android Verified Boot images."""
 
+import abc
 import argparse
-import base64
+import enum
 import hashlib
-import json
+import io
 import multiprocessing
 import os
 import queue
@@ -46,9 +47,7 @@
 
 # pylint: disable=wrong-import-position,import-error
 import avbtool
-import aftl_pb2
 import api_pb2
-from crypto.sigpb import sigpb_pb2
 # pylint: enable=wrong-import-position,import-error
 
 
@@ -350,14 +349,16 @@
     log_url_size: Length of the string representing the transparency log URL.
     leaf_index: Leaf index in the transparency log representing this entry.
     log_root_descriptor_size: Size of the transparency log's SignedLogRoot.
-    fw_info_leaf_size: Size of the FirmwareInfo leaf passed to the log.
+    annotation_leaf_size: Size of the SignedVBMetaPrimaryAnnotationLeaf passed
+        to the log.
     log_root_sig_size: Size in bytes of the log_root_signature
     proof_hash_count: Number of hashes comprising the inclusion proof.
     inc_proof_size: The total size of the inclusion proof, in bytes.
     log_url: The URL for the transparency log that generated this inclusion
         proof.
     log_root_descriptor: The data comprising the signed tree head structure.
-    fw_info_leaf: The data comprising the FirmwareInfo leaf.
+    annotation_leaf: The data comprising the SignedVBMetaPrimaryAnnotationLeaf
+        leaf.
     log_root_signature: The data comprising the log root signature.
     proofs: The hashes comprising the inclusion proof.
 
@@ -370,9 +371,8 @@
                    'H'    # log root signature size
                    'B'    # number of hashes in the inclusion proof
                    'L')   # size of the inclusion proof in bytes
-  # These are used to capture the log_url, log_root_descriptor,
-  # fw_info leaf, log root signature, and the proofs elements for the
-  # encode function.
+  # This header is followed by the log_url, log_root_descriptor,
+  # annotation leaf, log root signature, and the proofs elements.
 
   def __init__(self, data=None):
     """Initializes a new ICP entry object.
@@ -391,7 +391,7 @@
       (self._log_url_size_expected,
        self.leaf_index,
        self._log_root_descriptor_size_expected,
-       self._fw_info_leaf_size_expected,
+       self._annotation_leaf_size_expected,
        self._log_root_sig_size_expected,
        self._proof_hash_count_expected,
        self._inc_proof_size_expected) = struct.unpack(self.FORMAT_STRING,
@@ -401,18 +401,20 @@
       expected_format_string = '{}s{}s{}s{}s{}s'.format(
           self._log_url_size_expected,
           self._log_root_descriptor_size_expected,
-          self._fw_info_leaf_size_expected,
+          self._annotation_leaf_size_expected,
           self._log_root_sig_size_expected,
           self._inc_proof_size_expected)
 
-      (log_url, log_root_descriptor_bytes, fw_info_leaf_bytes,
+      (log_url, log_root_descriptor_bytes, annotation_leaf_bytes,
        self.log_root_signature, proof_bytes) = struct.unpack(
            expected_format_string, data[self.SIZE:self.get_expected_size()])
 
       self.log_url = log_url.decode('ascii')
       self.log_root_descriptor = TrillianLogRootDescriptor(
           log_root_descriptor_bytes)
-      self.fw_info_leaf = FirmwareInfoLeaf(fw_info_leaf_bytes)
+
+      self.annotation_leaf = SignedVBMetaPrimaryAnnotationLeaf.parse(
+          annotation_leaf_bytes)
 
       self.proofs = []
       if self._proof_hash_count_expected > 0:
@@ -427,7 +429,7 @@
       self.leaf_index = 0
       self.log_url = ''
       self.log_root_descriptor = TrillianLogRootDescriptor()
-      self.fw_info_leaf = FirmwareInfoLeaf()
+      self.annotation_leaf = SignedVBMetaPrimaryAnnotationLeaf()
       self.log_root_signature = b''
       self.proofs = []
     if not self.is_valid():
@@ -448,11 +450,11 @@
     return self._log_root_descriptor_size_expected
 
   @property
-  def fw_info_leaf_size(self):
-    """Gets the size of the fw_info_leaf attribute."""
-    if hasattr(self, 'fw_info_leaf'):
-      return self.fw_info_leaf.get_expected_size()
-    return self._fw_info_leaf_size_expected
+  def annotation_leaf_size(self):
+    """Gets the size of the annotation_leaf attribute."""
+    if hasattr(self, 'annotation_leaf'):
+      return self.annotation_leaf.get_expected_size()
+    return self._annotation_leaf_size_expected
 
   @property
   def log_root_sig_size(self):
@@ -490,7 +492,7 @@
     if not transparency_log_pub_key:
       return False
 
-    leaf_hash = rfc6962_hash_leaf(self.fw_info_leaf.encode())
+    leaf_hash = rfc6962_hash_leaf(self.annotation_leaf.encode())
     calc_root = root_from_icp(self.leaf_index,
                               self.log_root_descriptor.tree_size,
                               self.proofs,
@@ -513,7 +515,7 @@
 
     Returns:
       True if the inclusion proof validates and the vbmeta hash of the given
-      VBMeta image matches the one in the fw_info_leaf; otherwise False.
+      VBMeta image matches the one in the annotation leaf; otherwise False.
     """
     if not vbmeta_image:
       return False
@@ -524,13 +526,13 @@
     # Validates the inclusion proof and then compare the calculated vbmeta_hash
     # against the one in the inclusion proof.
     return (self.verify_icp(transparency_log_pub_key)
-            and self.fw_info_leaf.vbmeta_hash == vbmeta_hash)
+            and self.annotation_leaf.annotation.vbmeta_hash == vbmeta_hash)
 
   def encode(self):
-    """Serializes the header |SIZE| and data to a bytearray().
+    """Serializes the header |SIZE| and data to bytes.
 
     Returns:
-      A bytearray() with the encoded header.
+      bytes with the encoded header.
 
     Raises:
       AftlError: If invalid entry structure.
@@ -543,7 +545,7 @@
         self.FORMAT_STRING,
         self.log_url_size,
         self.log_root_descriptor_size,
-        self.fw_info_leaf_size,
+        self.annotation_leaf_size,
         self.log_root_sig_size,
         self.inc_proof_size)
 
@@ -552,30 +554,31 @@
 
     return struct.pack(expected_format_string,
                        self.log_url_size, self.leaf_index,
-                       self.log_root_descriptor_size, self.fw_info_leaf_size,
+                       self.log_root_descriptor_size, self.annotation_leaf_size,
                        self.log_root_sig_size, self.proof_hash_count,
                        self.inc_proof_size, self.log_url.encode('ascii'),
                        self.log_root_descriptor.encode(),
-                       self.fw_info_leaf.encode(),
+                       self.annotation_leaf.encode(),
                        self.log_root_signature,
                        proof_bytes)
 
-  def translate_response(self, log_url, afi_response):
-    """Translates an AddFirmwareInfoResponse object to an AftlIcpEntry.
+  def translate_response(self, log_url, avbm_response):
+    """Translates an AddVBMetaResponse object to an AftlIcpEntry.
 
     Arguments:
       log_url: String representing the transparency log URL.
-      afi_response: The AddFirmwareResponse object to translate.
+      avbm_response: The AddVBMetaResponse object to translate.
     """
     self.log_url = log_url
 
-    # Deserializes from AddFirmwareInfoResponse.
-    self.leaf_index = afi_response.fw_info_proof.proof.leaf_index
-    self.log_root_descriptor = TrillianLogRootDescriptor(
-        afi_response.fw_info_proof.sth.log_root)
-    self.fw_info_leaf = FirmwareInfoLeaf(afi_response.fw_info_leaf)
-    self.log_root_signature = afi_response.fw_info_proof.sth.log_root_signature
-    self.proofs = afi_response.fw_info_proof.proof.hashes
+    # Deserializes from AddVBMetaResponse.
+    proof = avbm_response.annotation_proof
+    self.leaf_index = proof.proof.leaf_index
+    self.log_root_descriptor = TrillianLogRootDescriptor(proof.sth.log_root)
+    self.annotation_leaf = SignedVBMetaPrimaryAnnotationLeaf.parse(
+        avbm_response.annotation_leaf)
+    self.log_root_signature = proof.sth.log_root_signature
+    self.proofs = proof.proof.hashes
 
   def get_expected_size(self):
     """Gets the expected size of the full entry out of the header.
@@ -584,7 +587,7 @@
       The expected size of the AftlIcpEntry from the header.
     """
     return (self.SIZE + self.log_url_size + self.log_root_descriptor_size +
-            self.fw_info_leaf_size + self.log_root_sig_size +
+            self.annotation_leaf_size + self.log_root_sig_size +
             self.inc_proof_size)
 
   def is_valid(self):
@@ -604,9 +607,9 @@
       sys.stderr.write('ICP entry: invalid TrillianLogRootDescriptor.\n')
       return False
 
-    if (not self.fw_info_leaf or
-        not isinstance(self.fw_info_leaf, FirmwareInfoLeaf)):
-      sys.stderr.write('ICP entry: invalid FirmwareInfo.\n')
+    if (not self.annotation_leaf or
+        not isinstance(self.annotation_leaf, Leaf)):
+      sys.stderr.write('ICP entry: invalid Leaf.\n')
       return False
     return True
 
@@ -626,7 +629,7 @@
         o.write(' ' * 29)
       o.write('{}\n'.format(proof_hash.hex()))
     self.log_root_descriptor.print_desc(o)
-    self.fw_info_leaf.print_desc(o)
+    self.annotation_leaf.print_desc(o)
 
 
 class TrillianLogRootDescriptor(object):
@@ -793,139 +796,268 @@
       o.write(fmt.format(i, 'Metadata:', self.metadata.hex()))
 
 
-class FirmwareInfoLeaf(object):
-  """A class representing the FirmwareInfo leaf.
+def tls_decode_bytes(byte_size, stream):
+  """Decodes a variable-length vector.
 
-  AFTL returns the fw_info_leaf as a JSON blob and this class is able to
-  parse the blog and provide necessary attributes needed for validation.
+  In the TLS presentation language, a variable-length vector is a pair
+  (size, value). |size| describes the size of the |value| to read
+  in bytes. All values are encoded in big-endian.
+  See https://tools.ietf.org/html/rfc8446#section-3 for more details.
+
+  Arguments:
+      byte_size: A format character as described in the struct module
+          which describes the expected length of the size. For
+          instance, "B", "H", "L" or "Q".
+      stream: a BytesIO which contains the value to decode.
+
+  Returns:
+    A bytes containing the value decoded.
+
+  Raises:
+    AftlError: If |byte_size| is not a known format character, or if not
+    enough data is available to decode the size or the value.
+  """
+  byte_size_format = "!" + byte_size
+  try:
+    byte_size_length = struct.calcsize(byte_size_format)
+  except struct.error:
+    raise AftlError("Invalid byte_size character: {}. It must be a "
+                    "format supported by struct.".format(byte_size))
+  try:
+    value_size = struct.unpack(byte_size_format,
+                               stream.read(byte_size_length))[0]
+  except struct.error:
+    raise AftlError("Not enough data to read size: {}".format(byte_size))
+  value = stream.read(value_size)
+  if value_size != len(value):
+    raise AftlError("Not enough data to read value: "
+                    "{} != {}".format(value_size, len(value)))
+  return value
+
+def tls_encode_bytes(byte_size, value, stream):
+  """Encodes a variable-length vector.
+
+  In the TLS presentation language, a variable-length vector is a pair
+  (size, value). |size| describes the size of the |value| to read
+  in bytes. All values are encoded in big-endian.
+  See https://tools.ietf.org/html/rfc8446#section-3 for more details.
+
+  Arguments:
+      byte_size: A format character as described in the struct module
+          which describes the expected length of the size. For
+          instance, "B", "H", "L" or "Q".
+      value: the value to encode. The length of |value| must be
+          representable with |byte_size|.
+      stream: a BytesIO to which the value is encoded to.
+
+  Raises:
+    AftlError: If |byte_size| is not a known format character, or if
+    |value|'s length cannot be represent with |byte_size|.
+  """
+  byte_size_format = "!" + byte_size
+  try:
+    stream.write(struct.pack(byte_size_format, len(value)))
+  except struct.error:
+    # Whether byte_size is invalid or not large enough to represent value,
+    # struct returns an struct.error exception. Instead of matching on the
+    # exception message, capture both cases in a generic message.
+    raise AftlError("Invalid byte_size to store {} bytes".format(len(value)))
+  stream.write(value)
+
+class HashAlgorithm(enum.Enum):
+  SHA256 = 0
+
+class SignatureAlgorithm(enum.Enum):
+  RSA = 0
+  ECDSA = 1
+
+class Signature(object):
+  """Represents a signature of some data.
+
+  It is usually made using a manufacturer key and used to sign part of a leaf
+  that belongs to the transparency log. The encoding of this structure must
+  match the server expectation.
 
   Attributes:
-    vbmeta_hash: This is the SHA256 hash of vbmeta.
-    version_incremental: Subcomponent of the build fingerprint as defined at
-      https://source.android.com/compatibility/android-cdd#3_2_2_build_parameters.
-      For example, a Pixel device with the following build fingerprint
-      google/crosshatch/crosshatch:9/PQ3A.190605.003/5524043:user/release-keys,
-      would have 5524043 for the version incremental.
-    platform_key: Public key of the platform. This is the same key used to sign
-      the vbmeta.
-    manufacturer_key_hash:  SHA256 of the manufacturer public key (DER-encoded,
-      x509 subjectPublicKeyInfo format). The public key MUST already be in the
-      list of root keys known and trusted by the AFTL.
-    description: Free form description field. It can be used to annotate this
-      message with further context on the build (e.g., carrier specific build).
+    hash_algorithm: the HashAlgorithm used for the signature.
+    signature_algorithm: the SignatureAlgorithm used.
+    signature: the raw signature in bytes.
   """
+  FORMAT_STRING = ('!B'    # Hash algorithm
+                   'B'     # Signing algorithm
+                  )
+  # Followed by the raw signature, encoded as a TLS variable-length vector
+  # which size is represented using 2 bytes.
 
-  def __init__(self, data=None):
-    """Initializes a new FirmwareInfoLeaf descriptor."""
-    if data:
-      # We have to preserve the original fw_info_leaf bytes in order to preserve
-      # hash equivalence with what is stored in the Trillian log and matches up
-      # with the proofs.
-      self._fw_info_leaf_bytes = data
+  def __init__(self, hash_algorithm=HashAlgorithm.SHA256,
+               signature_algorithm=SignatureAlgorithm.RSA, signature=b''):
+    self.hash_algorithm = hash_algorithm
+    self.signature_algorithm = signature_algorithm
+    self.signature = signature
 
-      # Deserialize the JSON blob and keep only the FirmwareInfo parts.
-      try:
-        fw_info_leaf = json.loads(self._fw_info_leaf_bytes)
-        self._fw_info_leaf_dict = (
-            fw_info_leaf['Value']['FwInfo']['info']['info'])
-      except (ValueError, KeyError) as e:
-        raise AftlError('Invalid structure for FirmwareInfoLeaf: {}'.format(e))
-    else:
-      self._fw_info_leaf_bytes = b''
-      self._fw_info_leaf_dict = dict()
-
-    if not self.is_valid():
-      raise AftlError('Invalid structure for FirmwareInfoLeaf.')
-
-  @property
-  def vbmeta_hash(self):
-    """Gets the vbmeta_hash attribute."""
-    return self._lookup_base64_attribute('vbmeta_hash')
-
-  @property
-  def version_incremental(self):
-    """Gets the version_incremental attribute."""
-    return self._fw_info_leaf_dict.get('version_incremental')
-
-  @property
-  def platform_key(self):
-    """Gets the platform key attribute."""
-    return self._lookup_base64_attribute('platform_key')
-
-  @property
-  def manufacturer_key_hash(self):
-    """Gets the manufacturer_key_hash attribute."""
-    return self._lookup_base64_attribute('manufacturer_key_hash')
-
-  @property
-  def description(self):
-    """Gets the description attribute."""
-    return self._fw_info_leaf_dict.get('description')
-
-  def _lookup_base64_attribute(self, key):
-    """Looks up an attribute that is Base64 encoded and decodes it.
+  @classmethod
+  def parse(cls, stream):
+    """Parses a TLS-encoded structure and returns a new Signature.
 
     Arguments:
-      key: The name of the attribute to look up.
+      stream: a BytesIO to read the signature from.
 
     Returns:
-      The attribute value or None if not defined.
+      A new Signature object.
+
+    Raises:
+      AftlError: If the hash algorithm or signature algorithm value is
+        unknown; or if the decoding failed.
     """
-    result = self._fw_info_leaf_dict.get(key)
-    if result:
-      result = base64.b64decode(result)
-    return result
+    data_length = struct.calcsize(cls.FORMAT_STRING)
+    (hash_algorithm, signature_algorithm) = struct.unpack(
+        cls.FORMAT_STRING, stream.read(data_length))
+    try:
+      hash_algorithm = HashAlgorithm(hash_algorithm)
+    except ValueError:
+      raise AftlError('unknown hash algorithm: {}'.format(hash_algorithm))
+    try:
+      signature_algorithm = SignatureAlgorithm(signature_algorithm)
+    except ValueError:
+      raise AftlError('unknown signature algorithm: {}'.format(
+          signature_algorithm))
+    signature = tls_decode_bytes('H', stream)
+    return Signature(hash_algorithm, signature_algorithm, signature)
 
   def get_expected_size(self):
-    """Gets the expected size of the JSON-serialized FirmwareInfoLeaf.
+    """Returns the size of the encoded Signature."""
+    return struct.calcsize(self.FORMAT_STRING) + \
+        struct.calcsize('H') + len(self.signature)
+
+  def encode(self, stream):
+    """Encodes the Signature.
+
+    Arguments:
+      stream: a BytesIO to which the signature is written.
+    """
+    stream.write(struct.pack(self.FORMAT_STRING, self.hash_algorithm.value,
+                             self.signature_algorithm.value))
+    tls_encode_bytes('H', self.signature, stream)
+
+class VBMetaPrimaryAnnotation(object):
+  """An annotation that contains metadata about a VBMeta image.
+
+  Attributes:
+    vbmeta_hash: the SHA256 of the VBMeta it references.
+    version_incremental: the version incremental of the build, as string.
+    manufacturer_key_hash: the hash of the manufacturer key that will
+        sign this annotation.
+    description: a free-form field.
+  """
+
+  def __init__(self, vbmeta_hash=b'', version_incremental='',
+               manufacturer_key_hash=b'', description=''):
+    """Default constructor."""
+    self.vbmeta_hash = vbmeta_hash
+    self.version_incremental = version_incremental
+    self.manufacturer_key_hash = manufacturer_key_hash
+    self.description = description
+
+  @classmethod
+  def parse(cls, stream):
+    """Parses a VBMetaPrimaryAnnotation from data.
+
+    Arguments:
+      stream: an io.BytesIO to decode the annotation from.
 
     Returns:
-      The expected size of the FirmwareInfo leaf in byte or 0 if not initalized.
-    """
-    if not self._fw_info_leaf_bytes:
-      return 0
-    return len(self._fw_info_leaf_bytes)
+      A new VBMetaPrimaryAnnotation.
 
-  def encode(self):
-    """Serializes the FirmwareInfoLeaf.
+    Raises:
+      AftlError: If an error occured while parsing the annotation.
+    """
+    vbmeta_hash = tls_decode_bytes("B", stream)
+    version_incremental = tls_decode_bytes("B", stream)
+    try:
+      version_incremental = version_incremental.decode("ascii")
+    except UnicodeError:
+      raise AftlError('Failed to convert version incremental to an ASCII'
+                      'string')
+    manufacturer_key_hash = tls_decode_bytes("B", stream)
+    description = tls_decode_bytes("H", stream)
+    try:
+      description = description.decode("utf-8")
+    except UnicodeError:
+      raise AftlError('Failed to convert description to an UTF-8 string')
+    return cls(vbmeta_hash, version_incremental, manufacturer_key_hash,
+               description)
+
+  def sign(self, manufacturer_key_path, signing_helper=None,
+           signing_helper_with_files=None):
+    """Signs the annotation.
+
+    Arguments:
+      manufacturer_key_path: Path to key used to sign messages sent to the
+        transparency log servers.
+      signing_helper: Program which signs a hash and returns a signature.
+      signing_helper_with_files: Same as signing_helper but uses files instead.
 
     Returns:
-      A bytearray() with the JSON-serialized FirmwareInfoLeaf.
-    """
-    return self._fw_info_leaf_bytes
+      A new SignedVBMetaPrimaryAnnotation.
 
-  def is_valid(self):
-    """Ensures that values in the descritor are sane.
-
-    Returns:
-      True if the values are sane; otherwise False.
+    Raises:
+      AftlError: If an error occured while signing the annotation.
     """
-    # Checks that the decode fw_info_leaf at max contains values defined in the
-    # FirmwareInfo proto buf.
-    expected_fields = set(aftl_pb2.FirmwareInfo()
-                          .DESCRIPTOR.fields_by_name.keys())
-    actual_fields = set(self._fw_info_leaf_dict.keys())
-    if actual_fields.issubset(expected_fields):
-      return True
-    return False
+    # AFTL supports SHA256_RSA4096 for now, more will be available.
+    algorithm_name = 'SHA256_RSA4096'
+    encoded_leaf = io.BytesIO()
+    self.encode(encoded_leaf)
+    try:
+      rsa_key = avbtool.RSAPublicKey(manufacturer_key_path)
+      raw_signature = rsa_key.sign(algorithm_name, encoded_leaf.getvalue(),
+                                   signing_helper, signing_helper_with_files)
+    except avbtool.AvbError as e:
+      raise AftlError('Failed to sign VBMetaPrimaryAnnotation with '
+                      '--manufacturer_key: {}'.format(e))
+    signature = Signature(hash_algorithm=HashAlgorithm.SHA256,
+                          signature_algorithm=SignatureAlgorithm.RSA,
+                          signature=raw_signature)
+    return SignedVBMetaPrimaryAnnotation(signature=signature, annotation=self)
+
+  def encode(self, stream):
+    """Encodes the VBMetaPrimaryAnnotation.
+
+    Arguments:
+      stream: a BytesIO to which the signature is written.
+
+    Raises:
+      AftlError: If the encoding failed.
+    """
+    tls_encode_bytes("B", self.vbmeta_hash, stream)
+    try:
+      tls_encode_bytes("B", self.version_incremental.encode("ascii"), stream)
+    except UnicodeError:
+      raise AftlError('Unable to encode version incremental to ASCII')
+    tls_encode_bytes("B", self.manufacturer_key_hash, stream)
+    try:
+      tls_encode_bytes("H", self.description.encode("utf-8"), stream)
+    except UnicodeError:
+      raise AftlError('Unable to encode description to UTF-8')
+
+  def get_expected_size(self):
+    """Returns the size of the encoded annotation."""
+    b = io.BytesIO()
+    self.encode(b)
+    return len(b.getvalue())
 
   def print_desc(self, o):
-    """Print the FirmwareInfoLeaf.
+    """Print the VBMetaPrimaryAnnotation.
 
     Arguments:
       o: The object to write the output to.
     """
-    o.write('    Firmware Info Leaf:\n')
-    # The order of the fields is based on the definition in
-    # proto.aftl_pb2.FirmwareInfo.
-    i = ' ' * 6
+    o.write('      VBMeta Primary Annotation:\n')
+    i = ' ' * 8
     fmt = '{}{:23}{}\n'
     if self.vbmeta_hash:
       o.write(fmt.format(i, 'VBMeta hash:', self.vbmeta_hash.hex()))
     if self.version_incremental:
       o.write(fmt.format(i, 'Version incremental:', self.version_incremental))
-    if self.platform_key:
-      o.write(fmt.format(i, 'Platform key:', self.platform_key))
     if self.manufacturer_key_hash:
       o.write(fmt.format(i, 'Manufacturer key hash:',
                          self.manufacturer_key_hash.hex()))
@@ -933,6 +1065,244 @@
       o.write(fmt.format(i, 'Description:', self.description))
 
 
+class SignedVBMetaPrimaryAnnotation(object):
+  """A Signed VBMetaPrimaryAnnotation.
+
+  Attributes:
+    signature: a Signature.
+    annotation: a VBMetaPrimaryAnnotation.
+  """
+
+  def __init__(self, signature=None, annotation=None):
+    """Default constructor."""
+    if not signature:
+      signature = Signature()
+    self.signature = signature
+    if not annotation:
+      annotation = VBMetaPrimaryAnnotation()
+    self.annotation = annotation
+
+  @classmethod
+  def parse(cls, stream):
+    """Parses a signed annotation."""
+    signature = Signature.parse(stream)
+    annotation = VBMetaPrimaryAnnotation.parse(stream)
+    return cls(signature, annotation)
+
+  def get_expected_size(self):
+    """Returns the size of the encoded signed annotation."""
+    return self.signature.get_expected_size() + \
+             self.annotation.get_expected_size()
+
+  def encode(self, stream):
+    """Encodes the SignedVBMetaPrimaryAnnotation.
+
+    Arguments:
+      stream: a BytesIO to which the object is written.
+
+    Raises:
+      AftlError: If the encoding failed.
+    """
+    self.signature.encode(stream)
+    self.annotation.encode(stream)
+
+  def print_desc(self, o):
+    """Prints the annotation.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    self.annotation.print_desc(o)
+
+class Leaf(abc.ABC):
+  """An abstract class to represent the leaves in the transparency log."""
+  FORMAT_STRING = ('!B'   # Version
+                   'Q'    # Timestamp
+                   'B'    # LeafType
+                  )
+
+  class LeafType(enum.Enum):
+    VBMetaType = 0
+    SignedVBMetaPrimaryAnnotationType = 1
+
+  def __init__(self, version=1, timestamp=0, leaf_type=LeafType.VBMetaType):
+    """Build a new leaf."""
+    self.version = version
+    self.timestamp = timestamp
+    self.leaf_type = leaf_type
+
+  @classmethod
+  def _parse_header(cls, stream):
+    """Parses the header of a leaf.
+
+    This is called with the parse method of the subclasses.
+
+    Arguments:
+      stream: a BytesIO to read the header from.
+
+    Returns:
+      A tuple (version, timestamp, leaf_type).
+
+    Raises:
+      AftlError: If the header cannot be decoded; or if the leaf type is
+          unknown.
+    """
+    data_length = struct.calcsize(cls.FORMAT_STRING)
+    try:
+      (version, timestamp, leaf_type) = struct.unpack(
+          cls.FORMAT_STRING, stream.read(data_length))
+    except struct.error:
+      raise AftlError("Not enough data to parse leaf header")
+    try:
+      leaf_type = cls.LeafType(leaf_type)
+    except ValueError:
+      raise AftlError("Unknown leaf type: {}".format(leaf_type))
+    return version, timestamp, leaf_type
+
+  @classmethod
+  @abc.abstractmethod
+  def parse(cls, data):
+    """Parses a leaf and returned a new object.
+
+    This abstract method must be implemented by the subclass. It may use
+    _parse_header to parse the common fields.
+
+    Arguments:
+      data: a bytes-like object.
+
+    Returns:
+      An object of the type of the particular subclass.
+
+    Raises:
+      AftlError: If the leaf type is incorrect; or if the decoding failed.
+    """
+
+  @abc.abstractmethod
+  def encode(self):
+    """Encodes a leaf.
+
+    This abstract method must be implemented by the subclass. It may use
+    _encode_header to encode the common fields.
+
+    Returns:
+      A bytes with the encoded leaf.
+
+    Raises:
+      AftlError: If the encoding failed.
+    """
+
+  def _get_expected_header_size(self):
+    """Returns the size of the leaf header."""
+    return struct.calcsize(self.FORMAT_STRING)
+
+  def _encode_header(self, stream):
+    """Encodes the header of the leaf.
+
+    This method is called by the encode method in the subclass.
+
+    Arguments:
+      stream: a BytesIO to which the object is written.
+
+    Raises:
+      AftlError: If the encoding failed.
+    """
+    try:
+      stream.write(struct.pack(self.FORMAT_STRING, self.version, self.timestamp,
+                               self.leaf_type.value))
+    except struct.error:
+      raise AftlError('Unable to encode the leaf header')
+
+  def print_desc(self, o):
+    """Prints the leaf header.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    i = ' ' * 6
+    fmt = '{}{:23}{}\n'
+    o.write(fmt.format(i, 'Version:', self.version))
+    o.write(fmt.format(i, 'Timestamp:', self.timestamp))
+    o.write(fmt.format(i, 'Type:', self.leaf_type))
+
+
+class SignedVBMetaPrimaryAnnotationLeaf(Leaf):
+  """A Signed VBMetaPrimaryAnnotation leaf."""
+
+  def __init__(self, version=1, timestamp=0,
+               signed_vbmeta_primary_annotation=None):
+    """Builds a new Signed VBMeta Primary Annotation leaf."""
+    super(SignedVBMetaPrimaryAnnotationLeaf, self).__init__(
+        version=version, timestamp=timestamp,
+        leaf_type=self.LeafType.SignedVBMetaPrimaryAnnotationType)
+    if not signed_vbmeta_primary_annotation:
+      signed_vbmeta_primary_annotation = SignedVBMetaPrimaryAnnotation()
+    self.signed_vbmeta_primary_annotation = signed_vbmeta_primary_annotation
+
+  @property
+  def annotation(self):
+    """Returns the VBMetaPrimaryAnnotation contained in the leaf."""
+    return self.signed_vbmeta_primary_annotation.annotation
+
+  @property
+  def signature(self):
+    """Returns the Signature contained in the leaf."""
+    return self.signed_vbmeta_primary_annotation.signature
+
+  @classmethod
+  def parse(cls, data):
+    """Parses an encoded contained in data.
+
+    Arguments:
+      data: a bytes-like object.
+
+    Returns:
+      A SignedVBMetaPrimaryAnnotationLeaf.
+
+    Raises:
+      AftlError if the leaf type is incorrect; or if the decoding failed.
+    """
+    encoded_leaf = io.BytesIO(data)
+    version, timestamp, leaf_type = Leaf._parse_header(encoded_leaf)
+    if leaf_type != Leaf.LeafType.SignedVBMetaPrimaryAnnotationType:
+      raise AftlError("Incorrect leaf type")
+    signed_annotation = SignedVBMetaPrimaryAnnotation.parse(encoded_leaf)
+    return cls(version=version, timestamp=timestamp,
+               signed_vbmeta_primary_annotation=signed_annotation)
+
+  def get_expected_size(self):
+    """Returns the size of the leaf."""
+    size = self._get_expected_header_size()
+    if self.signed_vbmeta_primary_annotation:
+      size += self.signed_vbmeta_primary_annotation.get_expected_size()
+    return size
+
+  def encode(self):
+    """Encodes the leaf.
+
+    Returns:
+      bytes which contains the encoded leaf.
+
+    Raises:
+      AftlError: If the encoding failed.
+    """
+    stream = io.BytesIO()
+    self._encode_header(stream)
+    self.signed_vbmeta_primary_annotation.encode(stream)
+    return stream.getvalue()
+
+  def print_desc(self, o):
+    """Prints the leaf.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    i = ' ' * 4
+    fmt = '{}{:25}{}\n'
+    o.write(fmt.format(i, 'Leaf:', ''))
+    super(SignedVBMetaPrimaryAnnotationLeaf, self).print_desc(o)
+    self.signed_vbmeta_primary_annotation.print_desc(o)
+
+
 class AftlImage(object):
   """A class for the AFTL image, which contains the transparency log ICPs.
 
@@ -1087,40 +1457,40 @@
     else:
       self.timeout = None
 
-  def add_firmware_info(self, request):
-    """Calls the AddFirmwareInfo RPC on the AFTL server.
+  def add_vbmeta(self, request):
+    """Calls the AddVBMeta RPC on the AFTL server.
 
     Arguments:
-      request: A AddFirmwareInfoRequest message.
+      request: An AddVBMetaRequest message.
 
     Returns:
-      An AddFirmwareInfoReponse message.
+      An AddVBMetaResponse message.
 
     Raises:
       AftlError: If grpc or the proto modules cannot be loaded, if there is an
         error communicating with the log.
     """
     raise NotImplementedError(
-        'AddFirmwareInfo() needs to be implemented by subclass.')
+        'add_vbmeta() needs to be implemented by subclass.')
 
 
 class AftlGrpcCommunication(AftlCommunication):
   """Class that implements GRPC communication to the AFTL server."""
 
-  def add_firmware_info(self, request):
-    """Calls the AddFirmwareInfo RPC on the AFTL server.
+  def add_vbmeta(self, request):
+    """Calls the AddVBMeta RPC on the AFTL server.
 
     Arguments:
-      request: A AddFirmwareInfoRequest message.
+      request: An AddVBMetaRequest message.
 
     Returns:
-      An AddFirmwareInfoReponse message.
+      An AddVBMetaResponse message.
 
     Raises:
       AftlError: If grpc or the proto modules cannot be loaded, if there is an
         error communicating with the log.
     """
-    # Import grpc now to avoid global dependencies as it otherwise breakes
+    # Import grpc now to avoid global dependencies as it otherwise breaks
     # running unittest with atest.
     try:
       import grpc  # pylint: disable=import-outside-toplevel
@@ -1145,8 +1515,8 @@
                      'with domain {}.\n'.format(
                          self.transparency_log_config.target))
     try:
-      response = stub.AddFirmwareInfo(request, timeout=self.timeout,
-                                      metadata=metadata)
+      response = stub.AddVBMeta(request, timeout=self.timeout,
+                                metadata=metadata)
     except grpc.RpcError as e:
       raise AftlError('Error: grpc failure ({})'.format(e))
     return response
@@ -1340,34 +1710,26 @@
     # Calculate the hash of the manufacturer key data.
     m_key_hash = hashlib.sha256(manufacturer_key_data).digest()
 
-    # Create an AddFirmwareInfoRequest protobuf for transmission to AFTL.
-    fw_info = aftl_pb2.FirmwareInfo(vbmeta_hash=vbmeta_hash,
-                                    version_incremental=version_inc,
-                                    manufacturer_key_hash=m_key_hash)
-    signed_fw_info = b''
-    # AFTL supports SHA256_RSA4096 for now, more will be available.
-    algorithm_name = 'SHA256_RSA4096'
-    try:
-      rsa_key = avbtool.RSAPublicKey(manufacturer_key_path)
-      signed_fw_info = rsa_key.sign(algorithm_name, fw_info.SerializeToString(),
-                                    signing_helper, signing_helper_with_files)
-    except avbtool.AvbError as e:
-      raise AftlError('Failed to sign FirmwareInfo with '
-                      '--manufacturer_key: {}'.format(e))
-    fw_info_sig = sigpb_pb2.DigitallySigned(
-        hash_algorithm='SHA256',
-        signature_algorithm='RSA',
-        signature=signed_fw_info)
+    # Build VBMetaPrimaryAnnotation with that data.
+    annotation = VBMetaPrimaryAnnotation(
+        vbmeta_hash=vbmeta_hash, version_incremental=version_inc,
+        manufacturer_key_hash=m_key_hash)
 
-    sfw_info = aftl_pb2.SignedFirmwareInfo(info=fw_info,
-                                           info_signature=fw_info_sig)
-    request = api_pb2.AddFirmwareInfoRequest(
-        vbmeta=vbmeta_image, fw_info=sfw_info)
+    # Sign annotation and add it to the request.
+    signed_annotation = annotation.sign(
+        manufacturer_key_path, signing_helper=signing_helper,
+        signing_helper_with_files=signing_helper_with_files)
 
-    # Submit signed FirmwareInfo to the server.
+    encoded_signed_annotation = io.BytesIO()
+    signed_annotation.encode(encoded_signed_annotation)
+    request = api_pb2.AddVBMetaRequest(
+        vbmeta=vbmeta_image,
+        signed_vbmeta_primary_annotation=encoded_signed_annotation.getvalue())
+
+    # Submit signed VBMeta annotation to the server.
     if not aftl_comms:
       aftl_comms = AftlGrpcCommunication(transparency_log_config, timeout)
-    response = aftl_comms.add_firmware_info(request)
+    response = aftl_comms.add_vbmeta(request)
 
     # Return an AftlIcpEntry representing this response.
     icp_entry = AftlIcpEntry()
@@ -1870,14 +2232,14 @@
     sub_parser.set_defaults(func=self.load_test_aftl)
 
     args = parser.parse_args(argv[1:])
-    try:
-      success = args.func(args)
-    except AttributeError:
+    if not 'func' in args:
       # This error gets raised when the command line tool is called without any
       # arguments. It mimics the original Python 2 behavior.
       parser.print_usage()
       print('aftltool: error: too few arguments')
       sys.exit(2)
+    try:
+      success = args.func(args)
     except AftlError as e:
       # Signals to calling tools that an unhandled exception occured.
       sys.stderr.write('Unhandled AftlError occured: {}\n'.format(e))
diff --git a/aftltool_test.py b/aftltool_test.py
index 006deb9..5fe9446 100755
--- a/aftltool_test.py
+++ b/aftltool_test.py
@@ -25,7 +25,6 @@
 """Unit tests for aftltool."""
 
 import argparse
-import base64
 import binascii
 import io
 import os
@@ -47,6 +46,107 @@
 # TODO(b/149307145): Remove workaround once the referenced bug is fixed.
 TEST_EXEC_PATH = os.path.dirname(os.path.realpath(__file__))
 
+class TlsDataTest(unittest.TestCase):
+
+  def test_decode(self):
+    data = io.BytesIO(b'\x01\x02')
+    value = aftltool.tls_decode_bytes('B', data)
+    self.assertEqual(value, b'\x02')
+    self.assertEqual(data.read(), b'')
+
+    data = io.BytesIO(b'\x00\x01\x03\xff')
+    value = aftltool.tls_decode_bytes('H', data)
+    self.assertEqual(value, b'\x03')
+    self.assertEqual(data.read(), b'\xff')
+
+    data = io.BytesIO(b'\x00\x00\x00\x02\x04\x05\xff\xff')
+    value = aftltool.tls_decode_bytes('L', data)
+    self.assertEqual(value, b'\x04\x05')
+    self.assertEqual(data.read(), b'\xff\xff')
+
+  def test_decode_invalid(self):
+    # Insufficient data for reading the size.
+    with self.assertRaises(aftltool.AftlError):
+      aftltool.tls_decode_bytes('B', io.BytesIO(b''))
+
+    # Invalid byte_size character.
+    with self.assertRaises(aftltool.AftlError):
+      aftltool.tls_decode_bytes('/o/', io.BytesIO(b'\x01\x02\xff'))
+
+    # Insufficient data for reading the value.
+    with self.assertRaises(aftltool.AftlError):
+      aftltool.tls_decode_bytes('B', io.BytesIO(b'\x01'))
+
+  def test_encode(self):
+    stream = io.BytesIO()
+    aftltool.tls_encode_bytes('B', b'\x01\x02\x03\x04', stream)
+    self.assertEqual(stream.getvalue(), b'\x04\x01\x02\x03\x04')
+
+    stream = io.BytesIO()
+    aftltool.tls_encode_bytes('H', b'\x01\x02\x03\x04', stream)
+    self.assertEqual(stream.getvalue(), b'\x00\x04\x01\x02\x03\x04')
+
+  def test_encode_invalid(self):
+    # Byte size is not large enough to encode the value.
+    stream = io.BytesIO()
+    with self.assertRaises(aftltool.AftlError):
+      aftltool.tls_encode_bytes('B', b'\x01'*256, stream)
+
+    # Invalid byte_size character.
+    stream = io.BytesIO()
+    with self.assertRaises(aftltool.AftlError):
+      aftltool.tls_encode_bytes('/o/', b'\x01\x02', stream)
+
+
+class VBMetaPrimaryAnnotationTest(unittest.TestCase):
+
+  def test_decode(self):
+    stream = io.BytesIO(b'\x00\x00\x00\x00\x00')
+    anno = aftltool.VBMetaPrimaryAnnotation.parse(stream)
+    self.assertEqual(anno.vbmeta_hash, b'')
+    self.assertEqual(anno.version_incremental, '')
+    self.assertEqual(anno.manufacturer_key_hash, b'')
+    self.assertEqual(anno.description, '')
+
+  def test_encode(self):
+    stream = io.BytesIO()
+    anno = aftltool.VBMetaPrimaryAnnotation()
+    anno.encode(stream)
+    self.assertEqual(stream.getvalue(), b'\x00\x00\x00\x00\x00')
+
+  def test_encode_invalid(self):
+    stream = io.BytesIO()
+    anno = aftltool.VBMetaPrimaryAnnotation()
+    # Version incremental should be ASCII only.
+    anno.version_incremental = 'ā˜ƒ'
+    with self.assertRaises(aftltool.AftlError):
+      anno.encode(stream)
+
+
+class SignedVBMetaAnnotationLeafTest(unittest.TestCase):
+
+  def test_encode(self):
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf()
+    self.assertEqual(leaf.encode(),
+                     b'\x01'   # Version
+                     b'\x00\x00\x00\x00\x00\x00\x00\x00'  # Timestamp
+                     b'\x01' + # Leaf Type
+                     b'\x00' * 4 + # Empty Signature
+                     b'\x00' * 5) # Empty Annotation
+
+  def test_encode_invalid_type(self):
+    # The version field must be a 1-byte integer.
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf()
+    leaf.version = 'x'
+    with self.assertRaises(aftltool.AftlError):
+      leaf.encode()
+
+  def test_encode_invalid_size(self):
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf()
+    leaf.version = 256
+    with self.assertRaises(aftltool.AftlError):
+      leaf.encode()
+
 
 class AftltoolTestCase(unittest.TestCase):
 
@@ -99,6 +199,28 @@
         b''                                  # metadata (empty)
     )
 
+    # Test Annotation #1
+    anno_1 = aftltool.VBMetaPrimaryAnnotation(vbmeta_hash=b'w'*32,
+                                              version_incremental='x'*5,
+                                              manufacturer_key_hash=b'y'*32,
+                                              description='z'*51)
+    signed_anno_1 = aftltool.SignedVBMetaPrimaryAnnotation(annotation=anno_1)
+
+    self.test_anno_1 = aftltool.SignedVBMetaPrimaryAnnotationLeaf(
+        signed_vbmeta_primary_annotation=signed_anno_1)
+    self.test_anno_1_bytes = (
+        b'\x01'                              # version
+        b'\x00\x00\x00\x00\x00\x00\x00\x00'  # timestamp
+        b'\x01'                              # leaf_type
+        b'\x00'                              # hash_algorithm
+        b'\x00'                              # signature_algorithm
+        + b'\x00\x00'                        # signature
+        + b'\x20' + b'w' * 32                # vbmeta_hash
+        + b'\x05' + b'x' * 5                 # version_incremental
+        + b'\x20' + b'y' * 32                # manufacturer_key_hash
+        + b'\x00\x33' + b'z' * 51            # description
+    )
+
     # Fill each structure with an easily observable pattern for easy validation.
     self.test_proof_hashes_1 = []
     self.test_proof_hashes_1.append(b'b' * 32)
@@ -110,6 +232,7 @@
     self.test_entry_1 = aftltool.AftlIcpEntry()
     self.test_entry_1.log_url = self.test_tl_url_1
     self.test_entry_1.leaf_index = 1
+    self.test_entry_1.annotation_leaf = self.test_anno_1
     self.test_entry_1.log_root_descriptor = self.test_sth_1
     self.test_entry_1.proofs = self.test_proof_hashes_1
     self.test_entry_1.log_root_signature = b'g' * 512
@@ -118,12 +241,13 @@
         b'\x00\x00\x00\x1b'                  # Transparency log url size.
         b'\x00\x00\x00\x00\x00\x00\x00\x01'  # Leaf index.
         b'\x00\x00\x00\x3d'                  # Log root descriptor size.
-        b'\x00\x00\x00\x00'                  # Firmware info leaf size.
+        b'\x00\x00\x00\x8b'                  # Annotation leaf size.
         b'\x02\x00'                          # Log root signature size.
         b'\x04'                              # Number of hashes in ICP.
         b'\x00\x00\x00\x80'                  # Size of ICP in bytes.
         + self.test_tl_url_1.encode('ascii') # Transparency log url.
         + self.test_sth_1_bytes
+        + self.test_anno_1_bytes
         + b'g' * 512                         # Log root signature.
         + b'b' * 32                          # Hashes...
         + b'c' * 32
@@ -161,6 +285,7 @@
     self.test_entry_2 = aftltool.AftlIcpEntry()
     self.test_entry_2.log_url = self.test_tl_url_2
     self.test_entry_2.leaf_index = 2
+    self.test_entry_2.annotation_leaf = self.test_anno_1
     self.test_entry_2.log_root_descriptor = self.test_sth_2
     self.test_entry_2.log_root_signature = b'd' * 512
     self.test_entry_2.proofs = self.test_proof_hashes_2
@@ -169,12 +294,13 @@
         b'\x00\x00\x00\x1a'                   # Transparency log url size.
         b'\x00\x00\x00\x00\x00\x00\x00\x02'   # Leaf index.
         b'\x00\x00\x00\x3f'                   # Log root descriptor size.
-        b'\x00\x00\x00\x00'                   # Firmware info leaf size.
+        b'\x00\x00\x00\x8b'                   # Annotation leaf size.
         b'\x02\x00'                           # Log root signature size.
         b'\x02'                               # Number of hashes in ICP.
         b'\x00\x00\x00\x40'                   # Size of ICP in bytes.
         + self.test_tl_url_2.encode('ascii')  # Transparency log url.
         + self.test_sth_2_bytes               # Log root
+        + self.test_anno_1_bytes
         + b'd' * 512                          # Log root signature.
         + b'g' * 32                           # Hashes...
         + b'h' * 32)
@@ -188,61 +314,88 @@
         b'AFTL'                                         # Magic.
         + struct.pack('!L', avbtool.AVB_VERSION_MAJOR)  # Major version.
         + struct.pack('!L', avbtool.AVB_VERSION_MINOR)  # Minor version.
-        + b'\x00\x00\x05\xb9'                           # Image size.
+        + b'\x00\x00\x06\xcf'                           # Image size.
         b'\x00\x02'                                     # Number of ICP entries.
         + self.test_entry_1_bytes
         + self.test_entry_2_bytes)
 
-    # pylint: disable=no-member
-    self.test_afi_resp = api_pb2.AddFirmwareInfoResponse()
-    self.test_afi_resp.fw_info_proof.proof.leaf_index = 6263
+    self.test_avbm_resp = api_pb2.AddVBMetaResponse()
+    self.test_avbm_resp.annotation_proof.proof.leaf_index = 9127
     hashes = [
-        '3ad99869646980c0a51d637a9791f892d12e0bc83f6bac5d305a9e289e7f7e8b',
-        '2e5c664d2aee64f71cb4d292e787d0eae7ca9ed80d1e08abb41d26baca386c05',
-        'a671dd99f8d97e9155cc2f0a9dc776a112a5ec5b821ec71571bb258ac790717a',
-        '78046b839595e4e49ad4b0c73f92bf4803aacd4a3351181086509d057ef0d7a9',
-        'c0a7e013f03e7c69e9402070e113dadb345868cf144ccb174fabc384b5605abf',
-        'dc36e5dbe36abe9f4ad10f14170aa0148b6fe3fcaba9df43deaf4dede01b02e8',
-        'b063e7fb665370a361718208756c363dc5206e2e9af9b4d847d81289cdae30de',
-        'a69ea5ba88a221103636d3f4245c800570eb86ad9276121481521f97d0a04a81']
+        '61076ca285b4982669e67757f55682ddc43ab5c11ba671260f82a8efa8831f94',
+        '89c2fbcc58da25a65ce5e9b4fb22aaf208b20601f0bc023f73f05d35bc1f3bac',
+        '75d26b5f754b4bed332a3ce2a2bfea0334706a974b7e00ee663f0279fa8b446e',
+        'e1cd9c96feb893b5ef7771e424ac1c6c47509c2b98bc578d22ad07369c9641aa',
+        'e83e0e4dd352b1670a55f93f88781a73bb41efcadb9927399f59459dfa14bc40',
+        '8d5d25996117c88655d66f685baa3c94390867a040507b10587b17fbe92b496a',
+        '5de4c627e9ca712f207d6056f56f0d3286ed4a5381ed7f3cc1aa470217734138',
+        '19acfdb424d7fe28d1f850c76302f78f9a50146a5b9c65f9fdfbbc0173fd6993']
     for h in hashes:
-      self.test_afi_resp.fw_info_proof.proof.hashes.append(
+      self.test_avbm_resp.annotation_proof.proof.hashes.append(
           binascii.unhexlify(h))
-    self.test_afi_resp.fw_info_proof.sth.key_hint = binascii.unhexlify(
+    self.test_avbm_resp.annotation_proof.sth.key_hint = binascii.unhexlify(
         '5af859abce8fe1ea')
-    self.test_afi_resp.fw_info_proof.sth.log_root = binascii.unhexlify(
-        '000100000000000018782053b182b55dc1377197c938637f50093131daea4'
-        'd0696b1eae5b8a014bfde884a15edb28f1fc7954400000000000013a50000'
+    self.test_avbm_resp.annotation_proof.sth.log_root = binascii.unhexlify(
+        '0001'
+        '00000000000023a8'
+        '20'
+        '9a5f71340f8dc98bdc6320f976dda5f34db8554cb273ba5ab60f1697c519d6f6'
+        '1609ae15024774b1'
+        '0000000000001e5a'
+        '0000'
     )
-    self.test_afi_resp.fw_info_proof.sth.log_root_signature = (
+    self.test_avbm_resp.annotation_proof.sth.log_root_signature = (
         binascii.unhexlify(
-            'c264bc7986a1cf56364ca4dd04989f45515cb8764d05b4fb2b880172585ea404'
-            '2105f95a0e0471fb6e0f8c762b14b2e526fb78eaddcc61484917795a12f6ab3b'
-            '557b5571d492d07d7950595f9ad8647a606c7c633f4697c5eb59c272aeca0419'
-            '397c70a3b9b51537537c4ea6b49d356110e70a9286902f814cc6afbeafe612e4'
-            '9e180146140e902bdd9e9dae66b37b4943150a9571949027a648db88a4eea3ad'
-            'f930b4fa6a183e97b762ab0e55a3a26aa6b0fd44d30531e2541ecb94bf645e62'
-            '59e8e3151e7c3b51a09fe24557ce2fd2c0ecdada7ce99c390d2ef10e5d075801'
-            '7c10d49c55cdee930959cc35f0104e04f296591eeb5defbc9ebb237da7b204ca'
-            'a4608cb98d6bc3a01f18585a04441caf8ec7a35aa2d35f7483b92b14fd0f4a41'
-            '3a91133545579309adc593222ca5032a103b00d8fcaea911936dbec11349e4dd'
-            '419b091ea7d1130570d70e2589dd9445fd77fd7492507e1c87736847b9741cc6'
-            '236868af42558ff6e833e12010c8ede786e43ada40ff488f5f1870d1619887d7'
-            '66a24ad0a06a47cc14e2f7db07361be191172adf3155f49713807c7c265f5a84'
-            '040fc84246ccf7913e44721f0043cea05ee774e457e13206775eee992620c3f9'
-            'd2b2584f58aac19e4afe35f0a17df699c45729f94101083f9fc4302659a7e6e0'
-            'e7eb36f8d1ca0be2c9010160d329bd2d17bb707b010fdd63c30b667a0b886cf9'
+            '7c37903cc76e8689a6b31da9ad56c3daeb6194029510297cc7d147278390da33'
+            '09c4d9eb1f6be0cdcd1de5315b0b3b573cc9fcd8620d3fab956abbe3c597a572'
+            '46e5a5d277c4cc4b590872d0292fa64e1d3285626b1dedeb00b6aa0a7a0717c0'
+            '7d4c89b68fda9091be06180be1369675a7c4ce7f42cca133ef0daf8dcc5ba1ee'
+            '930cef6dcb71b0a7690446e19661c8e18c089a5d6f6fc9299a0592efb33a4db5'
+            '4c640027fa4f0ad0009f8bf75ec5fc17e0fa1091fabe74fe52738443745066ab'
+            '48f99b297809b863c01016abda17a2479fce91f9929c60bc2ce15e474204fc5a'
+            '8e79b2190aadb7c149671e8c76a4da506860f8d6020fb2eaabfee025cc267bad'
+            '3c8257186c8aaf1da9eefe50cae4b3e8deb66033ebc4bfcda2b317f9e7d2dd78'
+            'b47f2d86795815d82058ad4cba8fc7983a3bbf843e9b8c7ec7f1ae137be6848d'
+            '03c76eefdac40ce5e66cc23d9f3e79ad87acbe7ec0c0bb419a7d368ae1e73c85'
+            '742871f847bde69c871e8797638e0e270282fb058ef1cbcba52aded9dcc8249b'
+            '38fbed8424c33b8cfcde4f49797c64dda8d089d73b84062602fd41c66091543c'
+            'e13c18cfa7f8300530ad4b7adb8924bbb86d17bcc5f1d3d74c522a7dcc8c3c1f'
+            '28a999f2fe1bfe5520c66f93f7c90996dc7f52e62dd95ace9ceace90324c3040'
+            '669b7f5aeb5c5a53f217f1de46e32f80d0aaaf7d9cc9d0e8f8fd7026c612103a'
         )
     )
-    self.test_afi_resp.fw_info_leaf = (
-        b'{\"timestamp\":{\"seconds\":1580115370,\"nanos\":621454825},\"Va'
-        b'lue\":{\"FwInfo\":{\"info\":{\"info\":{\"vbmeta_hash\":\"ViNzEQS'
-        b'/oc/bJ13yl40fk/cvXw90bxHQbzCRxgHDIGc=\",\"version_incremental\":'
-        b'\"1\",\"manufacturer_key_hash\":\"yBCrUOdjvaAh4git5EgqWa5neegUao'
-        b'XeLlB67+N8ObY=\"}}}}}')
 
-    self.test_fw_info_leaf = aftltool.FirmwareInfoLeaf(
-        self.test_afi_resp.fw_info_leaf)
+    anno = aftltool.VBMetaPrimaryAnnotation(
+        vbmeta_hash=bytes.fromhex(
+            '5623731104bfa1cfdb275df2978d1f93f72f5f0f746f11d06f3091c601c32067'),
+        version_incremental='only_for_testing',
+        manufacturer_key_hash=bytes.fromhex(
+            '83ab3b109b73a1d32dce4153a2de57a1a0485052db8364f3180d98614749d7f7'))
+    raw_signature = bytes.fromhex(
+        '6a523021bc5b933bb58c38c8238be3a5fe1166002f5df8b77dee9dd22d353595'
+        'be7996656d3824ebf4e1411a05ee3652d64669d3d62b167d3290dbdf4f2741ba'
+        '4b6472e1bd71fc1860465fdcdca1ff08c4ab0420d7dcbf4ad144f64e211d8f92'
+        '081ba51192358e2478195e573d000282423b23e6dd945069907dcf11520ff11a'
+        '250e26643b820f8a5d80ccfe7d5d84f58e549cd05630f2254ade8edc88d9aa8a'
+        'ec2089f84643854e1f265a4f746598ce4cae529c4eaa637f6e35fa1d1da9254e'
+        'ec8dfede7a4313f7b151547dcdde98782ce6fb3149326ee5b8e750813d3fd37a'
+        '738fe92f6111bf0dff4091769e216b842980e05716f2e50268a7dcca430e175e'
+        '711f80e41a1a28f20635741ac11a56f97492d30db6d1955a827daf8e83faebe5'
+        'a96e18a13c558ae561a02c90982514c853db0296c2e791e68b77c30e6232a3b7'
+        'ed355441d4706277f33a01735f56cb8279336491731939691683f96f1c3e3183'
+        'a0b77510d6ff0199b7688902044829793106546fd6fd4a5294d63c31c91256ad'
+        'f7be6d053e77875698ad32ffaaeaac5d54b432e537f72549d2543072ae35578f'
+        '138d82afcadd668511ba276ce02b6f9c18ef3b6f2f6ae0d123e9f8cb930f21a9'
+        'c49a6d9e95de741c7860593a956735e1b77e9851ecb1f6572abf6e2c8ba15085'
+        'e37e0f7bab0a30d108b997ed5edd74cf7f89cf082590a6f0af7a3a1f68c0077a')
+    signature = aftltool.Signature(signature=raw_signature)
+    signed_anno = aftltool.SignedVBMetaPrimaryAnnotation(annotation=anno,
+                                                         signature=signature)
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf(
+        timestamp=1587991742919072870,
+        signed_vbmeta_primary_annotation=signed_anno).encode()
+    self.test_avbm_resp.annotation_leaf = leaf
+
 
   def tearDown(self):
     """Tears down the test bed for the unit tests."""
@@ -448,56 +601,6 @@
     # Valid image but checked with empty list of keys.
     self.assertFalse(desc.verify_vbmeta_image(vbmeta_image, []))
 
-  def test_verify_vbmeta_image_with_2_icp_from_different_logs(self):
-    """Tests the verify_vbmeta_image method."""
-    # Valid vbmeta image without footer with 2 ICPs from different logs.
-    tool = aftltool.Aftl()
-    image_path = self.get_testdata_path(
-        'aftltool/aftl_output_vbmeta_with_2_icp_different_logs.img')
-    vbmeta_image, _ = tool.get_vbmeta_image(image_path)
-    desc = tool.get_aftl_image(image_path)
-
-    # Valid image checked against log keys from both logs.
-    self.assertTrue(desc.verify_vbmeta_image(
-        vbmeta_image, [
-            self.get_testdata_path('aftltool/aftl_pubkey_1.pub'),
-            self.get_testdata_path('aftltool/aftl_pubkey_2.pub')
-        ]))
-
-    # Valid image checked with one of the keys with an invalid file path.
-    self.assertFalse(desc.verify_vbmeta_image(
-        vbmeta_image, [
-            self.get_testdata_path('aftltool/aftl_pubkey_1.pub'),
-            self.get_testdata_path('non_existent_blabli')
-        ]))
-
-    # Valid image checked with one of the keys being a invalid key.
-    self.assertFalse(desc.verify_vbmeta_image(
-        vbmeta_image, [
-            self.get_testdata_path('aftltool/aftl_pubkey_1.pub'),
-            self.get_testdata_path('large_blob.bin')
-        ]))
-
-    # Valid image checked with one of the keys being None.
-    self.assertFalse(desc.verify_vbmeta_image(
-        vbmeta_image, [
-            self.get_testdata_path('aftltool/aftl_pubkey_1.pub'),
-            None
-        ]))
-
-    # Valid vbmeta image checked against only one of the log keys.
-    self.assertFalse(desc.verify_vbmeta_image(
-        vbmeta_image, [self.get_testdata_path('aftltool/aftl_pubkey_1.pub')]))
-    self.assertFalse(desc.verify_vbmeta_image(
-        vbmeta_image, [self.get_testdata_path('aftltool/aftl_pubkey_2.pub')]))
-
-    # Valid image checked with invalid key.
-    self.assertFalse(desc.verify_vbmeta_image(
-        vbmeta_image, [self.get_testdata_path('large_blob.bin')]))
-
-    # Valid image but checked with empty list of keys.
-    self.assertFalse(desc.verify_vbmeta_image(vbmeta_image, []))
-
   def test_encode(self):
     """Tests encode method."""
     desc_bytes = self.test_aftl_desc.encode()
@@ -649,7 +752,7 @@
     self.assertEqual(entry.log_url_size, 0)
     self.assertEqual(entry.leaf_index, 0)
     self.assertEqual(entry.log_root_descriptor_size, 29)
-    self.assertEqual(entry.fw_info_leaf_size, 0)
+    self.assertEqual(entry.annotation_leaf_size, 19)
     self.assertEqual(entry.log_root_sig_size, 0)
     self.assertEqual(entry.proof_hash_count, 0)
     self.assertEqual(entry.inc_proof_size, 0)
@@ -663,7 +766,7 @@
     entry = aftltool.AftlIcpEntry(self.test_entry_1_bytes)
     self.assertEqual(entry.log_url_size, len(self.test_tl_url_1))
     self.assertEqual(entry.leaf_index, 1)
-    self.assertEqual(entry.fw_info_leaf_size, 0)
+    self.assertEqual(entry.annotation_leaf_size, 139)
     self.assertEqual(entry.log_root_sig_size, 512)
     self.assertEqual(entry.proof_hash_count, len(self.test_proof_hashes_1))
     self.assertEqual(entry.inc_proof_size, 128)
@@ -680,11 +783,11 @@
     """Tests get_expected_size method."""
     # Default record.
     entry = aftltool.AftlIcpEntry()
-    self.assertEqual(entry.get_expected_size(), 56)
+    self.assertEqual(entry.get_expected_size(), 75)
     self.assertEqual(entry.get_expected_size(), len(entry.encode()))
 
     # Test record.
-    self.assertEqual(self.test_entry_1.get_expected_size(), 755)
+    self.assertEqual(self.test_entry_1.get_expected_size(), 894)
     self.assertEqual(self.test_entry_1.get_expected_size(),
                      len(self.test_entry_1.encode()))
 
@@ -717,15 +820,17 @@
   def test_translate_response(self):
     """Tests translate_response method."""
     entry = aftltool.AftlIcpEntry()
-    entry.translate_response('aftl-test.foo.bar:80', self.test_afi_resp)
+    entry.translate_response('aftl-test.foo.bar:80', self.test_avbm_resp)
     self.assertEqual(entry.log_url, 'aftl-test.foo.bar:80')
-    self.assertEqual(entry.leaf_index, 6263)
+    self.assertEqual(entry.leaf_index, 9127)
     self.assertEqual(entry.log_root_descriptor.encode(),
-                     self.test_afi_resp.fw_info_proof.sth.log_root)
-    self.assertEqual(entry.log_root_signature,
-                     self.test_afi_resp.fw_info_proof.sth.log_root_signature)
-    self.assertEqual(entry.proofs,
-                     self.test_afi_resp.fw_info_proof.proof.hashes)
+                     self.test_avbm_resp.annotation_proof.sth.log_root)
+    self.assertEqual(
+        entry.log_root_signature,
+        self.test_avbm_resp.annotation_proof.sth.log_root_signature)
+    self.assertEqual(
+        entry.proofs,
+        self.test_avbm_resp.annotation_proof.proof.hashes)
 
   def test_verify_icp(self):
     """Tests verify_icp method."""
@@ -735,17 +840,18 @@
 
       # Valid ICP.
       entry = aftltool.AftlIcpEntry()
-      entry.translate_response(self.test_tl_url_1, self.test_afi_resp)
+      entry.translate_response(self.test_tl_url_1, self.test_avbm_resp)
       self.assertTrue(entry.verify_icp(key_file.name))
 
-      # Invalid ICP where fw_info_leaf is not matching up with proofs.
+      # Invalid ICP where annotation_leaf is not matching up with proofs.
       # pylint: disable=protected-access
       entry = aftltool.AftlIcpEntry()
-      entry.translate_response(self.test_tl_url_1, self.test_afi_resp)
-      fw_info_leaf_bytes = entry.fw_info_leaf._fw_info_leaf_bytes.replace(
-          b'ViNzEQS', b'1234567')
-      entry.fw_info_leaf._fw_info_leaf_bytes = fw_info_leaf_bytes
-      self.assertFalse(entry.verify_icp(key_file.name))
+      entry.translate_response(self.test_tl_url_1, self.test_avbm_resp)
+      vbmeta_hash = entry.annotation_leaf.annotation.vbmeta_hash
+      vbmeta_hash = vbmeta_hash.replace(b"\x56\x23\x73\x11",
+                                        b"\x00\x00\x00\x00")
+      entry.annotation_leaf.annotation.vbmeta_hash = vbmeta_hash
+      self.assertFalse(entry.verify_icp(key_file))
 
   def test_verify_vbmeta_image(self):
     """Tests the verify_vbmeta_image method."""
@@ -995,94 +1101,54 @@
     self.assertIn('Metadata:', desc)
 
 
-class FirmwareInfoLeafTest(AftltoolTestCase):
-  """Test suite for testing the FirmwareInfoLeaf."""
+class SignedVBMetaPrimaryAnnotationLeafTest(AftltoolTestCase):
+  """Test suite for testing the Leaf."""
 
   def test__init__(self):
     """Tests constructor and properties methods."""
     # Calls constructor without data.
-    leaf = aftltool.FirmwareInfoLeaf()
-    self.assertTrue(leaf.is_valid())
-    self.assertEqual(leaf.vbmeta_hash, None)
-    self.assertEqual(leaf.version_incremental, None)
-    self.assertEqual(leaf.platform_key, None)
-    self.assertEqual(leaf.manufacturer_key_hash, None)
-    self.assertEqual(leaf.description, None)
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf()
+    self.assertEqual(leaf.version, 1)
+    self.assertEqual(leaf.timestamp, 0)
+    self.assertEqual(leaf.signature.signature, b'')
+    self.assertEqual(leaf.annotation.vbmeta_hash, b'')
+    self.assertEqual(leaf.annotation.description, '')
 
-    # Calls constructor with data.
-    leaf = aftltool.FirmwareInfoLeaf(self.test_afi_resp.fw_info_leaf)
-    self.assertTrue(leaf.is_valid())
-    self.assertEqual(
-        leaf.vbmeta_hash,
-        base64.b64decode('ViNzEQS/oc/bJ13yl40fk/cvXw90bxHQbzCRxgHDIGc='))
-    self.assertEqual(leaf.version_incremental, '1')
-    self.assertEqual(leaf.platform_key, None)
-    self.assertEqual(
-        leaf.manufacturer_key_hash,
-        base64.b64decode('yBCrUOdjvaAh4git5EgqWa5neegUaoXeLlB67+N8ObY='))
-    self.assertEqual(leaf.description, None)
+  def test_parse(self):
+    # Calls parse with valid data.
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf.parse(
+        self.test_anno_1_bytes)
+    self.assertEqual(leaf.annotation.vbmeta_hash, b'w'*32)
+    self.assertEqual(leaf.annotation.version_incremental, 'x'*5)
+    self.assertEqual(leaf.annotation.manufacturer_key_hash, b'y'*32)
+    self.assertEqual(leaf.annotation.description, 'z'*51)
 
-    # Calls constructor with invalid JSON data.
+    # Calls parse with invalid data.
     with self.assertRaises(aftltool.AftlError):
-      leaf = aftltool.FirmwareInfoLeaf('Invalid JSON.')
+      leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf.parse(b'Invalid data')
 
   def test_get_expected_size(self):
     """Tests get_expected_size method."""
     # Calls constructor without data.
-    leaf = aftltool.FirmwareInfoLeaf()
-    self.assertEqual(leaf.get_expected_size(), 0)
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf()
+    self.assertEqual(leaf.get_expected_size(), 19)
 
     # Calls constructor with data.
-    leaf = aftltool.FirmwareInfoLeaf(self.test_afi_resp.fw_info_leaf)
+    leaf = aftltool.SignedVBMetaPrimaryAnnotationLeaf.parse(
+        self.test_anno_1_bytes)
     self.assertEqual(leaf.get_expected_size(),
-                     len(self.test_afi_resp.fw_info_leaf))
+                     len(self.test_anno_1_bytes))
 
   def test_encode(self):
     """Tests encode method."""
-    # Calls constructor without data.
-    leaf = aftltool.FirmwareInfoLeaf()
-    self.assertEqual(leaf.encode(), b'')
-
     # Calls constructor with data.
-    self.assertEqual(self.test_fw_info_leaf.encode(),
-                     self.test_afi_resp.fw_info_leaf)
-
-  def test_is_valid(self):
-    """Tests is_valid method."""
-    # Calls constructor without data.
-    leaf = aftltool.FirmwareInfoLeaf()
-    self.assertTrue(leaf.is_valid())
-
-    # Calls constructor with data.
-    self.assertTrue(self.test_fw_info_leaf.is_valid())
-
-    # Incorrect name for Value key.
-    invalid_value_key_name = (
-        b'{\"timestamp\":{\"seconds\":1580115370,\"nanos\":621454825},\"In'
-        b'val\":{\"FwInfo\":{\"info\":{\"info\":{\"vbmeta_hash\":\"ViNzEQS'
-        b'/oc/bJ13yl40fk/cvXw90bxHQbzCRxgHDIGc=\",\"version_incremental\":'
-        b'\"1\",\"manufacturer_key_hash\":\"yBCrUOdjvaAh4git5EgqWa5neegUao'
-        b'XeLlB67+N8ObY=\"}}}}}')
-
-    with self.assertRaises(aftltool.AftlError):
-      aftltool.FirmwareInfoLeaf(invalid_value_key_name)
-
-    # Within Firmware Info having a field which does not exist in
-    # proto.aftl_pb2.FirmwareInfo.
-    invalid_fields = (
-        b'{\"timestamp\":{\"seconds\":1580115370,\"nanos\":621454825},\"Va'
-        b'lue\":{\"FwInfo\":{\"info\":{\"info\":{\"invalid_field\":\"ViNzEQS'
-        b'/oc/bJ13yl40fk/cvXw90bxHQbzCRxgHDIGc=\",\"version_incremental\":'
-        b'\"1\",\"manufacturer_key_hash\":\"yBCrUOdjvaAh4git5EgqWa5neegUao'
-        b'XeLlB67+N8ObY=\"}}}}}')
-
-    with self.assertRaises(aftltool.AftlError):
-      aftltool.FirmwareInfoLeaf(invalid_fields)
+    self.assertEqual(self.test_anno_1.encode(),
+                     self.test_anno_1_bytes)
 
   def test_print_desc(self):
     """Tests print_desc method."""
     buf = io.StringIO()
-    self.test_fw_info_leaf.print_desc(buf)
+    self.test_anno_1.print_desc(buf)
     desc = buf.getvalue()
 
     # Cursory check whether the printed description contains something useful.
@@ -1098,7 +1164,7 @@
 
     Arguments:
       transparency_log_config: An aftltool.TransparencyLogConfig instance.
-      canned_response: AddFirmwareInfoResponse to return or the Exception to
+      canned_response: AddVBMetaResponse to return or the Exception to
         raise.
     """
     super(AftlMockCommunication, self).__init__(transparency_log_config,
@@ -1106,7 +1172,7 @@
     self.request = None
     self.canned_response = canned_response
 
-  def add_firmware_info(self, request):
+  def add_vbmeta(self, request):
     """Records the request and returns the canned response."""
     self.request = request
 
@@ -1122,7 +1188,7 @@
     """Initializes the object.
 
     Arguments:
-      canned_response: AddFirmwareInfoResponse to return or the Exception to
+      canned_response: AddVBMetaResponse to return or the Exception to
         raise.
     """
     self.mock_canned_response = canned_response
@@ -1284,30 +1350,36 @@
   def test_request_inclusion_proof(self):
     """Tests the request_inclusion_proof method."""
     # Always work with a mock independent if run as unit or integration tests.
-    aftl = AftlMock(self.test_afi_resp)
+    aftl = AftlMock(self.test_avbm_resp)
 
     icp = aftl.request_inclusion_proof(
         self.transparency_log_config, b'a' * 1024, '1',
         self.get_testdata_path('testkey_rsa4096.pem'), None, None, None)
     self.assertEqual(icp.leaf_index,
-                     self.test_afi_resp.fw_info_proof.proof.leaf_index)
+                     self.test_avbm_resp.annotation_proof.proof.leaf_index)
     self.assertEqual(icp.proof_hash_count,
-                     len(self.test_afi_resp.fw_info_proof.proof.hashes))
+                     len(self.test_avbm_resp.annotation_proof.proof.hashes))
     self.assertEqual(icp.log_url, self.aftl_host)
     self.assertEqual(
         icp.log_root_descriptor.root_hash, binascii.unhexlify(
-            '53b182b55dc1377197c938637f50093131daea4d0696b1eae5b8a014bfde884a'))
+            '9a5f71340f8dc98bdc6320f976dda5f34db8554cb273ba5ab60f1697c519d6f6'))
 
-    self.assertEqual(icp.fw_info_leaf.version_incremental, '1')
+    self.assertEqual(icp.annotation_leaf.annotation.version_incremental,
+                     'only_for_testing')
     # To calculate the hash of the a RSA key use the following command:
     # openssl rsa -in test/data/testkey_rsa4096.pem -pubout \
     #    -outform DER | sha256sum
-    self.assertEqual(icp.fw_info_leaf.manufacturer_key_hash, base64.b64decode(
-        'yBCrUOdjvaAh4git5EgqWa5neegUaoXeLlB67+N8ObY='))
+    self.assertEqual(
+        icp.annotation_leaf.annotation.manufacturer_key_hash,
+        bytes.fromhex(
+            "83ab3b109b73a1d32dce4153a2de57a1a0485052db8364f3180d98614749d7f7"))
 
-    self.assertEqual(icp.log_root_signature,
-                     self.test_afi_resp.fw_info_proof.sth.log_root_signature)
-    self.assertEqual(icp.proofs, self.test_afi_resp.fw_info_proof.proof.hashes)
+    self.assertEqual(
+        icp.log_root_signature,
+        self.test_avbm_resp.annotation_proof.sth.log_root_signature)
+    self.assertEqual(
+        icp.proofs,
+        self.test_avbm_resp.annotation_proof.proof.hashes)
 
   # pylint: disable=no-member
   def test_request_inclusion_proof_failure(self):
@@ -1323,7 +1395,7 @@
   def test_request_inclusion_proof_manuf_key_not_4096(self):
     """Tests request_inclusion_proof with manufacturing key not of size 4096."""
     # Always work with a mock independent if run as unit or integration tests.
-    aftl = AftlMock(self.test_afi_resp)
+    aftl = AftlMock(self.test_avbm_resp)
     with self.assertRaises(aftltool.AftlError) as e:
       aftl.request_inclusion_proof(
           self.transparency_log_config, b'a' * 1024, 'version_inc',
@@ -1332,7 +1404,7 @@
 
   def test_make_and_verify_icp_with_1_log(self):
     """Tests make_icp_from_vbmeta, verify_image_icp & info_image_icp."""
-    aftl = self.get_aftl_implementation(self.test_afi_resp)
+    aftl = self.get_aftl_implementation(self.test_avbm_resp)
 
     # Make a VBmeta image with ICP.
     with tempfile.NamedTemporaryFile('wb+') as output_file:
@@ -1357,7 +1429,7 @@
 
   def test_make_and_verify_icp_with_2_logs(self):
     """Tests make_icp_from_vbmeta, verify_image_icp & info_image_icp."""
-    aftl = self.get_aftl_implementation(self.test_afi_resp)
+    aftl = self.get_aftl_implementation(self.test_avbm_resp)
 
     # Reconfigures default parameters with two transparency logs.
     self.make_icp_default_params['transparency_log_configs'] = [
@@ -1388,10 +1460,10 @@
   def test_info_image_icp(self):
     """Tests info_image_icp with vbmeta image with 2 ICP."""
     # Always work with a mock independent if run as unit or integration tests.
-    aftl = AftlMock(self.test_afi_resp)
+    aftl = AftlMock(self.test_avbm_resp)
 
     image_path = self.get_testdata_path(
-        'aftltool/aftl_output_vbmeta_with_2_icp_different_logs.img')
+        'aftltool/aftl_output_vbmeta_with_2_icp_same_log.img')
     self.info_icp_default_params['vbmeta_image_path'] = image_path
 
     # Verifies the generated image.
@@ -1401,7 +1473,7 @@
   def test_info_image_icp_fail(self):
     """Tests info_image_icp with invalid vbmeta image."""
     # Always work with a mock independent if run as unit or integration tests.
-    aftl = AftlMock(self.test_afi_resp)
+    aftl = AftlMock(self.test_avbm_resp)
 
     image_path = self.get_testdata_path('large_blob.bin')
     self.info_icp_default_params['vbmeta_image_path'] = image_path
@@ -1413,34 +1485,18 @@
   def test_verify_image_icp(self):
     """Tets verify_image_icp with 2 ICP with all matching log keys."""
     # Always work with a mock independent if run as unit or integration tests.
-    aftl = AftlMock(self.test_afi_resp)
+    aftl = AftlMock(self.test_avbm_resp)
 
     image_path = self.get_testdata_path(
-        'aftltool/aftl_output_vbmeta_with_2_icp_different_logs.img')
+        'aftltool/aftl_output_vbmeta_with_2_icp_same_log.img')
     self.verify_icp_default_params['vbmeta_image_path'] = image_path
     self.verify_icp_default_params['transparency_log_pub_keys'] = [
         self.get_testdata_path('aftltool/aftl_pubkey_1.pub'),
-        self.get_testdata_path('aftltool/aftl_pubkey_2.pub')
     ]
 
     result = aftl.verify_image_icp(**self.verify_icp_default_params)
     self.assertTrue(result)
 
-  def test_verify_image_icp_failure(self):
-    """Tests verify_image_icp with 2 ICP but only one matching log key."""
-    # Always work with a mock independent if run as unit or integration tests.
-    aftl = AftlMock(self.test_afi_resp)
-
-    image_path = self.get_testdata_path(
-        'aftltool/aftl_output_vbmeta_with_2_icp_different_logs.img')
-    self.verify_icp_default_params['vbmeta_image_path'] = image_path
-    self.verify_icp_default_params['transparency_log_pub_keys'] = [
-        self.get_testdata_path('aftltool/aftl_pubkey_1.pub')
-    ]
-
-    result = aftl.verify_image_icp(**self.verify_icp_default_params)
-    self.assertFalse(result)
-
   def test_make_icp_with_invalid_grpc_service(self):
     """Tests make_icp_from_vbmeta command with a host not supporting GRPC."""
     aftl = self.get_aftl_implementation(aftltool.AftlError('Comms error'))
@@ -1468,7 +1524,7 @@
 
   def test_load_test_single_process_single_submission(self):
     """Tests load_test_aftl command with 1 process which does 1 submission."""
-    aftl = self.get_aftl_implementation(self.test_afi_resp)
+    aftl = self.get_aftl_implementation(self.test_avbm_resp)
 
     with tempfile.TemporaryDirectory() as tmp_dir:
       self.load_test_aftl_default_params[
@@ -1485,7 +1541,7 @@
 
   def test_load_test_multi_process_multi_submission(self):
     """Tests load_test_aftl command with 2 processes and 2 submissions each."""
-    aftl = self.get_aftl_implementation(self.test_afi_resp)
+    aftl = self.get_aftl_implementation(self.test_avbm_resp)
 
     self.load_test_aftl_default_params['process_count'] = 2
     self.load_test_aftl_default_params['submission_count'] = 2
diff --git a/proto/README.md b/proto/README.md
index eb3966c..ee64b01 100644
--- a/proto/README.md
+++ b/proto/README.md
@@ -2,27 +2,20 @@
 ---
 
 This directory contains the proto definitions required to communicate with an
-AFTL server. Two (api.proto and aftl.proto) contain the definitions of the
-protos needed to communicate with the AFTL Trillian personality. The remainder
-are dependencies. The original repos and purpose for each proto file are as
+AFTL server. The original repos and purpose for each proto file are as
 follows:
 
-* aftl.proto
-   <!-- TODO(danielaustin): Add detailed message descriptions. -->
-   Contains messages used by the AFTL frontend and the Trillian log.
 * api.proto
-   <!-- TODO(danielaustin): Add detailed message descriptions. -->
-   Contains the messages to communicate through the AFTL personality.
+   Contains the messages to communicate with the AFTL personality.
 * crypto/keyspb/keyspb.proto
    From https://github.com/google/trillian
    Dependency of trillian.proto
    Contains the PublicKey message definition used by Tree.
 * crypto/sigpb/sigpb.proto
    From https://github.com/google/trillian
-   Dependency of trillian.proto and aftl.proto
+   Dependency of trillian.proto
    For trillian.proto, contains the DigitallySigned message used by Tree and
-   SignedEntryTimestamp. For aftl.proto, contains the DigitallySigned message
-   used by SignedFirmwareInfo.
+   SignedEntryTimestamp.
 * trillian.proto
    From https://github.com/google/trillian
    Dependency of aftl.proto
diff --git a/proto/aftl.proto b/proto/aftl.proto
deleted file mode 100644
index 41f1148..0000000
--- a/proto/aftl.proto
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// 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.
-
-syntax = "proto3";
-
-package aftl;
-option go_package = "proto";
-
-import "trillian.proto";
-import "crypto/sigpb/sigpb.proto";
-import "google/protobuf/timestamp.proto";
-
-// These messages are used both by the frontend API and the Trillian log.
-message FirmwareInfo {
-  // This is the SHA256 hash of vbmeta.
-  bytes vbmeta_hash = 1;
-
-  // Subcomponent of the build fingerprint as defined at
-  // https://source.android.com/compatibility/android-cdd#3_2_2_build_parameters.
-  // For example, a Pixel device with the following build fingerprint
-  // google/crosshatch/crosshatch:9/PQ3A.190605.003/5524043:user/release-keys,
-  // would have 5524043 for the version incremental.
-  string version_incremental = 2;
-
-  // Public key of the platform. This is the same key used to sign the vbmeta.
-  bytes platform_key = 3;
-
-  // SHA256 of the manufacturer public key (DER-encoded, x509
-  // subjectPublicKeyInfo format). The public key MUST already be in the list
-  // of root keys known and trusted by the AFTL.
-  // Internal: This field is required to be able to identify which manufacturer
-  // this request is coming from.
-  bytes manufacturer_key_hash = 4;
-
-  // Free form description field. It can be used to annotate this message with
-  // further context on the build (e.g., carrier specific build).
-  string description = 5;
-}
-
-message SignedFirmwareInfo {
-  FirmwareInfo info = 1;
-
-  // Signature of the info field, using manufacturer_pub_key.
-  // For the signature, info is first serialized to JSON. It is not
-  // expected to be able to reconstruct the info field from scratch.
-  // When verifying the inclusion proof associated with the info, it is
-  // expected that the leaf is provided.
-  sigpb.DigitallySigned info_signature = 2;
-}
-
-message FirmwareImageInfo {
-  // This is the SHA256 hash of vbmeta.
-  bytes vbmeta_hash = 1;
-
-  // SHA256 hash of the complete binary image. In case of Pixel, this would be
-  // the hash of the ZIP file that is offered for download at:
-  // https://developers.google.com/android/images
-  bytes hash = 2;
-
-  // Build fingerprint, e.g. in case of Pixel
-  // google/crosshatch/crosshatch:9/PQ3A.190605.003/5524043:user/release-keys
-  // See https://source.android.com/compatibility/android-cdd.html#3_2_2_build_parameters
-  // for the expected format of this field.
-  string build_fingerprint = 3;
-}
-
-message SignedFirmwareImageInfo {
-  FirmwareImageInfo image_info = 1;
-  sigpb.DigitallySigned image_info_signature = 2;
-}
-
-
-message InclusionProof {
-  trillian.Proof proof = 1;
-  trillian.SignedLogRoot sth = 2;
-}
-
-// Trillian-specific data types
-message Leaf {
-  int32 version = 1;
-
-  // Timestamp when the entry was added to the log.
-  google.protobuf.Timestamp timestamp = 2;
-
-  oneof value {
-    bytes vbmeta = 3;
-    FirmwareInfoAnnotation fw_info = 4;
-    FirmwareImageInfoAnnotation fw_image_info = 5;
-  }
-}
-
-message FirmwareInfoAnnotation {
-  SignedFirmwareInfo info = 1;
-}
-
-message FirmwareImageInfoAnnotation {
-  SignedFirmwareImageInfo info = 1;
-
-  // URL of the firmware image in the Cloud Storage bucket populated by AFTL.
-  string url = 2;
-}
diff --git a/proto/aftl_pb2.py b/proto/aftl_pb2.py
deleted file mode 100644
index ed811ae..0000000
--- a/proto/aftl_pb2.py
+++ /dev/null
@@ -1,469 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by the protocol buffer compiler.  DO NOT EDIT!
-# source: aftl.proto
-
-import sys
-_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import symbol_database as _symbol_database
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-import trillian_pb2 as trillian__pb2
-from crypto.sigpb import sigpb_pb2 as crypto_dot_sigpb_dot_sigpb__pb2
-from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
-  name='aftl.proto',
-  package='aftl',
-  syntax='proto3',
-  serialized_options=_b('Z\005proto'),
-  serialized_pb=_b('\n\naftl.proto\x12\x04\x61\x66tl\x1a\x0etrillian.proto\x1a\x18\x63rypto/sigpb/sigpb.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8a\x01\n\x0c\x46irmwareInfo\x12\x13\n\x0bvbmeta_hash\x18\x01 \x01(\x0c\x12\x1b\n\x13version_incremental\x18\x02 \x01(\t\x12\x14\n\x0cplatform_key\x18\x03 \x01(\x0c\x12\x1d\n\x15manufacturer_key_hash\x18\x04 \x01(\x0c\x12\x13\n\x0b\x64\x65scription\x18\x05 \x01(\t\"f\n\x12SignedFirmwareInfo\x12 \n\x04info\x18\x01 \x01(\x0b\x32\x12.aftl.FirmwareInfo\x12.\n\x0einfo_signature\x18\x02 \x01(\x0b\x32\x16.sigpb.DigitallySigned\"Q\n\x11\x46irmwareImageInfo\x12\x13\n\x0bvbmeta_hash\x18\x01 \x01(\x0c\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\x12\x19\n\x11\x62uild_fingerprint\x18\x03 \x01(\t\"|\n\x17SignedFirmwareImageInfo\x12+\n\nimage_info\x18\x01 \x01(\x0b\x32\x17.aftl.FirmwareImageInfo\x12\x34\n\x14image_info_signature\x18\x02 \x01(\x0b\x32\x16.sigpb.DigitallySigned\"V\n\x0eInclusionProof\x12\x1e\n\x05proof\x18\x01 \x01(\x0b\x32\x0f.trillian.Proof\x12$\n\x03sth\x18\x02 \x01(\x0b\x32\x17.trillian.SignedLogRoot\"\xce\x01\n\x04Leaf\x12\x0f\n\x07version\x18\x01 \x01(\x05\x12-\n\ttimestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x06vbmeta\x18\x03 \x01(\x0cH\x00\x12/\n\x07\x66w_info\x18\x04 \x01(\x0b\x32\x1c.aftl.FirmwareInfoAnnotationH\x00\x12:\n\rfw_image_info\x18\x05 \x01(\x0b\x32!.aftl.FirmwareImageInfoAnnotationH\x00\x42\x07\n\x05value\"@\n\x16\x46irmwareInfoAnnotation\x12&\n\x04info\x18\x01 \x01(\x0b\x32\x18.aftl.SignedFirmwareInfo\"W\n\x1b\x46irmwareImageInfoAnnotation\x12+\n\x04info\x18\x01 \x01(\x0b\x32\x1d.aftl.SignedFirmwareImageInfo\x12\x0b\n\x03url\x18\x02 \x01(\tB\x07Z\x05protob\x06proto3')
-  ,
-  dependencies=[trillian__pb2.DESCRIPTOR,crypto_dot_sigpb_dot_sigpb__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,])
-
-
-
-
-_FIRMWAREINFO = _descriptor.Descriptor(
-  name='FirmwareInfo',
-  full_name='aftl.FirmwareInfo',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='vbmeta_hash', full_name='aftl.FirmwareInfo.vbmeta_hash', index=0,
-      number=1, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b(""),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='version_incremental', full_name='aftl.FirmwareInfo.version_incremental', index=1,
-      number=2, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b("").decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='platform_key', full_name='aftl.FirmwareInfo.platform_key', index=2,
-      number=3, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b(""),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='manufacturer_key_hash', full_name='aftl.FirmwareInfo.manufacturer_key_hash', index=3,
-      number=4, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b(""),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='description', full_name='aftl.FirmwareInfo.description', index=4,
-      number=5, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b("").decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=96,
-  serialized_end=234,
-)
-
-
-_SIGNEDFIRMWAREINFO = _descriptor.Descriptor(
-  name='SignedFirmwareInfo',
-  full_name='aftl.SignedFirmwareInfo',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='info', full_name='aftl.SignedFirmwareInfo.info', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='info_signature', full_name='aftl.SignedFirmwareInfo.info_signature', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=236,
-  serialized_end=338,
-)
-
-
-_FIRMWAREIMAGEINFO = _descriptor.Descriptor(
-  name='FirmwareImageInfo',
-  full_name='aftl.FirmwareImageInfo',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='vbmeta_hash', full_name='aftl.FirmwareImageInfo.vbmeta_hash', index=0,
-      number=1, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b(""),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='hash', full_name='aftl.FirmwareImageInfo.hash', index=1,
-      number=2, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b(""),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='build_fingerprint', full_name='aftl.FirmwareImageInfo.build_fingerprint', index=2,
-      number=3, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b("").decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=340,
-  serialized_end=421,
-)
-
-
-_SIGNEDFIRMWAREIMAGEINFO = _descriptor.Descriptor(
-  name='SignedFirmwareImageInfo',
-  full_name='aftl.SignedFirmwareImageInfo',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='image_info', full_name='aftl.SignedFirmwareImageInfo.image_info', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='image_info_signature', full_name='aftl.SignedFirmwareImageInfo.image_info_signature', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=423,
-  serialized_end=547,
-)
-
-
-_INCLUSIONPROOF = _descriptor.Descriptor(
-  name='InclusionProof',
-  full_name='aftl.InclusionProof',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='proof', full_name='aftl.InclusionProof.proof', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='sth', full_name='aftl.InclusionProof.sth', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=549,
-  serialized_end=635,
-)
-
-
-_LEAF = _descriptor.Descriptor(
-  name='Leaf',
-  full_name='aftl.Leaf',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='version', full_name='aftl.Leaf.version', index=0,
-      number=1, type=5, cpp_type=1, label=1,
-      has_default_value=False, default_value=0,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='timestamp', full_name='aftl.Leaf.timestamp', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='vbmeta', full_name='aftl.Leaf.vbmeta', index=2,
-      number=3, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b(""),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='fw_info', full_name='aftl.Leaf.fw_info', index=3,
-      number=4, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='fw_image_info', full_name='aftl.Leaf.fw_image_info', index=4,
-      number=5, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-    _descriptor.OneofDescriptor(
-      name='value', full_name='aftl.Leaf.value',
-      index=0, containing_type=None, fields=[]),
-  ],
-  serialized_start=638,
-  serialized_end=844,
-)
-
-
-_FIRMWAREINFOANNOTATION = _descriptor.Descriptor(
-  name='FirmwareInfoAnnotation',
-  full_name='aftl.FirmwareInfoAnnotation',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='info', full_name='aftl.FirmwareInfoAnnotation.info', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=846,
-  serialized_end=910,
-)
-
-
-_FIRMWAREIMAGEINFOANNOTATION = _descriptor.Descriptor(
-  name='FirmwareImageInfoAnnotation',
-  full_name='aftl.FirmwareImageInfoAnnotation',
-  filename=None,
-  file=DESCRIPTOR,
-  containing_type=None,
-  fields=[
-    _descriptor.FieldDescriptor(
-      name='info', full_name='aftl.FirmwareImageInfoAnnotation.info', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='url', full_name='aftl.FirmwareImageInfoAnnotation.url', index=1,
-      number=2, type=9, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b("").decode('utf-8'),
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
-  ],
-  extensions=[
-  ],
-  nested_types=[],
-  enum_types=[
-  ],
-  serialized_options=None,
-  is_extendable=False,
-  syntax='proto3',
-  extension_ranges=[],
-  oneofs=[
-  ],
-  serialized_start=912,
-  serialized_end=999,
-)
-
-_SIGNEDFIRMWAREINFO.fields_by_name['info'].message_type = _FIRMWAREINFO
-_SIGNEDFIRMWAREINFO.fields_by_name['info_signature'].message_type = crypto_dot_sigpb_dot_sigpb__pb2._DIGITALLYSIGNED
-_SIGNEDFIRMWAREIMAGEINFO.fields_by_name['image_info'].message_type = _FIRMWAREIMAGEINFO
-_SIGNEDFIRMWAREIMAGEINFO.fields_by_name['image_info_signature'].message_type = crypto_dot_sigpb_dot_sigpb__pb2._DIGITALLYSIGNED
-_INCLUSIONPROOF.fields_by_name['proof'].message_type = trillian__pb2._PROOF
-_INCLUSIONPROOF.fields_by_name['sth'].message_type = trillian__pb2._SIGNEDLOGROOT
-_LEAF.fields_by_name['timestamp'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
-_LEAF.fields_by_name['fw_info'].message_type = _FIRMWAREINFOANNOTATION
-_LEAF.fields_by_name['fw_image_info'].message_type = _FIRMWAREIMAGEINFOANNOTATION
-_LEAF.oneofs_by_name['value'].fields.append(
-  _LEAF.fields_by_name['vbmeta'])
-_LEAF.fields_by_name['vbmeta'].containing_oneof = _LEAF.oneofs_by_name['value']
-_LEAF.oneofs_by_name['value'].fields.append(
-  _LEAF.fields_by_name['fw_info'])
-_LEAF.fields_by_name['fw_info'].containing_oneof = _LEAF.oneofs_by_name['value']
-_LEAF.oneofs_by_name['value'].fields.append(
-  _LEAF.fields_by_name['fw_image_info'])
-_LEAF.fields_by_name['fw_image_info'].containing_oneof = _LEAF.oneofs_by_name['value']
-_FIRMWAREINFOANNOTATION.fields_by_name['info'].message_type = _SIGNEDFIRMWAREINFO
-_FIRMWAREIMAGEINFOANNOTATION.fields_by_name['info'].message_type = _SIGNEDFIRMWAREIMAGEINFO
-DESCRIPTOR.message_types_by_name['FirmwareInfo'] = _FIRMWAREINFO
-DESCRIPTOR.message_types_by_name['SignedFirmwareInfo'] = _SIGNEDFIRMWAREINFO
-DESCRIPTOR.message_types_by_name['FirmwareImageInfo'] = _FIRMWAREIMAGEINFO
-DESCRIPTOR.message_types_by_name['SignedFirmwareImageInfo'] = _SIGNEDFIRMWAREIMAGEINFO
-DESCRIPTOR.message_types_by_name['InclusionProof'] = _INCLUSIONPROOF
-DESCRIPTOR.message_types_by_name['Leaf'] = _LEAF
-DESCRIPTOR.message_types_by_name['FirmwareInfoAnnotation'] = _FIRMWAREINFOANNOTATION
-DESCRIPTOR.message_types_by_name['FirmwareImageInfoAnnotation'] = _FIRMWAREIMAGEINFOANNOTATION
-_sym_db.RegisterFileDescriptor(DESCRIPTOR)
-
-FirmwareInfo = _reflection.GeneratedProtocolMessageType('FirmwareInfo', (_message.Message,), {
-  'DESCRIPTOR' : _FIRMWAREINFO,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.FirmwareInfo)
-  })
-_sym_db.RegisterMessage(FirmwareInfo)
-
-SignedFirmwareInfo = _reflection.GeneratedProtocolMessageType('SignedFirmwareInfo', (_message.Message,), {
-  'DESCRIPTOR' : _SIGNEDFIRMWAREINFO,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.SignedFirmwareInfo)
-  })
-_sym_db.RegisterMessage(SignedFirmwareInfo)
-
-FirmwareImageInfo = _reflection.GeneratedProtocolMessageType('FirmwareImageInfo', (_message.Message,), {
-  'DESCRIPTOR' : _FIRMWAREIMAGEINFO,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.FirmwareImageInfo)
-  })
-_sym_db.RegisterMessage(FirmwareImageInfo)
-
-SignedFirmwareImageInfo = _reflection.GeneratedProtocolMessageType('SignedFirmwareImageInfo', (_message.Message,), {
-  'DESCRIPTOR' : _SIGNEDFIRMWAREIMAGEINFO,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.SignedFirmwareImageInfo)
-  })
-_sym_db.RegisterMessage(SignedFirmwareImageInfo)
-
-InclusionProof = _reflection.GeneratedProtocolMessageType('InclusionProof', (_message.Message,), {
-  'DESCRIPTOR' : _INCLUSIONPROOF,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.InclusionProof)
-  })
-_sym_db.RegisterMessage(InclusionProof)
-
-Leaf = _reflection.GeneratedProtocolMessageType('Leaf', (_message.Message,), {
-  'DESCRIPTOR' : _LEAF,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.Leaf)
-  })
-_sym_db.RegisterMessage(Leaf)
-
-FirmwareInfoAnnotation = _reflection.GeneratedProtocolMessageType('FirmwareInfoAnnotation', (_message.Message,), {
-  'DESCRIPTOR' : _FIRMWAREINFOANNOTATION,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.FirmwareInfoAnnotation)
-  })
-_sym_db.RegisterMessage(FirmwareInfoAnnotation)
-
-FirmwareImageInfoAnnotation = _reflection.GeneratedProtocolMessageType('FirmwareImageInfoAnnotation', (_message.Message,), {
-  'DESCRIPTOR' : _FIRMWAREIMAGEINFOANNOTATION,
-  '__module__' : 'aftl_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.FirmwareImageInfoAnnotation)
-  })
-_sym_db.RegisterMessage(FirmwareImageInfoAnnotation)
-
-
-DESCRIPTOR._options = None
-# @@protoc_insertion_point(module_scope)
diff --git a/proto/aftl_pb2_grpc.py b/proto/aftl_pb2_grpc.py
deleted file mode 100644
index a894352..0000000
--- a/proto/aftl_pb2_grpc.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
-import grpc
-
diff --git a/proto/api.proto b/proto/api.proto
index 4c66333..e22ae47 100644
--- a/proto/api.proto
+++ b/proto/api.proto
@@ -1,4 +1,4 @@
-// Copyright 2019 Google LLC
+// Copyright 2019-2020 Google LLC
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,27 +17,35 @@
 package aftl;
 option go_package = "proto";
 
-import "aftl.proto";
+import "trillian.proto";
 
-message AddFirmwareInfoRequest {
+message InclusionProof {
+  trillian.Proof proof = 1;
+  trillian.SignedLogRoot sth = 2;
+}
+
+message AddVBMetaRequest {
   // VBMeta structure as described in
   // https://android.googlesource.com/platform/external/avb/+/master/README.md.
   // In case of chained partitions, each VBMeta is added via a separate call.
   // The default size for gRPC payload is about 4MB. We expect vbmeta to be
-  // in the order of 1kB.
+  // in the order of 64kB.
   bytes vbmeta = 1;
 
-  SignedFirmwareInfo fw_info = 2;
+  // Serialized SignedVBMetaPrimaryAnnotation. This annotation contains the hash
+  // of the vbmeta structure. It is signed using the manufacturer key.
+  // See types/types.go.
+  bytes signed_vbmeta_primary_annotation = 2;
 }
 
-message AddFirmwareInfoResponse {
+message AddVBMetaResponse {
   // Inclusion proof and the leaf that was added to the log, which contains
-  // information on the firmware.
+  // the annotation on VBMeta.
   // It is required to have the complete leaf to validate the inclusion proof.
   // For on-device verification, only these first 2 fields are required to
   // validate the inclusion.
-  InclusionProof fw_info_proof = 1;
-  bytes          fw_info_leaf = 2;
+  InclusionProof annotation_proof = 1;
+  bytes          annotation_leaf = 2;
 
   // Inclusion proof and leaf that was added to the log, which contains the full
   // vbmeta partition.
@@ -47,12 +55,13 @@
   bytes          vbmeta_leaf = 4;
 }
 
-message AddFirmwareImageRequest {
+message AnnotateVBMetaWithBuildRequest {
+  // Serialized SignedVBMetaBuildAnnotation.  This annotation contains the hash
+  // of the full build image. See types/types.go.
+  bytes signed_vbmeta_build_annotation = 1;
 
-  SignedFirmwareImageInfo fw_image_info = 1;
-
-  // Bytes of the binary images. These are not signed as their final
-  // hash value is already signed in fw_image_info.hash
+  // Bytes of the binary images. The hash value of the concatenation of these
+  // chunk is contained in SignedVBMetaBuildAnnotation.
   // This is ignored if any of the requests origin_url is set.
   bytes image_chunk = 2;
 
@@ -61,14 +70,13 @@
   string origin_url = 3;
 }
 
-message AddFirmwareImageResponse {
-
+message AnnotateVBMetaWithBuildResponse {
   // Inclusion proof and leaf for the firmware image. The leaf contains the URL
   // where the image was stored.
   // It is not required for vendors to keep this information. However, this can
   // be used for their records to ensure the correctness of the log.
-  InclusionProof fw_image_info_proof = 1;
-  Leaf           fw_image_info_leaf = 2;
+  InclusionProof  annotation_proof = 1;
+  bytes           annotation_leaf = 2;
 }
 
 service AFTLog {
@@ -76,12 +84,11 @@
   // Insert a new VBMeta structure into the log.
   // This request will effectively create 2 log entries:
   //  - VBMeta itself
-  //  - Vendor annotations, including a reference to the VBMeta leaf.
-  rpc AddFirmwareInfo(AddFirmwareInfoRequest) returns (AddFirmwareInfoResponse) {}
+  //  - Vendor annotations, which includes a reference to the VBMeta.
+  rpc AddVBMeta(AddVBMetaRequest) returns (AddVBMetaResponse) {}
 
   // Upload (or copy) the complete firmware image.
-  rpc AddFirmwareImage(stream AddFirmwareImageRequest) returns (AddFirmwareImageResponse) {}
+  rpc AnnotateVBMetaWithBuild(stream AnnotateVBMetaWithBuildResponse) returns (AnnotateVBMetaWithBuildResponse) {}
 
-  // TODO GetProofByHash, GetSthConsistency, GetEntries, GetRootKeys
+  // TODO(tweek): GetProofByHash, GetSthConsistency, GetEntries, GetRootKeys
 }
-
diff --git a/proto/api_pb2.py b/proto/api_pb2.py
index f8f9a01..fbe3bab 100644
--- a/proto/api_pb2.py
+++ b/proto/api_pb2.py
@@ -1,4 +1,3 @@
-# pylint: skip-file
 # -*- coding: utf-8 -*-
 # Generated by the protocol buffer compiler.  DO NOT EDIT!
 # source: api.proto
@@ -14,7 +13,7 @@
 _sym_db = _symbol_database.Default()
 
 
-import aftl_pb2 as aftl__pb2
+import trillian_pb2 as trillian__pb2
 
 
 DESCRIPTOR = _descriptor.FileDescriptor(
@@ -22,29 +21,29 @@
   package='aftl',
   syntax='proto3',
   serialized_options=_b('Z\005proto'),
-  serialized_pb=_b('\n\tapi.proto\x12\x04\x61\x66tl\x1a\naftl.proto\"S\n\x16\x41\x64\x64\x46irmwareInfoRequest\x12\x0e\n\x06vbmeta\x18\x01 \x01(\x0c\x12)\n\x07\x66w_info\x18\x02 \x01(\x0b\x32\x18.aftl.SignedFirmwareInfo\"\x9d\x01\n\x17\x41\x64\x64\x46irmwareInfoResponse\x12+\n\rfw_info_proof\x18\x01 \x01(\x0b\x32\x14.aftl.InclusionProof\x12\x14\n\x0c\x66w_info_leaf\x18\x02 \x01(\x0c\x12*\n\x0cvbmeta_proof\x18\x03 \x01(\x0b\x32\x14.aftl.InclusionProof\x12\x13\n\x0bvbmeta_leaf\x18\x04 \x01(\x0c\"x\n\x17\x41\x64\x64\x46irmwareImageRequest\x12\x34\n\rfw_image_info\x18\x01 \x01(\x0b\x32\x1d.aftl.SignedFirmwareImageInfo\x12\x13\n\x0bimage_chunk\x18\x02 \x01(\x0c\x12\x12\n\norigin_url\x18\x03 \x01(\t\"u\n\x18\x41\x64\x64\x46irmwareImageResponse\x12\x31\n\x13\x66w_image_info_proof\x18\x01 \x01(\x0b\x32\x14.aftl.InclusionProof\x12&\n\x12\x66w_image_info_leaf\x18\x02 \x01(\x0b\x32\n.aftl.Leaf2\xb1\x01\n\x06\x41\x46TLog\x12P\n\x0f\x41\x64\x64\x46irmwareInfo\x12\x1c.aftl.AddFirmwareInfoRequest\x1a\x1d.aftl.AddFirmwareInfoResponse\"\x00\x12U\n\x10\x41\x64\x64\x46irmwareImage\x12\x1d.aftl.AddFirmwareImageRequest\x1a\x1e.aftl.AddFirmwareImageResponse\"\x00(\x01\x42\x07Z\x05protob\x06proto3')
+  serialized_pb=_b('\n\tapi.proto\x12\x04\x61\x66tl\x1a\x0etrillian.proto\"V\n\x0eInclusionProof\x12\x1e\n\x05proof\x18\x01 \x01(\x0b\x32\x0f.trillian.Proof\x12$\n\x03sth\x18\x02 \x01(\x0b\x32\x17.trillian.SignedLogRoot\"L\n\x10\x41\x64\x64VBMetaRequest\x12\x0e\n\x06vbmeta\x18\x01 \x01(\x0c\x12(\n signed_vbmeta_primary_annotation\x18\x02 \x01(\x0c\"\x9d\x01\n\x11\x41\x64\x64VBMetaResponse\x12.\n\x10\x61nnotation_proof\x18\x01 \x01(\x0b\x32\x14.aftl.InclusionProof\x12\x17\n\x0f\x61nnotation_leaf\x18\x02 \x01(\x0c\x12*\n\x0cvbmeta_proof\x18\x03 \x01(\x0b\x32\x14.aftl.InclusionProof\x12\x13\n\x0bvbmeta_leaf\x18\x04 \x01(\x0c\"q\n\x1e\x41nnotateVBMetaWithBuildRequest\x12&\n\x1esigned_vbmeta_build_annotation\x18\x01 \x01(\x0c\x12\x13\n\x0bimage_chunk\x18\x02 \x01(\x0c\x12\x12\n\norigin_url\x18\x03 \x01(\t\"j\n\x1f\x41nnotateVBMetaWithBuildResponse\x12.\n\x10\x61nnotation_proof\x18\x01 \x01(\x0b\x32\x14.aftl.InclusionProof\x12\x17\n\x0f\x61nnotation_leaf\x18\x02 \x01(\x0c\x32\xb5\x01\n\x06\x41\x46TLog\x12>\n\tAddVBMeta\x12\x16.aftl.AddVBMetaRequest\x1a\x17.aftl.AddVBMetaResponse\"\x00\x12k\n\x17\x41nnotateVBMetaWithBuild\x12%.aftl.AnnotateVBMetaWithBuildResponse\x1a%.aftl.AnnotateVBMetaWithBuildResponse\"\x00(\x01\x42\x07Z\x05protob\x06proto3')
   ,
-  dependencies=[aftl__pb2.DESCRIPTOR,])
+  dependencies=[trillian__pb2.DESCRIPTOR,])
 
 
 
 
-_ADDFIRMWAREINFOREQUEST = _descriptor.Descriptor(
-  name='AddFirmwareInfoRequest',
-  full_name='aftl.AddFirmwareInfoRequest',
+_INCLUSIONPROOF = _descriptor.Descriptor(
+  name='InclusionProof',
+  full_name='aftl.InclusionProof',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='vbmeta', full_name='aftl.AddFirmwareInfoRequest.vbmeta', index=0,
-      number=1, type=12, cpp_type=9, label=1,
-      has_default_value=False, default_value=_b(""),
+      name='proof', full_name='aftl.InclusionProof.proof', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='fw_info', full_name='aftl.AddFirmwareInfoRequest.fw_info', index=1,
+      name='sth', full_name='aftl.InclusionProof.sth', index=1,
       number=2, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
@@ -62,41 +61,79 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=31,
-  serialized_end=114,
+  serialized_start=35,
+  serialized_end=121,
 )
 
 
-_ADDFIRMWAREINFORESPONSE = _descriptor.Descriptor(
-  name='AddFirmwareInfoResponse',
-  full_name='aftl.AddFirmwareInfoResponse',
+_ADDVBMETAREQUEST = _descriptor.Descriptor(
+  name='AddVBMetaRequest',
+  full_name='aftl.AddVBMetaRequest',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='fw_info_proof', full_name='aftl.AddFirmwareInfoResponse.fw_info_proof', index=0,
+      name='vbmeta', full_name='aftl.AddVBMetaRequest.vbmeta', index=0,
+      number=1, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='signed_vbmeta_primary_annotation', full_name='aftl.AddVBMetaRequest.signed_vbmeta_primary_annotation', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=123,
+  serialized_end=199,
+)
+
+
+_ADDVBMETARESPONSE = _descriptor.Descriptor(
+  name='AddVBMetaResponse',
+  full_name='aftl.AddVBMetaResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='annotation_proof', full_name='aftl.AddVBMetaResponse.annotation_proof', index=0,
       number=1, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='fw_info_leaf', full_name='aftl.AddFirmwareInfoResponse.fw_info_leaf', index=1,
+      name='annotation_leaf', full_name='aftl.AddVBMetaResponse.annotation_leaf', index=1,
       number=2, type=12, cpp_type=9, label=1,
       has_default_value=False, default_value=_b(""),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='vbmeta_proof', full_name='aftl.AddFirmwareInfoResponse.vbmeta_proof', index=2,
+      name='vbmeta_proof', full_name='aftl.AddVBMetaResponse.vbmeta_proof', index=2,
       number=3, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='vbmeta_leaf', full_name='aftl.AddFirmwareInfoResponse.vbmeta_leaf', index=3,
+      name='vbmeta_leaf', full_name='aftl.AddVBMetaResponse.vbmeta_leaf', index=3,
       number=4, type=12, cpp_type=9, label=1,
       has_default_value=False, default_value=_b(""),
       message_type=None, enum_type=None, containing_type=None,
@@ -114,34 +151,34 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=117,
-  serialized_end=274,
+  serialized_start=202,
+  serialized_end=359,
 )
 
 
-_ADDFIRMWAREIMAGEREQUEST = _descriptor.Descriptor(
-  name='AddFirmwareImageRequest',
-  full_name='aftl.AddFirmwareImageRequest',
+_ANNOTATEVBMETAWITHBUILDREQUEST = _descriptor.Descriptor(
+  name='AnnotateVBMetaWithBuildRequest',
+  full_name='aftl.AnnotateVBMetaWithBuildRequest',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='fw_image_info', full_name='aftl.AddFirmwareImageRequest.fw_image_info', index=0,
-      number=1, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
+      name='signed_vbmeta_build_annotation', full_name='aftl.AnnotateVBMetaWithBuildRequest.signed_vbmeta_build_annotation', index=0,
+      number=1, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='image_chunk', full_name='aftl.AddFirmwareImageRequest.image_chunk', index=1,
+      name='image_chunk', full_name='aftl.AnnotateVBMetaWithBuildRequest.image_chunk', index=1,
       number=2, type=12, cpp_type=9, label=1,
       has_default_value=False, default_value=_b(""),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='origin_url', full_name='aftl.AddFirmwareImageRequest.origin_url', index=2,
+      name='origin_url', full_name='aftl.AnnotateVBMetaWithBuildRequest.origin_url', index=2,
       number=3, type=9, cpp_type=9, label=1,
       has_default_value=False, default_value=_b("").decode('utf-8'),
       message_type=None, enum_type=None, containing_type=None,
@@ -159,29 +196,29 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=276,
-  serialized_end=396,
+  serialized_start=361,
+  serialized_end=474,
 )
 
 
-_ADDFIRMWAREIMAGERESPONSE = _descriptor.Descriptor(
-  name='AddFirmwareImageResponse',
-  full_name='aftl.AddFirmwareImageResponse',
+_ANNOTATEVBMETAWITHBUILDRESPONSE = _descriptor.Descriptor(
+  name='AnnotateVBMetaWithBuildResponse',
+  full_name='aftl.AnnotateVBMetaWithBuildResponse',
   filename=None,
   file=DESCRIPTOR,
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='fw_image_info_proof', full_name='aftl.AddFirmwareImageResponse.fw_image_info_proof', index=0,
+      name='annotation_proof', full_name='aftl.AnnotateVBMetaWithBuildResponse.annotation_proof', index=0,
       number=1, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
-      name='fw_image_info_leaf', full_name='aftl.AddFirmwareImageResponse.fw_image_info_leaf', index=1,
-      number=2, type=11, cpp_type=10, label=1,
-      has_default_value=False, default_value=None,
+      name='annotation_leaf', full_name='aftl.AnnotateVBMetaWithBuildResponse.annotation_leaf', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
@@ -197,49 +234,56 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=398,
-  serialized_end=515,
+  serialized_start=476,
+  serialized_end=582,
 )
 
-_ADDFIRMWAREINFOREQUEST.fields_by_name['fw_info'].message_type = aftl__pb2._SIGNEDFIRMWAREINFO
-_ADDFIRMWAREINFORESPONSE.fields_by_name['fw_info_proof'].message_type = aftl__pb2._INCLUSIONPROOF
-_ADDFIRMWAREINFORESPONSE.fields_by_name['vbmeta_proof'].message_type = aftl__pb2._INCLUSIONPROOF
-_ADDFIRMWAREIMAGEREQUEST.fields_by_name['fw_image_info'].message_type = aftl__pb2._SIGNEDFIRMWAREIMAGEINFO
-_ADDFIRMWAREIMAGERESPONSE.fields_by_name['fw_image_info_proof'].message_type = aftl__pb2._INCLUSIONPROOF
-_ADDFIRMWAREIMAGERESPONSE.fields_by_name['fw_image_info_leaf'].message_type = aftl__pb2._LEAF
-DESCRIPTOR.message_types_by_name['AddFirmwareInfoRequest'] = _ADDFIRMWAREINFOREQUEST
-DESCRIPTOR.message_types_by_name['AddFirmwareInfoResponse'] = _ADDFIRMWAREINFORESPONSE
-DESCRIPTOR.message_types_by_name['AddFirmwareImageRequest'] = _ADDFIRMWAREIMAGEREQUEST
-DESCRIPTOR.message_types_by_name['AddFirmwareImageResponse'] = _ADDFIRMWAREIMAGERESPONSE
+_INCLUSIONPROOF.fields_by_name['proof'].message_type = trillian__pb2._PROOF
+_INCLUSIONPROOF.fields_by_name['sth'].message_type = trillian__pb2._SIGNEDLOGROOT
+_ADDVBMETARESPONSE.fields_by_name['annotation_proof'].message_type = _INCLUSIONPROOF
+_ADDVBMETARESPONSE.fields_by_name['vbmeta_proof'].message_type = _INCLUSIONPROOF
+_ANNOTATEVBMETAWITHBUILDRESPONSE.fields_by_name['annotation_proof'].message_type = _INCLUSIONPROOF
+DESCRIPTOR.message_types_by_name['InclusionProof'] = _INCLUSIONPROOF
+DESCRIPTOR.message_types_by_name['AddVBMetaRequest'] = _ADDVBMETAREQUEST
+DESCRIPTOR.message_types_by_name['AddVBMetaResponse'] = _ADDVBMETARESPONSE
+DESCRIPTOR.message_types_by_name['AnnotateVBMetaWithBuildRequest'] = _ANNOTATEVBMETAWITHBUILDREQUEST
+DESCRIPTOR.message_types_by_name['AnnotateVBMetaWithBuildResponse'] = _ANNOTATEVBMETAWITHBUILDRESPONSE
 _sym_db.RegisterFileDescriptor(DESCRIPTOR)
 
-AddFirmwareInfoRequest = _reflection.GeneratedProtocolMessageType('AddFirmwareInfoRequest', (_message.Message,), {
-  'DESCRIPTOR' : _ADDFIRMWAREINFOREQUEST,
+InclusionProof = _reflection.GeneratedProtocolMessageType('InclusionProof', (_message.Message,), {
+  'DESCRIPTOR' : _INCLUSIONPROOF,
   '__module__' : 'api_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.AddFirmwareInfoRequest)
+  # @@protoc_insertion_point(class_scope:aftl.InclusionProof)
   })
-_sym_db.RegisterMessage(AddFirmwareInfoRequest)
+_sym_db.RegisterMessage(InclusionProof)
 
-AddFirmwareInfoResponse = _reflection.GeneratedProtocolMessageType('AddFirmwareInfoResponse', (_message.Message,), {
-  'DESCRIPTOR' : _ADDFIRMWAREINFORESPONSE,
+AddVBMetaRequest = _reflection.GeneratedProtocolMessageType('AddVBMetaRequest', (_message.Message,), {
+  'DESCRIPTOR' : _ADDVBMETAREQUEST,
   '__module__' : 'api_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.AddFirmwareInfoResponse)
+  # @@protoc_insertion_point(class_scope:aftl.AddVBMetaRequest)
   })
-_sym_db.RegisterMessage(AddFirmwareInfoResponse)
+_sym_db.RegisterMessage(AddVBMetaRequest)
 
-AddFirmwareImageRequest = _reflection.GeneratedProtocolMessageType('AddFirmwareImageRequest', (_message.Message,), {
-  'DESCRIPTOR' : _ADDFIRMWAREIMAGEREQUEST,
+AddVBMetaResponse = _reflection.GeneratedProtocolMessageType('AddVBMetaResponse', (_message.Message,), {
+  'DESCRIPTOR' : _ADDVBMETARESPONSE,
   '__module__' : 'api_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.AddFirmwareImageRequest)
+  # @@protoc_insertion_point(class_scope:aftl.AddVBMetaResponse)
   })
-_sym_db.RegisterMessage(AddFirmwareImageRequest)
+_sym_db.RegisterMessage(AddVBMetaResponse)
 
-AddFirmwareImageResponse = _reflection.GeneratedProtocolMessageType('AddFirmwareImageResponse', (_message.Message,), {
-  'DESCRIPTOR' : _ADDFIRMWAREIMAGERESPONSE,
+AnnotateVBMetaWithBuildRequest = _reflection.GeneratedProtocolMessageType('AnnotateVBMetaWithBuildRequest', (_message.Message,), {
+  'DESCRIPTOR' : _ANNOTATEVBMETAWITHBUILDREQUEST,
   '__module__' : 'api_pb2'
-  # @@protoc_insertion_point(class_scope:aftl.AddFirmwareImageResponse)
+  # @@protoc_insertion_point(class_scope:aftl.AnnotateVBMetaWithBuildRequest)
   })
-_sym_db.RegisterMessage(AddFirmwareImageResponse)
+_sym_db.RegisterMessage(AnnotateVBMetaWithBuildRequest)
+
+AnnotateVBMetaWithBuildResponse = _reflection.GeneratedProtocolMessageType('AnnotateVBMetaWithBuildResponse', (_message.Message,), {
+  'DESCRIPTOR' : _ANNOTATEVBMETAWITHBUILDRESPONSE,
+  '__module__' : 'api_pb2'
+  # @@protoc_insertion_point(class_scope:aftl.AnnotateVBMetaWithBuildResponse)
+  })
+_sym_db.RegisterMessage(AnnotateVBMetaWithBuildResponse)
 
 
 DESCRIPTOR._options = None
@@ -250,25 +294,25 @@
   file=DESCRIPTOR,
   index=0,
   serialized_options=None,
-  serialized_start=518,
-  serialized_end=695,
+  serialized_start=585,
+  serialized_end=766,
   methods=[
   _descriptor.MethodDescriptor(
-    name='AddFirmwareInfo',
-    full_name='aftl.AFTLog.AddFirmwareInfo',
+    name='AddVBMeta',
+    full_name='aftl.AFTLog.AddVBMeta',
     index=0,
     containing_service=None,
-    input_type=_ADDFIRMWAREINFOREQUEST,
-    output_type=_ADDFIRMWAREINFORESPONSE,
+    input_type=_ADDVBMETAREQUEST,
+    output_type=_ADDVBMETARESPONSE,
     serialized_options=None,
   ),
   _descriptor.MethodDescriptor(
-    name='AddFirmwareImage',
-    full_name='aftl.AFTLog.AddFirmwareImage',
+    name='AnnotateVBMetaWithBuild',
+    full_name='aftl.AFTLog.AnnotateVBMetaWithBuild',
     index=1,
     containing_service=None,
-    input_type=_ADDFIRMWAREIMAGEREQUEST,
-    output_type=_ADDFIRMWAREIMAGERESPONSE,
+    input_type=_ANNOTATEVBMETAWITHBUILDRESPONSE,
+    output_type=_ANNOTATEVBMETAWITHBUILDRESPONSE,
     serialized_options=None,
   ),
 ])
diff --git a/proto/api_pb2_grpc.py b/proto/api_pb2_grpc.py
index b1834c7..d487856 100644
--- a/proto/api_pb2_grpc.py
+++ b/proto/api_pb2_grpc.py
@@ -1,4 +1,3 @@
-# pylint: skip-file
 # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
 import grpc
 
@@ -15,15 +14,15 @@
     Args:
       channel: A grpc.Channel.
     """
-    self.AddFirmwareInfo = channel.unary_unary(
-        '/aftl.AFTLog/AddFirmwareInfo',
-        request_serializer=api__pb2.AddFirmwareInfoRequest.SerializeToString,
-        response_deserializer=api__pb2.AddFirmwareInfoResponse.FromString,
+    self.AddVBMeta = channel.unary_unary(
+        '/aftl.AFTLog/AddVBMeta',
+        request_serializer=api__pb2.AddVBMetaRequest.SerializeToString,
+        response_deserializer=api__pb2.AddVBMetaResponse.FromString,
         )
-    self.AddFirmwareImage = channel.stream_unary(
-        '/aftl.AFTLog/AddFirmwareImage',
-        request_serializer=api__pb2.AddFirmwareImageRequest.SerializeToString,
-        response_deserializer=api__pb2.AddFirmwareImageResponse.FromString,
+    self.AnnotateVBMetaWithBuild = channel.stream_unary(
+        '/aftl.AFTLog/AnnotateVBMetaWithBuild',
+        request_serializer=api__pb2.AnnotateVBMetaWithBuildResponse.SerializeToString,
+        response_deserializer=api__pb2.AnnotateVBMetaWithBuildResponse.FromString,
         )
 
 
@@ -31,17 +30,17 @@
   # missing associated documentation comment in .proto file
   pass
 
-  def AddFirmwareInfo(self, request, context):
+  def AddVBMeta(self, request, context):
     """Insert a new VBMeta structure into the log.
     This request will effectively create 2 log entries:
     - VBMeta itself
-    - Vendor annotations, including a reference to the VBMeta leaf.
+    - Vendor annotations, which includes a reference to the VBMeta.
     """
     context.set_code(grpc.StatusCode.UNIMPLEMENTED)
     context.set_details('Method not implemented!')
     raise NotImplementedError('Method not implemented!')
 
-  def AddFirmwareImage(self, request_iterator, context):
+  def AnnotateVBMetaWithBuild(self, request_iterator, context):
     """Upload (or copy) the complete firmware image.
     """
     context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -51,15 +50,15 @@
 
 def add_AFTLogServicer_to_server(servicer, server):
   rpc_method_handlers = {
-      'AddFirmwareInfo': grpc.unary_unary_rpc_method_handler(
-          servicer.AddFirmwareInfo,
-          request_deserializer=api__pb2.AddFirmwareInfoRequest.FromString,
-          response_serializer=api__pb2.AddFirmwareInfoResponse.SerializeToString,
+      'AddVBMeta': grpc.unary_unary_rpc_method_handler(
+          servicer.AddVBMeta,
+          request_deserializer=api__pb2.AddVBMetaRequest.FromString,
+          response_serializer=api__pb2.AddVBMetaResponse.SerializeToString,
       ),
-      'AddFirmwareImage': grpc.stream_unary_rpc_method_handler(
-          servicer.AddFirmwareImage,
-          request_deserializer=api__pb2.AddFirmwareImageRequest.FromString,
-          response_serializer=api__pb2.AddFirmwareImageResponse.SerializeToString,
+      'AnnotateVBMetaWithBuild': grpc.stream_unary_rpc_method_handler(
+          servicer.AnnotateVBMetaWithBuild,
+          request_deserializer=api__pb2.AnnotateVBMetaWithBuildResponse.FromString,
+          response_serializer=api__pb2.AnnotateVBMetaWithBuildResponse.SerializeToString,
       ),
   }
   generic_handler = grpc.method_handlers_generic_handler(
diff --git a/test/data/aftltool/aftl_output_vbmeta_with_1_icp.img b/test/data/aftltool/aftl_output_vbmeta_with_1_icp.img
index 25e0869..74b67bb 100644
--- a/test/data/aftltool/aftl_output_vbmeta_with_1_icp.img
+++ b/test/data/aftltool/aftl_output_vbmeta_with_1_icp.img
Binary files differ
diff --git a/test/data/aftltool/aftl_output_vbmeta_with_2_icp_different_logs.img b/test/data/aftltool/aftl_output_vbmeta_with_2_icp_different_logs.img
deleted file mode 100644
index ad36580..0000000
--- a/test/data/aftltool/aftl_output_vbmeta_with_2_icp_different_logs.img
+++ /dev/null
Binary files differ
diff --git a/test/data/aftltool/aftl_output_vbmeta_with_2_icp_same_log.img b/test/data/aftltool/aftl_output_vbmeta_with_2_icp_same_log.img
index e53178c..2a6368e 100644
--- a/test/data/aftltool/aftl_output_vbmeta_with_2_icp_same_log.img
+++ b/test/data/aftltool/aftl_output_vbmeta_with_2_icp_same_log.img
Binary files differ