simplify codebase.

This commit is contained in:
fiatjaf
2023-09-19 21:18:40 -03:00
parent 5afda80c1a
commit cd227e6986
14 changed files with 111 additions and 527 deletions

View File

@@ -97,10 +97,7 @@ func (b *BitcoinAttestation) encode(ctx *serializationContext) error {
const hashMerkleRootSize = 32 const hashMerkleRootSize = 32
// func (b *BitcoinAttestation) VerifyAgainstBlockHash(digest, blockHash []byte) error {
func (b *BitcoinAttestation) VerifyAgainstBlockHash(
digest, blockHash []byte,
) error {
if len(digest) != hashMerkleRootSize { if len(digest) != hashMerkleRootSize {
return fmt.Errorf("invalid digest size %d", len(digest)) return fmt.Errorf("invalid digest size %d", len(digest))
} }

7
bitcoind.go Normal file
View File

@@ -0,0 +1,7 @@
package opentimestamps
import "github.com/btcsuite/btcd/rpcclient"
func NewBitcoindInterface(config rpcclient.ConnConfig) (Bitcoin, error) {
return rpcclient.New(&config, nil)
}

View File

@@ -1,83 +0,0 @@
package client
import (
"fmt"
"net/url"
"os"
"testing"
"time"
"github.com/btcsuite/btcd/rpcclient"
"github.com/fiatjaf/opentimestamps"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const envvarRPCURL = "GOTS_TEST_BITCOIN_RPC"
func newTestBTCConn() (*rpcclient.Client, error) {
val := os.Getenv(envvarRPCURL)
if val == "" {
return nil, fmt.Errorf("envvar %q unset", envvarRPCURL)
}
connData, err := url.Parse(val)
if err != nil {
return nil, fmt.Errorf(
"could not parse %q=%q: %v", envvarRPCURL, val, err,
)
}
host := connData.Host
if connData.User == nil {
return nil, fmt.Errorf("no Userinfo in parsed url")
}
username := connData.User.Username()
password, ok := connData.User.Password()
if !ok {
return nil, fmt.Errorf("no password given in RPC URL")
}
connCfg := &rpcclient.ConnConfig{
Host: host,
User: username,
Pass: password,
HTTPPostMode: true,
DisableTLS: true,
}
return rpcclient.New(connCfg, nil)
}
func TestVerifyHelloWorld(t *testing.T) {
if os.Getenv(envvarRPCURL) == "" {
t.Skipf("envvar %s unset, skipping", envvarRPCURL)
}
// Format RFC3339
expectedTime := "2015-05-28T15:41:18Z"
helloWorld, err := opentimestamps.NewDetachedTimestampFromPath(
"../examples/hello-world.txt.ots",
)
require.NoError(t, err)
ts := helloWorld.Timestamp
btcConn, err := newTestBTCConn()
require.NoError(t, err)
verifier := BitcoinAttestationVerifier{btcConn}
// using BitcoinVerifications()
results := verifier.BitcoinVerifications(ts)
assert.Equal(t, 1, len(results))
result0 := results[0]
require.NoError(t, result0.Error)
assert.Equal(
t, expectedTime, result0.AttestationTime.Format(time.RFC3339),
)
// using Verify()
verifiedTime, err := verifier.Verify(ts)
require.NoError(t, err)
require.NotNil(t, verifiedTime)
assert.Equal(t, expectedTime, verifiedTime.Format(time.RFC3339))
}

View File

@@ -1,26 +0,0 @@
package opentimestamps
import (
"crypto/sha256"
"io"
"os"
)
func CreateDetachedTimestampForFile(
path string, cal *RemoteCalendar,
) (*DetachedTimestamp, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
hasher := sha256.New()
if _, err := io.Copy(hasher, f); err != nil {
return nil, err
}
digest := hasher.Sum([]byte{})
ts, err := cal.Submit(digest)
if err != nil {
return nil, err
}
return NewDetachedTimestamp(*opSHA256, digest, ts)
}

View File

@@ -1,100 +0,0 @@
package opentimestamps
import (
"bytes"
"fmt"
"io"
"os"
)
var fileHeaderMagic = []byte(
"\x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94",
)
const (
minFileDigestLength = 20
maxFileDigestLength = 32
fileMajorVersion = 1
)
type DetachedTimestamp struct {
HashOp cryptOp
FileHash []byte
Timestamp *Timestamp
}
func (d *DetachedTimestamp) Dump() string {
w := &bytes.Buffer{}
fmt.Fprintf(
w, "File %s hash: %x\n", d.HashOp.name, d.Timestamp.Message,
)
fmt.Fprint(w, d.Timestamp.Dump())
return w.String()
}
func (d *DetachedTimestamp) encode(ctx *serializationContext) error {
if err := ctx.writeBytes(fileHeaderMagic); err != nil {
return err
}
if err := ctx.writeVarUint(fileMajorVersion); err != nil {
return err
}
if err := d.HashOp.encode(ctx); err != nil {
return err
}
if err := ctx.writeBytes(d.FileHash); err != nil {
return err
}
return d.Timestamp.encode(ctx)
}
func (d *DetachedTimestamp) WriteToStream(w io.Writer) error {
return d.encode(&serializationContext{w})
}
func NewDetachedTimestamp(
hashOp cryptOp, fileHash []byte, ts *Timestamp,
) (*DetachedTimestamp, error) {
if len(fileHash) != hashOp.digestLength {
return nil, fmt.Errorf(
"op %v expects %d byte digest, got %d",
hashOp, hashOp.digestLength, len(fileHash),
)
}
return &DetachedTimestamp{hashOp, fileHash, ts}, nil
}
func NewDetachedTimestampFromReader(r io.Reader) (*DetachedTimestamp, error) {
ctx := newDeserializationContext(r)
if err := ctx.assertMagic([]byte(fileHeaderMagic)); err != nil {
return nil, err
}
major, err := ctx.readVarUint()
if err != nil {
return nil, err
}
if major != uint64(fileMajorVersion) {
return nil, fmt.Errorf("unexpected major version %d", major)
}
fileHashOp, err := parseCryptOp(ctx)
if err != nil {
return nil, err
}
fileHash, err := ctx.readBytes(fileHashOp.digestLength)
if err != nil {
return nil, err
}
ts, err := newTimestampFromContext(ctx, fileHash)
if err != nil {
return nil, err
}
return &DetachedTimestamp{*fileHashOp, fileHash, ts}, nil
}
func NewDetachedTimestampFromPath(p string) (*DetachedTimestamp, error) {
f, err := os.Open(p)
if err != nil {
return nil, err
}
return NewDetachedTimestampFromReader(f)
}

View File

@@ -1,110 +0,0 @@
package opentimestamps
import (
"bytes"
"encoding/hex"
"io/ioutil"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func examplePaths() []string {
matches, err := filepath.Glob("./examples/*ots")
if err != nil {
panic(err)
}
return matches
}
func containsUnknownAttestation(ts *Timestamp) (res bool) {
ts.Walk(func(subTs *Timestamp) {
for _, att := range subTs.Attestations {
if _, ok := att.(unknownAttestation); ok {
res = true
}
}
})
return
}
func TestDecodeHelloWorld(t *testing.T) {
dts, err := NewDetachedTimestampFromPath(
"./examples/hello-world.txt.ots",
)
assert.NoError(t, err)
attCount := 0
checkAttestation := func(ts *Timestamp, att Attestation) {
assert.Equal(t, 0, attCount)
expectedAtt := newBitcoinAttestation()
expectedAtt.Height = 358391
assert.Equal(t, expectedAtt, att)
// If ts.Message is correct, opcode parsing and execution should
// have succeeded.
assert.Equal(t,
"007ee445d23ad061af4a36b809501fab1ac4f2d7e7a739817dd0cbb7ec661b8a",
hex.EncodeToString(ts.Message),
)
attCount += 1
}
dts.Timestamp.Walk(func(ts *Timestamp) {
for _, att := range ts.Attestations {
// this should be called exactly once
checkAttestation(ts, att)
}
})
assert.Equal(t, 1, attCount)
}
func TestDecodeEncodeAll(t *testing.T) {
for _, path := range examplePaths() {
t.Log(path)
dts, err := NewDetachedTimestampFromPath(path)
assert.NoError(t, err, path)
if containsUnknownAttestation(dts.Timestamp) {
t.Logf("skipping encode cycle: unknownAttestation")
continue
}
buf := &bytes.Buffer{}
err = dts.Timestamp.encode(&serializationContext{buf})
if !assert.NoError(t, err, path) {
continue
}
buf = bytes.NewBuffer(buf.Bytes())
ts1, err := NewTimestampFromReader(buf, dts.Timestamp.Message)
if !assert.NoError(t, err, path) {
continue
}
dts1, err := NewDetachedTimestamp(
dts.HashOp, dts.FileHash, ts1,
)
if !assert.NoError(t, err) {
continue
}
dts1Target := &bytes.Buffer{}
err = dts1.WriteToStream(dts1Target)
if !assert.NoError(t, err) {
continue
}
orgBytes, err := ioutil.ReadFile(path)
if !assert.NoError(t, err) {
continue
}
assert.Equal(t, orgBytes, dts1Target.Bytes())
t.Log("encode cycle success")
}
}

63
esplora.go Normal file
View File

@@ -0,0 +1,63 @@
package opentimestamps
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
func NewEsploraClient(url string) Bitcoin {
if strings.HasSuffix(url, "/") {
url = url[0 : len(url)-1]
}
return esplora{url}
}
type esplora struct{ baseurl string }
func (e esplora) GetBlockHash(height int64) (*chainhash.Hash, error) {
resp, err := http.Get(e.baseurl + "/block-height/" + strconv.FormatInt(height, 10))
if err != nil {
return nil, err
}
defer resp.Body.Close()
hexb, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if _, err := hex.Decode(hexb, hexb); err != nil || len(hexb) != chainhash.HashSize {
return nil, err
}
var chash chainhash.Hash
copy(chash[:], hexb)
return &chash, nil
}
func (e esplora) GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error) {
resp, err := http.Get(fmt.Sprintf("%s/block/%x/header", e.baseurl, hash))
if err != nil {
return nil, err
}
defer resp.Body.Close()
hexb, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if _, err := hex.Decode(hexb, hexb); err != nil {
return nil, err
}
header := &wire.BlockHeader{}
if err := header.BtcDecode(bytes.NewBuffer(hexb), 0, 0); err != nil {
return nil, err
}
return header, nil
}

4
go.mod
View File

@@ -4,15 +4,14 @@ go 1.21
require ( 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/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.13.0
) )
require ( require (
github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect
github.com/btcsuite/btcd/btcutil v1.1.0 // indirect github.com/btcsuite/btcd/btcutil v1.1.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
@@ -20,6 +19,7 @@ require (
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/sys v0.12.0 // indirect golang.org/x/sys v0.12.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@@ -1,12 +1,9 @@
package opentimestamps package opentimestamps
import ( import (
"crypto/sha1"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"golang.org/x/crypto/ripemd160"
) )
const maxResultLength = 4096 const maxResultLength = 4096
@@ -49,25 +46,6 @@ func msgHexlify(msg []byte) ([]byte, error) {
return []byte(hex.EncodeToString(msg)), nil return []byte(hex.EncodeToString(msg)), nil
} }
func msgSHA1(msg []byte) ([]byte, error) {
res := sha1.Sum(msg)
return res[:], nil
}
func msgRIPEMD160(msg []byte) ([]byte, error) {
h := ripemd160.New()
_, err := h.Write(msg)
if err != nil {
return nil, err
}
return h.Sum([]byte{}), nil
}
func msgSHA256(msg []byte) ([]byte, error) {
res := sha256.Sum256(msg)
return res[:], nil
}
type opCode interface { type opCode interface {
match(byte) bool match(byte) bool
decode(*deserializationContext) (opCode, error) decode(*deserializationContext) (opCode, error)
@@ -110,30 +88,6 @@ func (u *unaryOp) apply(message []byte) ([]byte, error) {
return u.msgOp(message) return u.msgOp(message)
} }
// Crypto operations
// These are hash ops that define a digest length
type cryptOp struct {
unaryOp
digestLength int
}
func newCryptOp(
tag byte, name string, msgOp unaryMsgOp, digestLength int,
) *cryptOp {
return &cryptOp{
unaryOp: *newUnaryOp(tag, name, msgOp),
digestLength: digestLength,
}
}
func (c *cryptOp) decode(ctx *deserializationContext) (opCode, error) {
u, err := c.unaryOp.decode(ctx)
if err != nil {
return nil, err
}
return &cryptOp{*u.(*unaryOp), c.digestLength}, nil
}
// Binary operations // Binary operations
// We decode an extra varbyte argument and use it in apply() // We decode an extra varbyte argument and use it in apply()
@@ -179,20 +133,20 @@ func (b *binaryOp) String() string {
return fmt.Sprintf("%s %x", b.name, b.argument) return fmt.Sprintf("%s %x", b.name, b.argument)
} }
func msgSHA256(msg []byte) ([]byte, error) {
res := sha256.Sum256(msg)
return res[:], nil
}
var ( var (
opAppend = newBinaryOp(0xf0, "APPEND", msgAppend) opAppend = newBinaryOp(0xf0, "APPEND", msgAppend)
opPrepend = newBinaryOp(0xf1, "PREPEND", msgPrepend) opPrepend = newBinaryOp(0xf1, "PREPEND", msgPrepend)
opReverse = newUnaryOp(0xf2, "REVERSE", msgReverse) opReverse = newUnaryOp(0xf2, "REVERSE", msgReverse)
opHexlify = newUnaryOp(0xf3, "HEXLIFY", msgHexlify) opHexlify = newUnaryOp(0xf3, "HEXLIFY", msgHexlify)
opSHA1 = newCryptOp(0x02, "SHA1", msgSHA1, 20) opSHA256 = newUnaryOp(0x08, "SHA256", msgSHA256)
opRIPEMD160 = newCryptOp(0x03, "RIPEMD160", msgRIPEMD160, 20)
opSHA256 = newCryptOp(0x08, "SHA256", msgSHA256, 32)
) )
var opCodes []opCode = []opCode{ var opCodes []opCode = []opCode{opAppend, opPrepend, opReverse, opHexlify, opSHA256}
opAppend, opPrepend, opReverse, opHexlify, opSHA1, opRIPEMD160,
opSHA256,
}
func parseOp(ctx *deserializationContext, tag byte) (opCode, error) { func parseOp(ctx *deserializationContext, tag byte) (opCode, error) {
for _, op := range opCodes { for _, op := range opCodes {
@@ -202,19 +156,3 @@ func parseOp(ctx *deserializationContext, tag byte) (opCode, error) {
} }
return nil, fmt.Errorf("could not decode tag %02x", tag) return nil, fmt.Errorf("could not decode tag %02x", tag)
} }
func parseCryptOp(ctx *deserializationContext) (*cryptOp, error) {
tag, err := ctx.readByte()
if err != nil {
return nil, err
}
op, err := parseOp(ctx, tag)
if err != nil {
return nil, err
}
if cryptOp, ok := op.(*cryptOp); ok {
return cryptOp, nil
} else {
return nil, fmt.Errorf("expected cryptOp, got %#v", op)
}
}

View File

@@ -1,78 +0,0 @@
package opentimestamps
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMsgAppend(t *testing.T) {
msg := []byte("123")
res, err := msgAppend(msg, []byte("456"))
assert.NoError(t, err)
assert.Equal(t, "123456", string(res))
// make sure changes to input msg don't affect output
msg[0] = byte('0')
assert.Equal(t, "123456", string(res))
}
func TestMsgPrepend(t *testing.T) {
msg := []byte("123")
res, err := msgPrepend(msg, []byte("abc"))
assert.NoError(t, err)
assert.Equal(t, "abc123", string(res))
// make sure changes to input msg don't affect output
msg[0] = byte('0')
assert.Equal(t, "abc123", string(res))
}
func TestMsgReverse(t *testing.T) {
_, err := msgReverse([]byte{})
assert.Error(t, err)
res, err := msgReverse([]byte{1, 2, 3})
assert.NoError(t, err)
assert.Equal(t, []byte{3, 2, 1}, res)
}
func TestMsgHexlify(t *testing.T) {
_, err := msgHexlify([]byte{})
assert.Error(t, err)
res, err := msgHexlify([]byte{1, 2, 3, 0xff})
assert.NoError(t, err)
assert.Equal(t, []byte("010203ff"), res)
}
func TestMsgSHA1(t *testing.T) {
out, err := msgSHA1([]byte{})
assert.NoError(t, err)
assert.Equal(t,
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
hex.EncodeToString(out),
)
}
func TestMsgSHA256(t *testing.T) {
out, err := msgSHA256([]byte{})
assert.NoError(t, err)
assert.Equal(t,
"e3b0c44298fc1c149afbf4c8996fb924"+
"27ae41e4649b934ca495991b7852b855",
hex.EncodeToString(out),
)
}
func TestRIPEMD160(t *testing.T) {
out, err := msgRIPEMD160([]byte{})
assert.Equal(t,
"9c1185a5c5e9fc54612808977ee8f548b2258d31",
hex.EncodeToString(out),
)
out, err = msgRIPEMD160(out)
assert.NoError(t, err)
assert.Equal(t,
"38bbc57e4cbe8b6a1d2c999ef62503e0a6e58109",
hex.EncodeToString(out),
)
}

View File

@@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"strings" "strings"
@@ -49,7 +49,7 @@ func checkStatusOK(resp *http.Response) error {
return fmt.Errorf("%s (body=nil)", errMsg) return fmt.Errorf("%s (body=nil)", errMsg)
} }
defer resp.Body.Close() defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return fmt.Errorf("%s (bodyErr=%v)", errMsg, err) return fmt.Errorf("%s (bodyErr=%v)", errMsg, err)
} else { } else {
@@ -80,8 +80,8 @@ func (c *RemoteCalendar) url(path string) string {
return c.baseURL + path return c.baseURL + path
} }
func (c *RemoteCalendar) Submit(digest []byte) (*Timestamp, error) { func (c *RemoteCalendar) Submit(digest [32]byte) (*Timestamp, error) {
body := bytes.NewBuffer(digest) body := bytes.NewBuffer(digest[:])
req, err := http.NewRequest("POST", c.url("digest"), body) req, err := http.NewRequest("POST", c.url("digest"), body)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -96,7 +96,7 @@ func (c *RemoteCalendar) Submit(digest []byte) (*Timestamp, error) {
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("expected 200, got %v", resp.Status) return nil, fmt.Errorf("expected 200, got %v", resp.Status)
} }
return NewTimestampFromReader(resp.Body, digest) return NewTimestampFromReader(resp.Body, digest[:])
} }
func (c *RemoteCalendar) GetTimestamp(commitment []byte) (*Timestamp, error) { func (c *RemoteCalendar) GetTimestamp(commitment []byte) (*Timestamp, error) {

View File

@@ -27,9 +27,8 @@ func newTestCalendar(url string) *RemoteCalendar {
return cal return cal
} }
func newTestDigest(in string) []byte { func newTestDigest(in string) [32]byte {
hash := sha256.Sum256([]byte(in)) return sha256.Sum256([]byte(in))
return hash[:]
} }
func TestRemoteCalendarExample(t *testing.T) { func TestRemoteCalendarExample(t *testing.T) {

View File

@@ -114,13 +114,7 @@ func (t *Timestamp) Dump() string {
return t.DumpWithConfig(defaultDumpConfig) return t.DumpWithConfig(defaultDumpConfig)
} }
func parseTagOrAttestation( func parseTagOrAttestation(ts *Timestamp, ctx *deserializationContext, tag byte, message []byte, limit int) error {
ts *Timestamp,
ctx *deserializationContext,
tag byte,
message []byte,
limit int,
) error {
if tag == 0x00 { if tag == 0x00 {
a, err := ParseAttestation(ctx) a, err := ParseAttestation(ctx)
if err != nil { if err != nil {
@@ -147,9 +141,7 @@ func parseTagOrAttestation(
return nil return nil
} }
func parse( func parse(ts *Timestamp, ctx *deserializationContext, message []byte, limit int) error {
ts *Timestamp, ctx *deserializationContext, message []byte, limit int,
) error {
if limit == 0 { if limit == 0 {
return fmt.Errorf("recursion limit") return fmt.Errorf("recursion limit")
} }
@@ -176,9 +168,7 @@ func parse(
return parseTagOrAttestation(ts, ctx, tag, message, limit) return parseTagOrAttestation(ts, ctx, tag, message, limit)
} }
func newTimestampFromContext( func newTimestampFromContext(ctx *deserializationContext, message []byte) (*Timestamp, error) {
ctx *deserializationContext, message []byte,
) (*Timestamp, error) {
recursionLimit := 1000 recursionLimit := 1000
ts := &Timestamp{Message: message} ts := &Timestamp{Message: message}
err := parse(ts, ctx, message, recursionLimit) err := parse(ts, ctx, message, recursionLimit)

View File

@@ -1,40 +1,31 @@
package client package opentimestamps
import ( import (
"fmt" "fmt"
"math" "math"
"time" "time"
"github.com/fiatjaf/opentimestamps" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire"
) )
// A BitcoinAttestationVerifier uses a bitcoin RPC connection to verify bitcoin type Bitcoin interface {
// headers. GetBlockHash(height int64) (*chainhash.Hash, error)
type BitcoinAttestationVerifier struct { GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error)
btcrpcClient *rpcclient.Client
}
func NewBitcoinAttestationVerifier(
c *rpcclient.Client,
) *BitcoinAttestationVerifier {
return &BitcoinAttestationVerifier{c}
} }
// VerifyAttestation checks a BitcoinAttestation using a given hash digest. It // VerifyAttestation checks a BitcoinAttestation using a given hash digest. It
// returns the time of the block if the verification succeeds, an error // returns the time of the block if the verification succeeds, an error
// otherwise. // otherwise.
func (v *BitcoinAttestationVerifier) VerifyAttestation( func VerifyAttestation(bitcoinInterface Bitcoin, digest []byte, a *BitcoinAttestation) (*time.Time, error) {
digest []byte, a *opentimestamps.BitcoinAttestation,
) (*time.Time, error) {
if a.Height > math.MaxInt64 { if a.Height > math.MaxInt64 {
return nil, fmt.Errorf("illegal block height") return nil, fmt.Errorf("illegal block height")
} }
blockHash, err := v.btcrpcClient.GetBlockHash(int64(a.Height)) blockHash, err := bitcoinInterface.GetBlockHash(int64(a.Height))
if err != nil { if err != nil {
return nil, err return nil, err
} }
h, err := v.btcrpcClient.GetBlockHeader(blockHash) h, err := bitcoinInterface.GetBlockHeader(blockHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -51,24 +42,22 @@ func (v *BitcoinAttestationVerifier) VerifyAttestation(
// A BitcoinVerification is the result of verifying a BitcoinAttestation // A BitcoinVerification is the result of verifying a BitcoinAttestation
type BitcoinVerification struct { type BitcoinVerification struct {
Timestamp *opentimestamps.Timestamp Timestamp *Timestamp
Attestation *opentimestamps.BitcoinAttestation Attestation *BitcoinAttestation
AttestationTime *time.Time AttestationTime *time.Time
Error error Error error
} }
// BitcoinVerifications returns the all bitcoin attestation results for the // BitcoinVerifications returns the all bitcoin attestation results for the
// timestamp. // timestamp.
func (v *BitcoinAttestationVerifier) BitcoinVerifications( func BitcoinVerifications(bitcoinInterface Bitcoin, t *Timestamp) (res []BitcoinVerification) {
t *opentimestamps.Timestamp, t.Walk(func(ts *Timestamp) {
) (res []BitcoinVerification) {
t.Walk(func(ts *opentimestamps.Timestamp) {
for _, att := range ts.Attestations { for _, att := range ts.Attestations {
btcAtt, ok := att.(*opentimestamps.BitcoinAttestation) btcAtt, ok := att.(*BitcoinAttestation)
if !ok { if !ok {
continue continue
} }
attTime, err := v.VerifyAttestation(ts.Message, btcAtt) attTime, err := VerifyAttestation(bitcoinInterface, ts.Message, btcAtt)
res = append(res, BitcoinVerification{ res = append(res, BitcoinVerification{
Timestamp: ts, Timestamp: ts,
Attestation: btcAtt, Attestation: btcAtt,
@@ -82,10 +71,8 @@ func (v *BitcoinAttestationVerifier) BitcoinVerifications(
// Verify returns the earliest bitcoin-attested time, or nil if none can be // Verify returns the earliest bitcoin-attested time, or nil if none can be
// found or verified successfully. // found or verified successfully.
func (v *BitcoinAttestationVerifier) Verify( func Verify(bitcoinInterface Bitcoin, t *Timestamp) (ret *time.Time, err error) {
t *opentimestamps.Timestamp, res := BitcoinVerifications(bitcoinInterface, t)
) (ret *time.Time, err error) {
res := v.BitcoinVerifications(t)
for _, r := range res { for _, r := range res {
if r.Error != nil { if r.Error != nil {
err = r.Error err = r.Error