package main import ( "encoding/hex" "fmt" "log/slog" "os" "strings" "git.intruders.space/public/opentimestamps" "git.intruders.space/public/opentimestamps/ots" "github.com/spf13/cobra" ) // infoCmd represents the info command var infoCmd = &cobra.Command{ Use: "info [flags] ", Short: "Display information about a timestamp", Long: `Display detailed information about a timestamp file in human-readable format. Shows the file digest and the sequence of operations and attestations.`, Args: cobra.ExactArgs(1), Run: runInfoCmd, } func init() { // No specific flags needed for info command } func runInfoCmd(cmd *cobra.Command, args []string) { otsPath := args[0] // Read and parse the OTS file otsData, err := os.ReadFile(otsPath) if err != nil { slog.Error("Failed to read OTS file", "file", otsPath, "error", err) os.Exit(1) } timestampFile, err := opentimestamps.ReadFromFile(otsData) if err != nil { slog.Error("Failed to parse OTS file", "file", otsPath, "error", err) os.Exit(1) } // Print the timestamp information in the requested format fmt.Printf("File sha256 hash: %s\n", hex.EncodeToString(timestampFile.Digest)) fmt.Println("Timestamp:") // Format and print each sequence for i, seq := range timestampFile.Sequences { if i > 0 { // Add a separator between sequences if needed fmt.Println() } printSequenceInfo(seq, 0, timestampFile.Digest) } } func printSequenceInfo(seq ots.Sequence, depth int, initialDigest []byte) { prefix := strings.Repeat(" ", depth) // Track the current result as we apply operations current := initialDigest // For the first level (depth 0), don't add the arrow arrowPrefix := "" if depth > 0 { arrowPrefix = " -> " } for _, inst := range seq { // Skip attestation for now, we'll handle it at the end if inst.Attestation != nil { continue } // Print operation if inst.Operation != nil { line := fmt.Sprintf("%s%s%s", prefix, arrowPrefix, inst.Operation.Name) if inst.Operation.Binary { line += fmt.Sprintf(" %s", hex.EncodeToString(inst.Argument)) } fmt.Println(line) // Update current result current = inst.Operation.Apply(current, inst.Argument) // Only show arrow prefix for the first line arrowPrefix = "" } } // Check if there's an attestation at the end if len(seq) > 0 && seq[len(seq)-1].Attestation != nil { att := seq[len(seq)-1].Attestation var attLine string if att.BitcoinBlockHeight > 0 { attLine = fmt.Sprintf("verify BitcoinAttestation(block %d)", att.BitcoinBlockHeight) } else if att.CalendarServerURL != "" { attLine = fmt.Sprintf("verify PendingAttestation('%s')", att.CalendarServerURL) } if attLine != "" { fmt.Printf("%s%s\n", prefix, attLine) } } }