copy the other repo and make packages install.
This commit is contained in:
99
client/bitcoin.go
Normal file
99
client/bitcoin.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/opentimestamps"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
)
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if a.Height > math.MaxInt64 {
|
||||
return nil, fmt.Errorf("illegal block height")
|
||||
}
|
||||
blockHash, err := v.btcrpcClient.GetBlockHash(int64(a.Height))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := v.btcrpcClient.GetBlockHeader(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
merkleRootBytes := h.MerkleRoot[:]
|
||||
err = a.VerifyAgainstBlockHash(digest, merkleRootBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
utc := h.Timestamp.UTC()
|
||||
|
||||
return &utc, nil
|
||||
}
|
||||
|
||||
// A BitcoinVerification is the result of verifying a BitcoinAttestation
|
||||
type BitcoinVerification struct {
|
||||
Timestamp *opentimestamps.Timestamp
|
||||
Attestation *opentimestamps.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) {
|
||||
for _, att := range ts.Attestations {
|
||||
btcAtt, ok := att.(*opentimestamps.BitcoinAttestation)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
attTime, err := v.VerifyAttestation(ts.Message, btcAtt)
|
||||
res = append(res, BitcoinVerification{
|
||||
Timestamp: ts,
|
||||
Attestation: btcAtt,
|
||||
AttestationTime: attTime,
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 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)
|
||||
for _, r := range res {
|
||||
if r.Error != nil {
|
||||
err = r.Error
|
||||
continue
|
||||
}
|
||||
if ret == nil || r.AttestationTime.Before(*ret) {
|
||||
ret = r.AttestationTime
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
83
client/bitcoin_test.go
Normal file
83
client/bitcoin_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/opentimestamps"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"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))
|
||||
}
|
||||
Reference in New Issue
Block a user