mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-05-30 02:36:42 +00:00
245 lines
8.7 KiB
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"))
|
|
}
|
|
}
|