code: public byte[] join(Map<Integer, byte[]> parts) {
      checkArgument(parts.size() > 0, "No parts provided");
      final int[] lengths = parts.values().stream().mapToInt(v -> v.length).distinct().toArray();
      checkArgument(lengths.length == 1, "Varying lengths of part values");
      final byte[] secret = new byte[lengths[0]];
      for (int i = 0; i < secret.length; i++) {
        final byte[][] points = new byte[parts.size()][2];
        int j = 0;
        for (Map.Entry<Integer, byte[]> part : parts.entrySet()) {
          points[j][0] = part.getKey().byteValue();
          points[j][1] = part.getValue()[i];
          j++;
        }
        secret[i] = GF256.interpolate(points);
      }
      return secret;
    }
docs: Joins the given parts to recover the original secret.
  
  <p><b>N.B.:</b> There is no way to determine whether or not the returned value is actually the
  original secret. If the parts are incorrect, or are under the threshold value used to split the
  secret, a random value will be returned.
  
  @param parts a map of part IDs to part values
  @return the original secret
  @throws IllegalArgumentException if {@code parts} is empty or contains values of varying
  lengths