implement the basic binary parsing logic.
This commit is contained in:
7
go.mod
7
go.mod
@@ -5,8 +5,7 @@ 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/exp v0.0.0-20230905200255-921286631fa9
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -15,11 +14,9 @@ require (
|
||||
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
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
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
|
||||
github.com/stretchr/testify v1.8.4 // 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
|
||||
)
|
||||
|
||||
10
go.sum
10
go.sum
@@ -24,7 +24,6 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
@@ -60,10 +59,6 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -71,6 +66,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -86,7 +83,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -101,13 +97,11 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
211
ots.go
Normal file
211
ots.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package opentimestamps
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
/*
|
||||
* Header magic bytes
|
||||
* Designed to be give the user some information in a hexdump, while being identified as 'data' by the file utility.
|
||||
* \x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94
|
||||
*/
|
||||
var headerMagic = []byte{0x00, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x00, 0x00, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x00, 0xbf, 0x89, 0xe2, 0xe8, 0x84, 0xe8, 0x92, 0x94}
|
||||
|
||||
var (
|
||||
pendingMagic = []byte{0x83, 0xdf, 0xe3, 0x0d, 0x2e, 0xf9, 0x0c, 0x8e}
|
||||
bitcoinMagic = []byte{0x05, 0x88, 0x96, 0x0d, 0x73, 0xd7, 0x19, 0x01}
|
||||
)
|
||||
|
||||
type Operation struct {
|
||||
Name string
|
||||
Tag byte
|
||||
Binary bool // it's an operation that takes one argument, otherwise takes none
|
||||
Apply func(curr []byte, arg []byte) []byte
|
||||
}
|
||||
|
||||
var tags = map[byte]*Operation{
|
||||
0xf0: {"append", 0xf0, true, func(curr []byte, arg []byte) []byte { return append(curr, arg...) }},
|
||||
0xf1: {"prepend", 0xf1, true, func(curr []byte, arg []byte) []byte { return append(arg, curr...) }},
|
||||
0xf2: {"reverse", 0xf2, false, func(curr []byte, arg []byte) []byte { panic("reverse not implemented") }},
|
||||
0xf3: {"hexlify", 0xf3, false, func(curr []byte, arg []byte) []byte { panic("hexlify not implemented") }},
|
||||
0x02: {"sha1", 0x02, false, func(curr []byte, arg []byte) []byte { panic("sha1 not implemented") }},
|
||||
0x03: {"ripemd160", 0x03, false, func(curr []byte, arg []byte) []byte { panic("ripemd160 not implemented") }},
|
||||
0x08: {"sha256", 0x08, false, func(curr []byte, arg []byte) []byte {
|
||||
v := sha256.Sum256(curr)
|
||||
return v[:]
|
||||
}},
|
||||
0x67: {"keccak256", 0x67, false, func(curr []byte, arg []byte) []byte { panic("keccak256 not implemented") }},
|
||||
}
|
||||
|
||||
type Timestamp struct {
|
||||
Digest []byte
|
||||
Instructions [][]Instruction
|
||||
}
|
||||
|
||||
type Instruction struct {
|
||||
*Operation
|
||||
Argument []byte
|
||||
*Attestation
|
||||
}
|
||||
|
||||
type Attestation struct {
|
||||
Name string
|
||||
BitcoinBlockHeight uint64
|
||||
CalendarServerURL string
|
||||
}
|
||||
|
||||
func parseCalendarServerResponse(buf Buffer, digest []byte) (Timestamp, error) {
|
||||
ts := Timestamp{
|
||||
Digest: digest,
|
||||
}
|
||||
|
||||
err := parseTimestamp(buf, &ts)
|
||||
if err != nil {
|
||||
return ts, err
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
func parseOTSFile(buf Buffer) (Timestamp, error) {
|
||||
ts := Timestamp{}
|
||||
|
||||
// read magic
|
||||
// read version [1 byte]
|
||||
// read crypto operation for file digest [1 byte]
|
||||
// read file digest [32 byte (depends)]
|
||||
if magic, err := buf.readBytes(len(headerMagic)); err != nil || slices.Equal(headerMagic, magic) {
|
||||
return ts, fmt.Errorf("invalid ots file header '%s': %w", magic, err)
|
||||
}
|
||||
|
||||
if version, err := buf.readByte(); err != nil || version != '1' {
|
||||
return ts, fmt.Errorf("invalid ots file version '%v': %w", version, err)
|
||||
}
|
||||
|
||||
tag, err := buf.readByte()
|
||||
if err != nil {
|
||||
return ts, fmt.Errorf("failed to read operation byte: %w", err)
|
||||
}
|
||||
|
||||
if op, err := readInstruction(buf, tag); err != nil || op.Operation.Name != "sha256" {
|
||||
return ts, fmt.Errorf("invalid crypto operation '%v', only sha256 supported: %w", op, err)
|
||||
}
|
||||
|
||||
if err := parseTimestamp(buf, &ts); err != nil {
|
||||
return ts, err
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
func parseTimestamp(buf Buffer, ts *Timestamp) error {
|
||||
// read instructions
|
||||
// if operation = push
|
||||
// if 0x00 = attestation
|
||||
// read tag [8 bytes]
|
||||
// readvarbytes
|
||||
// interpret these depending on the type of attestation
|
||||
// if bitcoin: readvaruint as the block height
|
||||
// if pending from calendar: readvarbytes as the utf-8 calendar url
|
||||
// if 0xff = skip and start reading a new block of instructions?
|
||||
|
||||
currInstructionsBlock := 0
|
||||
ts.Instructions = make([][]Instruction, 0, 5)
|
||||
|
||||
// start first instruction block
|
||||
ts.Instructions = append(ts.Instructions, make([]Instruction, 0, 30))
|
||||
|
||||
// go read these tags
|
||||
for {
|
||||
tag, err := buf.readByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read operation byte: %w", err)
|
||||
}
|
||||
|
||||
if tag == 0x00 {
|
||||
// enter an attestation context
|
||||
magic, err := buf.readBytes(8)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read attestion magic bytes: %w", err)
|
||||
}
|
||||
|
||||
this, err := buf.readVarBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read attestation bytes: %w", err)
|
||||
}
|
||||
abuf := NewBuffer(this)
|
||||
|
||||
switch {
|
||||
case slices.Equal(magic, pendingMagic):
|
||||
val, err := abuf.readVarBytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading calendar server url: %w", err)
|
||||
}
|
||||
ts.Instructions[currInstructionsBlock] = append(
|
||||
ts.Instructions[currInstructionsBlock],
|
||||
Instruction{Attestation: &Attestation{Name: "pending", CalendarServerURL: string(val)}},
|
||||
)
|
||||
continue
|
||||
case slices.Equal(magic, bitcoinMagic):
|
||||
val, err := abuf.readVarUint()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading bitcoin block number: %w", err)
|
||||
}
|
||||
ts.Instructions[currInstructionsBlock] = append(
|
||||
ts.Instructions[currInstructionsBlock],
|
||||
Instruction{Attestation: &Attestation{Name: "bitcoin", BitcoinBlockHeight: val}},
|
||||
)
|
||||
continue
|
||||
default:
|
||||
return fmt.Errorf("unsupported attestation type %v", magic)
|
||||
}
|
||||
} else if tag == 0xff {
|
||||
// another block of instructions
|
||||
ts.Instructions = append(ts.Instructions, make([]Instruction, 0, 30))
|
||||
currInstructionsBlock++
|
||||
tag, err = buf.readByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read operation byte when starting a new block of instructions: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// a new operation in this block
|
||||
inst, err := readInstruction(buf, tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read instruction: %w", err)
|
||||
}
|
||||
|
||||
ts.Instructions[currInstructionsBlock] = append(ts.Instructions[currInstructionsBlock], *inst)
|
||||
}
|
||||
}
|
||||
|
||||
func readInstruction(buf Buffer, tag byte) (*Instruction, error) {
|
||||
op, ok := tags[tag]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown tag %v", tag)
|
||||
}
|
||||
|
||||
inst := Instruction{
|
||||
Operation: op,
|
||||
}
|
||||
|
||||
if op.Binary {
|
||||
val, err := buf.readVarBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading argument: %w", err)
|
||||
}
|
||||
inst.Argument = val
|
||||
}
|
||||
|
||||
return &inst, nil
|
||||
}
|
||||
39
stamp.go
Normal file
39
stamp.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package opentimestamps
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Stamp(ctx context.Context, calendarUrl string, digest [32]byte) error {
|
||||
body := bytes.NewBuffer(digest[:])
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", normalizeUrl(calendarUrl)+"/digest", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Add("User-Agent", "github.com/fiatjaf/opentimestamps")
|
||||
req.Header.Add("Accept", "application/vnd.opentimestamps.v1")
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("'%s' request failed: %w", calendarUrl, err)
|
||||
}
|
||||
|
||||
full, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response from '%s': %w", calendarUrl, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
fmt.Println("full", hex.EncodeToString(full))
|
||||
v, err := parseCalendarServerResponse(NewBuffer(full), digest[:])
|
||||
fmt.Println(err)
|
||||
fmt.Println(v)
|
||||
|
||||
return nil
|
||||
}
|
||||
90
utils.go
Normal file
90
utils.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package opentimestamps
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func normalizeUrl(u string) string {
|
||||
if strings.HasSuffix(u, "/") {
|
||||
u = u[0 : len(u)-1]
|
||||
}
|
||||
if !strings.HasPrefix(u, "https://") && !strings.HasPrefix(u, "http://") {
|
||||
u = "http://" + u
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
pos *int
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func NewBuffer(buf []byte) Buffer {
|
||||
zero := 0
|
||||
return Buffer{&zero, buf}
|
||||
}
|
||||
|
||||
func (buf Buffer) readBytes(n int) ([]byte, error) {
|
||||
fmt.Println("reading", n, "bytes")
|
||||
|
||||
if *buf.pos >= len(buf.buf) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
res := buf.buf[*buf.pos : *buf.pos+n]
|
||||
*buf.pos = *buf.pos + n
|
||||
fmt.Println("->", hex.EncodeToString(res))
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (buf Buffer) readByte() (byte, error) {
|
||||
fmt.Println("reading byte")
|
||||
|
||||
b, err := buf.readBytes(1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
fmt.Println("->", hex.EncodeToString(b))
|
||||
return b[0], nil
|
||||
}
|
||||
|
||||
func (buf Buffer) readVarUint() (uint64, error) {
|
||||
fmt.Println("reading varuint")
|
||||
|
||||
var value uint64 = 0
|
||||
var shift uint64 = 0
|
||||
|
||||
for {
|
||||
b, err := buf.readByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
value |= (uint64(b) & 0b01111111) << shift
|
||||
shift += 7
|
||||
if b&0b10000000 == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("->", value, "(num)")
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (buf Buffer) readVarBytes() ([]byte, error) {
|
||||
fmt.Println("reading varbytes")
|
||||
|
||||
v, err := buf.readVarUint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := buf.readBytes(int(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println("->", hex.EncodeToString(b))
|
||||
return b, nil
|
||||
}
|
||||
11
verifier.go
Normal file
11
verifier.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package opentimestamps
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
type Bitcoin interface {
|
||||
GetBlockHash(height int64) (*chainhash.Hash, error)
|
||||
GetBlockHeader(hash *chainhash.Hash) (*wire.BlockHeader, error)
|
||||
}
|
||||
Reference in New Issue
Block a user