refactor
This commit is contained in:
176
ots/sequence.go
Normal file
176
ots/sequence.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package ots
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
|
||||
"git.intruders.space/public/opentimestamps/varn"
|
||||
"git.intruders.space/public/opentimestamps/verifyer"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
type Sequence []Instruction
|
||||
|
||||
func (seq Sequence) GetAttestation() Attestation {
|
||||
if len(seq) == 0 {
|
||||
return Attestation{}
|
||||
}
|
||||
att := seq[len(seq)-1]
|
||||
if att.Attestation == nil {
|
||||
return Attestation{}
|
||||
}
|
||||
return *att.Attestation
|
||||
}
|
||||
|
||||
// Compute runs a sequence of operations on top of an initial digest and returns the result, which is often a
|
||||
// Bitcoin block merkle root. It also tries to identify the point in the sequence in which an actual Bitcoin
|
||||
// transaction is formed and parse that.
|
||||
func (seq Sequence) Compute(initial []byte) (merkleRoot []byte, bitcoinTx *wire.MsgTx) {
|
||||
current := initial
|
||||
for i, inst := range seq {
|
||||
if inst.Operation == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// the first time we do a double-sha256 that is likely a bitcoin transaction
|
||||
if bitcoinTx == nil &&
|
||||
inst.Operation.Name == "sha256" &&
|
||||
len(seq) > i+1 && seq[i+1].Operation != nil &&
|
||||
seq[i+1].Operation.Name == "sha256" {
|
||||
tx := &wire.MsgTx{}
|
||||
tx.Deserialize(bytes.NewReader(current))
|
||||
bitcoinTx = tx
|
||||
}
|
||||
|
||||
current = inst.Operation.Apply(current, inst.Argument)
|
||||
}
|
||||
return current, bitcoinTx
|
||||
}
|
||||
|
||||
// Verify validates sequence of operations that starts with digest and ends on a Bitcoin attestation against
|
||||
// an actual Bitcoin block, as given by the provided Bitcoin interface.
|
||||
func (seq Sequence) Verify(bitcoin verifyer.Bitcoin, digest []byte) (*wire.MsgTx, error) {
|
||||
if len(seq) == 0 {
|
||||
return nil, fmt.Errorf("empty sequence")
|
||||
}
|
||||
|
||||
att := seq[len(seq)-1]
|
||||
if att.Attestation == nil || att.BitcoinBlockHeight == 0 {
|
||||
return nil, fmt.Errorf("sequence doesn't include a bitcoin attestation")
|
||||
}
|
||||
|
||||
blockHash, err := bitcoin.GetBlockHash(int64(att.BitcoinBlockHeight))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get block %d hash: %w", att.BitcoinBlockHeight, err)
|
||||
}
|
||||
|
||||
blockHeader, err := bitcoin.GetBlockHeader(blockHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get block %s header: %w", blockHash, err)
|
||||
}
|
||||
|
||||
merkleRoot := blockHeader.MerkleRoot[:]
|
||||
result, tx := seq.Compute(digest)
|
||||
|
||||
if !bytes.Equal(result, merkleRoot) {
|
||||
return nil, fmt.Errorf("sequence result '%x' doesn't match the bitcoin merkle root for block %d: %x",
|
||||
result, att.BitcoinBlockHeight, merkleRoot)
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func ParseTimestamp(buf varn.Buffer) ([]Sequence, 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
|
||||
// end or go back to last continuation byte
|
||||
// if 0xff = pick up a continuation byte (checkpoint) and add it to stack
|
||||
|
||||
currInstructionsBlock := 0
|
||||
seqs := make([]Sequence, 0, 10)
|
||||
|
||||
// we will store checkpoints here
|
||||
checkpoints := make([][]Instruction, 0, 4)
|
||||
|
||||
// start first instruction block
|
||||
seqs = append(seqs, make([]Instruction, 0, 30))
|
||||
|
||||
// go read these tags
|
||||
for {
|
||||
tag, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return seqs, nil
|
||||
}
|
||||
return nil, 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 nil, fmt.Errorf("failed to read attestion magic bytes: %w", err)
|
||||
}
|
||||
|
||||
this, err := buf.ReadVarBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read attestation bytes: %w", err)
|
||||
}
|
||||
abuf := varn.NewBuffer(this)
|
||||
|
||||
switch {
|
||||
case slices.Equal(magic, PendingMagic):
|
||||
val, err := abuf.ReadVarBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed reading calendar server url: %w", err)
|
||||
}
|
||||
seqs[currInstructionsBlock] = append(
|
||||
seqs[currInstructionsBlock],
|
||||
Instruction{Attestation: &Attestation{CalendarServerURL: string(val)}},
|
||||
)
|
||||
case slices.Equal(magic, BitcoinMagic):
|
||||
val, err := abuf.ReadVarUint()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed reading bitcoin block number: %w", err)
|
||||
}
|
||||
seqs[currInstructionsBlock] = append(
|
||||
seqs[currInstructionsBlock],
|
||||
Instruction{Attestation: &Attestation{BitcoinBlockHeight: val}},
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported attestation type '%x': %x", magic, this)
|
||||
}
|
||||
|
||||
// check if we have checkpoints and, if yes, copy them in a new block of instructions
|
||||
ncheckpoints := len(checkpoints)
|
||||
if ncheckpoints > 0 {
|
||||
// use this checkpoint as the starting point for the next block
|
||||
chp := checkpoints[ncheckpoints-1]
|
||||
checkpoints = checkpoints[0 : ncheckpoints-1] // remove this from the stack
|
||||
seqs = append(seqs, chp)
|
||||
currInstructionsBlock++
|
||||
}
|
||||
} else if tag == 0xff {
|
||||
// pick up a checkpoint to be used later
|
||||
currentBlock := seqs[currInstructionsBlock]
|
||||
chp := make([]Instruction, len(currentBlock))
|
||||
copy(chp, currentBlock)
|
||||
checkpoints = append(checkpoints, chp)
|
||||
} else {
|
||||
// a new operation in this block
|
||||
inst, err := ReadInstruction(buf, tag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read instruction: %w", err)
|
||||
}
|
||||
seqs[currInstructionsBlock] = append(seqs[currInstructionsBlock], *inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user