implement verifier.

This commit is contained in:
fiatjaf
2023-09-28 14:57:03 -03:00
parent 3e702d758e
commit 7084284622
4 changed files with 61 additions and 17 deletions

View File

@@ -11,6 +11,7 @@ import (
"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 {
@@ -32,16 +33,23 @@ func (e esplora) GetBlockHash(height int64) (*chainhash.Hash, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, err := hex.Decode(hexb, hexb); err != nil || len(hexb) != chainhash.HashSize {
hash, err := hex.DecodeString(string(hexb))
if err != nil {
return nil, err return nil, err
} }
if len(hash) != chainhash.HashSize {
return nil, fmt.Errorf("got block hash (%x) of invalid size (expected %d)", hash, chainhash.HashSize)
}
slices.Reverse(hash)
var chash chainhash.Hash var chash chainhash.Hash
copy(chash[:], hexb) copy(chash[:], hash)
return &chash, nil return &chash, nil
} }
func (e esplora) GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error) { func (e esplora) GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error) {
resp, err := http.Get(fmt.Sprintf("%s/block/%x/header", e.baseurl, hash)) resp, err := http.Get(fmt.Sprintf("%s/block/%s/header", e.baseurl, hash.String()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -50,12 +58,14 @@ func (e esplora) GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, err := hex.Decode(hexb, hexb); err != nil {
headerHash, err := hex.DecodeString(string(hexb))
if err != nil {
return nil, err return nil, err
} }
header := &wire.BlockHeader{} header := &wire.BlockHeader{}
if err := header.BtcDecode(bytes.NewBuffer(hexb), 0, 0); err != nil { if err := header.BtcDecode(bytes.NewBuffer(headerHash), 0, 0); err != nil {
return nil, err return nil, err
} }

22
ots.go
View File

@@ -100,6 +100,17 @@ func (a Instruction) Equal(b Instruction) bool {
type Sequence []Instruction type Sequence []Instruction
func (seq Sequence) Compute(initial []byte) []byte {
current := initial
for _, inst := range seq {
if inst.Operation == nil {
break
}
current = inst.Operation.Apply(current, inst.Argument)
}
return current
}
func (ts Timestamp) GetPendingSequences() []Sequence { func (ts Timestamp) GetPendingSequences() []Sequence {
bitcoin := ts.GetBitcoinAttestedSequences() bitcoin := ts.GetBitcoinAttestedSequences()
@@ -238,14 +249,3 @@ func (att Attestation) Human() string {
return "unknown/broken" return "unknown/broken"
} }
} }
func ComputeSequence(initial []byte, seq []Instruction) []byte {
current := initial
for _, inst := range seq {
if inst.Operation == nil {
break
}
current = inst.Operation.Apply(current, inst.Argument)
}
return current
}

View File

@@ -45,7 +45,7 @@ func ReadFromFile(data []byte) (*Timestamp, error) {
} }
func (seq Sequence) Upgrade(ctx context.Context, initial []byte) (Sequence, error) { func (seq Sequence) Upgrade(ctx context.Context, initial []byte) (Sequence, error) {
result := ComputeSequence(initial, seq) result := seq.Compute(initial)
attestation := seq[len(seq)-1] attestation := seq[len(seq)-1]
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,45 @@
package opentimestamps package opentimestamps
import ( import (
"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 {
GetBlockHash(height int64) (*chainhash.Hash, error) GetBlockHash(height int64) (*chainhash.Hash, error)
GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error) GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error)
} }
func (seq Sequence) Verify(bitcoin Bitcoin, initial []byte) error {
if len(seq) == 0 {
return 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")
}
blockHash, err := bitcoin.GetBlockHash(int64(att.BitcoinBlockHeight))
if err != nil {
return 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)
}
merkleRoot := blockHeader.MerkleRoot[:]
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",
result, att.BitcoinBlockHeight, merkleRoot)
}
return nil
}