181 lines
3.8 KiB
Go
181 lines
3.8 KiB
Go
package opentimestamps
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
attestationTagSize = 8
|
|
attestationMaxPayloadSize = 8192
|
|
pendingAttestationMaxUriLength = 1000
|
|
)
|
|
|
|
var (
|
|
bitcoinAttestationTag = mustDecodeHex("0588960d73d71901")
|
|
pendingAttestationTag = mustDecodeHex("83dfe30d2ef90c8e")
|
|
)
|
|
|
|
type Attestation interface {
|
|
tag() []byte
|
|
decode(*deserializationContext) (Attestation, error)
|
|
encode(*serializationContext) error
|
|
}
|
|
|
|
type baseAttestation struct {
|
|
fixedTag []byte
|
|
}
|
|
|
|
func (b *baseAttestation) tag() []byte {
|
|
return b.fixedTag
|
|
}
|
|
|
|
type pendingAttestation struct {
|
|
baseAttestation
|
|
uri string
|
|
}
|
|
|
|
func newPendingAttestation() *pendingAttestation {
|
|
return &pendingAttestation{
|
|
baseAttestation: baseAttestation{
|
|
fixedTag: pendingAttestationTag,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (p *pendingAttestation) decode(
|
|
ctx *deserializationContext,
|
|
) (Attestation, error) {
|
|
uri, err := ctx.readVarBytes(0, pendingAttestationMaxUriLength)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// TODO utf8 checks
|
|
ret := *p
|
|
ret.uri = string(uri)
|
|
return &ret, nil
|
|
}
|
|
|
|
func (p *pendingAttestation) encode(ctx *serializationContext) error {
|
|
return ctx.writeVarBytes([]byte(p.uri))
|
|
}
|
|
|
|
func (p *pendingAttestation) String() string {
|
|
return fmt.Sprintf("VERIFY PendingAttestation(url=%s)", p.uri)
|
|
}
|
|
|
|
type BitcoinAttestation struct {
|
|
baseAttestation
|
|
Height uint64
|
|
}
|
|
|
|
func newBitcoinAttestation() *BitcoinAttestation {
|
|
return &BitcoinAttestation{
|
|
baseAttestation: baseAttestation{bitcoinAttestationTag},
|
|
}
|
|
}
|
|
|
|
func (b *BitcoinAttestation) String() string {
|
|
return fmt.Sprintf("VERIFY BitcoinAttestation(height=%d)", b.Height)
|
|
}
|
|
|
|
func (b *BitcoinAttestation) decode(
|
|
ctx *deserializationContext,
|
|
) (Attestation, error) {
|
|
height, err := ctx.readVarUint()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret := *b
|
|
ret.Height = height
|
|
return &ret, nil
|
|
}
|
|
|
|
func (b *BitcoinAttestation) encode(ctx *serializationContext) error {
|
|
return ctx.writeVarUint(uint64(b.Height))
|
|
}
|
|
|
|
const hashMerkleRootSize = 32
|
|
|
|
func (b *BitcoinAttestation) VerifyAgainstBlockHash(digest, blockHash []byte) error {
|
|
if len(digest) != hashMerkleRootSize {
|
|
return fmt.Errorf("invalid digest size %d", len(digest))
|
|
}
|
|
if !bytes.Equal(digest, blockHash) {
|
|
return fmt.Errorf(
|
|
"hash mismatch digest=%x blockHash=%x",
|
|
digest, blockHash,
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// This is a catch-all for when we don't know how to parse it
|
|
type unknownAttestation struct {
|
|
tagBytes []byte
|
|
bytes []byte
|
|
}
|
|
|
|
func (u unknownAttestation) tag() []byte {
|
|
return u.tagBytes
|
|
}
|
|
|
|
func (unknownAttestation) decode(*deserializationContext) (Attestation, error) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (unknownAttestation) encode(*serializationContext) error {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (u unknownAttestation) String() string {
|
|
return fmt.Sprintf("UnknownAttestation(bytes=%q)", u.bytes)
|
|
}
|
|
|
|
var attestations []Attestation = []Attestation{
|
|
newPendingAttestation(),
|
|
newBitcoinAttestation(),
|
|
}
|
|
|
|
func encodeAttestation(ctx *serializationContext, att Attestation) error {
|
|
if err := ctx.writeBytes(att.tag()); err != nil {
|
|
return err
|
|
}
|
|
buf := &bytes.Buffer{}
|
|
if err := att.encode(&serializationContext{buf}); err != nil {
|
|
return err
|
|
}
|
|
return ctx.writeVarBytes(buf.Bytes())
|
|
}
|
|
|
|
func ParseAttestation(ctx *deserializationContext) (Attestation, error) {
|
|
tag, err := ctx.readBytes(attestationTagSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
attBytes, err := ctx.readVarBytes(
|
|
0, attestationMaxPayloadSize,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attCtx := newDeserializationContext(
|
|
bytes.NewBuffer(attBytes),
|
|
)
|
|
|
|
for _, a := range attestations {
|
|
if bytes.Equal(tag, a.tag()) {
|
|
att, err := a.decode(attCtx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !attCtx.assertEOF() {
|
|
return nil, fmt.Errorf("expected EOF in attCtx")
|
|
}
|
|
return att, nil
|
|
}
|
|
}
|
|
return unknownAttestation{tag, attBytes}, nil
|
|
}
|