This commit is contained in:
2025-04-11 13:31:02 +02:00
parent ba1196a962
commit 03f30f1968
48 changed files with 1685 additions and 474 deletions

203
README.md
View File

@@ -1,80 +1,175 @@
# opentimestamps
Interact with calendar servers, create and verify OTS attestations.
A fork of [github.com/nbd-wtf/opentimestamps](https://github.com/nbd-wtf/opentimestamps) that lets you interact with calendar servers, create and verify OTS attestations.
# How to use
Full documentation at https://pkg.go.dev/github.com/nbd-wtf/opentimestamps. See some commented pseudocode below (you probably should not try to run it as it is).
Here's an example of how to use the library to create a timestamp, attempt to upgrade it periodically, and display information about it:
```go
package main
import "github.com/nbd-wtf/opentimestamps"
import (
"context"
"crypto/sha256"
"fmt"
"os"
"time"
func main () {
// create a timestamp at a specific calendar server
hash := sha256.Sum256([]byte{1,2,3,4,5,6})
seq, _ := opentimestamps.Stamp(context.Background(), "https://alice.btc.calendar.opentimestamps.org/", hash)
"git.intruders.space/public/opentimestamps"
"git.intruders.space/public/opentimestamps/ots"
)
// you can just call UpgradeSequence() to get the upgraded sequence (or an error if not yet available)
upgradedSeq, err := opentimestamps.UpgradeSequence(context.Background(), seq, hash[:])
if err != nil {
fmt.Println("wait more")
}
func main() {
// Read a file to timestamp
fileData, err := os.ReadFile("document.txt")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
// a File is a struct that represents the content of an .ots file, which contains the initial digest and any number of sequences
file := File{
Digest: hash,
Sequences: []Sequence{seq},
}
// Calculate the digest
digest := sha256.Sum256(fileData)
fmt.Printf("File digest: %x\n", digest)
// it can be written to disk
os.WriteFile("file.ots", file.SerializeToFile(), 0644)
// Define calendar servers
calendars := []string{
"https://alice.btc.calendar.opentimestamps.org",
"https://bob.btc.calendar.opentimestamps.org",
"https://finney.calendar.eternitywall.com",
}
// or printed in human-readable format
fmt.Println(file.Human())
// Create a timestamp using each calendar
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// sequences are always composed of a bunch of operation instructions -- these can be, for example, "append", "prepend", "sha256"
fmt.Println(seq[0].Operation.Name) // "append"
fmt.Println(seq[1].Operation.Name) // "sha256"
fmt.Println(seq[2].Operation.Name) // "prepend"
var sequences []ots.Sequence
for _, calendarURL := range calendars {
fmt.Printf("Submitting to %s...\n", calendarURL)
seq, err := opentimestamps.Stamp(ctx, calendarURL, digest)
if err != nil {
fmt.Printf("Failed to submit to %s: %v\n", calendarURL, err)
continue
}
fmt.Printf("Submission to %s successful\n", calendarURL)
sequences = append(sequences, seq)
}
// "prepend" and "append" are "binary", i.e. they take an argument
fmt.Println(hex.EncodeToString(seq[2].Argument)) // "c40fe258f9b828a0b5a7"
if len(sequences) == 0 {
fmt.Println("Failed to create any timestamps")
return
}
// all these instructions can be executed in order, starting from the initial hash
result := seq.Compute(hash) // this is the value we send to the calendar server in order to get the upgraded sequence
finalResult := upgradedSeq.Compute(hash) // this should be the merkle root of a bitcoin block if this sequence is upgraded
// Create the timestamp file
file := &ots.File{
Digest: digest[:],
Sequences: sequences,
}
// each sequence always ends in an "attestation"
// it can be either a pending attestation, i.e. a reference to a calendar server from which we will upgrade this sequence later
fmt.Println(seq[len(seq)-1].Attestation.CalendarServerURL) // "https://alice.btc.calendar.opentimestamps.org/"
// or it can be a reference to a bitcoin block, the merkle root of which we will check against the result of Compute() for verifying
fmt.Println(upgradedSeq[len(upgradedSeq)-1].Attestation.BitcoinBlockHeight) // 810041
// Save the OTS file
otsData := file.SerializeToFile()
if err := os.WriteFile("document.txt.ots", otsData, 0644); err != nil {
fmt.Println("Failed to save OTS file:", err)
return
}
fmt.Println("Timestamp file created successfully")
// speaking of verifying, this is how we do it:
// first we need some source of bitcoin blocks,
var bitcoin opentimestamps.Bitcoin
if useLocallyRunningBitcoindNode {
// it can be either a locally running bitcoind node
bitcoin, _ = opentimestamps.NewBitcoindInterface(rpcclient.ConnConfig{
User: "nakamoto",
Pass: "mumbojumbo",
HTTPPostMode: true,
})
} else {
// or an esplora HTTP endpoint
bitcoin = opentimestamps.NewEsploraClient("https://blockstream.info/api")
}
// Display initial timestamp info
fmt.Println("\nInitial timestamp info:")
fmt.Println(file.Human(false))
// then we pass that to a sequence
if err := upgradedSeq.Verify(bitcoin, hash); err == nil {
fmt.Println("it works!")
}
// Attempt to upgrade the timestamp every 20 minutes
fmt.Println("\nWill check for upgrades every 20 minutes...")
maxAttempts := 12 // Try for about 4 hours (12 * 20 minutes)
for attempt := 0; attempt < maxAttempts; attempt++ {
if attempt > 0 {
fmt.Printf("\nWaiting 20 minutes before next upgrade attempt (%d/%d)...\n", attempt+1, maxAttempts)
time.Sleep(20 * time.Minute)
}
upgraded := false
pendingSequences := file.GetPendingSequences()
if len(pendingSequences) == 0 {
fmt.Println("No pending sequences to upgrade")
break
}
fmt.Printf("Attempting to upgrade %d pending sequences...\n", len(pendingSequences))
upgradeCtx, upgradeCancel := context.WithTimeout(context.Background(), 30*time.Second)
for _, seq := range pendingSequences {
att := seq.GetAttestation()
fmt.Printf("Trying to upgrade sequence from %s...\n", att.CalendarServerURL)
upgradedSeq, err := opentimestamps.UpgradeSequence(upgradeCtx, seq, digest[:])
if err != nil {
fmt.Printf("Failed to upgrade sequence from %s: %v\n", att.CalendarServerURL, err)
continue
}
// Replace the sequence in the file
for i, origSeq := range file.Sequences {
origAtt := origSeq.GetAttestation()
if origAtt.CalendarServerURL == att.CalendarServerURL {
file.Sequences[i] = upgradedSeq
upgraded = true
newAtt := upgradedSeq.GetAttestation()
if newAtt.BitcoinBlockHeight > 0 {
fmt.Printf("Sequence upgraded! Confirmed in Bitcoin block %d\n", newAtt.BitcoinBlockHeight)
} else {
fmt.Println("Sequence updated but still pending")
}
break
}
}
}
upgradeCancel()
if upgraded {
// Save the upgraded file
otsData = file.SerializeToFile()
if err := os.WriteFile("document.txt.ots", otsData, 0644); err != nil {
fmt.Println("Failed to save upgraded OTS file:", err)
} else {
fmt.Println("Upgraded timestamp file saved")
}
}
// If all sequences are confirmed, we're done
if len(file.GetPendingSequences()) == 0 {
fmt.Println("All sequences are now confirmed in the Bitcoin blockchain!")
break
}
}
// Final report
fmt.Println("\nFinal timestamp status:")
confirmedSeqs := file.GetBitcoinAttestedSequences()
pendingSeqs := file.GetPendingSequences()
fmt.Printf("Confirmed attestations: %d\n", len(confirmedSeqs))
for _, seq := range confirmedSeqs {
att := seq.GetAttestation()
fmt.Printf("- Confirmed in Bitcoin block %d\n", att.BitcoinBlockHeight)
}
fmt.Printf("Pending attestations: %d\n", len(pendingSeqs))
for _, seq := range pendingSeqs {
att := seq.GetAttestation()
fmt.Printf("- Still pending at %s\n", att.CalendarServerURL)
}
fmt.Println("\nDetailed timestamp info:")
fmt.Println(file.Human(true))
}
```
You can also take a look at [`ots`](https://github.com/fiatjaf/ots), a simple CLI to OpenTimestamps which is basically a wrapper over this library.
This repository includes an `ots` command line tool which provides a convenient interface to the core library functionality. Run it without arguments to see usage information.
You can also take a look at the original [`ots`](https://github.com/fiatjaf/ots) CLI which is another implementation based on the same concepts.
# License