Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba1196a962 | ||
|
|
a0aba28a2a | ||
|
|
4cb1ec89c0 | ||
|
|
1b0ecd993e | ||
|
|
57291497e6 |
@@ -16,8 +16,8 @@ func main () {
|
|||||||
hash := sha256.Sum256([]byte{1,2,3,4,5,6})
|
hash := sha256.Sum256([]byte{1,2,3,4,5,6})
|
||||||
seq, _ := opentimestamps.Stamp(context.Background(), "https://alice.btc.calendar.opentimestamps.org/", hash)
|
seq, _ := opentimestamps.Stamp(context.Background(), "https://alice.btc.calendar.opentimestamps.org/", hash)
|
||||||
|
|
||||||
// you can just call .Upgrade() to get the upgraded sequence (or an error if not yet available)
|
// you can just call UpgradeSequence() to get the upgraded sequence (or an error if not yet available)
|
||||||
upgradedSeq, err := seq.Upgrade(context.Background(), hash[:])
|
upgradedSeq, err := opentimestamps.UpgradeSequence(context.Background(), seq, hash[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("wait more")
|
fmt.Println("wait more")
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ func main () {
|
|||||||
fmt.Println(hex.EncodeToString(seq[2].Argument)) // "c40fe258f9b828a0b5a7"
|
fmt.Println(hex.EncodeToString(seq[2].Argument)) // "c40fe258f9b828a0b5a7"
|
||||||
|
|
||||||
// all these instructions can be executed in order, starting from the initial hash
|
// 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 on .Upgrade()
|
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
|
finalResult := upgradedSeq.Compute(hash) // this should be the merkle root of a bitcoin block if this sequence is upgraded
|
||||||
|
|
||||||
// each sequence always ends in an "attestation"
|
// each sequence always ends in an "attestation"
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEsploraClient(url string) Bitcoin {
|
func NewEsploraClient(url string) Bitcoin {
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -6,7 +6,6 @@ require (
|
|||||||
github.com/btcsuite/btcd v0.23.4
|
github.com/btcsuite/btcd v0.23.4
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
|
||||||
golang.org/x/crypto v0.13.0
|
golang.org/x/crypto v0.13.0
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -66,8 +66,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
|
||||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package opentimestamps
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompareInstructions returns negative if a<b, 0 if a=b and positive if a>b.
|
// CompareInstructions returns negative if a<b, 0 if a=b and positive if a>b.
|
||||||
|
|||||||
43
ots.go
43
ots.go
@@ -1,12 +1,14 @@
|
|||||||
package opentimestamps
|
package opentimestamps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"github.com/btcsuite/btcd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -71,15 +73,40 @@ type Instruction struct {
|
|||||||
|
|
||||||
type Sequence []Instruction
|
type Sequence []Instruction
|
||||||
|
|
||||||
func (seq Sequence) Compute(initial []byte) []byte {
|
func (seq Sequence) GetAttestation() Attestation {
|
||||||
|
if len(seq) == 0 {
|
||||||
|
return Attestation{}
|
||||||
|
}
|
||||||
|
att := seq[len(seq)-1]
|
||||||
|
if att.Attestation == nil {
|
||||||
|
return Attestation{}
|
||||||
|
}
|
||||||
|
return *att.Attestation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute runs a sequence of operations on top of an initial digest and returns the result, which is often a
|
||||||
|
// Bitcoin block merkle root. It also tries to identify the point in the sequence in which an actual Bitcoin
|
||||||
|
// transaction is formed and parse that.
|
||||||
|
func (seq Sequence) Compute(initial []byte) (merkleRoot []byte, bitcoinTx *wire.MsgTx) {
|
||||||
current := initial
|
current := initial
|
||||||
for _, inst := range seq {
|
for i, inst := range seq {
|
||||||
if inst.Operation == nil {
|
if inst.Operation == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the first time we do a double-sha256 that is likely a bitcoin transaction
|
||||||
|
if bitcoinTx == nil &&
|
||||||
|
inst.Operation.Name == "sha256" &&
|
||||||
|
len(seq) > i+1 && seq[i+1].Operation != nil &&
|
||||||
|
seq[i+1].Operation.Name == "sha256" {
|
||||||
|
tx := &wire.MsgTx{}
|
||||||
|
tx.Deserialize(bytes.NewReader(current))
|
||||||
|
bitcoinTx = tx
|
||||||
|
}
|
||||||
|
|
||||||
current = inst.Operation.Apply(current, inst.Argument)
|
current = inst.Operation.Apply(current, inst.Argument)
|
||||||
}
|
}
|
||||||
return current
|
return current, bitcoinTx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts File) GetPendingSequences() []Sequence {
|
func (ts File) GetPendingSequences() []Sequence {
|
||||||
@@ -122,20 +149,26 @@ func (ts File) GetBitcoinAttestedSequences() []Sequence {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts File) Human() string {
|
func (ts File) Human(withPartials bool) string {
|
||||||
strs := make([]string, 0, 100)
|
strs := make([]string, 0, 100)
|
||||||
strs = append(strs, fmt.Sprintf("file digest: %x", ts.Digest))
|
strs = append(strs, fmt.Sprintf("file digest: %x", ts.Digest))
|
||||||
strs = append(strs, fmt.Sprintf("hashed with: sha256"))
|
strs = append(strs, fmt.Sprintf("hashed with: sha256"))
|
||||||
strs = append(strs, "instruction sequences:")
|
strs = append(strs, "instruction sequences:")
|
||||||
for _, seq := range ts.Sequences {
|
for _, seq := range ts.Sequences {
|
||||||
|
curr := ts.Digest
|
||||||
strs = append(strs, "~>")
|
strs = append(strs, "~>")
|
||||||
|
strs = append(strs, " start "+hex.EncodeToString(curr))
|
||||||
for _, inst := range seq {
|
for _, inst := range seq {
|
||||||
line := " "
|
line := " "
|
||||||
if inst.Operation != nil {
|
if inst.Operation != nil {
|
||||||
line += inst.Operation.Name
|
line += inst.Operation.Name
|
||||||
|
curr = inst.Operation.Apply(curr, inst.Argument)
|
||||||
if inst.Operation.Binary {
|
if inst.Operation.Binary {
|
||||||
line += " " + hex.EncodeToString(inst.Argument)
|
line += " " + hex.EncodeToString(inst.Argument)
|
||||||
}
|
}
|
||||||
|
if withPartials {
|
||||||
|
line += " = " + hex.EncodeToString(curr)
|
||||||
|
}
|
||||||
} else if inst.Attestation != nil {
|
} else if inst.Attestation != nil {
|
||||||
line += inst.Attestation.Human()
|
line += inst.Attestation.Human()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseCalendarServerResponse(buf Buffer) (Sequence, error) {
|
func parseCalendarServerResponse(buf Buffer) (Sequence, error) {
|
||||||
|
|||||||
12
stamp.go
12
stamp.go
@@ -41,9 +41,9 @@ func ReadFromFile(data []byte) (*File, error) {
|
|||||||
return parseOTSFile(newBuffer(data))
|
return parseOTSFile(newBuffer(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (seq Sequence) Upgrade(ctx context.Context, initial []byte) (Sequence, error) {
|
func UpgradeSequence(ctx context.Context, seq Sequence, initial []byte) (Sequence, error) {
|
||||||
result := seq.Compute(initial)
|
result, _ := seq.Compute(initial)
|
||||||
attestation := seq[len(seq)-1]
|
attestation := seq.GetAttestation()
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/timestamp/%x", normalizeUrl(attestation.CalendarServerURL), result)
|
url := fmt.Sprintf("%s/timestamp/%x", normalizeUrl(attestation.CalendarServerURL), result)
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
@@ -69,10 +69,14 @@ func (seq Sequence) Upgrade(ctx context.Context, initial []byte) (Sequence, erro
|
|||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
newSeq, err := parseCalendarServerResponse(newBuffer(body))
|
tail, err := parseCalendarServerResponse(newBuffer(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse response from '%s': %w", attestation.CalendarServerURL, err)
|
return nil, fmt.Errorf("failed to parse response from '%s': %w", attestation.CalendarServerURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newSeq := make(Sequence, len(seq)+len(tail)-1)
|
||||||
|
copy(newSeq, seq[0:len(seq)-1])
|
||||||
|
copy(newSeq[len(seq)-1:], tail)
|
||||||
|
|
||||||
return newSeq, nil
|
return newSeq, nil
|
||||||
}
|
}
|
||||||
|
|||||||
22
verifier.go
22
verifier.go
@@ -1,11 +1,11 @@
|
|||||||
package opentimestamps
|
package opentimestamps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bitcoin interface {
|
type Bitcoin interface {
|
||||||
@@ -13,33 +13,35 @@ type Bitcoin interface {
|
|||||||
GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error)
|
GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (seq Sequence) Verify(bitcoin Bitcoin, initial []byte) error {
|
// Verify validates sequence of operations that starts with digest and ends on a Bitcoin attestation against
|
||||||
|
// an actual Bitcoin block, as given by the provided Bitcoin interface.
|
||||||
|
func (seq Sequence) Verify(bitcoin Bitcoin, digest []byte) (*wire.MsgTx, error) {
|
||||||
if len(seq) == 0 {
|
if len(seq) == 0 {
|
||||||
return fmt.Errorf("empty sequence")
|
return nil, fmt.Errorf("empty sequence")
|
||||||
}
|
}
|
||||||
|
|
||||||
att := seq[len(seq)-1]
|
att := seq[len(seq)-1]
|
||||||
if att.Attestation == nil || att.BitcoinBlockHeight == 0 {
|
if att.Attestation == nil || att.BitcoinBlockHeight == 0 {
|
||||||
return fmt.Errorf("sequence doesn't include a bitcoin attestation")
|
return nil, fmt.Errorf("sequence doesn't include a bitcoin attestation")
|
||||||
}
|
}
|
||||||
|
|
||||||
blockHash, err := bitcoin.GetBlockHash(int64(att.BitcoinBlockHeight))
|
blockHash, err := bitcoin.GetBlockHash(int64(att.BitcoinBlockHeight))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get block %d hash: %w", att.BitcoinBlockHeight, err)
|
return nil, fmt.Errorf("failed to get block %d hash: %w", att.BitcoinBlockHeight, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blockHeader, err := bitcoin.GetBlockHeader(blockHash)
|
blockHeader, err := bitcoin.GetBlockHeader(blockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get block %s header: %w", blockHash, err)
|
return nil, fmt.Errorf("failed to get block %s header: %w", blockHash, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
merkleRoot := blockHeader.MerkleRoot[:]
|
merkleRoot := blockHeader.MerkleRoot[:]
|
||||||
|
result, tx := seq.Compute(digest)
|
||||||
|
|
||||||
result := seq.Compute(initial)
|
if !bytes.Equal(result, merkleRoot) {
|
||||||
if !slices.Equal(result, merkleRoot) {
|
return nil, fmt.Errorf("sequence result '%x' doesn't match the bitcoin merkle root for block %d: %x",
|
||||||
return fmt.Errorf("sequence result '%x' doesn't match the bitcoin merkle root for block %d: %x",
|
|
||||||
result, att.BitcoinBlockHeight, merkleRoot)
|
result, att.BitcoinBlockHeight, merkleRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return tx, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user