diff options
Diffstat (limited to 'types/http.go')
-rw-r--r-- | types/http.go | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/types/http.go b/types/http.go new file mode 100644 index 0000000..8bbe26d --- /dev/null +++ b/types/http.go @@ -0,0 +1,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 +} |