aboutsummaryrefslogtreecommitdiff
path: root/types/http.go
blob: 8bbe26d258949fa73e99f0247bed7b339d80b31d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package types

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

const (
	// HeaderPrefix is the start of every ST log HTTP header key
	HeaderPrefix = "stlog-"

	// New leaf
	HeaderShardHint            = HeaderPrefix + "shard_hint"
	HeaderChecksum             = HeaderPrefix + "checksum"
	HeaderSignatureOverMessage = HeaderPrefix + "signature_over_message"
	HeaderVerificationKey      = HeaderPrefix + "verification_key"
	HeaderDomainHint           = HeaderPrefix + "domain_hint"

	// Inclusion proof
	HeaderLeafHash      = HeaderPrefix + "leaf_hash"
	HeaderLeafIndex     = HeaderPrefix + "leaf_index"
	HeaderInclusionPath = HeaderPrefix + "inclusion_path"

	// Consistency proof
	HeaderNewSize         = HeaderPrefix + "new_size"
	HeaderOldSize         = HeaderPrefix + "old_size"
	HeaderConsistencyPath = HeaderPrefix + "consistency_path"

	// Range of leaves
	HeaderStartSize = HeaderPrefix + "start_size"
	HeaderEndSize   = HeaderPrefix + "end_size"

	// Tree head
	HeaderTimestamp = HeaderPrefix + "timestamp"
	HeaderTreeSize  = HeaderPrefix + "tree_size"
	HeaderRootHash  = HeaderPrefix + "root_hash"

	// Signature and signer identity
	HeaderSignature = HeaderPrefix + "signature"
	HeaderKeyHash   = HeaderPrefix + "key_hash"
)

// ToHTTP returns a signed tree-head as HTTP key-value pairs
func (sth *SignedTreeHead) ToHTTP() ([]byte, error) {
	hdr := http.Header{}
	hdr.Add(HeaderTimestamp, strconv.FormatUint(sth.Timestamp, 10))
	hdr.Add(HeaderTreeSize, strconv.FormatUint(sth.TreeSize, 10))
	hdr.Add(HeaderRootHash, hex.EncodeToString(sth.RootHash[:]))
	for _, sigident := range sth.SigIdent {
		hdr.Add(HeaderSignature, hex.EncodeToString(sigident.Signature[:]))
		hdr.Add(HeaderKeyHash, hex.EncodeToString(sigident.KeyHash[:]))
	}
	return headerToBytes(hdr)
}

// ToHTTP returns a consistency proof as HTTP key-value pairs
func (p *ConsistencyProof) ToHTTP() ([]byte, error) {
	hdr := http.Header{}
	hdr.Add(HeaderNewSize, strconv.FormatUint(p.NewSize, 10))
	hdr.Add(HeaderOldSize, strconv.FormatUint(p.OldSize, 10))
	for _, hash := range p.Path {
		hdr.Add(HeaderConsistencyPath, hex.EncodeToString(hash[:]))
	}
	return headerToBytes(hdr)
}

// ToHTTP returns an inclusion proof as HTTP key-value pairs
func (p *InclusionProof) ToHTTP() ([]byte, error) {
	hdr := http.Header{}
	hdr.Add(HeaderTreeSize, strconv.FormatUint(p.TreeSize, 10))
	hdr.Add(HeaderLeafIndex, strconv.FormatUint(p.LeafIndex, 10))
	for _, hash := range p.Path {
		hdr.Add(HeaderInclusionPath, hex.EncodeToString(hash[:]))
	}
	return headerToBytes(hdr)
}

// ToHTTP returns a leaf as HTTP key-value pairs
func (l *Leaf) ToHTTP() ([]byte, error) {
	hdr := http.Header{}
	hdr.Add(HeaderShardHint, strconv.FormatUint(l.ShardHint, 10))
	hdr.Add(HeaderChecksum, hex.EncodeToString(l.Checksum[:]))
	hdr.Add(HeaderSignature, hex.EncodeToString(l.Signature[:]))
	hdr.Add(HeaderKeyHash, hex.EncodeToString(l.KeyHash[:]))
	return headerToBytes(hdr)
}

