Replace byteBuilder and byteReader with cryptobyte

While I'm here, update x/crypto and x/net to their latest versions.
byteReader is a straightforward port, except there doesn't seem to be a
convenient way to read length-prefixed bytes without manually casting
from cryptobyte.String to []byte, so I've done that.

byteBuilder is a bit more involved because it's based on closures, but
still a mechanical change.

As part of this, I switched runner's ticket format to use u24 length
prefixes instead of u32, because cryptobyte.String doesn't have u32
length prefixes. (Although, oddly, cryptobyte.Builder does.)

Fixed: 374
Change-Id: If9bea0b41fe2b8bc48f040a667753b160da469bb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/61186
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/go.mod b/go.mod
index 23038f0..a6a6724 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,11 @@
 go 1.19
 
 require (
-	golang.org/x/crypto v0.6.0
-	golang.org/x/net v0.7.0
+	golang.org/x/crypto v0.10.0
+	golang.org/x/net v0.11.0
 )
 
 require (
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/term v0.5.0 // indirect
+	golang.org/x/sys v0.9.0 // indirect
+	golang.org/x/term v0.9.0 // indirect
 )
diff --git a/go.sum b/go.sum
index a97a960..05c20c3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,8 @@
-golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
+golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
+golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
+golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index a51ed55..0ed0094 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -21,6 +21,7 @@
 	"time"
 
 	"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
+	"golang.org/x/crypto/cryptobyte"
 )
 
 const echBadPayloadByte = 0xff
@@ -71,9 +72,12 @@
 	// Replace |newHellos|'s key shares with those of |hello|. For simplicity,
 	// we require their lengths match, which is satisfied by matching the
 	// DefaultCurves setting to the selection in the replacement ClientHello.
-	bb := newByteBuilder()
+	bb := cryptobyte.NewBuilder(nil)
 	hello.marshalKeyShares(bb)
-	keyShares := bb.finish()
+	keyShares, err := bb.Bytes()
+	if err != nil {
+		return nil, err
+	}
 	if len(keyShares) != len(newHello.keySharesRaw) {
 		return nil, errors.New("tls: ClientHello key share length is inconsistent with DefaultCurves setting")
 	}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index b253b0c..6ea7faa 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -5,239 +5,49 @@
 package runner
 
 import (
-	"encoding/binary"
 	"errors"
 	"fmt"
+
+	"golang.org/x/crypto/cryptobyte"
 )
 
