package neutrino import ( "fmt" "math/big" "sort" "time" "git.smesh.lol/nostrino/pkg/btcwire" "git.smesh.lol/nostrino/pkg/crypto/chainhash" ) // validateHeader checks linkage, proof of work, median-time-past and the // expected difficulty bits for a header that will be stored at height. func (c *Client) validateHeader(height uint32, h *btcwire.BlockHeader, prev chainhash.Hash) error { if h.PrevBlock != prev { return fmt.Errorf("neutrino: header %d does not link to %s", height, prev) } if !h.CheckProofOfWork() { return fmt.Errorf("neutrino: header %d fails proof of work", height) } now := time.Now().Unix() if int64(h.Timestamp) > now+2*60*60 { return fmt.Errorf("neutrino: header %d is more than two hours in the future", height) } if err := c.checkMedianTimePast(height, h); err != nil { return err } return c.checkDifficulty(height, h) } // checkMedianTimePast enforces the rule that a block's timestamp is greater // than the median of the previous eleven. With a checkpoint anchor there may // be fewer than eleven headers; the check starts once enough context exists. func (c *Client) checkMedianTimePast(height uint32, h *btcwire.BlockHeader) error { if height < 11 { return nil } var times [11]uint32 for i := uint32(1); i <= 11; i++ { prev, ok := c.store.Header(height - i) if !ok { return nil } times[i-1] = prev.Timestamp } sorted := times[:] sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] }) median := sorted[5] if h.Timestamp <= median { return fmt.Errorf("neutrino: header %d timestamp %d is not after median %d", height, h.Timestamp, median) } return nil } // checkDifficulty compares the header's bits with the expected value. It // returns nil when the profile or the available history does not determine // the value (the first retarget window after a checkpoint). func (c *Client) checkDifficulty(height uint32, h *btcwire.BlockHeader) error { expected, ok := c.expectedBits(height, h) if !ok { return nil } if h.Bits != expected { return fmt.Errorf("neutrino: header %d bits %08x, want %08x", height, h.Bits, expected) } return nil } func (c *Client) expectedBits(height uint32, h *btcwire.BlockHeader) (uint32, bool) { p := c.cfg.Profile if height == 0 { return p.PowLimitBits, true } if p.NoRetarget { return p.PowLimitBits, true } prev, ok := c.store.Header(height - 1) if !ok { if height == 1 { return c.cfg.Profile.GenesisHeader.Bits, true } return 0, false } interval := p.RetargetInterval if p.MinDifficulty && height%interval != 0 { if int64(h.Timestamp) > int64(prev.Timestamp)+2*p.TargetSpacing { return p.PowLimitBits, true } } if interval == 0 || height%interval != 0 { return prev.Bits, true } first, ok := c.store.Header(height - interval) if !ok { // The checkpoint is inside the first retarget window; the first block // of the window is not available. Trust the peer's bits for this // boundary and verify every later one. return 0, false } actual := int64(prev.Timestamp) - int64(first.Timestamp) min := p.TargetTimespan / 4 max := p.TargetTimespan * 4 if actual < min { actual = min } if actual > max { actual = max } oldTarget, err := btcwire.CompactToBig(prev.Bits) if err != nil { return 0, false } next := new(big.Int).Mul(oldTarget, big.NewInt(actual)) next.Div(next, big.NewInt(p.TargetTimespan)) limit, err := btcwire.CompactToBig(p.PowLimitBits) if err != nil { return 0, false } if next.Cmp(limit) > 0 { next = limit } return BigToCompact(next), true } // BigToCompact encodes a target as Bitcoin's compact "bits" representation. func BigToCompact(n *big.Int) uint32 { if n == nil || n.Sign() == 0 { return 0 } bytes := n.Bytes() size := uint32(len(bytes)) var mantissa uint32 if size <= 3 { v := new(big.Int).SetBytes(bytes) mantissa = uint32(v.Uint64()) << (8 * (3 - size)) } else { v := new(big.Int).Rsh(n, uint(8*(size-3))) mantissa = uint32(v.Uint64()) } if mantissa&0x00800000 != 0 { mantissa >>= 8 size++ } return mantissa | size<<24 }