copy the other repo and make packages install.

This commit is contained in:
fiatjaf
2023-09-18 16:29:38 -03:00
commit 0b433cd95a
18 changed files with 2042 additions and 0 deletions

99
client/bitcoin.go Normal file
View 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
View 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))
}