Skip to content

Commit

Permalink
Merge pull request #129 from SiaFoundation/hashall
Browse files Browse the repository at this point in the history
types,consensus: Add hashAll helper
  • Loading branch information
lukechampine authored Oct 16, 2023
2 parents 40520bd + 0c74079 commit c856714
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 347 deletions.
80 changes: 12 additions & 68 deletions consensus/merkle.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,94 +75,38 @@ func (l elementLeaf) proofRoot() types.Hash256 {

// chainIndexLeaf returns the elementLeaf for a ChainIndexElement.
func chainIndexLeaf(e *types.ChainIndexElement) elementLeaf {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("leaf/chainindex")
e.StateElement.ID.EncodeTo(h.E)
e.ChainIndex.EncodeTo(h.E)
return elementLeaf{
StateElement: &e.StateElement,
ElementHash: h.Sum(),
Spent: false,
}
elemHash := hashAll("leaf/chainindex", e.ID, e.ChainIndex)
return elementLeaf{&e.StateElement, elemHash, false}
}

// siacoinLeaf returns the elementLeaf for a SiacoinElement.
func siacoinLeaf(e *types.SiacoinElement, spent bool) elementLeaf {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("leaf/siacoin")
e.ID.EncodeTo(h.E)
e.SiacoinOutput.EncodeTo(h.E)
h.E.WriteUint64(e.MaturityHeight)
return elementLeaf{
StateElement: &e.StateElement,
ElementHash: h.Sum(),
Spent: spent,
}
elemHash := hashAll("leaf/siacoin", e.ID, e.SiacoinOutput, e.MaturityHeight)
return elementLeaf{&e.StateElement, elemHash, spent}
}

// siafundLeaf returns the elementLeaf for a SiafundElement.
func siafundLeaf(e *types.SiafundElement, spent bool) elementLeaf {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("leaf/siafund")
e.ID.EncodeTo(h.E)
e.SiafundOutput.EncodeTo(h.E)
e.ClaimStart.EncodeTo(h.E)
return elementLeaf{
StateElement: &e.StateElement,
ElementHash: h.Sum(),
Spent: spent,
}
elemHash := hashAll("leaf/siafund", e.ID, e.SiafundOutput, e.ClaimStart)
return elementLeaf{&e.StateElement, elemHash, spent}
}

// fileContractLeaf returns the elementLeaf for a FileContractElement.
func fileContractLeaf(e *types.FileContractElement, spent bool) elementLeaf {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("leaf/filecontract")
e.ID.EncodeTo(h.E)
e.FileContract.EncodeTo(h.E)
return elementLeaf{
StateElement: &e.StateElement,
ElementHash: h.Sum(),
Spent: spent,
}
elemHash := hashAll("leaf/filecontract", e.ID, e.FileContract)
return elementLeaf{&e.StateElement, elemHash, spent}
}

// v2FileContractLeaf returns the elementLeaf for a V2FileContractElement.
func v2FileContractLeaf(e *types.V2FileContractElement, spent bool) elementLeaf {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("leaf/v2filecontract")
e.ID.EncodeTo(h.E)
e.V2FileContract.EncodeTo(h.E)
return elementLeaf{
StateElement: &e.StateElement,
ElementHash: h.Sum(),
Spent: spent,
}
elemHash := hashAll("leaf/v2filecontract", e.ID, e.V2FileContract)
return elementLeaf{&e.StateElement, elemHash, spent}
}

// attestationLeaf returns the elementLeaf for an AttestationElement.
func attestationLeaf(e *types.AttestationElement) elementLeaf {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("leaf/attestation")
e.StateElement.ID.EncodeTo(h.E)
e.Attestation.EncodeTo(h.E)
return elementLeaf{
StateElement: &e.StateElement,
ElementHash: h.Sum(),
Spent: false,
}
elemHash := hashAll("leaf/attestation", e.ID, e.Attestation)
return elementLeaf{&e.StateElement, elemHash, false}
}

// An ElementAccumulator tracks the state of an unbounded number of elements
Expand Down
140 changes: 38 additions & 102 deletions consensus/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package consensus

import (
"encoding/binary"
"fmt"
"math/big"
"math/bits"
"sort"
Expand All @@ -19,6 +20,29 @@ import (
// implementation whose constructor returns a concrete type.
var hasherPool = &sync.Pool{New: func() interface{} { return types.NewHasher() }}

func hashAll(elems ...interface{}) [32]byte {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
for _, e := range elems {
if et, ok := e.(types.EncoderTo); ok {
et.EncodeTo(h.E)
} else {
switch e := e.(type) {
case string:
h.WriteDistinguisher(e)
case uint8:
h.E.WriteUint8(e)
case uint64:
h.E.WriteUint64(e)
default:
panic(fmt.Sprintf("unhandled type %T", e))
}
}
}
return h.Sum()
}

// A Network specifies the fixed parameters of a Sia blockchain.
type Network struct {
Name string `json:"name"`
Expand Down Expand Up @@ -334,7 +358,7 @@ func (s State) StorageProofLeafIndex(filesize uint64, windowID types.BlockID, fc
if numLeaves == 0 {
return 0
}
seed := types.HashBytes(append(windowID[:], fcid[:]...))
seed := hashAll(windowID, fcid)
var r uint64
for i := 0; i < len(seed); i += 8 {
_, r = bits.Div64(r, binary.BigEndian.Uint64(seed[i:]), numLeaves)
Expand Down Expand Up @@ -491,123 +515,35 @@ func (s *State) TransactionsCommitment(txns []types.Transaction, v2txns []types.
// Commitment computes the commitment hash for a child block with the given
// transactions and miner address.
func (s State) Commitment(txnsHash types.Hash256, minerAddr types.Address) types.Hash256 {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
s.EncodeTo(h.E)
stateHash := h.Sum()
h.Reset()
h.WriteDistinguisher("commitment")
h.E.WriteUint8(s.v2ReplayPrefix())
stateHash.EncodeTo(h.E)
minerAddr.EncodeTo(h.E)
txnsHash.EncodeTo(h.E)
return h.Sum()
// NOTE: The state is hashed separately so that miners don't have to rehash
// it every time the transaction set changes
stateHash := types.Hash256(hashAll(s))
return hashAll("commitment", s.v2ReplayPrefix(), stateHash, minerAddr, txnsHash)
}

// InputSigHash returns the hash that must be signed for each v2 transaction input.
func (s State) InputSigHash(txn types.V2Transaction) types.Hash256 {
// NOTE: This currently covers exactly the same fields as txn.ID(), for the
// same reasons.
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("sig/input")
h.E.WriteUint8(s.v2ReplayPrefix())
h.E.WritePrefix(len(txn.SiacoinInputs))
for _, in := range txn.SiacoinInputs {
in.Parent.ID.EncodeTo(h.E)
}
h.E.WritePrefix(len(txn.SiacoinOutputs))
for _, out := range txn.SiacoinOutputs {
out.EncodeTo(h.E)
}
h.E.WritePrefix(len(txn.SiafundInputs))
for _, in := range txn.SiafundInputs {
in.Parent.ID.EncodeTo(h.E)
}
h.E.WritePrefix(len(txn.SiafundOutputs))
for _, out := range txn.SiafundOutputs {
out.EncodeTo(h.E)
}
h.E.WritePrefix(len(txn.FileContracts))
for _, fc := range txn.FileContracts {
fc.EncodeTo(h.E)
}
h.E.WritePrefix(len(txn.FileContractRevisions))
for _, fcr := range txn.FileContractRevisions {
fcr.Parent.ID.EncodeTo(h.E)
fcr.Revision.EncodeTo(h.E)
}
h.E.WritePrefix(len(txn.FileContractResolutions))
for _, fcr := range txn.FileContractResolutions {
fcr.Parent.ID.EncodeTo(h.E)
// normalize proof
if sp, ok := fcr.Resolution.(*types.V2StorageProof); ok {
c := *sp // don't modify original
c.ProofIndex.MerkleProof = nil
fcr.Resolution = &c
}
fcr.Resolution.(types.EncoderTo).EncodeTo(h.E)
}
h.E.WritePrefix(len(txn.Attestations))
for _, a := range txn.Attestations {
a.EncodeTo(h.E)
}
h.E.WriteBytes(txn.ArbitraryData)
h.E.WriteBool(txn.NewFoundationAddress != nil)
if txn.NewFoundationAddress != nil {
txn.NewFoundationAddress.EncodeTo(h.E)
}
txn.MinerFee.EncodeTo(h.E)
return h.Sum()
return hashAll("sig/input", s.v2ReplayPrefix(), types.V2TransactionSemantics(txn))
}

// ContractSigHash returns the hash that must be signed for a v2 contract revision.
func (s State) ContractSigHash(fc types.V2FileContract) types.Hash256 {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("sig/filecontract")
h.E.WriteUint8(s.v2ReplayPrefix())
h.E.WriteUint64(fc.Filesize)
fc.FileMerkleRoot.EncodeTo(h.E)
h.E.WriteUint64(fc.ProofHeight)
h.E.WriteUint64(fc.ExpirationHeight)
fc.RenterOutput.EncodeTo(h.E)
fc.HostOutput.EncodeTo(h.E)
fc.MissedHostValue.EncodeTo(h.E)
fc.RenterPublicKey.EncodeTo(h.E)
fc.HostPublicKey.EncodeTo(h.E)
h.E.WriteUint64(fc.RevisionNumber)
return h.Sum()
fc.RenterSignature, fc.HostSignature = types.Signature{}, types.Signature{}
return hashAll("sig/filecontract", s.v2ReplayPrefix(), fc)
}

// RenewalSigHash returns the hash that must be signed for a file contract renewal.
func (s State) RenewalSigHash(fcr types.V2FileContractRenewal) types.Hash256 {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("sig/filecontractrenewal")
h.E.WriteUint8(s.v2ReplayPrefix())
fcr.FinalRevision.EncodeTo(h.E)
fcr.InitialRevision.EncodeTo(h.E)
fcr.RenterRollover.EncodeTo(h.E)
fcr.HostRollover.EncodeTo(h.E)
return h.Sum()
fcr.InitialRevision.RenterSignature, fcr.InitialRevision.HostSignature = types.Signature{}, types.Signature{}
fcr.FinalRevision.RenterSignature, fcr.FinalRevision.HostSignature = types.Signature{}, types.Signature{}
fcr.RenterSignature, fcr.HostSignature = types.Signature{}, types.Signature{}
return hashAll("sig/filecontractrenewal", s.v2ReplayPrefix(), fcr)
}

// AttestationSigHash returns the hash that must be signed for an attestation.
func (s State) AttestationSigHash(a types.Attestation) types.Hash256 {
h := hasherPool.Get().(*types.Hasher)
defer hasherPool.Put(h)
h.Reset()
h.WriteDistinguisher("sig/attestation")
h.E.WriteUint8(s.v2ReplayPrefix())
a.PublicKey.EncodeTo(h.E)
h.E.WriteString(a.Key)
h.E.WriteBytes(a.Value)
return h.Sum()
a.Signature = types.Signature{}
return hashAll("sig/attestation", s.v2ReplayPrefix(), a)
}

// A MidState represents the state of the chain within a block.
Expand Down
87 changes: 78 additions & 9 deletions types/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,16 +385,9 @@ func (ts TransactionSignature) EncodeTo(e *Encoder) {
e.WriteBytes(ts.Signature)
}

// EncodeTo implements types.EncoderTo.
func (txn Transaction) EncodeTo(e *Encoder) {
txn.encodeNoSignatures(e)
e.WritePrefix(len((txn.Signatures)))
for i := range txn.Signatures {
txn.Signatures[i].EncodeTo(e)
}
}
type txnSansSigs Transaction

func (txn *Transaction) encodeNoSignatures(e *Encoder) {
func (txn txnSansSigs) EncodeTo(e *Encoder) {
e.WritePrefix(len((txn.SiacoinInputs)))
for i := range txn.SiacoinInputs {
txn.SiacoinInputs[i].EncodeTo(e)
Expand Down Expand Up @@ -433,6 +426,15 @@ func (txn *Transaction) encodeNoSignatures(e *Encoder) {
}
}

// EncodeTo implements types.EncoderTo.
func (txn Transaction) EncodeTo(e *Encoder) {
txnSansSigs(txn).EncodeTo(e)
e.WritePrefix(len((txn.Signatures)))
for i := range txn.Signatures {
txn.Signatures[i].EncodeTo(e)
}
}

func (p SpendPolicy) encodePolicy(e *Encoder) {
const (
opInvalid = iota
Expand Down Expand Up @@ -726,6 +728,73 @@ func (txn V2Transaction) EncodeTo(e *Encoder) {
}
}

// V2TransactionSemantics is a helper type that provides a "semantic encoding"
// of a v2 transaction, for use in computing IDs and signature hashes.
type V2TransactionSemantics V2Transaction

// EncodeTo implements types.EncoderTo.
func (txn V2TransactionSemantics) EncodeTo(e *Encoder) {
e.WritePrefix(len(txn.SiacoinInputs))
for _, in := range txn.SiacoinInputs {
in.Parent.ID.EncodeTo(e)
}
e.WritePrefix(len(txn.SiacoinOutputs))
for _, out := range txn.SiacoinOutputs {
out.EncodeTo(e)
}
e.WritePrefix(len(txn.SiafundInputs))
for _, in := range txn.SiafundInputs {
in.Parent.ID.EncodeTo(e)
}
e.WritePrefix(len(txn.SiafundOutputs))
for _, out := range txn.SiafundOutputs {
out.EncodeTo(e)
}
e.WritePrefix(len(txn.FileContracts))
for _, fc := range txn.FileContracts {
fc.RenterSignature, fc.HostSignature = Signature{}, Signature{}
fc.EncodeTo(e)
}
e.WritePrefix(len(txn.FileContractRevisions))
for _, fcr := range txn.FileContractRevisions {
fcr.Revision.RenterSignature, fcr.Revision.HostSignature = Signature{}, Signature{}
fcr.Parent.ID.EncodeTo(e)
fcr.Revision.EncodeTo(e)
}
e.WritePrefix(len(txn.FileContractResolutions))
for _, fcr := range txn.FileContractResolutions {
fcr.Parent.ID.EncodeTo(e)
// normalize (being careful not to modify the original)
switch res := fcr.Resolution.(type) {
case *V2FileContractFinalization:
fc := *res
fc.RenterSignature, fc.HostSignature = Signature{}, Signature{}
fcr.Resolution = &fc
case *V2FileContractRenewal:
renewal := *res
renewal.InitialRevision.RenterSignature, renewal.InitialRevision.HostSignature = Signature{}, Signature{}
renewal.FinalRevision.RenterSignature, renewal.FinalRevision.HostSignature = Signature{}, Signature{}
renewal.RenterSignature, renewal.HostSignature = Signature{}, Signature{}
fcr.Resolution = &renewal
case *V2StorageProof:
sp := *res
sp.ProofIndex.MerkleProof = nil
fcr.Resolution = &sp
}
fcr.Resolution.(EncoderTo).EncodeTo(e)
}
e.WritePrefix(len(txn.Attestations))
for _, a := range txn.Attestations {
a.EncodeTo(e)
}
e.WriteBytes(txn.ArbitraryData)
e.WriteBool(txn.NewFoundationAddress != nil)
if txn.NewFoundationAddress != nil {
txn.NewFoundationAddress.EncodeTo(e)
}
txn.MinerFee.EncodeTo(e)
}

// EncodeTo implements types.EncoderTo.
func (b V2BlockData) EncodeTo(e *Encoder) {
e.WriteUint64(b.Height)
Expand Down
Loading

0 comments on commit c856714

Please sign in to comment.