Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ go get github.com/hashgraph-online/standards-sdk-go@latest
| `pkg/hcs21` | HCS-21 adapter registry/declaration publish flows, topic helpers, and signature/digest verification utilities. |
| `pkg/hcs26` | HCS-26 memo helpers and resolver flows for discovery, version, and manifest reconstruction. |
| `pkg/hcs27` | HCS-27 checkpoint topic creation, publish/retrieval, validation, Merkle/proof helpers. |
| `pkg/inscriber` | Kiloscribe auth flow, websocket-first high-level inscription utilities, quote generation, bulk-files support, registry-broker quote/job helpers, and skill inscription helpers. |
| `pkg/inscriber` | Inscriber auth flow, websocket-first high-level inscription utilities, quote generation, bulk-files support, registry-broker quote/job helpers, and skill inscription helpers. |
| `pkg/registrybroker` | Full Registry Broker client (search, adapters, agents, credits, verification, ledger auth, chat/encryption, feedback, skills). |
| `pkg/mirror` | Mirror node client used by HCS and inscriber packages. |
| `pkg/shared` | Network normalization, operator env loading, Hedera client/key parsing helpers. |
Expand Down Expand Up @@ -111,11 +111,7 @@ client, _ := hcs27.NewClient(hcs27.ClientConfig{
metadata := hcs27.CheckpointMetadata{
Type: "ans-checkpoint-v1",
Stream: hcs27.StreamID{Registry: "ans", LogID: "default"},
Root: hcs27.RootCommitment{TreeSize: 1, RootHashB64: "<base64url-root>"},
BatchRange: hcs27.BatchRange{
Start: 1,
End: 1,
},
Root: hcs27.RootCommitment{TreeSize: "1", RootHashB64u: "<base64url-root>"},
}
```

Expand Down
4 changes: 2 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Runnable examples for common [HOL Standards SDK](https://hol.org/docs/libraries/
| `hcs20-deploy-points` | [HCS-20](https://hol.org/docs/standards/hcs-20) | Deploys a private points topic |
| `hcs21-build-declaration` | [HCS-21](https://hol.org/docs/standards/hcs-21) | Builds an adapter declaration |
| `hcs26-parse-memos` | [HCS-26](https://hol.org/docs/standards/hcs-26) | Parses topic/transaction memos |
| `hcs27-publish-checkpoint` | [HCS-27](https://hol.org/docs/standards/hcs-27) | Publishes a Merkle checkpoint |
| `inscriber-auth-client` | [HCS-1](https://hol.org/docs/standards/hcs-1) | Authenticates with Kiloscribe |
| `hcs27-publish-checkpoint` | [HCS-27](https://hol.org/docs/standards/hcs-27) | Publishes inline + HRL-backed checkpoints and validates the chain |
| `inscriber-auth-client` | [HCS-1](https://hol.org/docs/standards/hcs-1) | Authenticates with the inscriber service |
| `registry-broker-skill-domain-proof` | [HCS-26](https://hol.org/docs/standards/hcs-26) | Creates/verifies DNS TXT proof and checks trust delta |
| `registry-broker-uaid-dns-verification` | [HCS-14](https://hol.org/docs/standards/hcs-14) | Verifies UAID DNS TXT binding and reads stored/live verification status |

Expand Down
6 changes: 4 additions & 2 deletions examples/hcs27-publish-checkpoint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ This example publishes an [HCS-27](https://hol.org/docs/standards/hcs-27) checkp

## What it does

1. Creates an HCS-27 checkpoint with a Merkle root commitment.
2. Publishes the checkpoint to an HCS topic on testnet.
1. Creates an HCS-27 checkpoint topic when `HCS27_CHECKPOINT_TOPIC_ID` is not set.
2. Publishes an inline HCS-27 checkpoint to the topic.
3. Publishes an overflow checkpoint that stores metadata via an `hcs://1/...` reference.
4. Fetches the topic records, prints the HRL reference, and validates the checkpoint chain.

## Run

Expand Down
118 changes: 102 additions & 16 deletions examples/hcs27-publish-checkpoint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/hashgraph-online/standards-sdk-go/pkg/hcs27"
)
Expand All @@ -14,8 +17,8 @@ func main() {
accountID := os.Getenv("HEDERA_ACCOUNT_ID")
privateKey := os.Getenv("HEDERA_PRIVATE_KEY")
checkpointTopicID := os.Getenv("HCS27_CHECKPOINT_TOPIC_ID")
if accountID == "" || privateKey == "" || checkpointTopicID == "" {
fmt.Println("HEDERA_ACCOUNT_ID, HEDERA_PRIVATE_KEY, and HCS27_CHECKPOINT_TOPIC_ID are required")
if accountID == "" || privateKey == "" {
fmt.Println("HEDERA_ACCOUNT_ID and HEDERA_PRIVATE_KEY are required")
return
}

Expand All @@ -28,43 +31,126 @@ func main() {
panic(err)
}

root := hashB64URL("standards-sdk-go-example-root")
metadata := hcs27.CheckpointMetadata{
ctx := context.Background()
if checkpointTopicID == "" {
topicID, transactionID, createErr := client.CreateCheckpointTopic(ctx, hcs27.CreateTopicOptions{
TTLSeconds: 3600,
UseOperatorAsAdmin: true,
UseOperatorAsSubmit: true,
})
if createErr != nil {
panic(createErr)
}
checkpointTopicID = topicID
fmt.Printf("created checkpoint topic=%s tx=%s\n", checkpointTopicID, transactionID)
} else {
fmt.Printf("using existing checkpoint topic=%s\n", checkpointTopicID)
}

inlineMetadata := hcs27.CheckpointMetadata{
Type: "ans-checkpoint-v1",
Stream: hcs27.StreamID{
Registry: "ans",
LogID: "default",
LogID: "go-example-inline",
},
Log: &hcs27.LogProfile{
Algorithm: "sha-256",
Leaf: "sha256(jcs(event))",
Merkle: "rfc6962",
Merkle: "rfc9162",
},
Root: hcs27.RootCommitment{
TreeSize: 1,
RootHashB64: root,
TreeSize: "1",
RootHashB64u: hashB64URL("standards-sdk-go-example-inline-root"),
},
BatchRange: hcs27.BatchRange{
Start: 1,
End: 1,
}

inlineResult, err := client.PublishCheckpoint(
ctx,
checkpointTopicID,
inlineMetadata,
"standards-sdk-go inline checkpoint",
"",
)
if err != nil {
panic(err)
}
fmt.Printf(
"published inline checkpoint sequence=%d tx=%s\n",
inlineResult.SequenceNumber,
inlineResult.TransactionID,
)

overflowMetadata := hcs27.CheckpointMetadata{
Type: "ans-checkpoint-v1",
Stream: hcs27.StreamID{
Registry: "ans",
LogID: "go-example-overflow",
},
Log: &hcs27.LogProfile{
Algorithm: "sha-256",
Leaf: strings.Repeat("sha256(jcs(event))-", 90),
Merkle: "rfc9162",
},
Root: hcs27.RootCommitment{
TreeSize: "2",
RootHashB64u: hashB64URL("standards-sdk-go-example-overflow-root"),
},
}

result, err := client.PublishCheckpoint(
context.Background(),
overflowResult, err := client.PublishCheckpoint(
ctx,
checkpointTopicID,
metadata,
"standards-sdk-go checkpoint",
overflowMetadata,
"standards-sdk-go overflow checkpoint",
"",
)
if err != nil {
panic(err)
}
fmt.Printf(
"published overflow checkpoint sequence=%d tx=%s\n",
overflowResult.SequenceNumber,
overflowResult.TransactionID,
)

fmt.Printf("published checkpoint sequence=%d tx=%s\n", result.SequenceNumber, result.TransactionID)
records, err := waitForCheckpoints(ctx, client, checkpointTopicID, 2)
if err != nil {
panic(err)
}

var overflowReference string
for _, record := range records {
var reference string
if json.Unmarshal(record.Message.Metadata, &reference) == nil && strings.HasPrefix(reference, "hcs://1/") {
overflowReference = reference
break
}
}
if overflowReference == "" {
panic("failed to find HCS-1 metadata reference in fetched checkpoints")
}

fmt.Printf("resolved overflow metadata reference=%s\n", overflowReference)
fmt.Printf("validated checkpoint chain=%t\n", hcs27.ValidateCheckpointChain(records) == nil)
}

func hashB64URL(input string) string {
sum := sha256.Sum256([]byte(input))
return base64.RawURLEncoding.EncodeToString(sum[:])
}

func waitForCheckpoints(
ctx context.Context,
client *hcs27.Client,
topicID string,
minRecords int,
) ([]hcs27.CheckpointRecord, error) {
for attempt := 0; attempt < 20; attempt++ {
records, err := client.GetCheckpoints(ctx, topicID, nil)
if err == nil && len(records) >= minRecords {
return records, nil
}
time.Sleep(3 * time.Second)
}
return nil, fmt.Errorf("timed out waiting for %d checkpoint records", minRecords)
}
4 changes: 2 additions & 2 deletions examples/inscriber-auth-client/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Inscriber: Authenticate and Initialize

This example authenticates against [Kiloscribe](https://hol.org/docs/standards/hcs-1) and initializes an inscriber client.
This example authenticates against the inscription service and initializes an inscriber client.

## What it does

1. Authenticates a Hedera account with the Kiloscribe inscription service.
1. Authenticates a Hedera account with the inscription service.
2. Initializes an inscriber client for writing data to HCS topics.

## Run
Expand Down
13 changes: 8 additions & 5 deletions pkg/hcs11/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Client struct {
operatorPrivateKey string
network string
keyType string
kiloScribeBaseURL string
inscriberBaseURL string
inscriberAuthURL string
inscriberAPIURL string
}
Expand Down Expand Up @@ -98,9 +98,12 @@ func NewClient(config ClientConfig) (*Client, error) {
keyType = "ed25519"
}

kiloScribeBaseURL := strings.TrimSpace(config.KiloScribeBaseURL)
if kiloScribeBaseURL == "" {
kiloScribeBaseURL = "https://kiloscribe.com"
inscriberBaseURL := strings.TrimSpace(config.InscriberBaseURL)
if inscriberBaseURL == "" {
inscriberBaseURL = strings.TrimSpace(config.KiloScribeBaseURL)
}
if inscriberBaseURL == "" {
inscriberBaseURL = "https://kiloscribe.com"
}

return &Client{
Expand All @@ -110,7 +113,7 @@ func NewClient(config ClientConfig) (*Client, error) {
operatorPrivateKey: operatorPrivateKey,
network: network,
keyType: keyType,
kiloScribeBaseURL: strings.TrimRight(kiloScribeBaseURL, "/"),
inscriberBaseURL: strings.TrimRight(inscriberBaseURL, "/"),
inscriberAuthURL: strings.TrimSpace(config.InscriberAuthURL),
inscriberAPIURL: strings.TrimSpace(config.InscriberAPIURL),
}, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/hcs11/client_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (c *Client) fetchFromHCSReference(
profileTopicID := strings.TrimSpace(parts[3])
cdnURL := fmt.Sprintf(
"%s/api/inscription-cdn/%s?network=%s",
c.kiloScribeBaseURL,
c.inscriberBaseURL,
profileTopicID,
strings.TrimSpace(network),
)
Expand Down
6 changes: 3 additions & 3 deletions pkg/hcs11/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ func TestClientFetchProfileByAccountID(t *testing.T) {
defer server.Close()

client, err := NewClient(ClientConfig{
Network: "testnet",
MirrorBaseURL: server.URL,
KiloScribeBaseURL: server.URL,
Network: "testnet",
MirrorBaseURL: server.URL,
InscriberBaseURL: server.URL,
})
if err != nil {
t.Fatalf("failed to initialize client: %v", err)
Expand Down
1 change: 1 addition & 0 deletions pkg/hcs11/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ type ClientConfig struct {
Auth Auth
KeyType string
MirrorBaseURL string
InscriberBaseURL string
KiloScribeBaseURL string
InscriberAuthURL string
InscriberAPIURL string
Expand Down
2 changes: 1 addition & 1 deletion pkg/hcs2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ func (c *Client) submitMessage(
}, nil
}

// inscribeOverflow inscribes the payload via the Kiloscribe inscriber API and
// inscribeOverflow inscribes the payload via the inscriber API and
// returns an HRL reference (e.g. "hcs://1/0.0.12345").
func (c *Client) inscribeOverflow(ctx context.Context, payload []byte) (string, error) {
network := inscriber.NetworkTestnet
Expand Down
26 changes: 20 additions & 6 deletions pkg/hcs27/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ func (c *Client) GetCheckpoints(
// ValidateCheckpointChain validates the provided input value.
func ValidateCheckpointChain(records []CheckpointRecord) error {
type previousRecord struct {
TreeSize uint64
RootHashB64 string
TreeSize uint64
RootHashB64u string
}

streams := map[string]previousRecord{}
Expand All @@ -409,9 +409,16 @@ func ValidateCheckpointChain(records []CheckpointRecord) error {
)

previous, exists := streams[streamID]
currentTreeSize, err := parseCanonicalUint64(
"metadata.root.treeSize",
record.EffectiveMetadata.Root.TreeSize,
)
if err != nil {
return err
}
current := previousRecord{
TreeSize: record.EffectiveMetadata.Root.TreeSize,
RootHashB64: record.EffectiveMetadata.Root.RootHashB64,
TreeSize: currentTreeSize,
RootHashB64u: record.EffectiveMetadata.Root.RootHashB64u,
}

if exists {
Expand All @@ -421,10 +428,17 @@ func ValidateCheckpointChain(records []CheckpointRecord) error {
if record.EffectiveMetadata.Previous == nil {
return fmt.Errorf("missing prev linkage for stream %s", streamID)
}
if record.EffectiveMetadata.Previous.TreeSize != previous.TreeSize {
previousTreeSize, err := parseCanonicalUint64(
"metadata.prev.treeSize",
record.EffectiveMetadata.Previous.TreeSize,
)
if err != nil {
return err
}
if previousTreeSize != previous.TreeSize {
return fmt.Errorf("prev.treeSize mismatch for stream %s", streamID)
}
if record.EffectiveMetadata.Previous.RootHashB64 != previous.RootHashB64 {
if record.EffectiveMetadata.Previous.RootHashB64u != previous.RootHashB64u {
return fmt.Errorf("prev.rootHashB64u mismatch for stream %s", streamID)
}
}
Expand Down
10 changes: 3 additions & 7 deletions pkg/hcs27/client_overflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,11 @@ func testCheckpointMetadata(leafProfile string) CheckpointMetadata {
Log: &LogProfile{
Algorithm: "sha-256",
Leaf: leaf,
Merkle: "rfc6962",
Merkle: "rfc9162",
},
Root: RootCommitment{
TreeSize: 1,
RootHashB64: base64.RawURLEncoding.EncodeToString(make([]byte, 32)),
},
BatchRange: BatchRange{
Start: 1,
End: 1,
TreeSize: canonicalUint64(1),
RootHashB64u: base64.RawURLEncoding.EncodeToString(make([]byte, 32)),
},
}
}
Expand Down
Loading
Loading