-func writeLen(buf []byte, v, size int) {
-	for i := 0; i < size; i++ {
-		buf[size-i-1] = byte(v)
-		v >>= 8
-	}
-	if v != 0 {
-		panic("length is too long")
-	}
-}
-
-type byteBuilder struct {
-	buf       *[]byte
-	start     int
-	prefixLen int
-	child     *byteBuilder
-}
-
-func newByteBuilder() *byteBuilder {
-	buf := make([]byte, 0, 32)
-	return &byteBuilder{buf: &buf}
-}
-
-func (bb *byteBuilder) len() int {
-	return len(*bb.buf) - bb.start - bb.prefixLen
-}
-
-func (bb *byteBuilder) data() []byte {
-	bb.flush()
-	return (*bb.buf)[bb.start+bb.prefixLen:]
-}
-
-func (bb *byteBuilder) flush() {
-	if bb.child == nil {
-		return
-	}
-	bb.child.flush()
-	writeLen((*bb.buf)[bb.child.start:], bb.child.len(), bb.child.prefixLen)
-	bb.child = nil
-	return
-}
-
-func (bb *byteBuilder) finish() []byte {
-	bb.flush()
-	return *bb.buf
-}
-
-func (bb *byteBuilder) addU8(u uint8) {
-	bb.flush()
-	*bb.buf = append(*bb.buf, u)
-}
-
-func (bb *byteBuilder) addU16(u uint16) {
-	bb.flush()
-	*bb.buf = append(*bb.buf, byte(u>>8), byte(u))
-}
-
-func (bb *byteBuilder) addU24(u int) {
-	bb.flush()
-	*bb.buf = append(*bb.buf, byte(u>>16), byte(u>>8), byte(u))
-}
-
-func (bb *byteBuilder) addU32(u uint32) {
-	bb.flush()
-	*bb.buf = append(*bb.buf, byte(u>>24), byte(u>>16), byte(u>>8), byte(u))
-}
-
-func (bb *byteBuilder) addU64(u uint64) {
-	bb.flush()
-	var b [8]byte
-	binary.BigEndian.PutUint64(b[:], u)
-	*bb.buf = append(*bb.buf, b[:]...)
-}
-
-func (bb *byteBuilder) addU8LengthPrefixed() *byteBuilder {
-	return bb.createChild(1)
-}
-
-func (bb *byteBuilder) addU16LengthPrefixed() *byteBuilder {
-	return bb.createChild(2)
-}
-
-func (bb *byteBuilder) addU24LengthPrefixed() *byteBuilder {
-	return bb.createChild(3)
-}
-
-func (bb *byteBuilder) addU32LengthPrefixed() *byteBuilder {
-	return bb.createChild(4)
-}
-
-func (bb *byteBuilder) addBytes(b []byte) {
-	bb.flush()
-	*bb.buf = append(*bb.buf, b...)
-}
-
-func (bb *byteBuilder) createChild(lengthPrefixSize int) *byteBuilder {
-	bb.flush()
-	bb.child = &byteBuilder{
-		buf:       bb.buf,
-		start:     len(*bb.buf),
-		prefixLen: lengthPrefixSize,
-	}
-	for i := 0; i < lengthPrefixSize; i++ {
-		*bb.buf = append(*bb.buf, 0)
-	}
-	return bb.child
-}
-
-func (bb *byteBuilder) discardChild() {
-	if bb.child == nil {
-		return
-	}
-	*bb.buf = (*bb.buf)[:bb.child.start]
-	bb.child = nil
-}
-
-type byteReader []byte
-
-func (br *byteReader) readInternal(out *byteReader, n int) bool {
-	if len(*br) < n {
+func readUint8LengthPrefixedBytes(s *cryptobyte.String, out *[]byte) bool {
+	var child cryptobyte.String
+	if !s.ReadUint8LengthPrefixed(&child) {
 		return false
 	}
-	*out = (*br)[:n]
-	*br = (*br)[n:]
+	*out = child
 	return true
 }
 
-func (br *byteReader) readBytes(out *[]byte, n int) bool {
-	var child byteReader
-	if !br.readInternal(&child, n) {
+func readUint16LengthPrefixedBytes(s *cryptobyte.String, out *[]byte) bool {
+	var child cryptobyte.String
+	if !s.ReadUint16LengthPrefixed(&child) {
 		return false
 	}
-	*out = []byte(child)
+	*out = child
 	return true
 }
 
-func (br *byteReader) readUint(out *uint64, n int) bool {
-	var b []byte
-	if !br.readBytes(&b, n) {
+func readUint24LengthPrefixedBytes(s *cryptobyte.String, out *[]byte) bool {
+	var child cryptobyte.String
+	if !s.ReadUint24LengthPrefixed(&child) {
 		return false
 	}
-	*out = 0
-	for _, v := range b {
-		*out <<= 8
-		*out |= uint64(v)
-	}
+	*out = child
 	return true
 }
 
-func (br *byteReader) readU8(out *uint8) bool {
-	var b []byte
-	if !br.readBytes(&b, 1) {
-		return false
-	}
-	*out = b[0]
-	return true
+func addUint8LengthPrefixedBytes(b *cryptobyte.Builder, v []byte) {
+	b.AddUint8LengthPrefixed(func(child *cryptobyte.Builder) { child.AddBytes(v) })
 }
 
-func (br *byteReader) readU16(out *uint16) bool {
-	var v uint64
-	if !br.readUint(&v, 2) {
-		return false
-	}
-	*out = uint16(v)
-	return true
+func addUint16LengthPrefixedBytes(b *cryptobyte.Builder, v []byte) {
+	b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) { child.AddBytes(v) })
 }
 
-func (br *byteReader) readU24(out *uint32) bool {
-	var v uint64
-	if !br.readUint(&v, 3) {
-		return false
-	}
-	*out = uint32(v)
-	return true
-}
-
-func (br *byteReader) readU32(out *uint32) bool {
-	var v uint64
-	if !br.readUint(&v, 4) {
-		return false
-	}
-	*out = uint32(v)
-	return true
-}
-
-func (br *byteReader) readU64(out *uint64) bool {
-	return br.readUint(out, 8)
-}
-
-func (br *byteReader) readLengthPrefixed(out *byteReader, n int) bool {
-	var length uint64
-	return br.readUint(&length, n) &&
-		uint64(len(*br)) >= length &&
-		br.readInternal(out, int(length))
-}
-
-func (br *byteReader) readLengthPrefixedBytes(out *[]byte, n int) bool {
-	var length uint64
-	return br.readUint(&length, n) &&
-		uint64(len(*br)) >= length &&
-		br.readBytes(out, int(length))
-}
-
-func (br *byteReader) readU8LengthPrefixed(out *byteReader) bool {
-	return br.readLengthPrefixed(out, 1)
-}
-func (br *byteReader) readU8LengthPrefixedBytes(out *[]byte) bool {
-	return br.readLengthPrefixedBytes(out, 1)
-}
-
-func (br *byteReader) readU16LengthPrefixed(out *byteReader) bool {
-	return br.readLengthPrefixed(out, 2)
-}
-func (br *byteReader) readU16LengthPrefixedBytes(out *[]byte) bool {
-	return br.readLengthPrefixedBytes(out, 2)
-}
-
-func (br *byteReader) readU24LengthPrefixed(out *byteReader) bool {
-	return br.readLengthPrefixed(out, 3)
-}
-func (br *byteReader) readU24LengthPrefixedBytes(out *[]byte) bool {
-	return br.readLengthPrefixedBytes(out, 3)
-}
-
-func (br *byteReader) readU32LengthPrefixed(out *byteReader) bool {
-	return br.readLengthPrefixed(out, 4)
-}
-func (br *byteReader) readU32LengthPrefixedBytes(out *[]byte) bool {
-	return br.readLengthPrefixedBytes(out, 4)
+func addUint24LengthPrefixedBytes(b *cryptobyte.Builder, v []byte) {
+	b.AddUint24LengthPrefixed(func(child *cryptobyte.Builder) { child.AddBytes(v) })
 }
 
 type keyShareEntry struct {
@@ -269,48 +79,52 @@
 }
 
 func CreateECHConfig(template *ECHConfig) *ECHConfig {
-	bb := newByteBuilder()
+	bb := cryptobyte.NewBuilder(nil)
 	// ECHConfig reuses the encrypted_client_hello extension codepoint as a
 	// version identifier.
-	bb.addU16(extensionEncryptedClientHello)
-	contents := bb.addU16LengthPrefixed()
-	contents.addU8(template.ConfigID)
-	contents.addU16(template.KEM)
-	contents.addU16LengthPrefixed().addBytes(template.PublicKey)
-	cipherSuites := contents.addU16LengthPrefixed()
-	for _, suite := range template.CipherSuites {
-		cipherSuites.addU16(suite.KDF)
-		cipherSuites.addU16(suite.AEAD)
-	}
-	contents.addU8(template.MaxNameLen)
-	contents.addU8LengthPrefixed().addBytes([]byte(template.PublicName))
-	extensions := contents.addU16LengthPrefixed()
-	// Mandatory extensions have the high bit set.
-	if template.UnsupportedExtension {
-		extensions.addU16(0x1111)
-		extensions.addU16LengthPrefixed().addBytes([]byte("test"))
-	}
-	if template.UnsupportedMandatoryExtension {
-		extensions.addU16(0xaaaa)
-		extensions.addU16LengthPrefixed().addBytes([]byte("test"))
-	}
+	bb.AddUint16(extensionEncryptedClientHello)
+	bb.AddUint16LengthPrefixed(func(contents *cryptobyte.Builder) {
+		contents.AddUint8(template.ConfigID)
+		contents.AddUint16(template.KEM)
+		addUint16LengthPrefixedBytes(contents, template.PublicKey)
+		contents.AddUint16LengthPrefixed(func(cipherSuites *cryptobyte.Builder) {
+			for _, suite := range template.CipherSuites {
+				cipherSuites.AddUint16(suite.KDF)
+				cipherSuites.AddUint16(suite.AEAD)
+			}
+		})
+		contents.AddUint8(template.MaxNameLen)
+		addUint8LengthPrefixedBytes(contents, []byte(template.PublicName))
+		contents.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+			// Mandatory extensions have the high bit set.
+			if template.UnsupportedExtension {
+				extensions.AddUint16(0x1111)
+				addUint16LengthPrefixedBytes(extensions, []byte("test"))
+			}
+			if template.UnsupportedMandatoryExtension {
+				extensions.AddUint16(0xaaaa)
+				addUint16LengthPrefixedBytes(extensions, []byte("test"))
+			}
+		})
+	})
 
-	// This ought to be a call to a function like ParseECHConfig(bb.finish()),
+	// This ought to be a call to a function like ParseECHConfig(bb.BytesOrPanic()),
 	// but this constrains us to constructing ECHConfigs we are willing to
 	// support. We need to test the client's behavior in response to unparsable
 	// or unsupported ECHConfigs, so populate fields from the template directly.
 	ret := *template
-	ret.Raw = bb.finish()
+	ret.Raw = bb.BytesOrPanic()
 	return &ret
 }
 
 func CreateECHConfigList(configs ...[]byte) []byte {
-	bb := newByteBuilder()
-	list := bb.addU16LengthPrefixed()
-	for _, config := range configs {
-		list.addBytes(config)
-	}
-	return bb.finish()
+	bb := cryptobyte.NewBuilder(nil)
+	bb.AddUint16LengthPrefixed(func(list *cryptobyte.Builder) {
+		for _, config := range configs {
+			list.AddBytes(config)
+		}
+	})
+	return bb.BytesOrPanic()
 }
 
 type ServerECHConfig struct {
@@ -392,16 +206,16 @@
 	rawExtensions   []byte
 }
 
-func (m *clientHelloMsg) marshalKeyShares(bb *byteBuilder) {
-	keyShares := bb.addU16LengthPrefixed()
-	for _, keyShare := range m.keyShares {
-		keyShares.addU16(uint16(keyShare.group))
-		keyExchange := keyShares.addU16LengthPrefixed()
-		keyExchange.addBytes(keyShare.keyExchange)
-	}
-	if m.trailingKeyShareData {
-		keyShares.addU8(0)
-	}
+func (m *clientHelloMsg) marshalKeyShares(bb *cryptobyte.Builder) {
+	bb.AddUint16LengthPrefixed(func(keyShares *cryptobyte.Builder) {
+		for _, keyShare := range m.keyShares {
+			keyShares.AddUint16(uint16(keyShare.group))
+			addUint16LengthPrefixedBytes(keyShares, keyShare.keyExchange)
+		}
+		if m.trailingKeyShareData {
+			keyShares.AddUint8(0)
+		}
+	})
 }
 
 type clientHelloType int
@@ -411,23 +225,27 @@
 	clientHelloEncodedInner
 )
 
-func (m *clientHelloMsg) marshalBody(hello *byteBuilder, typ clientHelloType) {
-	hello.addU16(m.vers)
-	hello.addBytes(m.random)
-	sessionID := hello.addU8LengthPrefixed()
-	if typ != clientHelloEncodedInner {
-		sessionID.addBytes(m.sessionID)
-	}
+func (m *clientHelloMsg) marshalBody(hello *cryptobyte.Builder, typ clientHelloType) {
+	hello.AddUint16(m.vers)
+	hello.AddBytes(m.random)
+	hello.AddUint8LengthPrefixed(func(sessionID *cryptobyte.Builder) {
+		if typ != clientHelloEncodedInner {
+			sessionID.AddBytes(m.sessionID)
+		}
+	})
 	if m.isDTLS {
-		cookie := hello.addU8LengthPrefixed()
-		cookie.addBytes(m.cookie)
+		hello.AddUint8LengthPrefixed(func(cookie *cryptobyte.Builder) {
+			cookie.AddBytes(m.cookie)
+		})
 	}
-	cipherSuites := hello.addU16LengthPrefixed()
-	for _, suite := range m.cipherSuites {
-		cipherSuites.addU16(suite)
-	}
-	compressionMethods := hello.addU8LengthPrefixed()
-	compressionMethods.addBytes(m.compressionMethods)
+	hello.AddUint16LengthPrefixed(func(cipherSuites *cryptobyte.Builder) {
+		for _, suite := range m.cipherSuites {
+			cipherSuites.AddUint16(suite)
+		}
+	})
+	hello.AddUint8LengthPrefixed(func(compressionMethods *cryptobyte.Builder) {
+		compressionMethods.AddBytes(m.compressionMethods)
+	})
 
 	type extension struct {
 		id   uint16
@@ -462,99 +280,99 @@
 		//     ServerName server_name_list<1..2^16-1>
 		// } ServerNameList;
 
-		serverNameList := newByteBuilder()
-		serverName := serverNameList.addU16LengthPrefixed()
-		serverName.addU8(0) // NameType host_name(0)
-		hostName := serverName.addU16LengthPrefixed()
-		hostName.addBytes([]byte(m.serverName))
+		serverNameList := cryptobyte.NewBuilder(nil)
+		serverNameList.AddUint16LengthPrefixed(func(serverName *cryptobyte.Builder) {
+			serverName.AddUint8(0) // NameType host_name(0)
+			addUint16LengthPrefixedBytes(serverName, []byte(m.serverName))
+		})
 
 		extensions = append(extensions, extension{
 			id:   extensionServerName,
-			body: serverNameList.finish(),
+			body: serverNameList.BytesOrPanic(),
 		})
 	}
 	if m.echOuter != nil {
-		body := newByteBuilder()
-		body.addU8(echClientTypeOuter)
-		body.addU16(m.echOuter.kdfID)
-		body.addU16(m.echOuter.aeadID)
-		body.addU8(m.echOuter.configID)
-		body.addU16LengthPrefixed().addBytes(m.echOuter.enc)
-		body.addU16LengthPrefixed().addBytes(m.echOuter.payload)
+		body := cryptobyte.NewBuilder(nil)
+		body.AddUint8(echClientTypeOuter)
+		body.AddUint16(m.echOuter.kdfID)
+		body.AddUint16(m.echOuter.aeadID)
+		body.AddUint8(m.echOuter.configID)
+		addUint16LengthPrefixedBytes(body, m.echOuter.enc)
+		addUint16LengthPrefixedBytes(body, m.echOuter.payload)
 		extensions = append(extensions, extension{
 			id:   extensionEncryptedClientHello,
-			body: body.finish(),
+			body: body.BytesOrPanic(),
 		})
 	}
 	if m.echInner {
-		body := newByteBuilder()
-		body.addU8(echClientTypeInner)
+		body := cryptobyte.NewBuilder(nil)
+		body.AddUint8(echClientTypeInner)
 		// If unset, invalidECHInner is empty, which is the correct serialization.
-		body.addBytes(m.invalidECHInner)
+		body.AddBytes(m.invalidECHInner)
 		extensions = append(extensions, extension{
 			id:   extensionEncryptedClientHello,
-			body: body.finish(),
+			body: body.BytesOrPanic(),
 		})
 	}
 	if m.ocspStapling {
-		certificateStatusRequest := newByteBuilder()
+		certificateStatusRequest := cryptobyte.NewBuilder(nil)
 		// RFC 4366, section 3.6
-		certificateStatusRequest.addU8(1) // OCSP type
+		certificateStatusRequest.AddUint8(1) // OCSP type
 		// Two zero valued uint16s for the two lengths.
-		certificateStatusRequest.addU16(0) // ResponderID length
-		certificateStatusRequest.addU16(0) // Extensions length
+		certificateStatusRequest.AddUint16(0) // ResponderID length
+		certificateStatusRequest.AddUint16(0) // Extensions length
 		extensions = append(extensions, extension{
 			id:   extensionStatusRequest,
-			body: certificateStatusRequest.finish(),
+			body: certificateStatusRequest.BytesOrPanic(),
 		})
 	}
 	if len(m.supportedCurves) > 0 {
 		// http://tools.ietf.org/html/rfc4492#section-5.1.1
-		supportedCurvesList := newByteBuilder()
-		supportedCurves := supportedCurvesList.addU16LengthPrefixed()
-		for _, curve := range m.supportedCurves {
-			supportedCurves.addU16(uint16(curve))
-		}
+		supportedCurvesList := cryptobyte.NewBuilder(nil)
+		supportedCurvesList.AddUint16LengthPrefixed(func(supportedCurves *cryptobyte.Builder) {
+			for _, curve := range m.supportedCurves {
+				supportedCurves.AddUint16(uint16(curve))
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionSupportedCurves,
-			body: supportedCurvesList.finish(),
+			body: supportedCurvesList.BytesOrPanic(),
 		})
 	}
 	if len(m.supportedPoints) > 0 {
 		// http://tools.ietf.org/html/rfc4492#section-5.1.2
-		supportedPointsList := newByteBuilder()
-		supportedPoints := supportedPointsList.addU8LengthPrefixed()
-		supportedPoints.addBytes(m.supportedPoints)
+		supportedPointsList := cryptobyte.NewBuilder(nil)
+		addUint8LengthPrefixedBytes(supportedPointsList, m.supportedPoints)
 		extensions = append(extensions, extension{
 			id:   extensionSupportedPoints,
-			body: supportedPointsList.finish(),
+			body: supportedPointsList.BytesOrPanic(),
 		})
 	}
 	if m.hasKeyShares {
-		keyShareList := newByteBuilder()
+		keyShareList := cryptobyte.NewBuilder(nil)
 		m.marshalKeyShares(keyShareList)
 		extensions = append(extensions, extension{
 			id:   extensionKeyShare,
-			body: keyShareList.finish(),
+			body: keyShareList.BytesOrPanic(),
 		})
 	}
 	if len(m.pskKEModes) > 0 {
-		pskModesExtension := newByteBuilder()
-		pskModesExtension.addU8LengthPrefixed().addBytes(m.pskKEModes)
+		pskModesExtension := cryptobyte.NewBuilder(nil)
+		addUint8LengthPrefixedBytes(pskModesExtension, m.pskKEModes)
 		extensions = append(extensions, extension{
 			id:   extensionPSKKeyExchangeModes,
-			body: pskModesExtension.finish(),
+			body: pskModesExtension.BytesOrPanic(),
 		})
 	}
 	if m.hasEarlyData {
 		extensions = append(extensions, extension{id: extensionEarlyData})
 	}
 	if len(m.tls13Cookie) > 0 {
-		body := newByteBuilder()
-		body.addU16LengthPrefixed().addBytes(m.tls13Cookie)
+		body := cryptobyte.NewBuilder(nil)
+		addUint16LengthPrefixedBytes(body, m.tls13Cookie)
 		extensions = append(extensions, extension{
 			id:   extensionCookie,
-			body: body.finish(),
+			body: body.BytesOrPanic(),
 		})
 	}
 	if m.ticketSupported {
@@ -566,57 +384,60 @@
 	}
 	if len(m.signatureAlgorithms) > 0 {
 		// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
-		signatureAlgorithmsExtension := newByteBuilder()
-		signatureAlgorithms := signatureAlgorithmsExtension.addU16LengthPrefixed()
-		for _, sigAlg := range m.signatureAlgorithms {
-			signatureAlgorithms.addU16(uint16(sigAlg))
-		}
+		signatureAlgorithmsExtension := cryptobyte.NewBuilder(nil)
+		signatureAlgorithmsExtension.AddUint16LengthPrefixed(func(signatureAlgorithms *cryptobyte.Builder) {
+			for _, sigAlg := range m.signatureAlgorithms {
+				signatureAlgorithms.AddUint16(uint16(sigAlg))
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionSignatureAlgorithms,
-			body: signatureAlgorithmsExtension.finish(),
+			body: signatureAlgorithmsExtension.BytesOrPanic(),
 		})
 	}
 	if len(m.signatureAlgorithmsCert) > 0 {
-		signatureAlgorithmsCertExtension := newByteBuilder()
-		signatureAlgorithmsCert := signatureAlgorithmsCertExtension.addU16LengthPrefixed()
-		for _, sigAlg := range m.signatureAlgorithmsCert {
-			signatureAlgorithmsCert.addU16(uint16(sigAlg))
-		}
+		signatureAlgorithmsCertExtension := cryptobyte.NewBuilder(nil)
+		signatureAlgorithmsCertExtension.AddUint16LengthPrefixed(func(signatureAlgorithmsCert *cryptobyte.Builder) {
+			for _, sigAlg := range m.signatureAlgorithmsCert {
+				signatureAlgorithmsCert.AddUint16(uint16(sigAlg))
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionSignatureAlgorithmsCert,
-			body: signatureAlgorithmsCertExtension.finish(),
+			body: signatureAlgorithmsCertExtension.BytesOrPanic(),
 		})
 	}
 	if len(m.supportedVersions) > 0 {
-		supportedVersionsExtension := newByteBuilder()
-		supportedVersions := supportedVersionsExtension.addU8LengthPrefixed()
-		for _, version := range m.supportedVersions {
-			supportedVersions.addU16(uint16(version))
-		}
+		supportedVersionsExtension := cryptobyte.NewBuilder(nil)
+		supportedVersionsExtension.AddUint8LengthPrefixed(func(supportedVersions *cryptobyte.Builder) {
+			for _, version := range m.supportedVersions {
+				supportedVersions.AddUint16(uint16(version))
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionSupportedVersions,
-			body: supportedVersionsExtension.finish(),
+			body: supportedVersionsExtension.BytesOrPanic(),
 		})
 	}
 	if m.secureRenegotiation != nil {
-		secureRenegoExt := newByteBuilder()
-		secureRenegoExt.addU8LengthPrefixed().addBytes(m.secureRenegotiation)
+		secureRenegoExt := cryptobyte.NewBuilder(nil)
+		addUint8LengthPrefixedBytes(secureRenegoExt, m.secureRenegotiation)
 		extensions = append(extensions, extension{
 			id:   extensionRenegotiationInfo,
-			body: secureRenegoExt.finish(),
+			body: secureRenegoExt.BytesOrPanic(),
 		})
 	}
 	if len(m.alpnProtocols) > 0 {
 		// https://tools.ietf.org/html/rfc7301#section-3.1
-		alpnExtension := newByteBuilder()
-		protocolNameList := alpnExtension.addU16LengthPrefixed()
-		for _, s := range m.alpnProtocols {
-			protocolName := protocolNameList.addU8LengthPrefixed()
-			protocolName.addBytes([]byte(s))
-		}
+		alpnExtension := cryptobyte.NewBuilder(nil)
+		alpnExtension.AddUint16LengthPrefixed(func(protocolNameList *cryptobyte.Builder) {
+			for _, s := range m.alpnProtocols {
+				addUint8LengthPrefixedBytes(protocolNameList, []byte(s))
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionALPN,
-			body: alpnExtension.finish(),
+			body: alpnExtension.BytesOrPanic(),
 		})
 	}
 	if len(m.quicTransportParams) > 0 {
@@ -644,18 +465,18 @@
 	}
 	if len(m.srtpProtectionProfiles) > 0 {
 		// https://tools.ietf.org/html/rfc5764#section-4.1.1
-		useSrtpExt := newByteBuilder()
+		useSrtpExt := cryptobyte.NewBuilder(nil)
 
-		srtpProtectionProfiles := useSrtpExt.addU16LengthPrefixed()
-		for _, p := range m.srtpProtectionProfiles {
-			srtpProtectionProfiles.addU16(p)
-		}
-		srtpMki := useSrtpExt.addU8LengthPrefixed()
-		srtpMki.addBytes([]byte(m.srtpMasterKeyIdentifier))
+		useSrtpExt.AddUint16LengthPrefixed(func(srtpProtectionProfiles *cryptobyte.Builder) {
+			for _, p := range m.srtpProtectionProfiles {
+				srtpProtectionProfiles.AddUint16(p)
+			}
+		})
+		addUint8LengthPrefixedBytes(useSrtpExt, []byte(m.srtpMasterKeyIdentifier))
 
 		extensions = append(extensions, extension{
 			id:   extensionUseSRTP,
-			body: useSrtpExt.finish(),
+			body: useSrtpExt.BytesOrPanic(),
 		})
 	}
 	if m.sctListSupported {
@@ -668,130 +489,138 @@
 		})
 	}
 	if len(m.compressedCertAlgs) > 0 {
-		body := newByteBuilder()
-		algIDs := body.addU8LengthPrefixed()
-		for _, v := range m.compressedCertAlgs {
-			algIDs.addU16(v)
-		}
+		body := cryptobyte.NewBuilder(nil)
+		body.AddUint8LengthPrefixed(func(algIDs *cryptobyte.Builder) {
+			for _, v := range m.compressedCertAlgs {
+				algIDs.AddUint16(v)
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionCompressedCertAlgs,
-			body: body.finish(),
+			body: body.BytesOrPanic(),
 		})
 	}
 	if m.delegatedCredentials {
-		body := newByteBuilder()
-		signatureSchemeList := body.addU16LengthPrefixed()
-		for _, sigAlg := range m.signatureAlgorithms {
-			signatureSchemeList.addU16(uint16(sigAlg))
-		}
+		body := cryptobyte.NewBuilder(nil)
+		body.AddUint16LengthPrefixed(func(signatureSchemeList *cryptobyte.Builder) {
+			for _, sigAlg := range m.signatureAlgorithms {
+				signatureSchemeList.AddUint16(uint16(sigAlg))
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionDelegatedCredentials,
-			body: body.finish(),
+			body: body.BytesOrPanic(),
 		})
 	}
 	if len(m.alpsProtocols) > 0 {
-		body := newByteBuilder()
-		protocolNameList := body.addU16LengthPrefixed()
-		for _, s := range m.alpsProtocols {
-			protocolNameList.addU8LengthPrefixed().addBytes([]byte(s))
-		}
+		body := cryptobyte.NewBuilder(nil)
+		body.AddUint16LengthPrefixed(func(protocolNameList *cryptobyte.Builder) {
+			for _, s := range m.alpsProtocols {
+				addUint8LengthPrefixedBytes(protocolNameList, []byte(s))
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionApplicationSettings,
-			body: body.finish(),
+			body: body.BytesOrPanic(),
 		})
 	}
 
 	// The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11
 	if len(m.pskIdentities) > 0 {
-		pskExtension := newByteBuilder()
-		pskIdentities := pskExtension.addU16LengthPrefixed()
-		for _, psk := range m.pskIdentities {
-			pskIdentities.addU16LengthPrefixed().addBytes(psk.ticket)
-			pskIdentities.addU32(psk.obfuscatedTicketAge)
-		}
-		pskBinders := pskExtension.addU16LengthPrefixed()
-		for _, binder := range m.pskBinders {
-			pskBinders.addU8LengthPrefixed().addBytes(binder)
-		}
+		pskExtension := cryptobyte.NewBuilder(nil)
+		pskExtension.AddUint16LengthPrefixed(func(pskIdentities *cryptobyte.Builder) {
+			for _, psk := range m.pskIdentities {
+				addUint16LengthPrefixedBytes(pskIdentities, psk.ticket)
+				pskIdentities.AddUint32(psk.obfuscatedTicketAge)
+			}
+		})
+		pskExtension.AddUint16LengthPrefixed(func(pskBinders *cryptobyte.Builder) {
+			for _, binder := range m.pskBinders {
+				addUint8LengthPrefixedBytes(pskBinders, binder)
+			}
+		})
 		extensions = append(extensions, extension{
 			id:   extensionPreSharedKey,
-			body: pskExtension.finish(),
+			body: pskExtension.BytesOrPanic(),
 		})
 	}
 
-	extensionsBB := hello.addU16LengthPrefixed()
-	extMap := make(map[uint16][]byte)
-	extsWritten := make(map[uint16]struct{})
-	for _, ext := range extensions {
-		extMap[ext.id] = ext.body
+	if m.omitExtensions {
+		return
 	}
-	// Write each of the prefix extensions, if we have it.
-	for _, extID := range m.prefixExtensions {
-		if body, ok := extMap[extID]; ok {
-			extensionsBB.addU16(extID)
-			extensionsBB.addU16LengthPrefixed().addBytes(body)
-			extsWritten[extID] = struct{}{}
+	hello.AddUint16LengthPrefixed(func(extensionsBB *cryptobyte.Builder) {
+		if m.emptyExtensions {
+			return
 		}
-	}
-	// Write outer extensions, possibly in compressed form.
-	if m.outerExtensions != nil {
-		if typ == clientHelloEncodedInner && !m.reorderOuterExtensionsWithoutCompressing {
-			extensionsBB.addU16(extensionECHOuterExtensions)
-			list := extensionsBB.addU16LengthPrefixed().addU8LengthPrefixed()
-			for _, extID := range m.outerExtensions {
-				list.addU16(extID)
+		extMap := make(map[uint16][]byte)
+		extsWritten := make(map[uint16]struct{})
+		for _, ext := range extensions {
+			extMap[ext.id] = ext.body
+		}
+		// Write each of the prefix extensions, if we have it.
+		for _, extID := range m.prefixExtensions {
+			if body, ok := extMap[extID]; ok {
+				extensionsBB.AddUint16(extID)
+				addUint16LengthPrefixedBytes(extensionsBB, body)
 				extsWritten[extID] = struct{}{}
 			}
-		} else {
-			for _, extID := range m.outerExtensions {
-				// m.outerExtensions may intentionally contain duplicates to test the
-				// server's reaction. If m.reorderOuterExtensionsWithoutCompressing
-				// is set, we are targetting the second ClientHello and wish to send a
-				// valid first ClientHello. In that case, deduplicate so the error
-				// only appears later.
-				if _, written := extsWritten[extID]; m.reorderOuterExtensionsWithoutCompressing && written {
-					continue
-				}
-				if body, ok := extMap[extID]; ok {
-					extensionsBB.addU16(extID)
-					extensionsBB.addU16LengthPrefixed().addBytes(body)
-					extsWritten[extID] = struct{}{}
+		}
+		// Write outer extensions, possibly in compressed form.
+		if m.outerExtensions != nil {
+			if typ == clientHelloEncodedInner && !m.reorderOuterExtensionsWithoutCompressing {
+				extensionsBB.AddUint16(extensionECHOuterExtensions)
+				extensionsBB.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
+					child.AddUint8LengthPrefixed(func(list *cryptobyte.Builder) {
+						for _, extID := range m.outerExtensions {
+							list.AddUint16(extID)
+							extsWritten[extID] = struct{}{}
+						}
+					})
+				})
+			} else {
+				for _, extID := range m.outerExtensions {
+					// m.outerExtensions may intentionally contain duplicates to test the
+					// server's reaction. If m.reorderOuterExtensionsWithoutCompressing
+					// is set, we are targetting the second ClientHello and wish to send a
+					// valid first ClientHello. In that case, deduplicate so the error
+					// only appears later.
+					if _, written := extsWritten[extID]; m.reorderOuterExtensionsWithoutCompressing && written {
+						continue
+					}
+					if body, ok := extMap[extID]; ok {
+						extensionsBB.AddUint16(extID)
+						addUint16LengthPrefixedBytes(extensionsBB, body)
+						extsWritten[extID] = struct{}{}
+					}
 				}
 			}
 		}
-	}
 
-	// Write each of the remaining extensions in their original order.
-	for _, ext := range extensions {
-		if _, written := extsWritten[ext.id]; !written {
-			extensionsBB.addU16(ext.id)
-			extensionsBB.addU16LengthPrefixed().addBytes(ext.body)
+		// Write each of the remaining extensions in their original order.
+		for _, ext := range extensions {
+			if _, written := extsWritten[ext.id]; !written {
+				extensionsBB.AddUint16(ext.id)
+				addUint16LengthPrefixedBytes(extensionsBB, ext.body)
+			}
 		}
-	}
 
-	if m.pad != 0 && hello.len()%m.pad != 0 {
-		extensionsBB.addU16(extensionPadding)
-		padding := extensionsBB.addU16LengthPrefixed()
-		// Note hello.len() has changed at this point from the length
-		// prefix.
-		if l := hello.len() % m.pad; l != 0 {
-			padding.addBytes(make([]byte, m.pad-l))
+		if m.pad != 0 && len(hello.BytesOrPanic())%m.pad != 0 {
+			extensionsBB.AddUint16(extensionPadding)
+			extensionsBB.AddUint16LengthPrefixed(func(padding *cryptobyte.Builder) {
+				// Note hello.len() has changed at this point from the length
+				// prefix.
+				if l := len(hello.BytesOrPanic()) % m.pad; l != 0 {
+					padding.AddBytes(make([]byte, m.pad-l))
+				}
+			})
 		}
-	}
-
-	if m.omitExtensions || m.emptyExtensions {
-		// Silently erase any extensions which were sent.
-		hello.discardChild()
-		if m.emptyExtensions {
-			hello.addU16(0)
-		}
-	}
+	})
 }
 
 func (m *clientHelloMsg) marshalForEncodedInner() []byte {
-	hello := newByteBuilder()
+	hello := cryptobyte.NewBuilder(nil)
 	m.marshalBody(hello, clientHelloEncodedInner)
-	return hello.finish()
+	return hello.BytesOrPanic()
 }
 
 func (m *clientHelloMsg) marshal() []byte {
@@ -800,26 +629,27 @@
 	}
 
 	if m.isV2ClientHello {
-		v2Msg := newByteBuilder()
-		v2Msg.addU8(1)
-		v2Msg.addU16(m.vers)
-		v2Msg.addU16(uint16(len(m.cipherSuites) * 3))
-		v2Msg.addU16(uint16(len(m.sessionID)))
-		v2Msg.addU16(uint16(len(m.v2Challenge)))
+		v2Msg := cryptobyte.NewBuilder(nil)
+		v2Msg.AddUint8(1)
+		v2Msg.AddUint16(m.vers)
+		v2Msg.AddUint16(uint16(len(m.cipherSuites) * 3))
+		v2Msg.AddUint16(uint16(len(m.sessionID)))
+		v2Msg.AddUint16(uint16(len(m.v2Challenge)))
 		for _, spec := range m.cipherSuites {
-			v2Msg.addU24(int(spec))
+			v2Msg.AddUint24(uint32(spec))
 		}
-		v2Msg.addBytes(m.sessionID)
-		v2Msg.addBytes(m.v2Challenge)
-		m.raw = v2Msg.finish()
+		v2Msg.AddBytes(m.sessionID)
+		v2Msg.AddBytes(m.v2Challenge)
+		m.raw = v2Msg.BytesOrPanic()
 		return m.raw
 	}
 
-	handshakeMsg := newByteBuilder()
-	handshakeMsg.addU8(typeClientHello)
-	hello := handshakeMsg.addU24LengthPrefixed()
-	m.marshalBody(hello, clientHelloNormal)
-	m.raw = handshakeMsg.finish()
+	handshakeMsg := cryptobyte.NewBuilder(nil)
+	handshakeMsg.AddUint8(typeClientHello)
+	handshakeMsg.AddUint24LengthPrefixed(func(hello *cryptobyte.Builder) {
+		m.marshalBody(hello, clientHelloNormal)
+	})
+	m.raw = handshakeMsg.BytesOrPanic()
 	// Sanity-check padding.
 	if m.pad != 0 && (len(m.raw)-4)%m.pad != 0 {
 		panic(fmt.Sprintf("%d is not a multiple of %d", len(m.raw)-4, m.pad))
@@ -827,9 +657,9 @@
 	return m.raw
 }
 
-func parseSignatureAlgorithms(reader *byteReader, out *[]signatureAlgorithm, allowEmpty bool) bool {
-	var sigAlgs byteReader
-	if !reader.readU16LengthPrefixed(&sigAlgs) {
+func parseSignatureAlgorithms(reader *cryptobyte.String, out *[]signatureAlgorithm, allowEmpty bool) bool {
+	var sigAlgs cryptobyte.String
+	if !reader.ReadUint16LengthPrefixed(&sigAlgs) {
 		return false
 	}
 	if !allowEmpty && len(sigAlgs) == 0 {
@@ -838,7 +668,7 @@
 	*out = make([]signatureAlgorithm, 0, len(sigAlgs)/2)
 	for len(sigAlgs) > 0 {
 		var v uint16
-		if !sigAlgs.readU16(&v) {
+		if !sigAlgs.ReadUint16(&v) {
 			return false
 		}
 		if signatureAlgorithm(v) == signatureRSAPKCS1WithMD5AndSHA1 {
@@ -852,13 +682,13 @@
 	return true
 }
 
-func checkDuplicateExtensions(extensions byteReader) bool {
+func checkDuplicateExtensions(extensions cryptobyte.String) bool {
 	seen := make(map[uint16]struct{})
 	for len(extensions) > 0 {
 		var extension uint16
-		var body byteReader
-		if !extensions.readU16(&extension) ||
-			!extensions.readU16LengthPrefixed(&body) {
+		var body cryptobyte.String
+		if !extensions.ReadUint16(&extension) ||
+			!extensions.ReadUint16LengthPrefixed(&body) {
 			return false
 		}
 		if _, ok := seen[extension]; ok {
@@ -871,26 +701,26 @@
 
 func (m *clientHelloMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
-	if !reader.readU16(&m.vers) ||
-		!reader.readBytes(&m.random, 32) ||
-		!reader.readU8LengthPrefixedBytes(&m.sessionID) ||
+	reader := cryptobyte.String(data[4:])
+	if !reader.ReadUint16(&m.vers) ||
+		!reader.ReadBytes(&m.random, 32) ||
+		!readUint8LengthPrefixedBytes(&reader, &m.sessionID) ||
 		len(m.sessionID) > 32 {
 		return false
 	}
-	if m.isDTLS && !reader.readU8LengthPrefixedBytes(&m.cookie) {
+	if m.isDTLS && !readUint8LengthPrefixedBytes(&reader, &m.cookie) {
 		return false
 	}
-	var cipherSuites byteReader
-	if !reader.readU16LengthPrefixed(&cipherSuites) ||
-		!reader.readU8LengthPrefixedBytes(&m.compressionMethods) {
+	var cipherSuites cryptobyte.String
+	if !reader.ReadUint16LengthPrefixed(&cipherSuites) ||
+		!readUint8LengthPrefixedBytes(&reader, &m.compressionMethods) {
 		return false
 	}
 
 	m.cipherSuites = make([]uint16, 0, len(cipherSuites)/2)
 	for len(cipherSuites) > 0 {
 		var v uint16
-		if !cipherSuites.readU16(&v) {
+		if !cipherSuites.ReadUint16(&v) {
 			return false
 		}
 		m.cipherSuites = append(m.cipherSuites, v)
@@ -921,29 +751,29 @@
 		return true
 	}
 
-	var extensions byteReader
-	if !reader.readU16LengthPrefixed(&extensions) || len(reader) != 0 || !checkDuplicateExtensions(extensions) {
+	var extensions cryptobyte.String
+	if !reader.ReadUint16LengthPrefixed(&extensions) || len(reader) != 0 || !checkDuplicateExtensions(extensions) {
 		return false
 	}
 	m.rawExtensions = extensions
 	for len(extensions) > 0 {
 		var extension uint16
-		var body byteReader
-		if !extensions.readU16(&extension) ||
-			!extensions.readU16LengthPrefixed(&body) {
+		var body cryptobyte.String
+		if !extensions.ReadUint16(&extension) ||
+			!extensions.ReadUint16LengthPrefixed(&body) {
 			return false
 		}
 		switch extension {
 		case extensionServerName:
-			var names byteReader
-			if !body.readU16LengthPrefixed(&names) || len(body) != 0 {
+			var names cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&names) || len(body) != 0 {
 				return false
 			}
 			for len(names) > 0 {
 				var nameType byte
 				var name []byte
-				if !names.readU8(&nameType) ||
-					!names.readU16LengthPrefixedBytes(&name) {
+				if !names.ReadUint8(&nameType) ||
+					!readUint16LengthPrefixedBytes(&names, &name) {
 					return false
 				}
 				if nameType == 0 {
@@ -952,17 +782,17 @@
 			}
 		case extensionEncryptedClientHello:
 			var typ byte
-			if !body.readU8(&typ) {
+			if !body.ReadUint8(&typ) {
 				return false
 			}
 			switch typ {
 			case echClientTypeOuter:
 				var echOuter echClientOuter
-				if !body.readU16(&echOuter.kdfID) ||
-					!body.readU16(&echOuter.aeadID) ||
-					!body.readU8(&echOuter.configID) ||
-					!body.readU16LengthPrefixedBytes(&echOuter.enc) ||
-					!body.readU16LengthPrefixedBytes(&echOuter.payload) ||
+				if !body.ReadUint16(&echOuter.kdfID) ||
+					!body.ReadUint16(&echOuter.aeadID) ||
+					!body.ReadUint8(&echOuter.configID) ||
+					!readUint16LengthPrefixedBytes(&body, &echOuter.enc) ||
+					!readUint16LengthPrefixedBytes(&body, &echOuter.payload) ||
 					len(echOuter.payload) == 0 ||
 					len(body) > 0 {
 					return false
@@ -989,11 +819,11 @@
 			// extensibility, but we expect our client to only send empty
 			// requests of type OCSP.
 			var statusType uint8
-			var responderIDList, innerExtensions byteReader
-			if !body.readU8(&statusType) ||
+			var responderIDList, innerExtensions cryptobyte.String
+			if !body.ReadUint8(&statusType) ||
 				statusType != statusTypeOCSP ||
-				!body.readU16LengthPrefixed(&responderIDList) ||
-				!body.readU16LengthPrefixed(&innerExtensions) ||
+				!body.ReadUint16LengthPrefixed(&responderIDList) ||
+				!body.ReadUint16LengthPrefixed(&innerExtensions) ||
 				len(responderIDList) != 0 ||
 				len(innerExtensions) != 0 ||
 				len(body) != 0 {
@@ -1002,21 +832,21 @@
 			m.ocspStapling = true
 		case extensionSupportedCurves:
 			// http://tools.ietf.org/html/rfc4492#section-5.5.1
-			var curves byteReader
-			if !body.readU16LengthPrefixed(&curves) || len(body) != 0 {
+			var curves cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&curves) || len(body) != 0 {
 				return false
 			}
 			m.supportedCurves = make([]CurveID, 0, len(curves)/2)
 			for len(curves) > 0 {
 				var v uint16
-				if !curves.readU16(&v) {
+				if !curves.ReadUint16(&v) {
 					return false
 				}
 				m.supportedCurves = append(m.supportedCurves, CurveID(v))
 			}
 		case extensionSupportedPoints:
 			// http://tools.ietf.org/html/rfc4492#section-5.1.2
-			if !body.readU8LengthPrefixedBytes(&m.supportedPoints) || len(m.supportedPoints) == 0 || len(body) != 0 {
+			if !readUint8LengthPrefixedBytes(&body, &m.supportedPoints) || len(m.supportedPoints) == 0 || len(body) != 0 {
 				return false
 			}
 		case extensionSessionTicket:
@@ -1027,15 +857,15 @@
 			// https://tools.ietf.org/html/rfc8446#section-4.2.8
 			m.hasKeyShares = true
 			m.keySharesRaw = body
-			var keyShares byteReader
-			if !body.readU16LengthPrefixed(&keyShares) || len(body) != 0 {
+			var keyShares cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&keyShares) || len(body) != 0 {
 				return false
 			}
 			for len(keyShares) > 0 {
 				var entry keyShareEntry
 				var group uint16
-				if !keyShares.readU16(&group) ||
-					!keyShares.readU16LengthPrefixedBytes(&entry.keyExchange) {
+				if !keyShares.ReadUint16(&group) ||
+					!readUint16LengthPrefixedBytes(&keyShares, &entry.keyExchange) {
 					return false
 				}
 				entry.group = CurveID(group)
@@ -1043,23 +873,23 @@
 			}
 		case extensionPreSharedKey:
 			// https://tools.ietf.org/html/rfc8446#section-4.2.11
-			var psks, binders byteReader
-			if !body.readU16LengthPrefixed(&psks) ||
-				!body.readU16LengthPrefixed(&binders) ||
+			var psks, binders cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&psks) ||
+				!body.ReadUint16LengthPrefixed(&binders) ||
 				len(body) != 0 {
 				return false
 			}
 			for len(psks) > 0 {
 				var psk pskIdentity
-				if !psks.readU16LengthPrefixedBytes(&psk.ticket) ||
-					!psks.readU32(&psk.obfuscatedTicketAge) {
+				if !readUint16LengthPrefixedBytes(&psks, &psk.ticket) ||
+					!psks.ReadUint32(&psk.obfuscatedTicketAge) {
 					return false
 				}
 				m.pskIdentities = append(m.pskIdentities, psk)
 			}
 			for len(binders) > 0 {
 				var binder []byte
-				if !binders.readU8LengthPrefixedBytes(&binder) {
+				if !readUint8LengthPrefixedBytes(&binders, &binder) {
 					return false
 				}
 				m.pskBinders = append(m.pskBinders, binder)
@@ -1071,7 +901,7 @@
 			}
 		case extensionPSKKeyExchangeModes:
 			// https://tools.ietf.org/html/rfc8446#section-4.2.9
-			if !body.readU8LengthPrefixedBytes(&m.pskKEModes) || len(body) != 0 {
+			if !readUint8LengthPrefixedBytes(&body, &m.pskKEModes) || len(body) != 0 {
 				return false
 			}
 		case extensionEarlyData:
@@ -1081,7 +911,7 @@
 			}
 			m.hasEarlyData = true
 		case extensionCookie:
-			if !body.readU16LengthPrefixedBytes(&m.tls13Cookie) || len(body) != 0 {
+			if !readUint16LengthPrefixedBytes(&body, &m.tls13Cookie) || len(body) != 0 {
 				return false
 			}
 		case extensionSignatureAlgorithms:
@@ -1094,30 +924,30 @@
 				return false
 			}
 		case extensionSupportedVersions:
-			var versions byteReader
-			if !body.readU8LengthPrefixed(&versions) || len(body) != 0 {
+			var versions cryptobyte.String
+			if !body.ReadUint8LengthPrefixed(&versions) || len(body) != 0 {
 				return false
 			}
 			m.supportedVersions = make([]uint16, 0, len(versions)/2)
 			for len(versions) > 0 {
 				var v uint16
-				if !versions.readU16(&v) {
+				if !versions.ReadUint16(&v) {
 					return false
 				}
 				m.supportedVersions = append(m.supportedVersions, v)
 			}
 		case extensionRenegotiationInfo:
-			if !body.readU8LengthPrefixedBytes(&m.secureRenegotiation) || len(body) != 0 {
+			if !readUint8LengthPrefixedBytes(&body, &m.secureRenegotiation) || len(body) != 0 {
 				return false
 			}
 		case extensionALPN:
-			var protocols byteReader
-			if !body.readU16LengthPrefixed(&protocols) || len(body) != 0 {
+			var protocols cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&protocols) || len(body) != 0 {
 				return false
 			}
 			for len(protocols) > 0 {
 				var protocol []byte
-				if !protocols.readU8LengthPrefixedBytes(&protocol) || len(protocol) == 0 {
+				if !readUint8LengthPrefixedBytes(&protocols, &protocol) || len(protocol) == 0 {
 					return false
 				}
 				m.alpnProtocols = append(m.alpnProtocols, string(protocol))
@@ -1137,17 +967,17 @@
 			}
 			m.extendedMasterSecret = true
 		case extensionUseSRTP:
-			var profiles byteReader
+			var profiles cryptobyte.String
 			var mki []byte
-			if !body.readU16LengthPrefixed(&profiles) ||
-				!body.readU8LengthPrefixedBytes(&mki) ||
+			if !body.ReadUint16LengthPrefixed(&profiles) ||
+				!readUint8LengthPrefixedBytes(&body, &mki) ||
 				len(body) != 0 {
 				return false
 			}
 			m.srtpProtectionProfiles = make([]uint16, 0, len(profiles)/2)
 			for len(profiles) > 0 {
 				var v uint16
-				if !profiles.readU16(&v) {
+				if !profiles.ReadUint16(&v) {
 					return false
 				}
 				m.srtpProtectionProfiles = append(m.srtpProtectionProfiles, v)
@@ -1161,15 +991,15 @@
 		case extensionCustom:
 			m.customExtension = string(body)
 		case extensionCompressedCertAlgs:
-			var algIDs byteReader
-			if !body.readU8LengthPrefixed(&algIDs) {
+			var algIDs cryptobyte.String
+			if !body.ReadUint8LengthPrefixed(&algIDs) {
 				return false
 			}
 
 			seen := make(map[uint16]struct{})
 			for len(algIDs) > 0 {
 				var algID uint16
-				if !algIDs.readU16(&algID) {
+				if !algIDs.ReadUint16(&algID) {
 					return false
 				}
 				if _, ok := seen[algID]; ok {
@@ -1191,13 +1021,13 @@
 			}
 			m.delegatedCredentials = true
 		case extensionApplicationSettings:
-			var protocols byteReader
-			if !body.readU16LengthPrefixed(&protocols) || len(body) != 0 {
+			var protocols cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&protocols) || len(body) != 0 {
 				return false
 			}
 			for len(protocols) > 0 {
 				var protocol []byte
-				if !protocols.readU8LengthPrefixedBytes(&protocol) || len(protocol) == 0 {
+				if !readUint8LengthPrefixedBytes(&protocols, &protocol) || len(protocol) == 0 {
 					return false
 				}
 				m.alpsProtocols = append(m.alpsProtocols, string(protocol))
@@ -1213,15 +1043,15 @@
 }
 
 func decodeClientHelloInner(config *Config, encoded []byte, helloOuter *clientHelloMsg) (*clientHelloMsg, error) {
-	reader := byteReader(encoded)
+	reader := cryptobyte.String(encoded)
 	var versAndRandom, sessionID, cipherSuites, compressionMethods []byte
-	var extensions byteReader
-	if !reader.readBytes(&versAndRandom, 2+32) ||
-		!reader.readU8LengthPrefixedBytes(&sessionID) ||
+	var extensions cryptobyte.String
+	if !reader.ReadBytes(&versAndRandom, 2+32) ||
+		!readUint8LengthPrefixedBytes(&reader, &sessionID) ||
 		len(sessionID) != 0 || // Copied from |helloOuter|
-		!reader.readU16LengthPrefixedBytes(&cipherSuites) ||
-		!reader.readU8LengthPrefixedBytes(&compressionMethods) ||
-		!reader.readU16LengthPrefixed(&extensions) {
+		!readUint16LengthPrefixedBytes(&reader, &cipherSuites) ||
+		!readUint8LengthPrefixedBytes(&reader, &compressionMethods) ||
+		!reader.ReadUint16LengthPrefixed(&extensions) {
 		return nil, errors.New("tls: error parsing EncodedClientHelloInner")
 	}
 
@@ -1232,64 +1062,77 @@
 		}
 	}
 
-	builder := newByteBuilder()
-	builder.addU8(typeClientHello)
-	body := builder.addU24LengthPrefixed()
-	body.addBytes(versAndRandom)
-	body.addU8LengthPrefixed().addBytes(helloOuter.sessionID)
-	body.addU16LengthPrefixed().addBytes(cipherSuites)
-	body.addU8LengthPrefixed().addBytes(compressionMethods)
-	newExtensions := body.addU16LengthPrefixed()
-
-	var seenOuterExtensions bool
-	outerExtensions := byteReader(helloOuter.rawExtensions)
 	copied := make(map[uint16]struct{})
-	for len(extensions) > 0 {
-		var extType uint16
-		var extBody byteReader
-		if !extensions.readU16(&extType) ||
-			!extensions.readU16LengthPrefixed(&extBody) {
-			return nil, errors.New("tls: error parsing EncodedClientHelloInner")
-		}
-		if extType != extensionECHOuterExtensions {
-			newExtensions.addU16(extType)
-			newExtensions.addU16LengthPrefixed().addBytes(extBody)
-			continue
-		}
-		if seenOuterExtensions {
-			return nil, errors.New("tls: duplicate ech_outer_extensions extension")
-		}
-		seenOuterExtensions = true
-		var extList byteReader
-		if !extBody.readU8LengthPrefixed(&extList) || len(extList) == 0 || len(extBody) != 0 {
-			return nil, errors.New("tls: error parsing ech_outer_extensions")
-		}
-		for len(extList) != 0 {
-			var newExtType uint16
-			if !extList.readU16(&newExtType) {
-				return nil, errors.New("tls: error parsing ech_outer_extensions")
-			}
-			if newExtType == extensionEncryptedClientHello {
-				return nil, errors.New("tls: error parsing ech_outer_extensions")
-			}
-			for {
-				if len(outerExtensions) == 0 {
-					return nil, fmt.Errorf("tls: extension %d not found in ClientHelloOuter", newExtType)
+	builder := cryptobyte.NewBuilder(nil)
+	builder.AddUint8(typeClientHello)
+	builder.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
+		body.AddBytes(versAndRandom)
+		addUint8LengthPrefixedBytes(body, helloOuter.sessionID)
+		addUint16LengthPrefixedBytes(body, cipherSuites)
+		addUint8LengthPrefixedBytes(body, compressionMethods)
+		body.AddUint16LengthPrefixed(func(newExtensions *cryptobyte.Builder) {
+			var seenOuterExtensions bool
+			outerExtensions := cryptobyte.String(helloOuter.rawExtensions)
+			for len(extensions) > 0 {
+				var extType uint16
+				var extBody cryptobyte.String
+				if !extensions.ReadUint16(&extType) ||
+					!extensions.ReadUint16LengthPrefixed(&extBody) {
+					newExtensions.SetError(errors.New("tls: error parsing EncodedClientHelloInner"))
+					return
 				}
-				var foundExt uint16
-				var newExtBody []byte
-				if !outerExtensions.readU16(&foundExt) ||
-					!outerExtensions.readU16LengthPrefixedBytes(&newExtBody) {
-					return nil, errors.New("tls: error parsing ClientHelloOuter")
+				if extType != extensionECHOuterExtensions {
+					newExtensions.AddUint16(extType)
+					addUint16LengthPrefixedBytes(newExtensions, extBody)
+					continue
 				}
-				if foundExt == newExtType {
-					newExtensions.addU16(newExtType)
-					newExtensions.addU16LengthPrefixed().addBytes(newExtBody)
-					copied[newExtType] = struct{}{}
-					break
+				if seenOuterExtensions {
+					newExtensions.SetError(errors.New("tls: duplicate ech_outer_extensions extension"))
+					return
+				}
+				seenOuterExtensions = true
+				var extList cryptobyte.String
+				if !extBody.ReadUint8LengthPrefixed(&extList) || len(extList) == 0 || len(extBody) != 0 {
+					newExtensions.SetError(errors.New("tls: error parsing ech_outer_extensions"))
+					return
+				}
+				for len(extList) != 0 {
+					var newExtType uint16
+					if !extList.ReadUint16(&newExtType) {
+						newExtensions.SetError(errors.New("tls: error parsing ech_outer_extensions"))
+						return
+					}
+					if newExtType == extensionEncryptedClientHello {
+						newExtensions.SetError(errors.New("tls: error parsing ech_outer_extensions"))
+						return
+					}
+					for {
+						if len(outerExtensions) == 0 {
+							newExtensions.SetError(fmt.Errorf("tls: extension %d not found in ClientHelloOuter", newExtType))
+							return
+						}
+						var foundExt uint16
+						var newExtBody []byte
+						if !outerExtensions.ReadUint16(&foundExt) ||
+							!readUint16LengthPrefixedBytes(&outerExtensions, &newExtBody) {
+							newExtensions.SetError(errors.New("tls: error parsing ClientHelloOuter"))
+							return
+						}
+						if foundExt == newExtType {
+							newExtensions.AddUint16(newExtType)
+							addUint16LengthPrefixedBytes(newExtensions, newExtBody)
+							copied[newExtType] = struct{}{}
+							break
+						}
+					}
 				}
 			}
-		}
+		})
+	})
+
+	bytes, err := builder.Bytes()
+	if err != nil {
+		return nil, err
 	}
 
 	for _, expected := range config.Bugs.ExpectECHOuterExtensions {
@@ -1304,9 +1147,10 @@
 	}
 
 	ret := new(clientHelloMsg)
-	if !ret.unmarshal(builder.finish()) {
+	if !ret.unmarshal(bytes) {
 		return nil, errors.New("tls: error parsing reconstructed ClientHello")
 	}
+
 	return ret, nil
 }
 
@@ -1337,102 +1181,100 @@
 		return m.raw
 	}
 
-	handshakeMsg := newByteBuilder()
-	handshakeMsg.addU8(typeServerHello)
-	hello := handshakeMsg.addU24LengthPrefixed()
-
-	// m.vers is used both to determine the format of the rest of the
-	// ServerHello and to override the value, so include a second version
-	// field.
-	vers, ok := wireToVersion(m.vers, m.isDTLS)
-	if !ok {
-		panic("unknown version")
-	}
-	if m.versOverride != 0 {
-		hello.addU16(m.versOverride)
-	} else if vers >= VersionTLS13 {
-		hello.addU16(VersionTLS12)
-	} else {
-		hello.addU16(m.vers)
-	}
-
-	hello.addBytes(m.random)
-	sessionID := hello.addU8LengthPrefixed()
-	sessionID.addBytes(m.sessionID)
-	hello.addU16(m.cipherSuite)
-	hello.addU8(m.compressionMethod)
-
-	extensions := hello.addU16LengthPrefixed()
-
-	if vers >= VersionTLS13 {
-		if m.hasKeyShare {
-			extensions.addU16(extensionKeyShare)
-			keyShare := extensions.addU16LengthPrefixed()
-			keyShare.addU16(uint16(m.keyShare.group))
-			keyExchange := keyShare.addU16LengthPrefixed()
-			keyExchange.addBytes(m.keyShare.keyExchange)
+	handshakeMsg := cryptobyte.NewBuilder(nil)
+	handshakeMsg.AddUint8(typeServerHello)
+	handshakeMsg.AddUint24LengthPrefixed(func(hello *cryptobyte.Builder) {
+		// m.vers is used both to determine the format of the rest of the
+		// ServerHello and to override the value, so include a second version
+		// field.
+		vers, ok := wireToVersion(m.vers, m.isDTLS)
+		if !ok {
+			panic("unknown version")
 		}
-		if m.hasPSKIdentity {
-			extensions.addU16(extensionPreSharedKey)
-			extensions.addU16(2) // Length
-			extensions.addU16(m.pskIdentity)
+		if m.versOverride != 0 {
+			hello.AddUint16(m.versOverride)
+		} else if vers >= VersionTLS13 {
+			hello.AddUint16(VersionTLS12)
+		} else {
+			hello.AddUint16(m.vers)
 		}
-		if !m.omitSupportedVers {
-			extensions.addU16(extensionSupportedVersions)
-			extensions.addU16(2) // Length
-			if m.supportedVersOverride != 0 {
-				extensions.addU16(m.supportedVersOverride)
+
+		hello.AddBytes(m.random)
+		addUint8LengthPrefixedBytes(hello, m.sessionID)
+		hello.AddUint16(m.cipherSuite)
+		hello.AddUint8(m.compressionMethod)
+
+		hello.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+			if vers >= VersionTLS13 {
+				if m.hasKeyShare {
+					extensions.AddUint16(extensionKeyShare)
+					extensions.AddUint16LengthPrefixed(func(keyShare *cryptobyte.Builder) {
+						keyShare.AddUint16(uint16(m.keyShare.group))
+						addUint16LengthPrefixedBytes(keyShare, m.keyShare.keyExchange)
+					})
+				}
+				if m.hasPSKIdentity {
+					extensions.AddUint16(extensionPreSharedKey)
+					extensions.AddUint16(2) // Length
+					extensions.AddUint16(m.pskIdentity)
+				}
+				if !m.omitSupportedVers {
+					extensions.AddUint16(extensionSupportedVersions)
+					extensions.AddUint16(2) // Length
+					if m.supportedVersOverride != 0 {
+						extensions.AddUint16(m.supportedVersOverride)
+					} else {
+						extensions.AddUint16(m.vers)
+					}
+				}
+				if len(m.customExtension) > 0 {
+					extensions.AddUint16(extensionCustom)
+					addUint16LengthPrefixedBytes(extensions, []byte(m.customExtension))
+				}
+				if len(m.unencryptedALPN) > 0 {
+					extensions.AddUint16(extensionALPN)
+					extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+						extension.AddUint16LengthPrefixed(func(protocolNameList *cryptobyte.Builder) {
+							addUint8LengthPrefixedBytes(protocolNameList, []byte(m.unencryptedALPN))
+						})
+					})
+				}
 			} else {
-				extensions.addU16(m.vers)
+				m.extensions.marshal(extensions)
 			}
+			if m.omitExtensions || m.emptyExtensions {
+				// Silently erasing server extensions will break the handshake. Instead,
+				// assert that tests which use this field also disable all features which
+				// would write an extension. Note the length includes the length prefix.
+				if b := extensions.BytesOrPanic(); len(b) != 2 {
+					panic(fmt.Sprintf("ServerHello unexpectedly contained extensions: %x, %+v", b, m))
+				}
+			}
+		})
+		// Remove the length prefix.
+		if m.omitExtensions {
+			hello.Unwrite(2)
 		}
-		if len(m.customExtension) > 0 {
-			extensions.addU16(extensionCustom)
-			customExt := extensions.addU16LengthPrefixed()
-			customExt.addBytes([]byte(m.customExtension))
-		}
-		if len(m.unencryptedALPN) > 0 {
-			extensions.addU16(extensionALPN)
-			extension := extensions.addU16LengthPrefixed()
+	})
 
-			protocolNameList := extension.addU16LengthPrefixed()
-			protocolName := protocolNameList.addU8LengthPrefixed()
-			protocolName.addBytes([]byte(m.unencryptedALPN))
-		}
-	} else {
-		m.extensions.marshal(extensions)
-		if m.omitExtensions || m.emptyExtensions {
-			// Silently erasing server extensions will break the handshake. Instead,
-			// assert that tests which use this field also disable all features which
-			// would write an extension.
-			if extensions.len() != 0 {
-				panic(fmt.Sprintf("ServerHello unexpectedly contained extensions: %x, %+v", extensions.data(), m))
-			}
-			hello.discardChild()
-			if m.emptyExtensions {
-				hello.addU16(0)
-			}
-		}
-	}
-
-	m.raw = handshakeMsg.finish()
+	m.raw = handshakeMsg.BytesOrPanic()
 	return m.raw
 }
 
 func (m *serverHelloMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
-	if !reader.readU16(&m.vers) ||
-		!reader.readBytes(&m.random, 32) {
+	reader := cryptobyte.String(data[4:])
+	if !reader.ReadUint16(&m.vers) ||
+		!reader.ReadBytes(&m.random, 32) {
 		return false
 	}
 	vers, ok := wireToVersion(m.vers, m.isDTLS)
 	if !ok {
 		return false
 	}
-	if !reader.readU8LengthPrefixedBytes(&m.sessionID) ||
-		!reader.readU16(&m.cipherSuite) ||
-		!reader.readU8(&m.compressionMethod) {
+	if !readUint8LengthPrefixedBytes(&reader, &m.sessionID) ||
+		!reader.ReadUint16(&m.cipherSuite) ||
+		!reader.ReadUint8(&m.compressionMethod) {
 		return false
 	}
 
@@ -1443,8 +1285,8 @@
 		return true
 	}
 
-	var extensions byteReader
-	if !reader.readU16LengthPrefixed(&extensions) || len(reader) != 0 || !checkDuplicateExtensions(extensions) {
+	var extensions cryptobyte.String
+	if !reader.ReadUint16LengthPrefixed(&extensions) || len(reader) != 0 || !checkDuplicateExtensions(extensions) {
 		return false
 	}
 
@@ -1453,13 +1295,13 @@
 		extensionsCopy := extensions
 		for len(extensionsCopy) > 0 {
 			var extension uint16
-			var body byteReader
-			if !extensionsCopy.readU16(&extension) ||
-				!extensionsCopy.readU16LengthPrefixed(&body) {
+			var body cryptobyte.String
+			if !extensionsCopy.ReadUint16(&extension) ||
+				!extensionsCopy.ReadUint16LengthPrefixed(&body) {
 				return false
 			}
 			if extension == extensionSupportedVersions {
-				if !body.readU16(&m.vers) || len(body) != 0 {
+				if !body.ReadUint16(&m.vers) || len(body) != 0 {
 					return false
 				}
 				vers, ok = wireToVersion(m.vers, m.isDTLS)
@@ -1473,23 +1315,23 @@
 	if vers >= VersionTLS13 {
 		for len(extensions) > 0 {
 			var extension uint16
-			var body byteReader
-			if !extensions.readU16(&extension) ||
-				!extensions.readU16LengthPrefixed(&body) {
+			var body cryptobyte.String
+			if !extensions.ReadUint16(&extension) ||
+				!extensions.ReadUint16LengthPrefixed(&body) {
 				return false
 			}
 			switch extension {
 			case extensionKeyShare:
 				m.hasKeyShare = true
 				var group uint16
-				if !body.readU16(&group) ||
-					!body.readU16LengthPrefixedBytes(&m.keyShare.keyExchange) ||
+				if !body.ReadUint16(&group) ||
+					!readUint16LengthPrefixedBytes(&body, &m.keyShare.keyExchange) ||
 					len(body) != 0 {
 					return false
 				}
 				m.keyShare.group = CurveID(group)
 			case extensionPreSharedKey:
-				if !body.readU16(&m.pskIdentity) || len(body) != 0 {
+				if !body.ReadUint16(&m.pskIdentity) || len(body) != 0 {
 					return false
 				}
 				m.hasPSKIdentity = true
@@ -1519,23 +1361,25 @@
 		return m.raw
 	}
 
-	encryptedExtensionsMsg := newByteBuilder()
-	encryptedExtensionsMsg.addU8(typeEncryptedExtensions)
-	encryptedExtensions := encryptedExtensionsMsg.addU24LengthPrefixed()
-	if !m.empty {
-		extensions := encryptedExtensions.addU16LengthPrefixed()
-		m.extensions.marshal(extensions)
-	}
+	encryptedExtensionsMsg := cryptobyte.NewBuilder(nil)
+	encryptedExtensionsMsg.AddUint8(typeEncryptedExtensions)
+	encryptedExtensionsMsg.AddUint24LengthPrefixed(func(encryptedExtensions *cryptobyte.Builder) {
+		if !m.empty {
+			encryptedExtensions.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+				m.extensions.marshal(extensions)
+			})
+		}
+	})
 
-	m.raw = encryptedExtensionsMsg.finish()
+	m.raw = encryptedExtensionsMsg.BytesOrPanic()
 	return m.raw
 }
 
 func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
-	var extensions byteReader
-	if !reader.readU16LengthPrefixed(&extensions) || len(reader) != 0 {
+	reader := cryptobyte.String(data[4:])
+	var extensions cryptobyte.String
+	if !reader.ReadUint16LengthPrefixed(&extensions) || len(reader) != 0 {
 		return false
 	}
 	return m.extensions.unmarshal(extensions, VersionTLS13)
@@ -1571,147 +1415,137 @@
 	echRetryConfigs           []byte
 }
 
-func (m *serverExtensions) marshal(extensions *byteBuilder) {
+func (m *serverExtensions) marshal(extensions *cryptobyte.Builder) {
 	if m.duplicateExtension {
 		// Add a duplicate bogus extension at the beginning and end.
-		extensions.addU16(extensionDuplicate)
-		extensions.addU16(0) // length = 0 for empty extension
+		extensions.AddUint16(extensionDuplicate)
+		extensions.AddUint16(0) // length = 0 for empty extension
 	}
 	if m.nextProtoNeg && !m.npnAfterAlpn {
-		extensions.addU16(extensionNextProtoNeg)
-		extension := extensions.addU16LengthPrefixed()
-
-		for _, v := range m.nextProtos {
-			if len(v) > 255 {
-				v = v[:255]
+		extensions.AddUint16(extensionNextProtoNeg)
+		extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+			for _, v := range m.nextProtos {
+				addUint8LengthPrefixedBytes(extension, []byte(v))
 			}
-			npn := extension.addU8LengthPrefixed()
-			npn.addBytes([]byte(v))
-		}
+		})
 	}
 	if m.ocspStapling {
-		extensions.addU16(extensionStatusRequest)
-		extensions.addU16(0)
+		extensions.AddUint16(extensionStatusRequest)
+		extensions.AddUint16(0)
 	}
 	if m.ticketSupported {
-		extensions.addU16(extensionSessionTicket)
-		extensions.addU16(0)
+		extensions.AddUint16(extensionSessionTicket)
+		extensions.AddUint16(0)
 	}
 	if m.secureRenegotiation != nil {
-		extensions.addU16(extensionRenegotiationInfo)
-		extension := extensions.addU16LengthPrefixed()
-		secureRenego := extension.addU8LengthPrefixed()
-		secureRenego.addBytes(m.secureRenegotiation)
+		extensions.AddUint16(extensionRenegotiationInfo)
+		extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+			addUint8LengthPrefixedBytes(extension, m.secureRenegotiation)
+		})
 	}
 	if len(m.alpnProtocol) > 0 || m.alpnProtocolEmpty {
-		extensions.addU16(extensionALPN)
-		extension := extensions.addU16LengthPrefixed()
-
-		protocolNameList := extension.addU16LengthPrefixed()
-		protocolName := protocolNameList.addU8LengthPrefixed()
-		protocolName.addBytes([]byte(m.alpnProtocol))
+		extensions.AddUint16(extensionALPN)
+		extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+			extension.AddUint16LengthPrefixed(func(protocolNameList *cryptobyte.Builder) {
+				addUint8LengthPrefixedBytes(protocolNameList, []byte(m.alpnProtocol))
+			})
+		})
 	}
 	if m.channelIDRequested {
-		extensions.addU16(extensionChannelID)
-		extensions.addU16(0)
+		extensions.AddUint16(extensionChannelID)
+		extensions.AddUint16(0)
 	}
 	if m.duplicateExtension {
 		// Add a duplicate bogus extension at the beginning and end.
-		extensions.addU16(extensionDuplicate)
-		extensions.addU16(0)
+		extensions.AddUint16(extensionDuplicate)
+		extensions.AddUint16(0)
 	}
 	if m.extendedMasterSecret {
-		extensions.addU16(extensionExtendedMasterSecret)
-		extensions.addU16(0)
+		extensions.AddUint16(extensionExtendedMasterSecret)
+		extensions.AddUint16(0)
 	}
 	if m.srtpProtectionProfile != 0 {
-		extensions.addU16(extensionUseSRTP)
-		extension := extensions.addU16LengthPrefixed()
-
-		srtpProtectionProfiles := extension.addU16LengthPrefixed()
-		srtpProtectionProfiles.addU16(m.srtpProtectionProfile)
-		srtpMki := extension.addU8LengthPrefixed()
-		srtpMki.addBytes([]byte(m.srtpMasterKeyIdentifier))
+		extensions.AddUint16(extensionUseSRTP)
+		extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+			extension.AddUint16LengthPrefixed(func(srtpProtectionProfiles *cryptobyte.Builder) {
+				srtpProtectionProfiles.AddUint16(m.srtpProtectionProfile)
+			})
+			addUint8LengthPrefixedBytes(extension, []byte(m.srtpMasterKeyIdentifier))
+		})
 	}
 	if m.sctList != nil {
-		extensions.addU16(extensionSignedCertificateTimestamp)
-		extension := extensions.addU16LengthPrefixed()
-		extension.addBytes(m.sctList)
+		extensions.AddUint16(extensionSignedCertificateTimestamp)
+		addUint16LengthPrefixedBytes(extensions, m.sctList)
 	}
 	if l := len(m.customExtension); l > 0 {
-		extensions.addU16(extensionCustom)
-		customExt := extensions.addU16LengthPrefixed()
-		customExt.addBytes([]byte(m.customExtension))
+		extensions.AddUint16(extensionCustom)
+		addUint16LengthPrefixedBytes(extensions, []byte(m.customExtension))
 	}
 	if m.nextProtoNeg && m.npnAfterAlpn {
-		extensions.addU16(extensionNextProtoNeg)
-		extension := extensions.addU16LengthPrefixed()
-
-		for _, v := range m.nextProtos {
-			if len(v) > 255 {
-				v = v[0:255]
+		extensions.AddUint16(extensionNextProtoNeg)
+		extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+			for _, v := range m.nextProtos {
+				addUint8LengthPrefixedBytes(extension, []byte(v))
 			}
-			npn := extension.addU8LengthPrefixed()
-			npn.addBytes([]byte(v))
-		}
+		})
 	}
 	if m.hasKeyShare {
-		extensions.addU16(extensionKeyShare)
-		keyShare := extensions.addU16LengthPrefixed()
-		keyShare.addU16(uint16(m.keyShare.group))
-		keyExchange := keyShare.addU16LengthPrefixed()
-		keyExchange.addBytes(m.keyShare.keyExchange)
+		extensions.AddUint16(extensionKeyShare)
+		extensions.AddUint16LengthPrefixed(func(keyShare *cryptobyte.Builder) {
+			keyShare.AddUint16(uint16(m.keyShare.group))
+			addUint16LengthPrefixedBytes(keyShare, m.keyShare.keyExchange)
+		})
 	}
 	if m.supportedVersion != 0 {
-		extensions.addU16(extensionSupportedVersions)
-		extensions.addU16(2) // Length
-		extensions.addU16(m.supportedVersion)
+		extensions.AddUint16(extensionSupportedVersions)
+		extensions.AddUint16(2) // Length
+		extensions.AddUint16(m.supportedVersion)
 	}
 	if len(m.supportedPoints) > 0 {
 		// http://tools.ietf.org/html/rfc4492#section-5.1.2
-		extensions.addU16(extensionSupportedPoints)
-		supportedPointsList := extensions.addU16LengthPrefixed()
-		supportedPoints := supportedPointsList.addU8LengthPrefixed()
-		supportedPoints.addBytes(m.supportedPoints)
+		extensions.AddUint16(extensionSupportedPoints)
+		extensions.AddUint16LengthPrefixed(func(supportedPointsList *cryptobyte.Builder) {
+			addUint8LengthPrefixedBytes(supportedPointsList, m.supportedPoints)
+		})
 	}
 	if len(m.supportedCurves) > 0 {
 		// https://tools.ietf.org/html/rfc8446#section-4.2.7
-		extensions.addU16(extensionSupportedCurves)
-		supportedCurvesList := extensions.addU16LengthPrefixed()
-		supportedCurves := supportedCurvesList.addU16LengthPrefixed()
-		for _, curve := range m.supportedCurves {
-			supportedCurves.addU16(uint16(curve))
-		}
+		extensions.AddUint16(extensionSupportedCurves)
+		extensions.AddUint16LengthPrefixed(func(supportedCurvesList *cryptobyte.Builder) {
+			supportedCurvesList.AddUint16LengthPrefixed(func(supportedCurves *cryptobyte.Builder) {
+				for _, curve := range m.supportedCurves {
+					supportedCurves.AddUint16(uint16(curve))
+				}
+			})
+		})
 	}
 	if len(m.quicTransportParams) > 0 {
-		extensions.addU16(extensionQUICTransportParams)
-		params := extensions.addU16LengthPrefixed()
-		params.addBytes(m.quicTransportParams)
+		extensions.AddUint16(extensionQUICTransportParams)
+		addUint16LengthPrefixedBytes(extensions, m.quicTransportParams)
 	}
 	if len(m.quicTransportParamsLegacy) > 0 {
-		extensions.addU16(extensionQUICTransportParamsLegacy)
-		params := extensions.addU16LengthPrefixed()
-		params.addBytes(m.quicTransportParamsLegacy)
+		extensions.AddUint16(extensionQUICTransportParamsLegacy)
+		addUint16LengthPrefixedBytes(extensions, m.quicTransportParamsLegacy)
 	}
 	if m.hasEarlyData {
-		extensions.addU16(extensionEarlyData)
-		extensions.addBytes([]byte{0, 0})
+		extensions.AddUint16(extensionEarlyData)
+		extensions.AddBytes([]byte{0, 0})
 	}
 	if m.serverNameAck {
-		extensions.addU16(extensionServerName)
-		extensions.addU16(0) // zero length
+		extensions.AddUint16(extensionServerName)
+		extensions.AddUint16(0) // zero length
 	}
 	if m.hasApplicationSettings {
-		extensions.addU16(extensionApplicationSettings)
-		extensions.addU16LengthPrefixed().addBytes(m.applicationSettings)
+		extensions.AddUint16(extensionApplicationSettings)
+		addUint16LengthPrefixedBytes(extensions, m.applicationSettings)
 	}
 	if len(m.echRetryConfigs) > 0 {
-		extensions.addU16(extensionEncryptedClientHello)
-		extensions.addU16LengthPrefixed().addBytes(m.echRetryConfigs)
+		extensions.AddUint16(extensionEncryptedClientHello)
+		addUint16LengthPrefixedBytes(extensions, m.echRetryConfigs)
 	}
 }
 
-func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool {
+func (m *serverExtensions) unmarshal(data cryptobyte.String, version uint16) bool {
 	// Reset all fields.
 	*m = serverExtensions{}
 
@@ -1721,9 +1555,9 @@
 
 	for len(data) > 0 {
 		var extension uint16
-		var body byteReader
-		if !data.readU16(&extension) ||
-			!data.readU16LengthPrefixed(&body) {
+		var body cryptobyte.String
+		if !data.ReadUint16(&extension) ||
+			!data.ReadUint16LengthPrefixed(&body) {
 			return false
 		}
 		switch extension {
@@ -1731,7 +1565,7 @@
 			m.nextProtoNeg = true
 			for len(body) > 0 {
 				var protocol []byte
-				if !body.readU8LengthPrefixedBytes(&protocol) {
+				if !readUint8LengthPrefixedBytes(&body, &protocol) {
 					return false
 				}
 				m.nextProtos = append(m.nextProtos, string(protocol))
@@ -1747,14 +1581,14 @@
 			}
 			m.ticketSupported = true
 		case extensionRenegotiationInfo:
-			if !body.readU8LengthPrefixedBytes(&m.secureRenegotiation) || len(body) != 0 {
+			if !readUint8LengthPrefixedBytes(&body, &m.secureRenegotiation) || len(body) != 0 {
 				return false
 			}
 		case extensionALPN:
-			var protocols, protocol byteReader
-			if !body.readU16LengthPrefixed(&protocols) ||
+			var protocols, protocol cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&protocols) ||
 				len(body) != 0 ||
-				!protocols.readU8LengthPrefixed(&protocol) ||
+				!protocols.ReadUint8LengthPrefixed(&protocol) ||
 				len(protocols) != 0 {
 				return false
 			}
@@ -1771,11 +1605,11 @@
 			}
 			m.extendedMasterSecret = true
 		case extensionUseSRTP:
-			var profiles, mki byteReader
-			if !body.readU16LengthPrefixed(&profiles) ||
-				!profiles.readU16(&m.srtpProtectionProfile) ||
+			var profiles, mki cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&profiles) ||
+				!profiles.ReadUint16(&m.srtpProtectionProfile) ||
 				len(profiles) != 0 ||
-				!body.readU8LengthPrefixed(&mki) ||
+				!body.ReadUint8LengthPrefixed(&mki) ||
 				len(body) != 0 {
 				return false
 			}
@@ -1795,7 +1629,7 @@
 				return false
 			}
 			// http://tools.ietf.org/html/rfc4492#section-5.5.2
-			if !body.readU8LengthPrefixedBytes(&m.supportedPoints) || len(body) != 0 {
+			if !readUint8LengthPrefixedBytes(&body, &m.supportedPoints) || len(body) != 0 {
 				return false
 			}
 		case extensionSupportedCurves:
@@ -1822,15 +1656,15 @@
 			m.echRetryConfigs = body
 
 			// Validate the ECHConfig with a top-level parse.
-			var echConfigs byteReader
-			if !body.readU16LengthPrefixed(&echConfigs) {
+			var echConfigs cryptobyte.String
+			if !body.ReadUint16LengthPrefixed(&echConfigs) {
 				return false
 			}
 			for len(echConfigs) > 0 {
 				var version uint16
-				var contents byteReader
-				if !echConfigs.readU16(&version) ||
-					!echConfigs.readU16LengthPrefixed(&contents) {
+				var contents cryptobyte.String
+				if !echConfigs.ReadUint16(&version) ||
+					!echConfigs.ReadUint16LengthPrefixed(&contents) {
 					return false
 				}
 			}
@@ -1858,29 +1692,31 @@
 		return m.raw
 	}
 
-	builder := newByteBuilder()
-	builder.addU8(typeEncryptedExtensions)
-	body := builder.addU24LengthPrefixed()
-	extensions := body.addU16LengthPrefixed()
-	if m.hasApplicationSettings {
-		extensions.addU16(extensionApplicationSettings)
-		extensions.addU16LengthPrefixed().addBytes(m.applicationSettings)
-	}
-	if len(m.customExtension) > 0 {
-		extensions.addU16(extensionCustom)
-		extensions.addU16LengthPrefixed().addBytes(m.customExtension)
-	}
+	builder := cryptobyte.NewBuilder(nil)
+	builder.AddUint8(typeEncryptedExtensions)
+	builder.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
+		body.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+			if m.hasApplicationSettings {
+				extensions.AddUint16(extensionApplicationSettings)
+				addUint16LengthPrefixedBytes(extensions, m.applicationSettings)
+			}
+			if len(m.customExtension) > 0 {
+				extensions.AddUint16(extensionCustom)
+				addUint16LengthPrefixedBytes(extensions, m.customExtension)
+			}
+		})
+	})
 
-	m.raw = builder.finish()
+	m.raw = builder.BytesOrPanic()
 	return m.raw
 }
 
 func (m *clientEncryptedExtensionsMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
+	reader := cryptobyte.String(data[4:])
 
-	var extensions byteReader
-	if !reader.readU16LengthPrefixed(&extensions) ||
+	var extensions cryptobyte.String
+	if !reader.ReadUint16LengthPrefixed(&extensions) ||
 		len(reader) != 0 {
 		return false
 	}
@@ -1891,9 +1727,9 @@
 
 	for len(extensions) > 0 {
 		var extension uint16
-		var body byteReader
-		if !extensions.readU16(&extension) ||
-			!extensions.readU16LengthPrefixed(&body) {
+		var body cryptobyte.String
+		if !extensions.ReadUint16(&extension) ||
+			!extensions.ReadUint16LengthPrefixed(&body) {
 			return false
 		}
 		switch extension {
@@ -1928,92 +1764,93 @@
 		return m.raw
 	}
 
-	retryRequestMsg := newByteBuilder()
-	retryRequestMsg.addU8(typeServerHello)
-	retryRequest := retryRequestMsg.addU24LengthPrefixed()
-	retryRequest.addU16(VersionTLS12)
-	retryRequest.addBytes(tls13HelloRetryRequest)
-	sessionID := retryRequest.addU8LengthPrefixed()
-	sessionID.addBytes(m.sessionID)
-	retryRequest.addU16(m.cipherSuite)
-	retryRequest.addU8(m.compressionMethod)
+	retryRequestMsg := cryptobyte.NewBuilder(nil)
+	retryRequestMsg.AddUint8(typeServerHello)
+	retryRequestMsg.AddUint24LengthPrefixed(func(retryRequest *cryptobyte.Builder) {
+		retryRequest.AddUint16(VersionTLS12)
+		retryRequest.AddBytes(tls13HelloRetryRequest)
+		addUint8LengthPrefixedBytes(retryRequest, m.sessionID)
+		retryRequest.AddUint16(m.cipherSuite)
+		retryRequest.AddUint8(m.compressionMethod)
 
-	extensions := retryRequest.addU16LengthPrefixed()
+		retryRequest.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+			count := 1
+			if m.duplicateExtensions {
+				count = 2
+			}
 
-	count := 1
-	if m.duplicateExtensions {
-		count = 2
-	}
+			for i := 0; i < count; i++ {
+				extensions.AddUint16(extensionSupportedVersions)
+				extensions.AddUint16(2) // Length
+				extensions.AddUint16(m.vers)
+				if m.hasSelectedGroup {
+					extensions.AddUint16(extensionKeyShare)
+					extensions.AddUint16(2) // length
+					extensions.AddUint16(uint16(m.selectedGroup))
+				}
+				// m.cookie may be a non-nil empty slice for empty cookie tests.
+				if m.cookie != nil {
+					extensions.AddUint16(extensionCookie)
+					extensions.AddUint16LengthPrefixed(func(body *cryptobyte.Builder) {
+						addUint16LengthPrefixedBytes(body, m.cookie)
+					})
+				}
+				if len(m.customExtension) > 0 {
+					extensions.AddUint16(extensionCustom)
+					addUint16LengthPrefixedBytes(extensions, []byte(m.customExtension))
+				}
+				if len(m.echConfirmation) > 0 {
+					extensions.AddUint16(extensionEncryptedClientHello)
+					addUint16LengthPrefixedBytes(extensions, m.echConfirmation)
+				}
+			}
+		})
+	})
 
-	for i := 0; i < count; i++ {
-		extensions.addU16(extensionSupportedVersions)
-		extensions.addU16(2) // Length
-		extensions.addU16(m.vers)
-		if m.hasSelectedGroup {
-			extensions.addU16(extensionKeyShare)
-			extensions.addU16(2) // length
-			extensions.addU16(uint16(m.selectedGroup))
-		}
-		// m.cookie may be a non-nil empty slice for empty cookie tests.
-		if m.cookie != nil {
-			extensions.addU16(extensionCookie)
-			body := extensions.addU16LengthPrefixed()
-			body.addU16LengthPrefixed().addBytes(m.cookie)
-		}
-		if len(m.customExtension) > 0 {
-			extensions.addU16(extensionCustom)
-			extensions.addU16LengthPrefixed().addBytes([]byte(m.customExtension))
-		}
-		if len(m.echConfirmation) > 0 {
-			extensions.addU16(extensionEncryptedClientHello)
-			extensions.addU16LengthPrefixed().addBytes(m.echConfirmation)
-		}
-	}
-
-	m.raw = retryRequestMsg.finish()
+	m.raw = retryRequestMsg.BytesOrPanic()
 	return m.raw
 }
 
 func (m *helloRetryRequestMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
+	reader := cryptobyte.String(data[4:])
 	var legacyVers uint16
 	var random []byte
 	var compressionMethod byte
-	var extensions byteReader
-	if !reader.readU16(&legacyVers) ||
+	var extensions cryptobyte.String
+	if !reader.ReadUint16(&legacyVers) ||
 		legacyVers != VersionTLS12 ||
-		!reader.readBytes(&random, 32) ||
-		!reader.readU8LengthPrefixedBytes(&m.sessionID) ||
-		!reader.readU16(&m.cipherSuite) ||
-		!reader.readU8(&compressionMethod) ||
+		!reader.ReadBytes(&random, 32) ||
+		!readUint8LengthPrefixedBytes(&reader, &m.sessionID) ||
+		!reader.ReadUint16(&m.cipherSuite) ||
+		!reader.ReadUint8(&compressionMethod) ||
 		compressionMethod != 0 ||
-		!reader.readU16LengthPrefixed(&extensions) ||
+		!reader.ReadUint16LengthPrefixed(&extensions) ||
 		len(reader) != 0 {
 		return false
 	}
 	for len(extensions) > 0 {
 		var extension uint16
-		var body byteReader
-		if !extensions.readU16(&extension) ||
-			!extensions.readU16LengthPrefixed(&body) {
+		var body cryptobyte.String
+		if !extensions.ReadUint16(&extension) ||
+			!extensions.ReadUint16LengthPrefixed(&body) {
 			return false
 		}
 		switch extension {
 		case extensionSupportedVersions:
-			if !body.readU16(&m.vers) ||
+			if !body.ReadUint16(&m.vers) ||
 				len(body) != 0 {
 				return false
 			}
 		case extensionKeyShare:
 			var v uint16
-			if !body.readU16(&v) || len(body) != 0 {
+			if !body.ReadUint16(&v) || len(body) != 0 {
 				return false
 			}
 			m.hasSelectedGroup = true
 			m.selectedGroup = CurveID(v)
 		case extensionCookie:
-			if !body.readU16LengthPrefixedBytes(&m.cookie) ||
+			if !readUint16LengthPrefixedBytes(&body, &m.cookie) ||
 				len(m.cookie) == 0 ||
 				len(body) != 0 {
 				return false
@@ -2063,85 +1900,86 @@
 		return m.raw
 	}
 
-	certMsg := newByteBuilder()
-	certMsg.addU8(typeCertificate)
-	certificate := certMsg.addU24LengthPrefixed()
-	if m.hasRequestContext {
-		context := certificate.addU8LengthPrefixed()
-		context.addBytes(m.requestContext)
-	}
-	certificateList := certificate.addU24LengthPrefixed()
-	for _, cert := range m.certificates {
-		certEntry := certificateList.addU24LengthPrefixed()
-		certEntry.addBytes(cert.data)
+	certMsg := cryptobyte.NewBuilder(nil)
+	certMsg.AddUint8(typeCertificate)
+	certMsg.AddUint24LengthPrefixed(func(certificate *cryptobyte.Builder) {
 		if m.hasRequestContext {
-			extensions := certificateList.addU16LengthPrefixed()
-			count := 1
-			if cert.duplicateExtensions {
-				count = 2
-			}
-
-			for i := 0; i < count; i++ {
-				if cert.ocspResponse != nil {
-					extensions.addU16(extensionStatusRequest)
-					body := extensions.addU16LengthPrefixed()
-					body.addU8(statusTypeOCSP)
-					response := body.addU24LengthPrefixed()
-					response.addBytes(cert.ocspResponse)
-				}
-
-				if cert.sctList != nil {
-					extensions.addU16(extensionSignedCertificateTimestamp)
-					extension := extensions.addU16LengthPrefixed()
-					extension.addBytes(cert.sctList)
-				}
-			}
-			if cert.extraExtension != nil {
-				extensions.addBytes(cert.extraExtension)
-			}
+			addUint8LengthPrefixedBytes(certificate, m.requestContext)
 		}
-	}
+		certificate.AddUint24LengthPrefixed(func(certificateList *cryptobyte.Builder) {
+			for _, cert := range m.certificates {
+				addUint24LengthPrefixedBytes(certificateList, cert.data)
+				if m.hasRequestContext {
+					certificateList.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+						count := 1
+						if cert.duplicateExtensions {
+							count = 2
+						}
 
-	m.raw = certMsg.finish()
+						for i := 0; i < count; i++ {
+							if cert.ocspResponse != nil {
+								extensions.AddUint16(extensionStatusRequest)
+								extensions.AddUint16LengthPrefixed(func(body *cryptobyte.Builder) {
+									body.AddUint8(statusTypeOCSP)
+									addUint24LengthPrefixedBytes(body, cert.ocspResponse)
+								})
+							}
+
+							if cert.sctList != nil {
+								extensions.AddUint16(extensionSignedCertificateTimestamp)
+								addUint16LengthPrefixedBytes(extensions, cert.sctList)
+							}
+						}
+						if cert.extraExtension != nil {
+							extensions.AddBytes(cert.extraExtension)
+						}
+					})
+				}
+			}
+		})
+
+	})
+
+	m.raw = certMsg.BytesOrPanic()
 	return m.raw
 }
 
 func (m *certificateMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
+	reader := cryptobyte.String(data[4:])
 
-	if m.hasRequestContext && !reader.readU8LengthPrefixedBytes(&m.requestContext) {
+	if m.hasRequestContext && !readUint8LengthPrefixedBytes(&reader, &m.requestContext) {
 		return false
 	}
 
-	var certs byteReader
-	if !reader.readU24LengthPrefixed(&certs) || len(reader) != 0 {
+	var certs cryptobyte.String
+	if !reader.ReadUint24LengthPrefixed(&certs) || len(reader) != 0 {
 		return false
 	}
 	m.certificates = nil
 	for len(certs) > 0 {
 		var cert certificateEntry
-		if !certs.readU24LengthPrefixedBytes(&cert.data) {
+		if !readUint24LengthPrefixedBytes(&certs, &cert.data) {
 			return false
 		}
 		if m.hasRequestContext {
-			var extensions byteReader
-			if !certs.readU16LengthPrefixed(&extensions) || !checkDuplicateExtensions(extensions) {
+			var extensions cryptobyte.String
+			if !certs.ReadUint16LengthPrefixed(&extensions) || !checkDuplicateExtensions(extensions) {
 				return false
 			}
 			for len(extensions) > 0 {
 				var extension uint16
-				var body byteReader
-				if !extensions.readU16(&extension) ||
-					!extensions.readU16LengthPrefixed(&body) {
+				var body cryptobyte.String
+				if !extensions.ReadUint16(&extension) ||
+					!extensions.ReadUint16LengthPrefixed(&body) {
 					return false
 				}
 				switch extension {
 				case extensionStatusRequest:
 					var statusType byte
-					if !body.readU8(&statusType) ||
+					if !body.ReadUint8(&statusType) ||
 						statusType != statusTypeOCSP ||
-						!body.readU24LengthPrefixedBytes(&cert.ocspResponse) ||
+						!readUint24LengthPrefixedBytes(&body, &cert.ocspResponse) ||
 						len(body) != 0 {
 						return false
 					}
@@ -2157,11 +1995,11 @@
 					origBody := body
 					var expectedCertVerifyAlgo, algorithm uint16
 
-					if !body.readU32(&dc.lifetimeSecs) ||
-						!body.readU16(&expectedCertVerifyAlgo) ||
-						!body.readU24LengthPrefixedBytes(&dc.pkixPublicKey) ||
-						!body.readU16(&algorithm) ||
-						!body.readU16LengthPrefixedBytes(&dc.signature) ||
+					if !body.ReadUint32(&dc.lifetimeSecs) ||
+						!body.ReadUint16(&expectedCertVerifyAlgo) ||
+						!readUint24LengthPrefixedBytes(&body, &dc.pkixPublicKey) ||
+						!body.ReadUint16(&algorithm) ||
+						!readUint16LengthPrefixedBytes(&body, &dc.signature) ||
 						len(body) != 0 {
 						return false
 					}
@@ -2193,25 +2031,25 @@
 		return m.raw
 	}
 
-	certMsg := newByteBuilder()
-	certMsg.addU8(typeCompressedCertificate)
-	certificate := certMsg.addU24LengthPrefixed()
-	certificate.addU16(m.algID)
-	certificate.addU24(int(m.uncompressedLength))
-	compressed := certificate.addU24LengthPrefixed()
-	compressed.addBytes(m.compressed)
+	certMsg := cryptobyte.NewBuilder(nil)
+	certMsg.AddUint8(typeCompressedCertificate)
+	certMsg.AddUint24LengthPrefixed(func(certificate *cryptobyte.Builder) {
+		certificate.AddUint16(m.algID)
+		certificate.AddUint24(m.uncompressedLength)
+		addUint24LengthPrefixedBytes(certificate, m.compressed)
+	})
 
-	m.raw = certMsg.finish()
+	m.raw = certMsg.BytesOrPanic()
 	return m.raw
 }
 
 func (m *compressedCertificateMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
+	reader := cryptobyte.String(data[4:])
 
-	if !reader.readU16(&m.algID) ||
-		!reader.readU24(&m.uncompressedLength) ||
-		!reader.readU24LengthPrefixedBytes(&m.compressed) ||
+	if !reader.ReadUint16(&m.algID) ||
+		!reader.ReadUint24(&m.uncompressedLength) ||
+		!readUint24LengthPrefixedBytes(&reader, &m.compressed) ||
 		len(reader) != 0 {
 		return false
 	}
@@ -2232,10 +2070,10 @@
 	if m.raw != nil {
 		return m.raw
 	}
-	msg := newByteBuilder()
-	msg.addU8(typeServerKeyExchange)
-	msg.addU24LengthPrefixed().addBytes(m.key)
-	m.raw = msg.finish()
+	msg := cryptobyte.NewBuilder(nil)
+	msg.AddUint8(typeServerKeyExchange)
+	addUint24LengthPrefixedBytes(msg, m.key)
+	m.raw = msg.BytesOrPanic()
 	return m.raw
 }
 
@@ -2261,12 +2099,13 @@
 
 	var x []byte
 	if m.statusType == statusTypeOCSP {
-		msg := newByteBuilder()
-		msg.addU8(typeCertificateStatus)
-		body := msg.addU24LengthPrefixed()
-		body.addU8(statusTypeOCSP)
-		body.addU24LengthPrefixed().addBytes(m.response)
-		x = msg.finish()
+		msg := cryptobyte.NewBuilder(nil)
+		msg.AddUint8(typeCertificateStatus)
+		msg.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
+			body.AddUint8(statusTypeOCSP)
+			addUint24LengthPrefixedBytes(body, m.response)
+		})
+		x = msg.BytesOrPanic()
 	} else {
 		x = []byte{typeCertificateStatus, 0, 0, 1, m.statusType}
 	}
@@ -2277,10 +2116,10 @@
 
 func (m *certificateStatusMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
-	if !reader.readU8(&m.statusType) ||
+	reader := cryptobyte.String(data[4:])
+	if !reader.ReadUint8(&m.statusType) ||
 		m.statusType != statusTypeOCSP ||
-		!reader.readU24LengthPrefixedBytes(&m.response) ||
+		!readUint24LengthPrefixedBytes(&reader, &m.response) ||
 		len(reader) != 0 {
 		return false
 	}
@@ -2308,10 +2147,10 @@
 	if m.raw != nil {
 		return m.raw
 	}
-	msg := newByteBuilder()
-	msg.addU8(typeClientKeyExchange)
-	msg.addU24LengthPrefixed().addBytes(m.ciphertext)
-	m.raw = msg.finish()
+	msg := cryptobyte.NewBuilder(nil)
+	msg.AddUint8(typeClientKeyExchange)
+	addUint24LengthPrefixedBytes(msg, m.ciphertext)
+	m.raw = msg.BytesOrPanic()
 	return m.raw
 }
 
@@ -2338,10 +2177,10 @@
 		return m.raw
 	}
 
-	msg := newByteBuilder()
-	msg.addU8(typeFinished)
-	msg.addU24LengthPrefixed().addBytes(m.verifyData)
-	m.raw = msg.finish()
+	msg := cryptobyte.NewBuilder(nil)
+	msg.AddUint8(typeFinished)
+	addUint24LengthPrefixedBytes(msg, m.verifyData)
+	m.raw = msg.BytesOrPanic()
 	return m.raw
 }
 
@@ -2366,21 +2205,22 @@
 
 	padding := 32 - (len(m.proto)+2)%32
 
-	msg := newByteBuilder()
-	msg.addU8(typeNextProtocol)
-	body := msg.addU24LengthPrefixed()
-	body.addU8LengthPrefixed().addBytes([]byte(m.proto))
-	body.addU8LengthPrefixed().addBytes(make([]byte, padding))
-	m.raw = msg.finish()
+	msg := cryptobyte.NewBuilder(nil)
+	msg.AddUint8(typeNextProtocol)
+	msg.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
+		addUint8LengthPrefixedBytes(body, []byte(m.proto))
+		addUint8LengthPrefixedBytes(body, make([]byte, padding))
+	})
+	m.raw = msg.BytesOrPanic()
 	return m.raw
 }
 
 func (m *nextProtoMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
+	reader := cryptobyte.String(data[4:])
 	var proto, padding []byte
-	if !reader.readU8LengthPrefixedBytes(&proto) ||
-		!reader.readU8LengthPrefixedBytes(&padding) ||
+	if !readUint8LengthPrefixedBytes(&reader, &proto) ||
+		!readUint8LengthPrefixedBytes(&reader, &padding) ||
 		len(reader) != 0 {
 		return false
 	}
@@ -2427,72 +2267,79 @@
 	}
 
 	// See http://tools.ietf.org/html/rfc4346#section-7.4.4
-	builder := newByteBuilder()
-	builder.addU8(typeCertificateRequest)
-	body := builder.addU24LengthPrefixed()
+	builder := cryptobyte.NewBuilder(nil)
+	builder.AddUint8(typeCertificateRequest)
+	builder.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
+		if m.hasRequestContext {
+			addUint8LengthPrefixedBytes(body, m.requestContext)
+			body.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+				if m.hasSignatureAlgorithm {
+					extensions.AddUint16(extensionSignatureAlgorithms)
+					extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+						extension.AddUint16LengthPrefixed(func(signatureAlgorithms *cryptobyte.Builder) {
+							for _, sigAlg := range m.signatureAlgorithms {
+								signatureAlgorithms.AddUint16(uint16(sigAlg))
+							}
+						})
+					})
+				}
+				if len(m.signatureAlgorithmsCert) > 0 {
+					extensions.AddUint16(extensionSignatureAlgorithmsCert)
+					extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+						extension.AddUint16LengthPrefixed(func(signatureAlgorithmsCert *cryptobyte.Builder) {
+							for _, sigAlg := range m.signatureAlgorithmsCert {
+								signatureAlgorithmsCert.AddUint16(uint16(sigAlg))
+							}
+						})
+					})
+				}
+				if len(m.certificateAuthorities) > 0 {
+					extensions.AddUint16(extensionCertificateAuthorities)
+					extensions.AddUint16LengthPrefixed(func(extension *cryptobyte.Builder) {
+						extension.AddUint16LengthPrefixed(func(certificateAuthorities *cryptobyte.Builder) {
+							for _, ca := range m.certificateAuthorities {
+								addUint16LengthPrefixedBytes(certificateAuthorities, ca)
+							}
+						})
+					})
+				}
 
-	if m.hasRequestContext {
-		requestContext := body.addU8LengthPrefixed()
-		requestContext.addBytes(m.requestContext)
-		extensions := newByteBuilder()
-		extensions = body.addU16LengthPrefixed()
-		if m.hasSignatureAlgorithm {
-			extensions.addU16(extensionSignatureAlgorithms)
-			signatureAlgorithms := extensions.addU16LengthPrefixed().addU16LengthPrefixed()
-			for _, sigAlg := range m.signatureAlgorithms {
-				signatureAlgorithms.addU16(uint16(sigAlg))
+				if m.customExtension > 0 {
+					extensions.AddUint16(m.customExtension)
+					extensions.AddUint16(0) // Empty extension
+				}
+			})
+		} else {
+			addUint8LengthPrefixedBytes(body, m.certificateTypes)
+
+			if m.hasSignatureAlgorithm {
+				body.AddUint16LengthPrefixed(func(signatureAlgorithms *cryptobyte.Builder) {
+					for _, sigAlg := range m.signatureAlgorithms {
+						signatureAlgorithms.AddUint16(uint16(sigAlg))
+					}
+				})
 			}
-		}
-		if len(m.signatureAlgorithmsCert) > 0 {
-			extensions.addU16(extensionSignatureAlgorithmsCert)
-			signatureAlgorithmsCert := extensions.addU16LengthPrefixed().addU16LengthPrefixed()
-			for _, sigAlg := range m.signatureAlgorithmsCert {
-				signatureAlgorithmsCert.addU16(uint16(sigAlg))
-			}
-		}
-		if len(m.certificateAuthorities) > 0 {
-			extensions.addU16(extensionCertificateAuthorities)
-			certificateAuthorities := extensions.addU16LengthPrefixed().addU16LengthPrefixed()
-			for _, ca := range m.certificateAuthorities {
-				caEntry := certificateAuthorities.addU16LengthPrefixed()
-				caEntry.addBytes(ca)
-			}
-		}
 
-		if m.customExtension > 0 {
-			extensions.addU16(m.customExtension)
-			extensions.addU16LengthPrefixed()
+			body.AddUint16LengthPrefixed(func(certificateAuthorities *cryptobyte.Builder) {
+				for _, ca := range m.certificateAuthorities {
+					addUint16LengthPrefixedBytes(certificateAuthorities, ca)
+				}
+			})
 		}
-	} else {
-		certificateTypes := body.addU8LengthPrefixed()
-		certificateTypes.addBytes(m.certificateTypes)
+	})
 
-		if m.hasSignatureAlgorithm {
-			signatureAlgorithms := body.addU16LengthPrefixed()
-			for _, sigAlg := range m.signatureAlgorithms {
-				signatureAlgorithms.addU16(uint16(sigAlg))
-			}
-		}
-
-		certificateAuthorities := body.addU16LengthPrefixed()
-		for _, ca := range m.certificateAuthorities {
-			caEntry := certificateAuthorities.addU16LengthPrefixed()
-			caEntry.addBytes(ca)
-		}
-	}
-
-	m.raw = builder.finish()
+	m.raw = builder.BytesOrPanic()
 	return m.raw
 }
 
-func parseCAs(reader *byteReader, out *[][]byte) bool {
-	var cas byteReader
-	if !reader.readU16LengthPrefixed(&cas) {
+func parseCAs(reader *cryptobyte.String, out *[][]byte) bool {
+	var cas cryptobyte.String
+	if !reader.ReadUint16LengthPrefixed(&cas) {
 		return false
 	}
 	for len(cas) > 0 {
 		var ca []byte
-		if !cas.readU16LengthPrefixedBytes(&ca) {
+		if !readUint16LengthPrefixedBytes(&cas, &ca) {
 			return false
 		}
 		*out = append(*out, ca)
@@ -2502,21 +2349,21 @@
 
 func (m *certificateRequestMsg) unmarshal(data []byte) bool {
 	m.raw = data
-	reader := byteReader(data[4:])
+	reader := cryptobyte.String(data[4:])
 
 	if m.hasRequestContext {
-		var extensions byteReader
-		if !reader.readU8LengthPrefixedBytes(&m.requestContext) ||
-			!reader.readU16LengthPrefixed(&extensions) ||
+		var extensions cryptobyte.String
+		if !readUint8LengthPrefixedBytes(&reader, &m.requestContext) ||
+			!reader.ReadUint16LengthPrefixed(&extensions) ||
 			len(reader) != 0 ||
 			!checkDuplicateExtensions(extensions) {
 			return false
 		}
 		for len(extensions) > 0 {
 			var extension uint16
-			var body byteReader
-			if !extensions.readU16(&extension) ||
-				!extensions.readU16LengthPrefixed(&body) {
+			var body cryptobyte.String
+			if !extensions.ReadUint16(&extension) ||
+				!extensions.ReadUint16LengthPrefixed(&body) {
 				return false
 			}
 			switch extension {
@@ -2536,7 +2383,7 @@
 			}
 		}
 	} else {
-		if !reader.readU8LengthPrefixedBytes(&m.certificateTypes) {
+		if !readUint8LengthPrefixedBytes(&reader, &m.certificateTypes) {
 			return false
 		}
 		// In TLS 1.2, the supported_signature_algorithms field in
@@ -2648,35 +2495,40 @@
 	}
 
 	// See http://tools.ietf.org/html/rfc5077#section-3.3
-	ticketMsg := newByteBuilder()
-	ticketMsg.addU8(typeNewSessionTicket)
-	body := ticketMsg.addU24LengthPrefixed()
-	body.addU32(m.ticketLifetime)
-	if version >= VersionTLS13 {
-		body.addU32(m.ticketAgeAdd)
-		body.addU8LengthPrefixed().addBytes(m.ticketNonce)
-	}
-
-	ticket := body.addU16LengthPrefixed()
-	ticket.addBytes(m.ticket)
-
-	if version >= VersionTLS13 {
-		extensions := body.addU16LengthPrefixed()
-		if m.maxEarlyDataSize > 0 {
-			extensions.addU16(extensionEarlyData)
-			extensions.addU16LengthPrefixed().addU32(m.maxEarlyDataSize)
-			if m.duplicateEarlyDataExtension {
-				extensions.addU16(extensionEarlyData)
-				extensions.addU16LengthPrefixed().addU32(m.maxEarlyDataSize)
-			}
+	ticketMsg := cryptobyte.NewBuilder(nil)
+	ticketMsg.AddUint8(typeNewSessionTicket)
+	ticketMsg.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
+		body.AddUint32(m.ticketLifetime)
+		if version >= VersionTLS13 {
+			body.AddUint32(m.ticketAgeAdd)
+			addUint8LengthPrefixedBytes(body, m.ticketNonce)
 		}
-		if len(m.customExtension) > 0 {
-			extensions.addU16(extensionCustom)
-			extensions.addU16LengthPrefixed().addBytes([]byte(m.customExtension))
-		}
-	}
 
-	m.raw = ticketMsg.finish()
+		addUint16LengthPrefixedBytes(body, m.ticket)
+
+		if version >= VersionTLS13 {
+			body.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
+				if m.maxEarlyDataSize > 0 {
+					extensions.AddUint16(extensionEarlyData)
+					extensions.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
+						child.AddUint32(m.maxEarlyDataSize)
+					})
+					if m.duplicateEarlyDataExtension {
+						extensions.AddUint16(extensionEarlyData)
+						extensions.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
+							child.AddUint32(m.maxEarlyDataSize)
+						})
+					}
+				}
+				if len(m.customExtension) > 0 {
+					extensions.AddUint16(extensionCustom)
+					addUint16LengthPrefixedBytes(extensions, []byte(m.customExtension))
+				}
+			})
+		}
+	})
+
+	m.raw = ticketMsg.BytesOrPanic()
 	return m.raw
 }
 
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 4130d9b..5c49afb 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -20,6 +20,7 @@
 	"time"
 
 	"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
+	"golang.org/x/crypto/cryptobyte"
 )
 
 // serverHandshakeState contains details of a server handshake in progress.
@@ -2443,18 +2444,18 @@
 	}
 
 	// Skip the handshake message header.
-	aReader := byteReader(a[4:])
-	bReader := byteReader(b[4:])
+	aReader := cryptobyte.String(a[4:])
+	bReader := cryptobyte.String(b[4:])
 
 	var aVers, bVers uint16
 	var aRandom, bRandom []byte
 	var aSessionID, bSessionID []byte
-	if !aReader.readU16(&aVers) ||
-		!bReader.readU16(&bVers) ||
-		!aReader.readBytes(&aRandom, 32) ||
-		!bReader.readBytes(&bRandom, 32) ||
-		!aReader.readU8LengthPrefixedBytes(&aSessionID) ||
-		!bReader.readU8LengthPrefixedBytes(&bSessionID) {
+	if !aReader.ReadUint16(&aVers) ||
+		!bReader.ReadUint16(&bVers) ||
+		!aReader.ReadBytes(&aRandom, 32) ||
+		!bReader.ReadBytes(&bRandom, 32) ||
+		!readUint8LengthPrefixedBytes(&aReader, &aSessionID) ||
+		!readUint8LengthPrefixedBytes(&bReader, &bSessionID) {
 		return errors.New("tls: could not parse ClientHello")
 	}
 
@@ -2474,17 +2475,17 @@
 		// cookie altogether. If we implement DTLS 1.3, we'll need to ensure
 		// that parsing logic above this function rejects this cookie.
 		var aCookie, bCookie []byte
-		if !aReader.readU8LengthPrefixedBytes(&aCookie) ||
-			!bReader.readU8LengthPrefixedBytes(&bCookie) {
+		if !readUint8LengthPrefixedBytes(&aReader, &aCookie) ||
+			!readUint8LengthPrefixedBytes(&bReader, &bCookie) {
 			return errors.New("tls: could not parse ClientHello")
 		}
 	}
 
 	var aCipherSuites, bCipherSuites, aCompressionMethods, bCompressionMethods []byte
-	if !aReader.readU16LengthPrefixedBytes(&aCipherSuites) ||
-		!bReader.readU16LengthPrefixedBytes(&bCipherSuites) ||
-		!aReader.readU8LengthPrefixedBytes(&aCompressionMethods) ||
-		!bReader.readU8LengthPrefixedBytes(&bCompressionMethods) {
+	if !readUint16LengthPrefixedBytes(&aReader, &aCipherSuites) ||
+		!readUint16LengthPrefixedBytes(&bReader, &bCipherSuites) ||
+		!readUint8LengthPrefixedBytes(&aReader, &aCompressionMethods) ||
+		!readUint8LengthPrefixedBytes(&bReader, &bCompressionMethods) {
 		return errors.New("tls: could not parse ClientHello")
 	}
 	if !bytes.Equal(aCipherSuites, bCipherSuites) {
@@ -2499,9 +2500,9 @@
 		return nil
 	}
 
-	var aExtensions, bExtensions byteReader
-	if !aReader.readU16LengthPrefixed(&aExtensions) ||
-		!bReader.readU16LengthPrefixed(&bExtensions) ||
+	var aExtensions, bExtensions cryptobyte.String
+	if !aReader.ReadUint16LengthPrefixed(&aExtensions) ||
+		!bReader.ReadUint16LengthPrefixed(&bExtensions) ||
 		len(aReader) != 0 ||
 		len(bReader) != 0 {
 		return errors.New("tls: could not parse ClientHello")
@@ -2510,8 +2511,8 @@
 	for len(aExtensions) != 0 {
 		var aID uint16
 		var aBody []byte
-		if !aExtensions.readU16(&aID) ||
-			!aExtensions.readU16LengthPrefixedBytes(&aBody) {
+		if !aExtensions.ReadUint16(&aID) ||
+			!readUint16LengthPrefixedBytes(&aExtensions, &aBody) {
 			return errors.New("tls: could not parse ClientHello")
 		}
 		if _, ok := ignoreExtensionsSet[aID]; ok {
@@ -2524,8 +2525,8 @@
 			}
 			var bID uint16
 			var bBody []byte
-			if !bExtensions.readU16(&bID) ||
-				!bExtensions.readU16LengthPrefixedBytes(&bBody) {
+			if !bExtensions.ReadUint16(&bID) ||
+				!readUint16LengthPrefixedBytes(&bExtensions, &bBody) {
 				return errors.New("tls: could not parse ClientHello")
 			}
 			if _, ok := ignoreExtensionsSet[bID]; ok {
@@ -2546,8 +2547,8 @@
 	for len(bExtensions) != 0 {
 		var id uint16
 		var body []byte
-		if !bExtensions.readU16(&id) ||
-			!bExtensions.readU16LengthPrefixedBytes(&body) {
+		if !bExtensions.ReadUint16(&id) ||
+			!readUint16LengthPrefixedBytes(&bExtensions, &body) {
 			return errors.New("tls: could not parse ClientHello")
 		}
 		if _, ok := ignoreExtensionsSet[id]; !ok {
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go
index fc67d75..4cdc7c8 100644
--- a/ssl/test/runner/prf.go
+++ b/ssl/test/runner/prf.go
@@ -13,6 +13,7 @@
 	"encoding"
 	"hash"
 
+	"golang.org/x/crypto/cryptobyte"
 	"golang.org/x/crypto/hkdf"
 )
 
@@ -228,15 +229,15 @@
 }
 
 func (h *finishedHash) UpdateForHelloRetryRequest() {
-	data := newByteBuilder()
-	data.addU8(typeMessageHash)
-	data.addU24(h.hash.Size())
-	data.addBytes(h.Sum())
+	data := cryptobyte.NewBuilder(nil)
+	data.AddUint8(typeMessageHash)
+	data.AddUint24(uint32(h.hash.Size()))
+	data.AddBytes(h.Sum())
 	h.hash = h.suite.hash().New()
 	if h.buffer != nil {
 		h.buffer = []byte{}
 	}
-	h.Write(data.finish())
+	h.Write(data.BytesOrPanic())
 }
 
 func (h *finishedHash) Write(msg []byte) (n int, err error) {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index e01e1d5..fcdd11a 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -47,6 +47,7 @@
 
 	"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
 	"boringssl.googlesource.com/boringssl/util/testresult"
+	"golang.org/x/crypto/cryptobyte"
 )
 
 var (
@@ -819,10 +820,10 @@
 				if err := os.MkdirAll(dir, 0755); err != nil {
 					return err
 				}
-				bb := newByteBuilder()
-				bb.addU24LengthPrefixed().addBytes(encodedInner)
-				bb.addBytes(outer)
-				return os.WriteFile(filepath.Join(dir, name), bb.finish(), 0644)
+				bb := cryptobyte.NewBuilder(nil)
+				addUint24LengthPrefixedBytes(bb, encodedInner)
+				bb.AddBytes(outer)
+				return os.WriteFile(filepath.Join(dir, name), bb.BytesOrPanic(), 0644)
 			}
 		}
 
diff --git a/ssl/test/runner/ticket.go b/ssl/test/runner/ticket.go
index 46a6b35..f0a8bf1 100644
--- a/ssl/test/runner/ticket.go
+++ b/ssl/test/runner/ticket.go
@@ -13,6 +13,8 @@
 	"errors"
 	"io"
 	"time"
+
+	"golang.org/x/crypto/cryptobyte"
 )
 
 // sessionState contains the information that is serialized into a session
@@ -35,49 +37,45 @@
 }
 
 func (s *sessionState) marshal() []byte {
-	msg := newByteBuilder()
-	msg.addU16(s.vers)
-	msg.addU16(s.cipherSuite)
-	secret := msg.addU16LengthPrefixed()
-	secret.addBytes(s.secret)
-	handshakeHash := msg.addU16LengthPrefixed()
-	handshakeHash.addBytes(s.handshakeHash)
-	msg.addU16(uint16(len(s.certificates)))
+	msg := cryptobyte.NewBuilder(nil)
+	msg.AddUint16(s.vers)
+	msg.AddUint16(s.cipherSuite)
+	addUint16LengthPrefixedBytes(msg, s.secret)
+	addUint16LengthPrefixedBytes(msg, s.handshakeHash)
+	msg.AddUint16(uint16(len(s.certificates)))
 	for _, cert := range s.certificates {
-		certMsg := msg.addU32LengthPrefixed()
-		certMsg.addBytes(cert)
+		addUint24LengthPrefixedBytes(msg, cert)
 	}
 
 	if s.extendedMasterSecret {
-		msg.addU8(1)
+		msg.AddUint8(1)
 	} else {
-		msg.addU8(0)
+		msg.AddUint8(0)
 	}
 
 	if s.vers >= VersionTLS13 {
-		msg.addU64(uint64(s.ticketCreationTime.UnixNano()))
-		msg.addU64(uint64(s.ticketExpiration.UnixNano()))
-		msg.addU32(s.ticketFlags)
-		msg.addU32(s.ticketAgeAdd)
+		msg.AddUint64(uint64(s.ticketCreationTime.UnixNano()))
+		msg.AddUint64(uint64(s.ticketExpiration.UnixNano()))
+		msg.AddUint32(s.ticketFlags)
+		msg.AddUint32(s.ticketAgeAdd)
 	}
 
-	earlyALPN := msg.addU16LengthPrefixed()
-	earlyALPN.addBytes(s.earlyALPN)
+	addUint16LengthPrefixedBytes(msg, s.earlyALPN)
 
 	if s.hasApplicationSettings {
-		msg.addU8(1)
-		msg.addU16LengthPrefixed().addBytes(s.localApplicationSettings)
-		msg.addU16LengthPrefixed().addBytes(s.peerApplicationSettings)
+		msg.AddUint8(1)
+		addUint16LengthPrefixedBytes(msg, s.localApplicationSettings)
+		addUint16LengthPrefixedBytes(msg, s.peerApplicationSettings)
 	} else {
-		msg.addU8(0)
+		msg.AddUint8(0)
 	}
 
-	return msg.finish()
+	return msg.BytesOrPanic()
 }
 
-func readBool(reader *byteReader, out *bool) bool {
+func readBool(reader *cryptobyte.String, out *bool) bool {
 	var value uint8
-	if !reader.readU8(&value) {
+	if !reader.ReadUint8(&value) {
 		return false
 	}
 	if value == 0 {
@@ -92,19 +90,19 @@
 }
 
 func (s *sessionState) unmarshal(data []byte) bool {
-	reader := byteReader(data)
+	reader := cryptobyte.String(data)
 	var numCerts uint16
-	if !reader.readU16(&s.vers) ||
-		!reader.readU16(&s.cipherSuite) ||
-		!reader.readU16LengthPrefixedBytes(&s.secret) ||
-		!reader.readU16LengthPrefixedBytes(&s.handshakeHash) ||
-		!reader.readU16(&numCerts) {
+	if !reader.ReadUint16(&s.vers) ||
+		!reader.ReadUint16(&s.cipherSuite) ||
+		!readUint16LengthPrefixedBytes(&reader, &s.secret) ||
+		!readUint16LengthPrefixedBytes(&reader, &s.handshakeHash) ||
+		!reader.ReadUint16(&numCerts) {
 		return false
 	}
 
 	s.certificates = make([][]byte, int(numCerts))
 	for i := range s.certificates {
-		if !reader.readU32LengthPrefixedBytes(&s.certificates[i]) {
+		if !readUint24LengthPrefixedBytes(&reader, &s.certificates[i]) {
 			return false
 		}
 	}
@@ -115,24 +113,24 @@
 
 	if s.vers >= VersionTLS13 {
 		var ticketCreationTime, ticketExpiration uint64
-		if !reader.readU64(&ticketCreationTime) ||
-			!reader.readU64(&ticketExpiration) ||
-			!reader.readU32(&s.ticketFlags) ||
-			!reader.readU32(&s.ticketAgeAdd) {
+		if !reader.ReadUint64(&ticketCreationTime) ||
+			!reader.ReadUint64(&ticketExpiration) ||
+			!reader.ReadUint32(&s.ticketFlags) ||
+			!reader.ReadUint32(&s.ticketAgeAdd) {
 			return false
 		}
 		s.ticketCreationTime = time.Unix(0, int64(ticketCreationTime))
 		s.ticketExpiration = time.Unix(0, int64(ticketExpiration))
 	}
 
-	if !reader.readU16LengthPrefixedBytes(&s.earlyALPN) ||
+	if !readUint16LengthPrefixedBytes(&reader, &s.earlyALPN) ||
 		!readBool(&reader, &s.hasApplicationSettings) {
 		return false
 	}
 
 	if s.hasApplicationSettings {
-		if !reader.readU16LengthPrefixedBytes(&s.localApplicationSettings) ||
-			!reader.readU16LengthPrefixedBytes(&s.peerApplicationSettings) {
+		if !readUint16LengthPrefixedBytes(&reader, &s.localApplicationSettings) ||
+			!readUint16LengthPrefixedBytes(&reader, &s.peerApplicationSettings) {
 			return false
 		}
 	}