simplify codebase.
This commit is contained in:
@@ -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))
|
||||
}
|
||||
|
||||
7
bitcoind.go
Normal file
7
bitcoind.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
26
commands.go
26
commands.go
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
63
esplora.go
Normal 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
4
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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
16
timestamp.go
16
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)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user