3 Commits

Author SHA1 Message Date
fiatjaf
ba1196a962 File.Human() option to print partial results. 2024-10-27 09:37:34 -03:00
fiatjaf
a0aba28a2a return bitcoin transaction from sequence computation. 2024-10-27 09:37:17 -03:00
fiatjaf
4cb1ec89c0 use stdlib "slices". 2024-02-08 16:36:18 -03:00
8 changed files with 44 additions and 22 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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.

32
ots.go
View File

@@ -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"
) )
/* /*
@@ -82,15 +84,29 @@ func (seq Sequence) GetAttestation() Attestation {
return *att.Attestation return *att.Attestation
} }
func (seq Sequence) Compute(initial []byte) []byte { // 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 {
@@ -133,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 {

View File

@@ -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) {

View File

@@ -42,7 +42,7 @@ func ReadFromFile(data []byte) (*File, error) {
} }
func UpgradeSequence(ctx context.Context, seq Sequence, 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.GetAttestation() attestation := seq.GetAttestation()
url := fmt.Sprintf("%s/timestamp/%x", normalizeUrl(attestation.CalendarServerURL), result) url := fmt.Sprintf("%s/timestamp/%x", normalizeUrl(attestation.CalendarServerURL), result)

View File

@@ -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
} }