From a0aba28a2a900f6d0fa1ab499db337f352d5ac1e Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 27 Oct 2024 09:37:17 -0300 Subject: [PATCH] return bitcoin transaction from sequence computation. --- ots.go | 24 ++++++++++++++++++++---- stamp.go | 2 +- verifier.go | 23 ++++++++++++----------- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/ots.go b/ots.go index 49f9f6c..ab2ac14 100644 --- a/ots.go +++ b/ots.go @@ -1,12 +1,14 @@ package opentimestamps import ( + "bytes" "crypto/sha256" "encoding/hex" "fmt" + "slices" "strings" - "slices" + "github.com/btcsuite/btcd/wire" ) /* @@ -82,15 +84,29 @@ func (seq Sequence) GetAttestation() 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 - for _, inst := range seq { + for i, inst := range seq { if inst.Operation == nil { 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) } - return current + return current, bitcoinTx } func (ts File) GetPendingSequences() []Sequence { diff --git a/stamp.go b/stamp.go index f3c471c..5b690c0 100644 --- a/stamp.go +++ b/stamp.go @@ -42,7 +42,7 @@ func ReadFromFile(data []byte) (*File, error) { } func UpgradeSequence(ctx context.Context, seq Sequence, initial []byte) (Sequence, error) { - result := seq.Compute(initial) + result, _ := seq.Compute(initial) attestation := seq.GetAttestation() url := fmt.Sprintf("%s/timestamp/%x", normalizeUrl(attestation.CalendarServerURL), result) diff --git a/verifier.go b/verifier.go index c179299..4449a76 100644 --- a/verifier.go +++ b/verifier.go @@ -1,10 +1,9 @@ package opentimestamps import ( + "bytes" "fmt" - "slices" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) @@ -14,33 +13,35 @@ type Bitcoin interface { 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 { - return fmt.Errorf("empty sequence") + return nil, fmt.Errorf("empty sequence") } att := seq[len(seq)-1] 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)) 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) 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[:] + result, tx := seq.Compute(digest) - result := seq.Compute(initial) - if !slices.Equal(result, merkleRoot) { - return fmt.Errorf("sequence result '%x' doesn't match the bitcoin merkle root for block %d: %x", + if !bytes.Equal(result, merkleRoot) { + return nil, fmt.Errorf("sequence result '%x' doesn't match the bitcoin merkle root for block %d: %x", result, att.BitcoinBlockHeight, merkleRoot) } - return nil + return tx, nil }