From cd227e698671b7fbdfb5bc1b9e3692d9917aa790 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 19 Sep 2023 21:18:40 -0300 Subject: [PATCH] simplify codebase. --- attestations.go | 5 +- bitcoind.go | 7 ++ client/bitcoin_test.go | 83 ----------------------- commands.go | 26 -------- detached_timestamp.go | 100 ---------------------------- detached_timestamp_test.go | 110 ------------------------------- esplora.go | 63 ++++++++++++++++++ go.mod | 4 +- operations.go | 84 ++++------------------- operations_test.go | 78 ---------------------- remote_calendar.go | 10 +-- remote_calendar_test.go | 5 +- timestamp.go | 16 +---- client/bitcoin.go => verifier.go | 47 +++++-------- 14 files changed, 111 insertions(+), 527 deletions(-) create mode 100644 bitcoind.go delete mode 100644 client/bitcoin_test.go delete mode 100644 commands.go delete mode 100644 detached_timestamp.go delete mode 100644 detached_timestamp_test.go create mode 100644 esplora.go delete mode 100644 operations_test.go rename client/bitcoin.go => verifier.go (54%) diff --git a/attestations.go b/attestations.go index 5e3822f..12f0858 100644 --- a/attestations.go +++ b/attestations.go @@ -97,10 +97,7 @@ func (b *BitcoinAttestation) encode(ctx *serializationContext) error { const hashMerkleRootSize = 32 -// -func (b *BitcoinAttestation) VerifyAgainstBlockHash( - digest, blockHash []byte, -) error { +func (b *BitcoinAttestation) VerifyAgainstBlockHash(digest, blockHash []byte) error { if len(digest) != hashMerkleRootSize { return fmt.Errorf("invalid digest size %d", len(digest)) } diff --git a/bitcoind.go b/bitcoind.go new file mode 100644 index 0000000..a5f2134 --- /dev/null +++ b/bitcoind.go @@ -0,0 +1,7 @@ +package opentimestamps + +import "github.com/btcsuite/btcd/rpcclient" + +func NewBitcoindInterface(config rpcclient.ConnConfig) (Bitcoin, error) { + return rpcclient.New(&config, nil) +} diff --git a/client/bitcoin_test.go b/client/bitcoin_test.go deleted file mode 100644 index c021f43..0000000 --- a/client/bitcoin_test.go +++ /dev/null @@ -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)) -} diff --git a/commands.go b/commands.go deleted file mode 100644 index 113e7b8..0000000 --- a/commands.go +++ /dev/null @@ -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) -} diff --git a/detached_timestamp.go b/detached_timestamp.go deleted file mode 100644 index 9adaa61..0000000 --- a/detached_timestamp.go +++ /dev/null @@ -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) -} diff --git a/detached_timestamp_test.go b/detached_timestamp_test.go deleted file mode 100644 index 364eaa8..0000000 --- a/detached_timestamp_test.go +++ /dev/null @@ -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") - } -} diff --git a/esplora.go b/esplora.go new file mode 100644 index 0000000..1180f96 --- /dev/null +++ b/esplora.go @@ -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 +} diff --git a/go.mod b/go.mod index cc85c5d..4d0409a 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,14 @@ go 1.21 require ( 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/stretchr/testify v1.8.4 - golang.org/x/crypto v0.13.0 ) require ( github.com/btcsuite/btcd/btcec/v2 v2.1.3 // 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/go-socks v0.0.0-20170105172521-4720035b7bfd // 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/dcrec/secp256k1/v4 v4.0.1 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/operations.go b/operations.go index 3f4d090..4aa9cd7 100644 --- a/operations.go +++ b/operations.go @@ -1,12 +1,9 @@ package opentimestamps import ( - "crypto/sha1" "crypto/sha256" "encoding/hex" "fmt" - - "golang.org/x/crypto/ripemd160" ) const maxResultLength = 4096 @@ -49,25 +46,6 @@ func msgHexlify(msg []byte) ([]byte, error) { 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 { match(byte) bool decode(*deserializationContext) (opCode, error) @@ -110,30 +88,6 @@ func (u *unaryOp) apply(message []byte) ([]byte, error) { 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 // 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) } +func msgSHA256(msg []byte) ([]byte, error) { + res := sha256.Sum256(msg) + return res[:], nil +} + var ( - opAppend = newBinaryOp(0xf0, "APPEND", msgAppend) - opPrepend = newBinaryOp(0xf1, "PREPEND", msgPrepend) - opReverse = newUnaryOp(0xf2, "REVERSE", msgReverse) - opHexlify = newUnaryOp(0xf3, "HEXLIFY", msgHexlify) - opSHA1 = newCryptOp(0x02, "SHA1", msgSHA1, 20) - opRIPEMD160 = newCryptOp(0x03, "RIPEMD160", msgRIPEMD160, 20) - opSHA256 = newCryptOp(0x08, "SHA256", msgSHA256, 32) + opAppend = newBinaryOp(0xf0, "APPEND", msgAppend) + opPrepend = newBinaryOp(0xf1, "PREPEND", msgPrepend) + opReverse = newUnaryOp(0xf2, "REVERSE", msgReverse) + opHexlify = newUnaryOp(0xf3, "HEXLIFY", msgHexlify) + opSHA256 = newUnaryOp(0x08, "SHA256", msgSHA256) ) -var opCodes []opCode = []opCode{ - opAppend, opPrepend, opReverse, opHexlify, opSHA1, opRIPEMD160, - opSHA256, -} +var opCodes []opCode = []opCode{opAppend, opPrepend, opReverse, opHexlify, opSHA256} func parseOp(ctx *deserializationContext, tag byte) (opCode, error) { 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) } - -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) - } -} diff --git a/operations_test.go b/operations_test.go deleted file mode 100644 index c53df3d..0000000 --- a/operations_test.go +++ /dev/null @@ -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), - ) -} diff --git a/remote_calendar.go b/remote_calendar.go index 7d5347a..b2f0edb 100644 --- a/remote_calendar.go +++ b/remote_calendar.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/hex" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httputil" "strings" @@ -49,7 +49,7 @@ func checkStatusOK(resp *http.Response) error { return fmt.Errorf("%s (body=nil)", errMsg) } defer resp.Body.Close() - bodyBytes, err := ioutil.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("%s (bodyErr=%v)", errMsg, err) } else { @@ -80,8 +80,8 @@ func (c *RemoteCalendar) url(path string) string { return c.baseURL + path } -func (c *RemoteCalendar) Submit(digest []byte) (*Timestamp, error) { - body := bytes.NewBuffer(digest) +func (c *RemoteCalendar) Submit(digest [32]byte) (*Timestamp, error) { + body := bytes.NewBuffer(digest[:]) req, err := http.NewRequest("POST", c.url("digest"), body) if err != nil { return nil, err @@ -96,7 +96,7 @@ func (c *RemoteCalendar) Submit(digest []byte) (*Timestamp, error) { if resp.StatusCode != http.StatusOK { 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) { diff --git a/remote_calendar_test.go b/remote_calendar_test.go index ce50af1..306980a 100644 --- a/remote_calendar_test.go +++ b/remote_calendar_test.go @@ -27,9 +27,8 @@ func newTestCalendar(url string) *RemoteCalendar { return cal } -func newTestDigest(in string) []byte { - hash := sha256.Sum256([]byte(in)) - return hash[:] +func newTestDigest(in string) [32]byte { + return sha256.Sum256([]byte(in)) } func TestRemoteCalendarExample(t *testing.T) { diff --git a/timestamp.go b/timestamp.go index 4780d96..ec83443 100644 --- a/timestamp.go +++ b/timestamp.go @@ -114,13 +114,7 @@ func (t *Timestamp) Dump() string { return t.DumpWithConfig(defaultDumpConfig) } -func parseTagOrAttestation( - ts *Timestamp, - ctx *deserializationContext, - tag byte, - message []byte, - limit int, -) error { +func parseTagOrAttestation(ts *Timestamp, ctx *deserializationContext, tag byte, message []byte, limit int) error { if tag == 0x00 { a, err := ParseAttestation(ctx) if err != nil { @@ -147,9 +141,7 @@ func parseTagOrAttestation( return nil } -func parse( - ts *Timestamp, ctx *deserializationContext, message []byte, limit int, -) error { +func parse(ts *Timestamp, ctx *deserializationContext, message []byte, limit int) error { if limit == 0 { return fmt.Errorf("recursion limit") } @@ -176,9 +168,7 @@ func parse( return parseTagOrAttestation(ts, ctx, tag, message, limit) } -func newTimestampFromContext( - ctx *deserializationContext, message []byte, -) (*Timestamp, error) { +func newTimestampFromContext(ctx *deserializationContext, message []byte) (*Timestamp, error) { recursionLimit := 1000 ts := &Timestamp{Message: message} err := parse(ts, ctx, message, recursionLimit) diff --git a/client/bitcoin.go b/verifier.go similarity index 54% rename from client/bitcoin.go rename to verifier.go index 76a8171..0a077c0 100644 --- a/client/bitcoin.go +++ b/verifier.go @@ -1,40 +1,31 @@ -package client +package opentimestamps import ( "fmt" "math" "time" - "github.com/fiatjaf/opentimestamps" - "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" ) -// A BitcoinAttestationVerifier uses a bitcoin RPC connection to verify bitcoin -// headers. -type BitcoinAttestationVerifier struct { - btcrpcClient *rpcclient.Client -} - -func NewBitcoinAttestationVerifier( - c *rpcclient.Client, -) *BitcoinAttestationVerifier { - return &BitcoinAttestationVerifier{c} +type Bitcoin interface { + GetBlockHash(height int64) (*chainhash.Hash, error) + GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error) } // VerifyAttestation checks a BitcoinAttestation using a given hash digest. It // returns the time of the block if the verification succeeds, an error // otherwise. -func (v *BitcoinAttestationVerifier) VerifyAttestation( - digest []byte, a *opentimestamps.BitcoinAttestation, -) (*time.Time, error) { +func VerifyAttestation(bitcoinInterface Bitcoin, digest []byte, a *BitcoinAttestation) (*time.Time, error) { if a.Height > math.MaxInt64 { 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 { return nil, err } - h, err := v.btcrpcClient.GetBlockHeader(blockHash) + h, err := bitcoinInterface.GetBlockHeader(blockHash) if err != nil { return nil, err } @@ -51,24 +42,22 @@ func (v *BitcoinAttestationVerifier) VerifyAttestation( // A BitcoinVerification is the result of verifying a BitcoinAttestation type BitcoinVerification struct { - Timestamp *opentimestamps.Timestamp - Attestation *opentimestamps.BitcoinAttestation + Timestamp *Timestamp + Attestation *BitcoinAttestation AttestationTime *time.Time Error error } // BitcoinVerifications returns the all bitcoin attestation results for the // timestamp. -func (v *BitcoinAttestationVerifier) BitcoinVerifications( - t *opentimestamps.Timestamp, -) (res []BitcoinVerification) { - t.Walk(func(ts *opentimestamps.Timestamp) { +func BitcoinVerifications(bitcoinInterface Bitcoin, t *Timestamp) (res []BitcoinVerification) { + t.Walk(func(ts *Timestamp) { for _, att := range ts.Attestations { - btcAtt, ok := att.(*opentimestamps.BitcoinAttestation) + btcAtt, ok := att.(*BitcoinAttestation) if !ok { continue } - attTime, err := v.VerifyAttestation(ts.Message, btcAtt) + attTime, err := VerifyAttestation(bitcoinInterface, ts.Message, btcAtt) res = append(res, BitcoinVerification{ Timestamp: ts, Attestation: btcAtt, @@ -82,10 +71,8 @@ func (v *BitcoinAttestationVerifier) BitcoinVerifications( // Verify returns the earliest bitcoin-attested time, or nil if none can be // found or verified successfully. -func (v *BitcoinAttestationVerifier) Verify( - t *opentimestamps.Timestamp, -) (ret *time.Time, err error) { - res := v.BitcoinVerifications(t) +func Verify(bitcoinInterface Bitcoin, t *Timestamp) (ret *time.Time, err error) { + res := BitcoinVerifications(bitcoinInterface, t) for _, r := range res { if r.Error != nil { err = r.Error