Skip to content

Commit 6d5c31c

Browse files
committed
fix: keep hcs27 reads strict
Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com>
1 parent 1d32233 commit 6d5c31c

File tree

4 files changed

+13
-132
lines changed

4 files changed

+13
-132
lines changed

pkg/hcs27/client.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,14 @@ func (c *Client) PublishCheckpoint(
143143
messageMemo string,
144144
transactionMemo string,
145145
) (PublishResult, error) {
146-
normalizedMetadata := metadata
147-
if normalizedMetadata.Log != nil && isLegacyMerkleProfile(normalizedMetadata.Log.Merkle) {
148-
normalizedLog := *normalizedMetadata.Log
149-
normalizedLog.Merkle = merkleProfileRFC9162
150-
normalizedMetadata.Log = &normalizedLog
151-
}
152-
if err := validateMetadata(normalizedMetadata); err != nil {
146+
if err := validateMetadata(metadata); err != nil {
153147
return PublishResult{}, err
154148
}
155149
if len(messageMemo) >= 300 {
156150
return PublishResult{}, fmt.Errorf("message memo must be less than 300 characters")
157151
}
158152

159-
metadataBytes, err := json.Marshal(normalizedMetadata)
153+
metadataBytes, err := json.Marshal(metadata)
160154
if err != nil {
161155
return PublishResult{}, fmt.Errorf("failed to encode checkpoint metadata: %w", err)
162156
}

pkg/hcs27/types.go

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@ package hcs27
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
7-
"strings"
86

97
hedera "github.com/hashgraph/hedera-sdk-go/v2"
108
)
119

1210
const (
13-
ProtocolID = "hcs-27"
14-
OperationName = "register"
15-
checkpointMetadataType = "ans-checkpoint-v1"
16-
merkleProfileRFC9162 = "rfc9162"
17-
legacyMerkleProfileRFC6962 = "rfc6962"
11+
ProtocolID = "hcs-27"
12+
OperationName = "register"
13+
checkpointMetadataType = "ans-checkpoint-v1"
14+
merkleProfileRFC9162 = "rfc9162"
1815
)
1916

2017
type StreamID struct {
@@ -33,53 +30,11 @@ type RootCommitment struct {
3330
RootHashB64u string `json:"rootHashB64u"`
3431
}
3532

36-
func (commitment *RootCommitment) UnmarshalJSON(data []byte) error {
37-
type rawRootCommitment struct {
38-
TreeSize json.RawMessage `json:"treeSize"`
39-
RootHashB64u string `json:"rootHashB64u"`
40-
}
41-
42-
var raw rawRootCommitment
43-
if err := json.Unmarshal(data, &raw); err != nil {
44-
return err
45-
}
46-
47-
treeSize, err := decodeLegacyTreeSize(raw.TreeSize)
48-
if err != nil {
49-
return fmt.Errorf("treeSize must be a JSON string or number: %w", err)
50-
}
51-
52-
commitment.TreeSize = treeSize
53-
commitment.RootHashB64u = raw.RootHashB64u
54-
return nil
55-
}
56-
5733
type PreviousCommitment struct {
5834
TreeSize string `json:"treeSize"`
5935
RootHashB64u string `json:"rootHashB64u"`
6036
}
6137

62-
func (commitment *PreviousCommitment) UnmarshalJSON(data []byte) error {
63-
type rawPreviousCommitment struct {
64-
TreeSize json.RawMessage `json:"treeSize"`
65-
RootHashB64u string `json:"rootHashB64u"`
66-
}
67-
68-
var raw rawPreviousCommitment
69-
if err := json.Unmarshal(data, &raw); err != nil {
70-
return err
71-
}
72-
73-
treeSize, err := decodeLegacyTreeSize(raw.TreeSize)
74-
if err != nil {
75-
return fmt.Errorf("treeSize must be a JSON string or number: %w", err)
76-
}
77-
78-
commitment.TreeSize = treeSize
79-
commitment.RootHashB64u = raw.RootHashB64u
80-
return nil
81-
}
82-
8338
type Signature struct {
8439
Algorithm string `json:"alg"`
8540
KeyID string `json:"kid"`
@@ -163,22 +118,3 @@ type PublishResult struct {
163118
TransactionID string `json:"transaction_id"`
164119
SequenceNumber int64 `json:"sequence_number"`
165120
}
166-
167-
func decodeLegacyTreeSize(raw json.RawMessage) (string, error) {
168-
trimmed := strings.TrimSpace(string(raw))
169-
if trimmed == "" || trimmed == "null" {
170-
return "", fmt.Errorf("treeSize is required")
171-
}
172-
173-
var asString string
174-
if err := json.Unmarshal(raw, &asString); err == nil {
175-
return asString, nil
176-
}
177-
178-
var asUint uint64
179-
if err := json.Unmarshal(raw, &asUint); err == nil {
180-
return canonicalUint64(asUint), nil
181-
}
182-
183-
return "", fmt.Errorf("unsupported treeSize encoding")
184-
}

pkg/hcs27/validation.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,8 @@ func validateMetadata(metadata CheckpointMetadata) error {
106106
if strings.TrimSpace(metadata.Log.Leaf) == "" {
107107
return fmt.Errorf("metadata.log.leaf is required")
108108
}
109-
if !isAcceptedMerkleProfile(metadata.Log.Merkle) {
110-
return fmt.Errorf(
111-
"metadata.log.merkle must be %s or %s",
112-
merkleProfileRFC9162,
113-
legacyMerkleProfileRFC6962,
114-
)
109+
if strings.TrimSpace(metadata.Log.Merkle) != merkleProfileRFC9162 {
110+
return fmt.Errorf("metadata.log.merkle must be %s", merkleProfileRFC9162)
115111
}
116112
rootTreeSize, err := parseCanonicalUint64("metadata.root.treeSize", metadata.Root.TreeSize)
117113
if err != nil {
@@ -147,15 +143,5 @@ func validateMetadata(metadata CheckpointMetadata) error {
147143
return fmt.Errorf("metadata.sig.b64u must be base64url: %w", err)
148144
}
149145
}
150-
151146
return nil
152147
}
153-
154-
func isAcceptedMerkleProfile(value string) bool {
155-
normalized := strings.TrimSpace(value)
156-
return normalized == merkleProfileRFC9162 || normalized == legacyMerkleProfileRFC6962
157-
}
158-
159-
func isLegacyMerkleProfile(value string) bool {
160-
return strings.TrimSpace(value) == legacyMerkleProfileRFC6962
161-
}

pkg/hcs27/validation_test.go

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,25 @@ import (
99
"testing"
1010
)
1111

12-
const (
13-
expectedTopicMemo = "hcs-27:0:86400:0"
14-
testHCS1Reference = "hcs://1/0.0.99999"
15-
)
12+
const testHCS1Reference = "hcs://1/0.0.99999"
1613

1714
func TestBuildTopicMemo(t *testing.T) {
1815
memo := BuildTopicMemo(86400)
19-
if memo != expectedTopicMemo {
16+
if memo != "hcs-27:0:86400:0" {
2017
t.Fatalf("unexpected memo: %s", memo)
2118
}
2219
}
2320

2421
func TestBuildTopicMemoDefault(t *testing.T) {
2522
memo := BuildTopicMemo(0)
26-
if memo != expectedTopicMemo {
23+
if memo != "hcs-27:0:86400:0" {
2724
t.Fatalf("expected default TTL, got: %s", memo)
2825
}
2926
}
3027

3128
func TestBuildTopicMemoNegative(t *testing.T) {
3229
memo := BuildTopicMemo(-1)
33-
if memo != expectedTopicMemo {
30+
if memo != "hcs-27:0:86400:0" {
3431
t.Fatalf("expected default TTL for negative, got: %s", memo)
3532
}
3633
}
@@ -43,7 +40,7 @@ func TestBuildTopicMemoCustom(t *testing.T) {
4340
}
4441

4542
func TestParseTopicMemo(t *testing.T) {
46-
parsed, ok := ParseTopicMemo(expectedTopicMemo)
43+
parsed, ok := ParseTopicMemo("hcs-27:0:86400:0")
4744
if !ok {
4845
t.Fatal("expected parse to succeed")
4946
}
@@ -226,14 +223,6 @@ func TestValidateMetadataLogMerkle(t *testing.T) {
226223
}
227224
}
228225

229-
func TestValidateMetadataLegacyMerkleAccepted(t *testing.T) {
230-
metadata := buildValidMetadata()
231-
metadata.Log.Merkle = legacyMerkleProfileRFC6962
232-
if err := validateMetadata(metadata); err != nil {
233-
t.Fatalf("expected legacy merkle label to be accepted on reads, got: %v", err)
234-
}
235-
}
236-
237226
func TestValidateMetadataRootHash(t *testing.T) {
238227
metadata := buildValidMetadata()
239228
metadata.Root.RootHashB64u = "!invalid!"
@@ -356,30 +345,6 @@ func TestValidateMetadataTreeSizeCanonicalDecimal(t *testing.T) {
356345
}
357346
}
358347

359-
func TestValidateCheckpointMessageLegacyNumericTreeSize(t *testing.T) {
360-
metadataBytes := []byte(`{
361-
"type":"ans-checkpoint-v1",
362-
"stream":{"registry":"0.0.12345","log_id":"log-1"},
363-
"log":{"alg":"sha-256","leaf":"sha256(jcs(event))","merkle":"rfc6962"},
364-
"root":{"treeSize":10,"rootHashB64u":"n4bQgYhMfBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"}
365-
}`)
366-
367-
result, err := ValidateCheckpointMessage(context.Background(), CheckpointMessage{
368-
Protocol: ProtocolID,
369-
Operation: OperationName,
370-
Metadata: metadataBytes,
371-
}, nil)
372-
if err != nil {
373-
t.Fatalf("expected legacy numeric treeSize to validate, got: %v", err)
374-
}
375-
if result.Root.TreeSize != canonicalUint64(10) {
376-
t.Fatalf("expected numeric treeSize to normalize to canonical string, got: %s", result.Root.TreeSize)
377-
}
378-
if result.Log == nil || result.Log.Merkle != legacyMerkleProfileRFC6962 {
379-
t.Fatalf("expected legacy merkle label to round-trip, got %+v", result.Log)
380-
}
381-
}
382-
383348
func TestValidateCheckpointMessageWithReference(t *testing.T) {
384349
metadata := buildValidMetadata()
385350
metadataBytes, _ := json.Marshal(metadata)

0 commit comments

Comments
 (0)