// SignedTreeHeadFromHTTP parses a signed tree head from HTTP key-value pairs
func SignedTreeHeadFromHTTP(buf []byte) (*SignedTreeHead, error) {
	hdr, err := headerFromBuf(buf)
	if err != nil {
		return nil, fmt.Errorf("headerFromBuf(): %v", err)
	}

	// TreeHead
	var sth SignedTreeHead
	sth.Timestamp, err = strconv.ParseUint(hdr.Get(HeaderTimestamp), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid timestamp: %v", err)
	}
	sth.TreeSize, err = strconv.ParseUint(hdr.Get(HeaderTreeSize), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid tree size: %v", err)
	}
	if err := decodeHex(hdr.Get(HeaderRootHash), sth.RootHash[:]); err != nil {
		return nil, fmt.Errorf("decodeHex(): %v", err)
	}

	// SigIdent
	signatures := hdr.Values(HeaderSignature)
	keyHashes := hdr.Values(HeaderKeyHash)
	if len(signatures) == 0 {
		return nil, fmt.Errorf("no signer")
	}
	if len(signatures) != len(keyHashes) {
		return nil, fmt.Errorf("mismatched signature-signer count")
	}
	for i := 0; i < len(signatures); i++ {
		var sigident SigIdent
		if err := decodeHex(signatures[i], sigident.Signature[:]); err != nil {
			return nil, fmt.Errorf("decodeHex(): %v", err)
		}
		if err := decodeHex(keyHashes[i], sigident.KeyHash[:]); err != nil {
			return nil, fmt.Errorf("decodeHex(): %v", err)
		}
		sth.SigIdent = append(sth.SigIdent, sigident)
	}
	return &sth, nil
}

// ConsistencyProofFromHTTP parses a consistency proof from HTTP key-value pairs
func ConsistencyProofFromHTTP(buf []byte) (*ConsistencyProof, error) {
	return nil, nil // TODO
}

// InclusionProofFromHTTP parses an inclusion proof from HTTP key-value pairs
func InclusionProofFromHTTP(buf []byte) (*InclusionProof, error) {
	return nil, nil // TODO
}

// LeavesFromHTTP parses a list of leaves from HTTP key-value pairs
func LeavesFromHTTP(buf []byte) ([]*Leaf, error) {
	return nil, nil // TODO
}

// headerFromBuf parses ST log HTTP header key-value pairs from a response body
func headerFromBuf(buf []byte) (http.Header, error) {
	hdr := http.Header{}
	lines := strings.Split(string(buf), "\r\n")
	lines = lines[:len(lines)-1] // skip the final empty line
	for _, line := range lines {
		split := strings.Split(line, ":")
		if len(split) != 2 {
			return nil, fmt.Errorf("invalid ST log HTTP header: %s", line)
		}
		if !strings.HasPrefix(strings.ToLower(split[0]), HeaderPrefix) {
			return nil, fmt.Errorf("invalid ST log HTTP header prefix: %s", line)
		}
		hdr.Add(split[0], strings.TrimSpace(split[1]))
	}
	return hdr, nil
}

// decodeHex decodes a hex-encoded string into a fixed-size output slice
func decodeHex(str string, out []byte) error {
	buf, err := hex.DecodeString(str)
	if err != nil {
		return fmt.Errorf("hex.DecodeString(): %v", err)
	}
	if len(buf) != len(out) {
		return fmt.Errorf("invalid length: %v", len(buf))
	}
	copy(out, buf)
	return nil
}

// headerToBytes encodes a header as HTTP key-value pairs
func headerToBytes(hdr http.Header) ([]byte, error) {
	buf := bytes.NewBuffer(nil)
	if err := hdr.Write(buf); err != nil {
		return nil, fmt.Errorf("hdr.Write(): %v", err) // should not happen
	}
	return buf.Bytes(), nil
}