Allow subclassing bytes
diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes32.java b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes32.java
index 571ac90..c0cc80b 100644
--- a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes32.java
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes32.java
@@ -82,7 +82,8 @@
     if (value instanceof Bytes32) {
       return (Bytes32) value;
     }
-    return DelegatingBytes32.delegateTo(value);
+    checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size());
+    return new DelegatingBytes32(value);
   }
 
   /**
@@ -110,7 +111,7 @@
     if (slice instanceof Bytes32) {
       return (Bytes32) slice;
     }
-    return DelegatingBytes32.delegateTo(slice);
+    return new DelegatingBytes32(value);
   }
 
   /**
diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes48.java b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes48.java
index 518e5cf..cdb29aa 100644
--- a/bytes/src/main/java/org/apache/tuweni/bytes/Bytes48.java
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/Bytes48.java
@@ -82,7 +82,8 @@
     if (value instanceof Bytes48) {
       return (Bytes48) value;
     }
-    return DelegatingBytes48.delegateTo(value);
+    checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size());
+    return new DelegatingBytes48(value);
   }
 
   /**
@@ -110,7 +111,7 @@
     if (slice instanceof Bytes48) {
       return (Bytes48) slice;
     }
-    return DelegatingBytes48.delegateTo(slice);
+    return new DelegatingBytes48(Bytes48.wrap(slice));
   }
 
   /**
diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes.java b/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes.java
new file mode 100644
index 0000000..8f4a2f5
--- /dev/null
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You 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.
+ */
+package org.apache.tuweni.bytes;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+
+import io.vertx.core.buffer.Buffer;
+
+/**
+ * A class that holds and delegates all operations to its inner bytes field.
+ *
+ * <p>
+ * This class may be used to create more types that represent bytes, but need a different name for business logic.
+ */
+public class DelegatingBytes implements Bytes {
+
+  private final Bytes delegate;
+
+  protected DelegatingBytes(Bytes delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public int size() {
+    return delegate.size();
+  }
+
+  @Override
+  public byte get(int i) {
+    return delegate.get(i);
+  }
+
+  @Override
+  public int getInt(int i) {
+    return delegate.getInt(i);
+  }
+
+  @Override
+  public int toInt() {
+    return delegate.toInt();
+  }
+
+  @Override
+  public long getLong(int i) {
+    return delegate.getLong(i);
+  }
+
+  @Override
+  public long toLong() {
+    return delegate.toLong();
+  }
+
+  @Override
+  public BigInteger toBigInteger() {
+    return delegate.toBigInteger();
+  }
+
+  @Override
+  public BigInteger toUnsignedBigInteger() {
+    return delegate.toUnsignedBigInteger();
+  }
+
+  @Override
+  public boolean isZero() {
+    return delegate.isZero();
+  }
+
+  @Override
+  public int numberOfLeadingZeros() {
+    return delegate.numberOfLeadingZeros();
+  }
+
+  @Override
+  public int numberOfLeadingZeroBytes() {
+    return delegate.numberOfLeadingZeroBytes();
+  }
+
+  @Override
+  public boolean hasLeadingZeroByte() {
+    return delegate.hasLeadingZeroByte();
+  }
+
+  @Override
+  public boolean hasLeadingZero() {
+    return delegate.hasLeadingZero();
+  }
+
+  @Override
+  public int bitLength() {
+    return delegate.bitLength();
+  }
+
+  @Override
+  public Bytes and(Bytes other) {
+    return delegate.and(other);
+  }
+
+  @Override
+  public <T extends MutableBytes> T and(Bytes other, T result) {
+    return delegate.and(other, result);
+  }
+
+  @Override
+  public Bytes or(Bytes other) {
+    return delegate.or(other);
+  }
+
+  @Override
+  public <T extends MutableBytes> T or(Bytes other, T result) {
+    return delegate.or(other, result);
+  }
+
+  @Override
+  public Bytes xor(Bytes other) {
+    return delegate.xor(other);
+  }
+
+  @Override
+  public <T extends MutableBytes> T xor(Bytes other, T result) {
+    return delegate.xor(other, result);
+  }
+
+  @Override
+  public <T extends MutableBytes> T not(T result) {
+    return delegate.not(result);
+  }
+
+  @Override
+  public <T extends MutableBytes> T shiftRight(int distance, T result) {
+    return delegate.shiftRight(distance, result);
+  }
+
+  @Override
+  public <T extends MutableBytes> T shiftLeft(int distance, T result) {
+    return delegate.shiftLeft(distance, result);
+  }
+
+  @Override
+  public Bytes slice(int index) {
+    return delegate.slice(index);
+  }
+
+  @Override
+  public Bytes slice(int index, int length) {
+    return delegate.slice(index, length);
+  }
+
+  @Override
+  public Bytes copy() {
+    return Bytes.wrap(toArray());
+  }
+
+  @Override
+  public MutableBytes mutableCopy() {
+    return MutableBytes.wrap(toArray());
+  }
+
+  @Override
+  public void copyTo(MutableBytes destination) {
+    delegate.copyTo(destination);
+  }
+
+  @Override
+  public void copyTo(MutableBytes destination, int destinationOffset) {
+    delegate.copyTo(destination, destinationOffset);
+  }
+
+  @Override
+  public void appendTo(ByteBuffer byteBuffer) {
+    delegate.appendTo(byteBuffer);
+  }
+
+  @Override
+  public void appendTo(Buffer buffer) {
+    delegate.appendTo(buffer);
+  }
+
+  @Override
+  public int commonPrefixLength(Bytes other) {
+    return delegate.commonPrefixLength(other);
+  }
+
+  @Override
+  public Bytes commonPrefix(Bytes other) {
+    return delegate.commonPrefix(other);
+  }
+
+  @Override
+  public void update(MessageDigest digest) {
+    delegate.update(digest);
+  }
+
+  @Override
+  public byte[] toArray() {
+    return delegate.toArray();
+  }
+
+  @Override
+  public byte[] toArrayUnsafe() {
+    return delegate.toArrayUnsafe();
+  }
+
+  @Override
+  public String toString() {
+    return delegate.toString();
+  }
+
+  @Override
+  public String toHexString() {
+    return delegate.toHexString();
+  }
+
+  @Override
+  public String toShortHexString() {
+    return delegate.toShortHexString();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    return delegate.equals(obj);
+  }
+
+  @Override
+  public int hashCode() {
+    return delegate.hashCode();
+  }
+}
diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes32.java b/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes32.java
index c29a60a..0e2f36e 100644
--- a/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes32.java
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes32.java
@@ -12,27 +12,26 @@
  */
 package org.apache.tuweni.bytes;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
 import java.security.MessageDigest;
 
 import io.vertx.core.buffer.Buffer;
 
-final class DelegatingBytes32 implements Bytes32 {
+/**
+ * A class that holds and delegates all operations to its inner bytes field.
+ *
+ * <p>
+ * This class may be used to create more types that represent 32 bytes, but need a different name for business logic.
+ */
+public class DelegatingBytes32 implements Bytes32 {
 
   private final Bytes delegate;
 
-  private DelegatingBytes32(Bytes delegate) {
+  protected DelegatingBytes32(Bytes delegate) {
     this.delegate = delegate;
   }
 
-  static Bytes32 delegateTo(Bytes value) {
-    checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size());
-    return new DelegatingBytes32(value);
-  }
-
   @Override
   public int size() {
     return Bytes32.SIZE;
diff --git a/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes48.java b/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes48.java
index 9f3198d..1441f73 100644
--- a/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes48.java
+++ b/bytes/src/main/java/org/apache/tuweni/bytes/DelegatingBytes48.java
@@ -12,7 +12,6 @@
  */
 package org.apache.tuweni.bytes;
 
-import static com.google.common.base.Preconditions.checkArgument;
 
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
@@ -20,19 +19,20 @@
 
 import io.vertx.core.buffer.Buffer;
 
-final class DelegatingBytes48 implements Bytes48 {
+/**
+ * A class that holds and delegates all operations to its inner bytes field.
+ *
+ * <p>
+ * This class may be used to create more types that represent 48 bytes, but need a different name for business logic.
+ */
+public class DelegatingBytes48 implements Bytes48 {
 
   private final Bytes delegate;
 
-  private DelegatingBytes48(Bytes delegate) {
+  protected DelegatingBytes48(Bytes delegate) {
     this.delegate = delegate;
   }
 
-  static Bytes48 delegateTo(Bytes value) {
-    checkArgument(value.size() == SIZE, "Expected %s bytes but got %s", SIZE, value.size());
-    return new DelegatingBytes48(value);
-  }
-
   @Override
   public int size() {
     return Bytes48.SIZE;
diff --git a/bytes/src/test/java/org/apache/tuweni/bytes/DelegateBytesTest.java b/bytes/src/test/java/org/apache/tuweni/bytes/DelegateBytesTest.java
new file mode 100644
index 0000000..bfc667c
--- /dev/null
+++ b/bytes/src/test/java/org/apache/tuweni/bytes/DelegateBytesTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to You 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.
+ */
+package org.apache.tuweni.bytes;
+
+public class DelegateBytesTest extends CommonBytesTests {
+
+  @Override
+  Bytes h(String hex) {
+    return new DelegatingBytes(Bytes.fromHexString(hex));
+  }
+
+  @Override
+  MutableBytes m(int size) {
+    // no-op
+    return MutableBytes.create(size);
+  }
+
+  @Override
+  Bytes w(byte[] bytes) {
+    return new DelegatingBytes(Bytes.wrap(bytes));
+  }
+
+  @Override
+  Bytes of(int... bytes) {
+    return new DelegatingBytes(Bytes.of(bytes));
+  }
+}