kaspad/domain/consensus/utils/consensushashing/calculate_signature_hash.go

245 lines
8.7 KiB
Go

package consensushashing
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/serialization"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/pkg/errors"
)
// SigHashType represents hash type bits at the end of a signature.
type SigHashType uint8
// Hash type bits from the end of a signature.
const (
SigHashAll SigHashType = 0b00000001
SigHashNone SigHashType = 0b00000010
SigHashSingle SigHashType = 0b00000100
SigHashAnyOneCanPay SigHashType = 0b10000000
// SigHashMask defines the number of bits of the hash type which is used
// to identify which outputs are signed.
SigHashMask = 0b00000111
)
// IsStandardSigHashType returns true if sht represents a standard SigHashType
func (sht SigHashType) IsStandardSigHashType() bool {
switch sht {
case SigHashAll, SigHashNone, SigHashSingle,
SigHashAll | SigHashAnyOneCanPay, SigHashNone | SigHashAnyOneCanPay, SigHashSingle | SigHashAnyOneCanPay:
return true
default:
return false
}
}
func (sht SigHashType) isSigHashAll() bool {
return sht&SigHashMask == SigHashAll
}
func (sht SigHashType) isSigHashNone() bool {
return sht&SigHashMask == SigHashNone
}
func (sht SigHashType) isSigHashSingle() bool {
return sht&SigHashMask == SigHashSingle
}
func (sht SigHashType) isSigHashAnyOneCanPay() bool {
return sht&SigHashAnyOneCanPay == SigHashAnyOneCanPay
}
// SighashReusedValues holds all fields used in the calculation of a transaction's sigHash, that are
// the same for all transaction inputs.
// Reuse of such values prevents the quadratic hashing problem.
type SighashReusedValues struct {
previousOutputsHash *externalapi.DomainHash
sequencesHash *externalapi.DomainHash
sigOpCountsHash *externalapi.DomainHash
outputsHash *externalapi.DomainHash
payloadHash *externalapi.DomainHash
}
// CalculateSignatureHashSchnorr will, given a script and hash type calculate the signature hash
// to be used for signing and verification for Schnorr.
// This returns error only if one of the provided parameters are consensus-invalid.
func CalculateSignatureHashSchnorr(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
if !hashType.IsStandardSigHashType() {
return nil, errors.Errorf("SigHashType %d is not a valid SigHash type", hashType)
}
txIn := tx.Inputs[inputIndex]
prevScriptPublicKey := txIn.UTXOEntry.ScriptPublicKey()
return calculateSignatureHash(tx, inputIndex, txIn, prevScriptPublicKey, hashType, reusedValues)
}
// CalculateSignatureHashECDSA will, given a script and hash type calculate the signature hash
// to be used for signing and verification for ECDSA.
// This returns error only if one of the provided parameters are consensus-invalid.
func CalculateSignatureHashECDSA(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType,
reusedValues *SighashReusedValues) (*externalapi.DomainHash, error) {
hash, err := CalculateSignatureHashSchnorr(tx, inputIndex, hashType, reusedValues)
if err != nil {
return nil, err
}
hashWriter := hashes.NewTransactionSigningHashECDSAWriter()
hashWriter.InfallibleWrite(hash.ByteSlice())
return hashWriter.Finalize(), nil
}
func calculateSignatureHash(tx *externalapi.DomainTransaction, inputIndex int, txIn *externalapi.DomainTransactionInput,
prevScriptPublicKey *externalapi.ScriptPublicKey, hashType SigHashType, reusedValues *SighashReusedValues) (
*externalapi.DomainHash, error) {
hashWriter := hashes.NewTransactionSigningHashWriter()
infallibleWriteElement(hashWriter, tx.Version)
previousOutputsHash := getPreviousOutputsHash(tx, hashType, reusedValues)
infallibleWriteElement(hashWriter, previousOutputsHash)
sequencesHash := getSequencesHash(tx, hashType, reusedValues)
infallibleWriteElement(hashWriter, sequencesHash)
sigOpCountsHash := getSigOpCountsHash(tx, hashType, reusedValues)
infallibleWriteElement(hashWriter, sigOpCountsHash)
hashOutpoint(hashWriter, txIn.PreviousOutpoint)
infallibleWriteElement(hashWriter, prevScriptPublicKey.Version)
infallibleWriteElement(hashWriter, prevScriptPublicKey.Script)
infallibleWriteElement(hashWriter, txIn.UTXOEntry.Amount())
infallibleWriteElement(hashWriter, txIn.Sequence)
infallibleWriteElement(hashWriter, txIn.SigOpCount)
outputsHash := getOutputsHash(tx, inputIndex, hashType, reusedValues)
infallibleWriteElement(hashWriter, outputsHash)
infallibleWriteElement(hashWriter, tx.LockTime)
infallibleWriteElement(hashWriter, tx.SubnetworkID)
infallibleWriteElement(hashWriter, tx.Gas)
payloadHash := getPayloadHash(tx, reusedValues)
infallibleWriteElement(hashWriter, payloadHash)
infallibleWriteElement(hashWriter, uint8(hashType))
return hashWriter.Finalize(), nil
}
func getPreviousOutputsHash(tx *externalapi.DomainTransaction, hashType SigHashType, reusedValues *SighashReusedValues) *externalapi.DomainHash {
if hashType.isSigHashAnyOneCanPay() {
return externalapi.NewZeroHash()
}
if reusedValues.previousOutputsHash == nil {
hashWriter := hashes.NewTransactionSigningHashWriter()
for _, txIn := range tx.Inputs {
hashOutpoint(hashWriter, txIn.PreviousOutpoint)
}
reusedValues.previousOutputsHash = hashWriter.Finalize()
}
return reusedValues.previousOutputsHash
}
func getSequencesHash(tx *externalapi.DomainTransaction, hashType SigHashType, reusedValues *SighashReusedValues) *externalapi.DomainHash {
if hashType.isSigHashSingle() || hashType.isSigHashAnyOneCanPay() || hashType.isSigHashNone() {
return externalapi.NewZeroHash()
}
if reusedValues.sequencesHash == nil {
hashWriter := hashes.NewTransactionSigningHashWriter()
for _, txIn := range tx.Inputs {
infallibleWriteElement(hashWriter, txIn.Sequence)
}
reusedValues.sequencesHash = hashWriter.Finalize()
}
return reusedValues.sequencesHash
}
func getSigOpCountsHash(tx *externalapi.DomainTransaction, hashType SigHashType, reusedValues *SighashReusedValues) *externalapi.DomainHash {
if hashType.isSigHashAnyOneCanPay() {
return externalapi.NewZeroHash()
}
if reusedValues.sigOpCountsHash == nil {
hashWriter := hashes.NewTransactionSigningHashWriter()
for _, txIn := range tx.Inputs {
infallibleWriteElement(hashWriter, txIn.SigOpCount)
}
reusedValues.sigOpCountsHash = hashWriter.Finalize()
}
return reusedValues.sigOpCountsHash
}
func getOutputsHash(tx *externalapi.DomainTransaction, inputIndex int, hashType SigHashType, reusedValues *SighashReusedValues) *externalapi.DomainHash {
// SigHashNone: return zero-hash
if hashType.isSigHashNone() {
return externalapi.NewZeroHash()
}
// SigHashSingle: If the relevant output exists - return it's hash, otherwise return zero-hash
if hashType.isSigHashSingle() {
if inputIndex >= len(tx.Outputs) {
return externalapi.NewZeroHash()
}
hashWriter := hashes.NewTransactionSigningHashWriter()
hashTxOut(hashWriter, tx.Outputs[inputIndex])
return hashWriter.Finalize()
}
// SigHashAll: Return hash of all outputs. Re-use hash if available.
if reusedValues.outputsHash == nil {
hashWriter := hashes.NewTransactionSigningHashWriter()
for _, txOut := range tx.Outputs {
hashTxOut(hashWriter, txOut)
}
reusedValues.outputsHash = hashWriter.Finalize()
}
return reusedValues.outputsHash
}
func getPayloadHash(tx *externalapi.DomainTransaction, reusedValues *SighashReusedValues) *externalapi.DomainHash {
if tx.SubnetworkID.Equal(&subnetworks.SubnetworkIDNative) && len(tx.Payload) == 0 {
return externalapi.NewZeroHash()
}
if reusedValues.payloadHash == nil {
hashWriter := hashes.NewTransactionSigningHashWriter()
infallibleWriteElement(hashWriter, tx.Payload)
reusedValues.payloadHash = hashWriter.Finalize()
}
return reusedValues.payloadHash
}
func hashTxOut(hashWriter hashes.HashWriter, txOut *externalapi.DomainTransactionOutput) {
infallibleWriteElement(hashWriter, txOut.Value)
infallibleWriteElement(hashWriter, txOut.ScriptPublicKey.Version)
infallibleWriteElement(hashWriter, txOut.ScriptPublicKey.Script)
}
func hashOutpoint(hashWriter hashes.HashWriter, outpoint externalapi.DomainOutpoint) {
infallibleWriteElement(hashWriter, outpoint.TransactionID)
infallibleWriteElement(hashWriter, outpoint.Index)
}
func infallibleWriteElement(hashWriter hashes.HashWriter, element interface{}) {
err := serialization.WriteElement(hashWriter, element)
if err != nil {
// It seems like this could only happen if the writer returned an error.
// and this writer should never return an error (no allocations or possible failures)
// the only non-writer error path here is unknown types in `WriteElement`
panic(errors.Wrap(err, "TransactionHashForSigning() failed. this should never fail for structurally-valid transactions"))
}
}