Repository: open-telemetry/opentelemetry-go (go)

Issue:
Consider using a byte array for `TraceID` (and possibly `SpanID`)
A 16-byte byte array feels like it would be more intuitive for holding `TraceID`s.

In particular, it would make formatting and conversions (particularly to/from hex) easier, as well as make it easier to use a standard generator such as [UUIDs](https://github.com/gofrs/uuid).

Pull request:
api(trace): change trace id to byte array.
Closes #183.

There is still things that needs to be figured out, e.g., It got a little tedious to create a TraceID from a hex string, could we create helpers inside the API? How this should be printed out on the stdout exporter?

Patch:
diff --git a/api/core/span_context.go b/api/core/span_context.go
index a1bee2e6..ed06b718 100644
--- a/api/core/span_context.go
+++ b/api/core/span_context.go
@@ -15,14 +15,13 @@
 package core
 
 import (
+	"bytes"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
 	"fmt"
 )
 
-type TraceID struct {
-	High uint64
-	Low  uint64
-}
-
 const (
 	traceFlagsBitMaskSampled = byte(0x01)
 	traceFlagsBitMaskUnused  = byte(0xFE)
@@ -33,23 +32,81 @@ const (
 	TraceFlagsUnused  = traceFlagsBitMaskUnused
 )
 
+type TraceID [16]byte
+
+var nilTraceID TraceID
+
+func (t TraceID) isValid() bool {
+	return !bytes.Equal(t[:], nilTraceID[:])
+}
+
+// TraceIDFromHex returns a TraceID from a hex string if it is compliant
+// with the w3c trace-context specification.
+// See more at https://www.w3.org/TR/trace-context/#trace-id
+func TraceIDFromHex(h string) (TraceID, error) {
+	t := TraceID{}
+	if len(h) != 32 {
+		return t, errors.New("hex encoded trace-id must have length equals to 32")
+	}
+
+	for _, r := range h {
+		switch {
+		case 'a' <= r && r <= 'f':
+			continue
+		case '0' <= r && r <= '9':
+			continue
+		default:
+			return t, errors.New("trace-id can only contain [0-9a-f] characters, all lowercase")
+		}
+	}
+
+	b, err := hex.DecodeString(h)
+	if err != nil {
+		return t, err
+	}
+	copy(t[:], b)
+
+	if !t.isValid() {
+		return t, errors.New("trace-id can't be all zero")
+	}
+	return t, nil
+}
+
 type SpanContext struct {
 	TraceID    TraceID
 	SpanID     uint64
 	TraceFlags byte
 }
 
+var _ json.Marshaler = (*SpanContext)(nil)
+
 // EmptySpanContext is meant for internal use to return invalid span context during error conditions.
 func EmptySpanContext() SpanContext {
 	return SpanContext{}
 }
 
+// MarshalJSON implements a custom marshal function to encode SpanContext
+// in a human readable format with hex encoded TraceID and SpanID.
+func (sc SpanContext) MarshalJSON() ([]byte, error) {
+	type JSONSpanContext struct {
+		TraceID    string
+		SpanID     string
+		TraceFlags byte
+	}
+
+	return json.Marshal(JSONSpanContext{
+		TraceID:    sc.TraceIDString(),
+		SpanID:     sc.SpanIDString(),
+		TraceFlags: sc.TraceFlags,
+	})
+}
+
 func (sc SpanContext) IsValid() bool {
 	return sc.HasTraceID() && sc.HasSpanID()
 }
 
 func (sc SpanContext) HasTraceID() bool {
-	return sc.TraceID.High != 0 || sc.TraceID.Low != 0
+	return sc.TraceID.isValid()
 }
 
 func (sc SpanContext) HasSpanID() bool {
@@ -61,9 +118,7 @@ func (sc SpanContext) SpanIDString() string {
 }
 
 func (sc SpanContext) TraceIDString() string {
-	p1 := fmt.Sprintf("%.16x", sc.TraceID.High)
-	p2 := fmt.Sprintf("%.16x", sc.TraceID.Low)
-	return p1 + p2
+	return hex.EncodeToString(sc.TraceID[:])
 }
 
 func (sc SpanContext) IsSampled() bool {
diff --git a/experimental/bridge/opentracing/bridge.go b/experimental/bridge/opentracing/bridge.go
index 7fd43fed..a32fa110 100644
--- a/experimental/bridge/opentracing/bridge.go
+++ b/experimental/bridge/opentracing/bridge.go
@@ -31,7 +31,7 @@ import (
 	otelcore "go.opentelemetry.io/api/core"
 	oteltrace "go.opentelemetry.io/api/trace"
 
-	migration "go.opentelemetry.io/experimental/bridge/opentracing/migration"
+	"go.opentelemetry.io/experimental/bridge/opentracing/migration"
 )
 
 type bridgeSpanContext struct {
@@ -512,7 +512,7 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int
 	if !ok {
 		return ot.ErrInvalidCarrier
 	}
-	hhcarrier.Set(traceIDHeader, traceIDString(bridgeSC.otelSpanContext.TraceID))
+	hhcarrier.Set(traceIDHeader, bridgeSC.otelSpanContext.TraceIDString())
 	hhcarrier.Set(spanIDHeader, spanIDToString(bridgeSC.otelSpanContext.SpanID))
 	hhcarrier.Set(traceFlagsHeader, traceFlagsToString(bridgeSC.otelSpanContext.TraceFlags))
 	bridgeSC.ForeachBaggageItem(func(k, v string) bool {
@@ -523,12 +523,6 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int
 	return nil
 }
 
-// mostly copied from core/span_context.go, but I prefer not to rely
-// on some impl details
-func traceIDString(traceID otelcore.TraceID) string {
-	return fmt.Sprintf("%.16x%.16x", traceID.High, traceID.Low)
-}
-
 func spanIDToString(spanID uint64) string {
 	return fmt.Sprintf("%.16x", spanID)
 }
@@ -558,7 +552,7 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
 		ck := http.CanonicalHeaderKey(k)
 		switch ck {
 		case traceIDHeader:
-			traceID, err := traceIDFromString(v)
+			traceID, err := otelcore.TraceIDFromHex(v)
 			if err != nil {
 				return err
 			}
@@ -588,23 +582,6 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
 	return bridgeSC, nil
 }
 
-func traceIDFromString(s string) (otelcore.TraceID, error) {
-	traceID := otelcore.TraceID{}
-	if len(s) != 32 {
-		return traceID, fmt.Errorf("invalid trace ID")
-	}
-	high, err := strconv.ParseUint(s[0:16], 16, 64)
-	if err != nil {
-		return traceID, err
-	}
-	low, err := strconv.ParseUint(s[16:32], 16, 64)
-	if err != nil {
-		return traceID, err
-	}
-	traceID.High, traceID.Low = high, low
-	return traceID, nil
-}
-
 func spanIDFromString(s string) (uint64, error) {
 	if len(s) != 16 {
 		return 0, fmt.Errorf("invalid span ID")
diff --git a/experimental/bridge/opentracing/internal/mock.go b/experimental/bridge/opentracing/internal/mock.go
index 270e57b6..83bcf54a 100644
--- a/experimental/bridge/opentracing/internal/mock.go
+++ b/experimental/bridge/opentracing/internal/mock.go
@@ -145,11 +145,7 @@ func (t *MockTracer) getTraceID(ctx context.Context, spanOpts *oteltrace.SpanOpt
 		}
 		return traceID
 	}
-	uints := t.getNRandUint64(2)
-	return otelcore.TraceID{
-		High: uints[0],
-		Low:  uints[1],
-	}
+	return t.getRandTraceID()
 }
 
 func (t *MockTracer) getParentSpanID(ctx context.Context, spanOpts *oteltrace.SpanOptions) uint64 {
@@ -183,17 +179,19 @@ func (t *MockTracer) getSpanID() uint64 {
 }
 
 func (t *MockTracer) getRandUint64() uint64 {
-	return t.getNRandUint64(1)[0]
+	t.randLock.Lock()
+	defer t.randLock.Unlock()
+	return t.rand.Uint64()
 }
 
-func (t *MockTracer) getNRandUint64(n int) []uint64 {
-	uints := make([]uint64, n)
+func (t *MockTracer) getRandTraceID() otelcore.TraceID {
 	t.randLock.Lock()
 	defer t.randLock.Unlock()
-	for i := 0; i < n; i++ {
-		uints[i] = t.rand.Uint64()
-	}
-	return uints
+
+	tid := otelcore.TraceID{}
+	t.rand.Read(tid[:])
+
+	return tid
 }
 
 func (t *MockTracer) DeferredContextSetupHook(ctx context.Context, span oteltrace.Span) context.Context {
diff --git a/exporter/trace/jaeger/jaeger.go b/exporter/trace/jaeger/jaeger.go
index 197af17e..fda0869c 100644
--- a/exporter/trace/jaeger/jaeger.go
+++ b/exporter/trace/jaeger/jaeger.go
@@ -16,6 +16,7 @@ package jaeger
 
 import (
 	"context"
+	"encoding/binary"
 	"log"
 	"sync"
 
@@ -196,8 +197,8 @@ func spanDataToThrift(data *export.SpanData) *gen.Span {
 	var refs []*gen.SpanRef
 	for _, link := range data.Links {
 		refs = append(refs, &gen.SpanRef{
-			TraceIdHigh: int64(link.TraceID.High),
-			TraceIdLow:  int64(link.TraceID.Low),
+			TraceIdHigh: int64(binary.BigEndian.Uint64(link.TraceID[0:8])),
+			TraceIdLow:  int64(binary.BigEndian.Uint64(link.TraceID[8:16])),
 			SpanId:      int64(link.SpanID),
 			// TODO(paivagustavo): properly set the reference type when specs are defined
 			//  see https://github.com/open-telemetry/opentelemetry-specification/issues/65
@@ -206,8 +207,8 @@ func spanDataToThrift(data *export.SpanData) *gen.Span {
 	}
 
 	return &gen.Span{
-		TraceIdHigh:   int64(data.SpanContext.TraceID.High),
-		TraceIdLow:    int64(data.SpanContext.TraceID.Low),
+		TraceIdHigh:   int64(binary.BigEndian.Uint64(data.SpanContext.TraceID[0:8])),
+		TraceIdLow:    int64(binary.BigEndian.Uint64(data.SpanContext.TraceID[8:16])),
 		SpanId:        int64(data.SpanContext.SpanID),
 		ParentSpanId:  int64(data.ParentSpanID),
 		OperationName: data.Name, // TODO: if span kind is added then add prefix "Sent"/"Recv"
diff --git a/internal/trace/mock_tracer.go b/internal/trace/mock_tracer.go
index 54eebf9f..721e95c7 100644
--- a/internal/trace/mock_tracer.go
+++ b/internal/trace/mock_tracer.go
@@ -16,7 +16,7 @@ package trace
 
 import (
 	"context"
-	"math/rand"
+	"crypto/rand"
 	"sync/atomic"
 
 	"go.opentelemetry.io/api/core"
@@ -69,12 +69,8 @@ func (mt *MockTracer) Start(ctx context.Context, name string, o ...apitrace.Span
 	var span *MockSpan
 	var sc core.SpanContext
 	if !opts.Relation.SpanContext.IsValid() {
-		sc = core.SpanContext{
-			TraceID: core.TraceID{
-				High: rand.Uint64(),
-				Low:  rand.Uint64(),
-			},
-		}
+		sc = core.SpanContext{}
+		_, _ = rand.Read(sc.TraceID[:])
 		if mt.Sampled {
 			sc.TraceFlags = core.TraceFlagsSampled
 		}
diff --git a/propagation/binary_propagator.go b/propagation/binary_propagator.go
index 2b4229ee..91991c73 100644
--- a/propagation/binary_propagator.go
+++ b/propagation/binary_propagator.go
@@ -38,8 +38,7 @@ func (bp binaryPropagator) ToBytes(sc core.SpanContext) []byte {
 		return nil
 	}
 	var b [29]byte
-	binary.BigEndian.PutUint64(b[2:10], sc.TraceID.High)
-	binary.BigEndian.PutUint64(b[10:18], sc.TraceID.Low)
+	copy(b[2:18], sc.TraceID[:])
 	b[18] = 1
 	binary.BigEndian.PutUint64(b[19:27], sc.SpanID)
 	b[27] = 2
@@ -55,8 +54,7 @@ func (bp binaryPropagator) FromBytes(b []byte) (sc core.SpanContext) {
 	}
 	b = b[1:]
 	if len(b) >= 17 && b[0] == 0 {
-		sc.TraceID.High = binary.BigEndian.Uint64(b[1:9])
-		sc.TraceID.Low = binary.BigEndian.Uint64(b[9:17])
+		copy(sc.TraceID[:], b[1:17])
 		b = b[17:]
 	} else {
 		return core.EmptySpanContext()
diff --git a/propagation/http_b3_propagator.go b/propagation/http_b3_propagator.go
index 90fca860..22b7c3c0 100644
--- a/propagation/http_b3_propagator.go
+++ b/propagation/http_b3_propagator.go
@@ -57,7 +57,6 @@ type HTTPB3Propagator struct {
 
 var _ apipropagation.TextFormatPropagator = HTTPB3Propagator{}
 
-var hexStr32ByteRegex = regexp.MustCompile("^[a-f0-9]{32}$")
 var hexStr16ByteRegex = regexp.MustCompile("^[a-f0-9]{16}$")
 
 func (b3 HTTPB3Propagator) Inject(ctx context.Context, supplier apipropagation.Supplier) {
@@ -66,10 +65,9 @@ func (b3 HTTPB3Propagator) Inject(ctx context.Context, supplier apipropagation.S
 		if b3.SingleHeader {
 			sampled := sc.TraceFlags & core.TraceFlagsSampled
 			supplier.Set(B3SingleHeader,
-				fmt.Sprintf("%.16x%.16x-%.16x-%.1d", sc.TraceID.High, sc.TraceID.Low, sc.SpanID, sampled))
+				fmt.Sprintf("%s-%.16x-%.1d", sc.TraceIDString(), sc.SpanID, sampled))
 		} else {
-			supplier.Set(B3TraceIDHeader,
-				fmt.Sprintf("%.16x%.16x", sc.TraceID.High, sc.TraceID.Low))
+			supplier.Set(B3TraceIDHeader, sc.TraceIDString())
 			supplier.Set(B3SpanIDHeader,
 				fmt.Sprintf("%.16x", sc.SpanID))
 
@@ -183,15 +181,8 @@ func (b3 HTTPB3Propagator) extractSingleHeader(supplier apipropagation.Supplier)
 
 // extractTraceID parses the value of the X-B3-TraceId b3Header.
 func (b3 HTTPB3Propagator) extractTraceID(tid string) (traceID core.TraceID, ok bool) {
-	if hexStr32ByteRegex.MatchString(tid) {
-		traceID.High, _ = strconv.ParseUint(tid[0:(16)], 16, 64)
-		traceID.Low, _ = strconv.ParseUint(tid[(16):32], 16, 64)
-		ok = true
-	} else if b3.SingleHeader && hexStr16ByteRegex.MatchString(tid) {
-		traceID.Low, _ = strconv.ParseUint(tid[:16], 16, 64)
-		ok = true
-	}
-	return traceID, ok
+	traceID, err := core.TraceIDFromHex(tid)
+	return traceID, err == nil
 }
 
 // extractSpanID parses the value of the X-B3-SpanId or X-B3-ParentSpanId headers.
diff --git a/propagation/http_trace_context_propagator.go b/propagation/http_trace_context_propagator.go
index 1cf79292..8dc62d9f 100644
--- a/propagation/http_trace_context_propagator.go
+++ b/propagation/http_trace_context_propagator.go
@@ -46,10 +46,9 @@ var traceCtxRegExp = regexp.MustCompile("^[0-9a-f]{2}-[a-f0-9]{32}-[a-f0-9]{16}-
 func (hp HTTPTraceContextPropagator) Inject(ctx context.Context, supplier apipropagation.Supplier) {
 	sc := trace.CurrentSpan(ctx).SpanContext()
 	if sc.IsValid() {
-		h := fmt.Sprintf("%.2x-%.16x%.16x-%.16x-%.2x",
+		h := fmt.Sprintf("%.2x-%s-%.16x-%.2x",
 			supportedVersion,
-			sc.TraceID.High,
-			sc.TraceID.Low,
+			sc.TraceIDString(),
 			sc.SpanID,
 			sc.TraceFlags&core.TraceFlagsSampled)
 		supplier.Set(TraceparentHeader, h)
@@ -118,24 +117,17 @@ func (hp HTTPTraceContextPropagator) extractSpanContext(
 		return core.EmptySpanContext()
 	}
 
-	result, err := strconv.ParseUint(sections[1][0:16], 16, 64)
-	if err != nil {
-		return core.EmptySpanContext()
-	}
 	var sc core.SpanContext
 
-	sc.TraceID.High = result
-
-	result, err = strconv.ParseUint(sections[1][16:32], 16, 64)
+	_, err = hex.Decode(sc.TraceID[0:16], []byte(sections[1][0:32]))
 	if err != nil {
 		return core.EmptySpanContext()
 	}
-	sc.TraceID.Low = result
 
 	if len(sections[2]) != 16 {
 		return core.EmptySpanContext()
 	}
-	result, err = strconv.ParseUint(sections[2][0:], 16, 64)
+	result, err := strconv.ParseUint(sections[2][0:], 16, 64)
 	if err != nil {
 		return core.EmptySpanContext()
 	}
diff --git a/sdk/trace/id_generator.go b/sdk/trace/id_generator.go
index 7efe1db4..e150f7b9 100644
--- a/sdk/trace/id_generator.go
+++ b/sdk/trace/id_generator.go
@@ -59,9 +59,7 @@ func (gen *defaultIDGenerator) NewTraceID() core.TraceID {
 	defer gen.Unlock()
 	// Construct the trace ID from two outputs of traceIDRand, with a constant
 	// added to each half for additional entropy.
-	tid := core.TraceID{
-		High: gen.traceIDRand.Uint64() + gen.traceIDAdd[0],
-		Low:  gen.traceIDRand.Uint64() + gen.traceIDAdd[1],
-	}
+	tid := core.TraceID{}
+	gen.traceIDRand.Read(tid[:])
 	return tid
 }
diff --git a/sdk/trace/sampling.go b/sdk/trace/sampling.go
index a05400ef..e90a46dc 100644
--- a/sdk/trace/sampling.go
+++ b/sdk/trace/sampling.go
@@ -15,6 +15,8 @@
 package trace
 
 import (
+	"encoding/binary"
+
 	"go.opentelemetry.io/api/core"
 )
 
@@ -52,7 +54,7 @@ func ProbabilitySampler(fraction float64) Sampler {
 		if p.ParentContext.IsSampled() {
 			return SamplingDecision{Sample: true}
 		}
-		x := p.TraceID.High >> 1
+		x := binary.BigEndian.Uint64(p.TraceID[0:8]) >> 1
 		return SamplingDecision{Sample: x < traceIDUpperBound}
 	})
 }


Test patch:
diff --git a/api/core/span_context_test.go b/api/core/span_context_test.go
index f0b5fa42..3485f560 100644
--- a/api/core/span_context_test.go
+++ b/api/core/span_context_test.go
@@ -29,22 +29,22 @@ func TestIsValid(t *testing.T) {
 	}{
 		{
 			name: "SpanContext.IsValid() returns true if sc has both an Trace ID and Span ID",
-			tid:  core.TraceID{High: uint64(42)},
+			tid:  core.TraceID([16]byte{1}),
 			sid:  uint64(42),
 			want: true,
 		}, {
 			name: "SpanContext.IsValid() returns false if sc has neither an Trace ID nor Span ID",
-			tid:  core.TraceID{High: uint64(0)},
+			tid:  core.TraceID([16]byte{}),
 			sid:  uint64(0),
 			want: false,
 		}, {
 			name: "SpanContext.IsValid() returns false if sc has a Span ID but not a Trace ID",
-			tid:  core.TraceID{High: uint64(0)},
+			tid:  core.TraceID([16]byte{}),
 			sid:  uint64(42),
 			want: false,
 		}, {
 			name: "SpanContext.IsValid() returns false if sc has a Trace ID but not a Span ID",
-			tid:  core.TraceID{High: uint64(42)},
+			tid:  core.TraceID([16]byte{1}),
 			sid:  uint64(0),
 			want: false,
 		},
@@ -62,6 +62,50 @@ func TestIsValid(t *testing.T) {
 	}
 }
 
+func TestIsValidFromHex(t *testing.T) {
+	for _, testcase := range []struct {
+		name  string
+		hex   string
+		tid   core.TraceID
+		valid bool
+	}{
+		{
+			name:  "Valid TraceID",
+			tid:   core.TraceID([16]byte{128, 241, 152, 238, 86, 52, 59, 168, 100, 254, 139, 42, 87, 211, 239, 247}),
+			hex:   "80f198ee56343ba864fe8b2a57d3eff7",
+			valid: true,
+		}, {
+			name:  "Invalid TraceID with invalid length",
+			hex:   "80f198ee56343ba864fe8b2a57d3eff",
+			valid: false,
+		}, {
+			name:  "Invalid TraceID with invalid char",
+			hex:   "80f198ee56343ba864fe8b2a57d3efg7",
+			valid: false,
+		}, {
+			name:  "Invalid TraceID with uppercase",
+			hex:   "80f198ee56343ba864fe8b2a57d3efF7",
+			valid: false,
+		},
+	} {
+		t.Run(testcase.name, func(t *testing.T) {
+			tid, err := core.TraceIDFromHex(testcase.hex)
+
+			if testcase.valid && err != nil {
+				t.Errorf("Expected TraceID %s to be valid but end with error %s", testcase.hex, err.Error())
+			}
+
+			if !testcase.valid && err == nil {
+				t.Errorf("Expected TraceID %s to be invalid but end no error", testcase.hex)
+			}
+
+			if tid != testcase.tid {
+				t.Errorf("Want: %v, but have: %v", testcase.tid, tid)
+			}
+		})
+	}
+}
+
 func TestHasTraceID(t *testing.T) {
 	for _, testcase := range []struct {
 		name string
@@ -70,20 +114,12 @@ func TestHasTraceID(t *testing.T) {
 	}{
 		{
 			name: "SpanContext.HasTraceID() returns true if both Low and High are nonzero",
-			tid:  core.TraceID{High: uint64(42), Low: uint64(42)},
+			tid:  core.TraceID([16]byte{1}),
 			want: true,
 		}, {
 			name: "SpanContext.HasTraceID() returns false if neither Low nor High are nonzero",
 			tid:  core.TraceID{},
 			want: false,
-		}, {
-			name: "SpanContext.HasTraceID() returns true if High != 0",
-			tid:  core.TraceID{High: uint64(42)},
-			want: true,
-		}, {
-			name: "SpanContext.HasTraceID() returns true if Low != 0",
-			tid:  core.TraceID{Low: uint64(42)},
-			want: true,
 		},
 	} {
 		t.Run(testcase.name, func(t *testing.T) {
@@ -158,12 +194,9 @@ func TestTraceIDString(t *testing.T) {
 		{
 			name: "SpanContext.TraceIDString returns string representation of self.TraceID values > 0",
 			sc: core.SpanContext{
-				TraceID: core.TraceID{
-					High: uint64(42),
-					Low:  uint64(42),
-				},
+				TraceID: core.TraceID([16]byte{255}),
 			},
-			want: `000000000000002a000000000000002a`,
+			want: `ff000000000000000000000000000000`,
 		}, {
 			name: "SpanContext.TraceIDString returns string representation of self.TraceID values == 0",
 			sc:   core.SpanContext{TraceID: core.TraceID{}},
@@ -189,20 +222,14 @@ func TestSpanContextIsSampled(t *testing.T) {
 		{
 			name: "sampled",
 			sc: core.SpanContext{
-				TraceID: core.TraceID{
-					High: uint64(42),
-					Low:  uint64(42),
-				},
+				TraceID:    core.TraceID([16]byte{1}),
 				TraceFlags: core.TraceFlagsSampled,
 			},
 			want: true,
 		}, {
 			name: "sampled plus unused",
 			sc: core.SpanContext{
-				TraceID: core.TraceID{
-					High: uint64(42),
-					Low:  uint64(42),
-				},
+				TraceID:    core.TraceID([16]byte{1}),
 				TraceFlags: core.TraceFlagsSampled | core.TraceFlagsUnused,
 			},
 			want: true,
diff --git a/experimental/bridge/opentracing/mix_test.go b/experimental/bridge/opentracing/mix_test.go
index 8ada1976..4c82f2d2 100644
--- a/experimental/bridge/opentracing/mix_test.go
+++ b/experimental/bridge/opentracing/mix_test.go
@@ -504,10 +504,7 @@ func reverse(length int, swap func(i, j int)) {
 }
 
 func simpleTraceID() otelcore.TraceID {
-	return otelcore.TraceID{
-		High: 1357,
-		Low:  2468,
-	}
+	return [16]byte{123, 42}
 }
 
 func simpleSpanIDs(count int) []uint64 {
diff --git a/exporter/trace/jaeger/jaeger_test.go b/exporter/trace/jaeger/jaeger_test.go
index 9f48335f..de2670d4 100644
--- a/exporter/trace/jaeger/jaeger_test.go
+++ b/exporter/trace/jaeger/jaeger_test.go
@@ -15,6 +15,7 @@
 package jaeger
 
 import (
+	"encoding/binary"
 	"sort"
 	"testing"
 	"time"
@@ -35,10 +36,10 @@ import (
 
 func Test_spanDataToThrift(t *testing.T) {
 	now := time.Now()
-	traceID := core.TraceID{High: 0x0102030405060708, Low: 0x090a0b0c0d0e0f10}
+	traceID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
 	spanID := uint64(0x0102030405060708)
 
-	linkTraceID := core.TraceID{High: 0x0102030405060709, Low: 0x090a0b0c0d0e0f11}
+	linkTraceID, _ := core.TraceIDFromHex("0102030405060709090a0b0c0d0e0f11")
 	linkSpanID := uint64(0x0102030405060709)
 
 	keyValue := "value"
@@ -99,8 +100,8 @@ func Test_spanDataToThrift(t *testing.T) {
 				References: []*gen.SpanRef{
 					{
 						RefType:     gen.SpanRefType_CHILD_OF,
-						TraceIdLow:  int64(linkTraceID.Low),
-						TraceIdHigh: int64(linkTraceID.High),
+						TraceIdHigh: int64(binary.BigEndian.Uint64(linkTraceID[0:8])),
+						TraceIdLow:  int64(binary.BigEndian.Uint64(linkTraceID[8:16])),
 						SpanId:      int64(linkSpanID),
 					},
 				},
diff --git a/exporter/trace/stdout/stdout_test.go b/exporter/trace/stdout/stdout_test.go
index 3a85fd10..d2416ee4 100644
--- a/exporter/trace/stdout/stdout_test.go
+++ b/exporter/trace/stdout/stdout_test.go
@@ -39,7 +39,7 @@ func TestExporter_ExportSpan(t *testing.T) {
 
 	// setup test span
 	now := time.Now()
-	traceID := core.TraceID{High: 0x0102030405060708, Low: 0x090a0b0c0d0e0f10}
+	traceID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
 	spanID := uint64(0x0102030405060708)
 	keyValue := "value"
 	doubleValue := float64(123.456)
@@ -70,8 +70,8 @@ func TestExporter_ExportSpan(t *testing.T) {
 
 	got := b.String()
 	expectedOutput := `{"SpanContext":{` +
-		`"TraceID":{"High":72623859790382856,"Low":651345242494996240},` +
-		`"SpanID":72623859790382856,"TraceFlags":0},` +
+		`"TraceID":"0102030405060708090a0b0c0d0e0f10",` +
+		`"SpanID":"0102030405060708","TraceFlags":0},` +
 		`"ParentSpanID":0,` +
 		`"SpanKind":0,` +
 		`"Name":"/foo",` +
diff --git a/propagation/binary_propagator_test.go b/propagation/binary_propagator_test.go
index 07244c47..dcf3bb45 100644
--- a/propagation/binary_propagator_test.go
+++ b/propagation/binary_propagator_test.go
@@ -15,6 +15,7 @@
 package propagation_test
 
 import (
+	"encoding/hex"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -24,7 +25,10 @@ import (
 )
 
 func TestExtractSpanContextFromBytes(t *testing.T) {
-	traceID := core.TraceID{High: 0x4bf92f3577b34da6, Low: 0xa3ce929d0e0e4736}
+	bTraceID, _ := hex.DecodeString("4bf92f3577b34da6a3ce929d0e0e4736")
+	traceID := core.TraceID{}
+	copy(traceID[:], bTraceID)
+
 	spanID := uint64(0x00f067aa0ba902b7)
 	propagator := propagation.BinaryPropagator()
 	tests := []struct {
@@ -119,7 +123,10 @@ func TestExtractSpanContextFromBytes(t *testing.T) {
 }
 
 func TestConvertSpanContextToBytes(t *testing.T) {
-	traceID := core.TraceID{High: 0x4bf92f3577b34da6, Low: 0xa3ce929d0e0e4736}
+	bTraceID, _ := hex.DecodeString("4bf92f3577b34da6a3ce929d0e0e4736")
+	traceID := core.TraceID{}
+	copy(traceID[:], bTraceID)
+
 	spanID := uint64(0x00f067aa0ba902b7)
 	propagator := propagation.BinaryPropagator()
 	tests := []struct {
diff --git a/propagation/http_trace_context_propagator_benchmark_test.go b/propagation/http_trace_context_propagator_benchmark_test.go
index 660b4d16..06144bc3 100644
--- a/propagation/http_trace_context_propagator_benchmark_test.go
+++ b/propagation/http_trace_context_propagator_benchmark_test.go
@@ -2,6 +2,7 @@ package propagation
 
 import (
 	"context"
+	"encoding/hex"
 	"net/http"
 	"testing"
 
@@ -27,8 +28,11 @@ func injectSubBenchmarks(b *testing.B, fn func(context.Context, *testing.B)) {
 		var (
 			id      uint64
 			spanID  = uint64(0x00f067aa0ba902b7)
-			traceID = core.TraceID{High: 0x4bf92f3577b34da6, Low: 0xa3ce929d0e0e4736}
+			traceID = core.TraceID{}
 		)
+		bt, _ := hex.DecodeString("4bf92f3577b34da6a3ce929d0e0e4736")
+		copy(traceID[:], bt)
+
 		mockTracer := &mocktrace.MockTracer{
 			Sampled:     false,
 			StartSpanID: &id,
diff --git a/propagation/http_trace_context_propagator_test.go b/propagation/http_trace_context_propagator_test.go
index 612233c7..898a167a 100644
--- a/propagation/http_trace_context_propagator_test.go
+++ b/propagation/http_trace_context_propagator_test.go
@@ -31,10 +31,15 @@ import (
 )
 
 var (
-	traceID = core.TraceID{High: 0x4bf92f3577b34da6, Low: 0xa3ce929d0e0e4736}
+	traceID = mustTraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
 	spanID  = uint64(0x00f067aa0ba902b7)
 )
 
+func mustTraceIDFromHex(s string) (t core.TraceID) {
+	t, _ = core.TraceIDFromHex(s)
+	return
+}
+
 func TestExtractValidTraceContextFromHTTPReq(t *testing.T) {
 	var propagator propagation.HTTPTraceContextPropagator
 	tests := []struct {
diff --git a/sdk/trace/batch_span_processor_test.go b/sdk/trace/batch_span_processor_test.go
index e21cde3f..cdef3c2e 100644
--- a/sdk/trace/batch_span_processor_test.go
+++ b/sdk/trace/batch_span_processor_test.go
@@ -16,6 +16,7 @@ package trace_test
 
 import (
 	"context"
+	"encoding/binary"
 	"sync"
 	"testing"
 	"time"
@@ -164,7 +165,7 @@ func TestNewBatchSpanProcessorWithOptions(t *testing.T) {
 		// Check first Span is reported. Most recent one is dropped.
 		sc := getSpanContext()
 		wantTraceID := sc.TraceID
-		wantTraceID.High = 1
+		binary.BigEndian.PutUint64(wantTraceID[0:8], uint64(1))
 		gotTraceID := te.get(0).SpanContext.TraceID
 		if wantTraceID != gotTraceID {
 			t.Errorf("%s: first exported span: got %+v, want %+v\n", option.name, gotTraceID, wantTraceID)
@@ -185,14 +186,14 @@ func generateSpan(t *testing.T, tr apitrace.Tracer, option testOption) {
 	sc := getSpanContext()
 
 	for i := 0; i < option.genNumSpans; i++ {
-		sc.TraceID.High = uint64(i + 1)
+		binary.BigEndian.PutUint64(sc.TraceID[0:8], uint64(i+1))
 		_, span := tr.Start(context.Background(), option.name, apitrace.ChildOf(sc))
 		span.End()
 	}
 }
 
 func getSpanContext() core.SpanContext {
-	tid := core.TraceID{High: 0x0102030405060708, Low: 0x0102040810203040}
+	tid, _ := core.TraceIDFromHex("01020304050607080102040810203040")
 	sid := uint64(0x0102040810203040)
 	return core.SpanContext{
 		TraceID:    tid,
diff --git a/sdk/trace/benchmark_test.go b/sdk/trace/benchmark_test.go
index 01b04b6e..543ac84a 100644
--- a/sdk/trace/benchmark_test.go
+++ b/sdk/trace/benchmark_test.go
@@ -147,7 +147,8 @@ func BenchmarkSpanWithAttributes_all_2x(b *testing.B) {
 
 func BenchmarkTraceID_DotString(b *testing.B) {
 	traceBenchmark(b, func(b *testing.B) {
-		sc := core.SpanContext{TraceID: core.TraceID{High: 1, Low: 0x2a}}
+		t, _ := core.TraceIDFromHex("0000000000000001000000000000002a")
+		sc := core.SpanContext{TraceID: t}
 
 		want := "0000000000000001000000000000002a"
 		for i := 0; i < b.N; i++ {
diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go
index e84f7a00..6d8a1b84 100644
--- a/sdk/trace/simple_span_processor_test.go
+++ b/sdk/trace/simple_span_processor_test.go
@@ -55,9 +55,10 @@ func TestSimpleSpanProcessorOnEnd(t *testing.T) {
 	if ssp == nil {
 		t.Errorf("Error creating new instance of SimpleSpanProcessor with nil Exporter\n")
 	}
+
 	tp.RegisterSpanProcessor(ssp)
 	tr := tp.GetTracer("SimpleSpanProcessor")
-	tid := core.TraceID{High: 0x0102030405060708, Low: 0x0102040810203040}
+	tid, _ := core.TraceIDFromHex("01020304050607080102040810203040")
 	sid := uint64(0x0102040810203040)
 	sc := core.SpanContext{
 		TraceID:    tid,
diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go
index 7e949ee5..56b4fc98 100644
--- a/sdk/trace/trace_test.go
+++ b/sdk/trace/trace_test.go
@@ -34,10 +34,14 @@ import (
 )
 
 var (
-	tid = core.TraceID{High: 0x0102030405060708, Low: 0x0102040810203040}
+	tid core.TraceID
 	sid = uint64(0x0102040810203040)
 )
 
+func init() {
+	tid, _ = core.TraceIDFromHex("01020304050607080102040810203040")
+}
+
 func TestTracerFollowsExpectedAPIBehaviour(t *testing.T) {
 	tp, err := NewProvider(WithConfig(Config{DefaultSampler: ProbabilitySampler(0)}))
 	if err != nil {
@@ -386,8 +390,8 @@ func TestAddLinks(t *testing.T) {
 	k1v1 := key.New("key1").String("value1")
 	k2v2 := key.New("key2").String("value2")
 
-	sc1 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x1}, SpanID: 0x3}
-	sc2 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x2}, SpanID: 0x3}
+	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
 
 	link1 := apitrace.Link{SpanContext: sc1, Attributes: []core.KeyValue{k1v1}}
 	link2 := apitrace.Link{SpanContext: sc2, Attributes: []core.KeyValue{k2v2}}
@@ -426,8 +430,8 @@ func TestLinks(t *testing.T) {
 	k2v2 := key.New("key2").String("value2")
 	k3v3 := key.New("key3").String("value3")
 
-	sc1 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x1}, SpanID: 0x3}
-	sc2 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x2}, SpanID: 0x3}
+	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
 
 	span.Link(sc1, key.New("key1").String("value1"))
 	span.Link(sc2,
@@ -460,12 +464,13 @@ func TestLinks(t *testing.T) {
 func TestLinksOverLimit(t *testing.T) {
 	te := &testExporter{}
 	cfg := Config{MaxLinksPerSpan: 2}
-	tp, _ := NewProvider(WithConfig(cfg), WithSyncer(te))
 
+	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+	sc3 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+
+	tp, _ := NewProvider(WithConfig(cfg), WithSyncer(te))
 	span := startSpan(tp, "LinksOverLimit")
-	sc1 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x1}, SpanID: 0x3}
-	sc2 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x2}, SpanID: 0x3}
-	sc3 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x3}, SpanID: 0x3}
 
 	k2v2 := key.New("key2").String("value2")
 	k3v3 := key.New("key3").String("value3")
@@ -741,12 +746,14 @@ func TestExecutionTracerTaskEnd(t *testing.T) {
 	s.executionTracerTaskEnd = executionTracerTaskEnd
 	spans = append(spans, s) // never sample
 
+	tID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f")
+
 	_, apiSpan = tr.Start(
 		context.Background(),
 		"foo",
 		apitrace.ChildOf(
 			core.SpanContext{
-				TraceID:    core.TraceID{High: 0x0102030405060708, Low: 0x090a0b0c0d0e0f},
+				TraceID:    tID,
 				SpanID:     uint64(0x0001020304050607),
 				TraceFlags: 0,
 			},