// Copyright (c) 2021-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package stdscript import ( "fmt" "github.com/decred/dcrd/dcrec" "github.com/decred/dcrd/txscript/v4" ) const ( // MaxDataCarrierSizeV0 is the maximum number of bytes allowed in pushed // data to be considered a standard version 0 provably pruneable nulldata // script. MaxDataCarrierSizeV0 = 256 ) // ExtractCompressedPubKeyV0 extracts a compressed public key from the passed // script if it is a standard version 0 pay-to-compressed-secp256k1-pubkey // script. It will return nil otherwise. func ExtractCompressedPubKeyV0(script []byte) []byte { // A pay-to-compressed-pubkey script is of the form: // OP_DATA_33 <33-byte compressed pubkey> OP_CHECKSIG // All compressed secp256k1 public keys must start with 0x02 or 0x03. if len(script) == 35 && script[34] == txscript.OP_CHECKSIG && script[0] == txscript.OP_DATA_33 && (script[1] == 0x02 || script[1] == 0x03) { return script[1:34] } return nil } // ExtractUncompressedPubKeyV0 extracts an uncompressed public key from the // passed script if it is a standard version 0 // pay-to-uncompressed-secp256k1-pubkey script. It will return nil otherwise. func ExtractUncompressedPubKeyV0(script []byte) []byte { // A pay-to-uncompressed-pubkey script is of the form: // OP_DATA_65 <65-byte uncompressed pubkey> OP_CHECKSIG // All non-hybrid uncompressed secp256k1 public keys must start with 0x04. if len(script) == 67 && script[66] == txscript.OP_CHECKSIG && script[0] == txscript.OP_DATA_65 && script[1] == 0x04 { return script[1:66] } return nil } // ExtractPubKeyV0 extracts either a compressed or uncompressed public key from // the passed script if it is either a standard version 0 // pay-to-compressed-secp256k1-pubkey or pay-to-uncompressed-secp256k1-pubkey // script, respectively. It will return nil otherwise. func ExtractPubKeyV0(script []byte) []byte { if pubKey := ExtractCompressedPubKeyV0(script); pubKey != nil { return pubKey } return ExtractUncompressedPubKeyV0(script) } // IsPubKeyScriptV0 returns whether or not the passed script is either a // standard version 0 pay-to-compressed-secp256k1-pubkey or // pay-to-uncompressed-secp256k1-pubkey script. func IsPubKeyScriptV0(script []byte) bool { return ExtractPubKeyV0(script) != nil } // ExtractPubKeyAltDetailsV0 extracts the public key and signature type from the // passed script if it is a standard version 0 pay-to-alt-pubkey script. It // will return nil otherwise. func ExtractPubKeyAltDetailsV0(script []byte) ([]byte, dcrec.SignatureType) { // A pay-to-alt-pubkey script is of the form: // PUBKEY SIGTYPE OP_CHECKSIGALT // // The only two currently supported alternative signature types are ed25519 // and schnorr + secp256k1 (with a compressed pubkey). // // OP_DATA_32 <32-byte pubkey> <1-byte ed25519 sigtype> OP_CHECKSIGALT // OP_DATA_33 <33-byte pubkey> <1-byte schnorr+secp sigtype> OP_CHECKSIGALT // The script can't possibly be a pay-to-alt-pubkey script if it doesn't // end with OP_CHECKSIGALT or have at least two small integer pushes // preceding it (although any reasonable pubkey will certainly be larger). // Fail fast to avoid more work below. if len(script) < 3 || script[len(script)-1] != txscript.OP_CHECKSIGALT { return nil, 0 } if len(script) == 35 && script[0] == txscript.OP_DATA_32 && txscript.IsSmallInt(script[33]) && txscript.AsSmallInt(script[33]) == dcrec.STEd25519 { return script[1:33], dcrec.STEd25519 } if len(script) == 36 && script[0] == txscript.OP_DATA_33 && txscript.IsSmallInt(script[34]) && txscript.AsSmallInt(script[34]) == dcrec.STSchnorrSecp256k1 && txscript.IsStrictCompressedPubKeyEncoding(script[1:34]) { return script[1:34], dcrec.STSchnorrSecp256k1 } return nil, 0 } // ExtractPubKeyEd25519V0 extracts a public key from the passed script if it is // a standard version 0 pay-to-ed25519-pubkey script. It will return nil // otherwise. func ExtractPubKeyEd25519V0(script []byte) []byte { pk, sigType := ExtractPubKeyAltDetailsV0(script) if pk == nil || sigType != dcrec.STEd25519 { return nil } return pk } // IsPubKeyEd25519ScriptV0 returns whether or not the passed script is a // standard version 0 pay-to-ed25519-pubkey script. func IsPubKeyEd25519ScriptV0(script []byte) bool { return ExtractPubKeyEd25519V0(script) != nil } // ExtractPubKeySchnorrSecp256k1V0 extracts a public key from the passed script // if it is a standard version 0 pay-to-schnorr-secp256k1-pubkey script. It // will return nil otherwise. func ExtractPubKeySchnorrSecp256k1V0(script []byte) []byte { pk, sigType := ExtractPubKeyAltDetailsV0(script) if pk == nil || sigType != dcrec.STSchnorrSecp256k1 { return nil } return pk } // IsPubKeySchnorrSecp256k1ScriptV0 returns whether or not the passed script is // a standard version 0 pay-to-schnorr-secp256k1-pubkey script. func IsPubKeySchnorrSecp256k1ScriptV0(script []byte) bool { return ExtractPubKeySchnorrSecp256k1V0(script) != nil } // ExtractPubKeyHashV0 extracts the public key hash from the passed script if it // is a standard version 0 pay-to-pubkey-hash-ecdsa-secp256k1 script. It will // return nil otherwise. func ExtractPubKeyHashV0(script []byte) []byte { // A pay-to-pubkey-hash script is of the form: // OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG if len(script) == 25 && script[0] == txscript.OP_DUP && script[1] == txscript.OP_HASH160 && script[2] == txscript.OP_DATA_20 && script[23] == txscript.OP_EQUALVERIFY && script[24] == txscript.OP_CHECKSIG { return script[3:23] } return nil } // IsPubKeyHashScriptV0 returns whether or not the passed script is a standard // version 0 pay-to-pubkey-hash-ecdsa-secp256k1 script. func IsPubKeyHashScriptV0(script []byte) bool { return ExtractPubKeyHashV0(script) != nil } // IsStandardAltSignatureTypeV0 returns whether or not the provided version 0 // script opcode represents a push of a standard alt signature type. func IsStandardAltSignatureTypeV0(op byte) bool { if !txscript.IsSmallInt(op) { return false } sigType := txscript.AsSmallInt(op) return sigType == dcrec.STEd25519 || sigType == dcrec.STSchnorrSecp256k1 } // ExtractPubKeyHashAltDetailsV0 extracts the public key hash and signature type // from the passed script if it is a standard version 0 pay-to-alt-pubkey-hash // script. It will return nil otherwise. func ExtractPubKeyHashAltDetailsV0(script []byte) ([]byte, dcrec.SignatureType) { // A pay-to-alt-pubkey-hash script is of the form: // DUP HASH160 <20-byte hash> EQUALVERIFY SIGTYPE CHECKSIG // // The only two currently supported alternative signature types are ed25519 // and schnorr + secp256k1 (with a compressed pubkey). // // DUP HASH160 <20-byte hash> EQUALVERIFY <1-byte ed25519 sigtype> CHECKSIGALT // DUP HASH160 <20-byte hash> EQUALVERIFY <1-byte schnorr+secp sigtype> CHECKSIGALT // // Notice that OP_0 is not specified since signature type 0 disabled. if len(script) == 26 && script[0] == txscript.OP_DUP && script[1] == txscript.OP_HASH160 && script[2] == txscript.OP_DATA_20 && script[23] == txscript.OP_EQUALVERIFY && IsStandardAltSignatureTypeV0(script[24]) && script[25] == txscript.OP_CHECKSIGALT { return script[3:23], dcrec.SignatureType(txscript.AsSmallInt(script[24])) } return nil, 0 } // ExtractPubKeyHashEd25519V0 extracts the public key hash from the passed // script if it is a standard version 0 pay-to-pubkey-hash-ed25519 script. It // will return nil otherwise. func ExtractPubKeyHashEd25519V0(script []byte) []byte { pkHash, sigType := ExtractPubKeyHashAltDetailsV0(script) if pkHash == nil || sigType != dcrec.STEd25519 { return nil } return pkHash } // IsPubKeyHashEd25519ScriptV0 returns whether or not the passed script is a // standard version 0 pay-to-pubkey-hash-ed25519 script. func IsPubKeyHashEd25519ScriptV0(script []byte) bool { return ExtractPubKeyHashEd25519V0(script) != nil } // ExtractPubKeyHashSchnorrSecp256k1V0 extracts the public key hash from the // passed script if it is a standard version 0 // pay-to-pubkey-hash-schnorr-secp256k1 script. It will return nil otherwise. func ExtractPubKeyHashSchnorrSecp256k1V0(script []byte) []byte { pkHash, sigType := ExtractPubKeyHashAltDetailsV0(script) if pkHash == nil || sigType != dcrec.STSchnorrSecp256k1 { return nil } return pkHash } // IsPubKeyHashSchnorrSecp256k1ScriptV0 returns whether or not the passed script // is a standard version 0 pay-to-pubkey-hash-schnorr-secp256k1 script. func IsPubKeyHashSchnorrSecp256k1ScriptV0(script []byte) bool { return ExtractPubKeyHashSchnorrSecp256k1V0(script) != nil } // ExtractScriptHashV0 extracts the script hash from the passed script if it is // a standard version 0 pay-to-script-hash script. It will return nil // otherwise. func ExtractScriptHashV0(script []byte) []byte { // Defer to consensus code. return txscript.ExtractScriptHash(script) } // IsScriptHashScriptV0 returns whether or not the passed script is a standard // version 0 pay-to-script-hash script. func IsScriptHashScriptV0(script []byte) bool { return ExtractScriptHashV0(script) != nil } // MultiSigDetailsV0 houses details extracted from a standard version 0 ECDSA // multisig script. type MultiSigDetailsV0 struct { RequiredSigs uint16 NumPubKeys uint16 PubKeys [][]byte Valid bool } // ExtractMultiSigScriptDetailsV0 attempts to extract details from the passed // version 0 script if it is a standard ECDSA multisig script. The returned // details struct will have the valid flag set to false otherwise. // // The extract pubkeys flag indicates whether or not the pubkeys themselves // should also be extracted and is provided because extracting them results in // an allocation that the caller might wish to avoid. The PubKeys member of the // returned details struct will be nil when the flag is false. func ExtractMultiSigScriptDetailsV0(script []byte, extractPubKeys bool) MultiSigDetailsV0 { // nolint: dupword // // A multi-signature script is of the form: // REQ_SIGS PUBKEY PUBKEY PUBKEY ... NUM_PUBKEYS OP_CHECKMULTISIG // The script can't possibly be a multisig script if it doesn't end with // OP_CHECKMULTISIG or have at least two small integer pushes preceding it. // Fail fast to avoid more work below. if len(script) < 3 || script[len(script)-1] != txscript.OP_CHECKMULTISIG { return MultiSigDetailsV0{} } // The first opcode must be a small integer specifying the number of // signatures required. const scriptVersion = 0 tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script) if !tokenizer.Next() || !txscript.IsSmallInt(tokenizer.Opcode()) { return MultiSigDetailsV0{} } requiredSigs := txscript.AsSmallInt(tokenizer.Opcode()) // There must be at least one required signature. if requiredSigs == 0 { return MultiSigDetailsV0{} } // The next series of opcodes must either push public keys or be a small // integer specifying the number of public keys. It should be noted that // although the consensus rules allow a higher maximum number of pubkeys, // this intentionally further restricts the maximum number to what can be // represented by a small integer push (up to a max of 16). var numPubKeys int var pubKeys [][]byte if extractPubKeys { pubKeys = make([][]byte, 0, txscript.MaxPubKeysPerMultiSig) } for tokenizer.Next() { data := tokenizer.Data() if !txscript.IsStrictCompressedPubKeyEncoding(data) { break } numPubKeys++ if extractPubKeys { pubKeys = append(pubKeys, data) } } if tokenizer.Done() { return MultiSigDetailsV0{} } // The next opcode must be a small integer specifying the number of public // keys required. op := tokenizer.Opcode() if !txscript.IsSmallInt(op) || txscript.AsSmallInt(op) != numPubKeys { return MultiSigDetailsV0{} } // There must be at least as many pubkeys as required signatures. if numPubKeys < requiredSigs { return MultiSigDetailsV0{} } // There must only be a single opcode left unparsed which will be // OP_CHECKMULTISIG per the check above. if int32(len(tokenizer.Script()))-tokenizer.ByteIndex() != 1 { return MultiSigDetailsV0{} } return MultiSigDetailsV0{ RequiredSigs: uint16(requiredSigs), NumPubKeys: uint16(numPubKeys), PubKeys: pubKeys, Valid: true, } } // IsMultiSigScriptV0 returns whether or not the passed script is a standard // version 0 ECDSA multisig script. // // NOTE: This function is only valid for version 0 scripts. It will always // return false for other script versions. func IsMultiSigScriptV0(script []byte) bool { // Since this is only checking the form of the script, don't extract the // public keys to avoid the allocation. details := ExtractMultiSigScriptDetailsV0(script, false) return details.Valid } // finalOpcodeDataV0 returns the data associated with the final opcode in the // passed version 0 script. It will return nil if the script fails to parse. func finalOpcodeDataV0(script []byte) []byte { // Avoid unnecessary work. if len(script) == 0 { return nil } var data []byte const scriptVersion = 0 tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script) for tokenizer.Next() { data = tokenizer.Data() } if tokenizer.Err() != nil { return nil } return data } // IsMultiSigSigScriptV0 returns whether or not the passed script appears to be // a version 0 signature script which consists of a pay-to-script-hash // multi-signature redeem script. Determining if a signature script is actually // a redemption of pay-to-script-hash requires the associated public key script // which is often expensive to obtain. Therefore, this makes a fast best effort // guess that has a high probability of being correct by checking if the // signature script ends with a data push and treating that data push as if it // were a p2sh redeem script. func IsMultiSigSigScriptV0(script []byte) bool { // The script can't possibly be a multisig signature script if it doesn't // end with OP_CHECKMULTISIG in the redeem script or have at least two small // integers preceding it, and the redeem script itself must be preceded by // at least a data push opcode. Fail fast to avoid more work below. if len(script) < 4 || script[len(script)-1] != txscript.OP_CHECKMULTISIG { return false } // Parse through the script to find the last opcode and any data it might // push and treat it as a p2sh redeem script even though it might not // actually be one. possibleRedeemScript := finalOpcodeDataV0(script) if possibleRedeemScript == nil { return false } // Finally, return if that possible redeem script is a multisig script. return IsMultiSigScriptV0(possibleRedeemScript) } // MultiSigRedeemScriptFromScriptSigV0 attempts to extract a multi-signature // redeem script from a version 0 P2SH-redeeming input. The script is expected // to already have been checked to be a version 0 multisignature script prior to // calling this function. The results are undefined for other script types. func MultiSigRedeemScriptFromScriptSigV0(script []byte) []byte { // The redeemScript is always the last item on the stack of the script sig. return finalOpcodeDataV0(script) } // isCanonicalPushV0 returns whether or not the given version 0 opcode and // associated data is a push instruction that uses the smallest instruction to // do the job. // // For example, it is possible to push a value of 1 to the stack as "OP_1", // "OP_DATA_1 0x01", "OP_PUSHDATA1 0x01 0x01", and others, however, the first // only takes a single byte, while the rest take more. Only the first is // considered canonical. func isCanonicalPushV0(opcode byte, data []byte) bool { dataLen := len(data) if opcode > txscript.OP_16 { return false } if opcode < txscript.OP_PUSHDATA1 && opcode > txscript.OP_0 && (dataLen == 1 && data[0] <= 16) { return false } if opcode == txscript.OP_PUSHDATA1 && dataLen < txscript.OP_PUSHDATA1 { return false } if opcode == txscript.OP_PUSHDATA2 && dataLen <= 0xff { return false } if opcode == txscript.OP_PUSHDATA4 && dataLen <= 0xffff { return false } return true } // IsNullDataScriptV0 returns whether or not the passed script is a standard // version 0 null data script. func IsNullDataScriptV0(script []byte) bool { // A null script is of the form: // OP_RETURN // // Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a // canonical data push up to MaxDataCarrierSizeV0 bytes. // The script can't possibly be a null data script if it doesn't start // with OP_RETURN. Fail fast to avoid more work below. if len(script) < 1 || script[0] != txscript.OP_RETURN { return false } // Single OP_RETURN. if len(script) == 1 { return true } // OP_RETURN followed by a canonical data push up to MaxDataCarrierSizeV0 // bytes in length. const scriptVersion = 0 tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script[1:]) return tokenizer.Next() && tokenizer.Done() && len(tokenizer.Data()) <= MaxDataCarrierSizeV0 && isCanonicalPushV0(tokenizer.Opcode(), tokenizer.Data()) } // extractStakePubKeyHashV0 extracts the public key hash from the passed script // if it is a standard version 0 stake-tagged pay-to-pubkey-hash script with the // provided stake opcode. It will return nil otherwise. func extractStakePubKeyHashV0(script []byte, stakeOpcode byte) []byte { // A stake-tagged pay-to-pubkey-hash is of the form: // // The script can't possibly be a stake-tagged pay-to-pubkey-hash if it // doesn't start with the given stake opcode. Fail fast to avoid more work // below. if len(script) < 1 || script[0] != stakeOpcode { return nil } return ExtractPubKeyHashV0(script[1:]) } // ExtractStakeSubmissionPubKeyHashV0 extracts the public key hash from the // passed script if it is a standard version 0 stake submission // pay-to-pubkey-hash script. It will return nil otherwise. func ExtractStakeSubmissionPubKeyHashV0(script []byte) []byte { // A stake submission pay-to-pubkey-hash script is of the form: // OP_SSTX const stakeOpcode = txscript.OP_SSTX return extractStakePubKeyHashV0(script, stakeOpcode) } // IsStakeSubmissionPubKeyHashScriptV0 returns whether or not the passed script // is a standard version 0 stake submission pay-to-pubkey-hash script. func IsStakeSubmissionPubKeyHashScriptV0(script []byte) bool { return ExtractStakeSubmissionPubKeyHashV0(script) != nil } // ExtractStakePubKeyHashV0 extracts the public key hash from the passed script // if it is any one of the supported standard version 0 stake-tagged // pay-to-pubkey-hash scripts. It will return nil otherwise. func ExtractStakePubKeyHashV0(script []byte) []byte { if h := ExtractStakeSubmissionPubKeyHashV0(script); h != nil { return h } if h := ExtractStakeGenPubKeyHashV0(script); h != nil { return h } if h := ExtractStakeRevocationPubKeyHashV0(script); h != nil { return h } if h := ExtractStakeChangePubKeyHashV0(script); h != nil { return h } if h := ExtractTreasuryGenPubKeyHashV0(script); h != nil { return h } return nil } // extractStakeScriptHashV0 extracts the script hash from the passed script if // it is a standard version 0 stake-tagged pay-to-script-hash script with the // provided stake opcode. It will return nil otherwise. func extractStakeScriptHashV0(script []byte, stakeOpcode byte) []byte { // A stake-tagged pay-to-script-hash is of the form: // // The script can't possibly be a stake-tagged pay-to-script-hash if it // doesn't start with the given stake opcode. Fail fast to avoid more work // below. if len(script) < 1 || script[0] != stakeOpcode { return nil } return txscript.ExtractScriptHash(script[1:]) } // ExtractStakeSubmissionScriptHashV0 extracts the script hash from the // passed script if it is a standard version 0 stake submission // pay-to-script-hash script. It will return nil otherwise. func ExtractStakeSubmissionScriptHashV0(script []byte) []byte { // A stake submission pay-to-script-hash script is of the form: // OP_SSTX const stakeOpcode = txscript.OP_SSTX return extractStakeScriptHashV0(script, stakeOpcode) } // IsStakeSubmissionScriptHashScriptV0 returns whether or not the passed script // is a standard version 0 stake submission pay-to-script-hash script. func IsStakeSubmissionScriptHashScriptV0(script []byte) bool { return ExtractStakeSubmissionScriptHashV0(script) != nil } // ExtractStakeGenPubKeyHashV0 extracts the public key hash from the // passed script if it is a standard version 0 stake generation // pay-to-pubkey-hash script. It will return nil otherwise. func ExtractStakeGenPubKeyHashV0(script []byte) []byte { // A stake generation pay-to-pubkey-hash script is of the form: // OP_SSGEN const stakeOpcode = txscript.OP_SSGEN return extractStakePubKeyHashV0(script, stakeOpcode) } // IsStakeGenPubKeyHashScriptV0 returns whether or not the passed script is a // standard version 0 stake generation pay-to-pubkey-hash script. func IsStakeGenPubKeyHashScriptV0(script []byte) bool { return ExtractStakeGenPubKeyHashV0(script) != nil } // ExtractStakeGenScriptHashV0 extracts the script hash from the passed // script if it is a standard version 0 stake generation pay-to-script-hash // script. It will return nil otherwise. func ExtractStakeGenScriptHashV0(script []byte) []byte { // A stake generation pay-to-script-hash script is of the form: // OP_SSGEN const stakeOpcode = txscript.OP_SSGEN return extractStakeScriptHashV0(script, stakeOpcode) } // IsStakeGenScriptHashScriptV0 returns whether or not the passed script is a // standard version 0 stake generation pay-to-script-hash script. func IsStakeGenScriptHashScriptV0(script []byte) bool { return ExtractStakeGenScriptHashV0(script) != nil } // ExtractStakeRevocationPubKeyHashV0 extracts the public key hash from // the passed script if it is a standard version 0 stake revocation // pay-to-pubkey-hash script. It will return nil otherwise. func ExtractStakeRevocationPubKeyHashV0(script []byte) []byte { // A stake revocation pay-to-pubkey-hash script is of the form: // OP_SSRTX const stakeOpcode = txscript.OP_SSRTX return extractStakePubKeyHashV0(script, stakeOpcode) } // IsStakeRevocationPubKeyHashScriptV0 returns whether or not the passed script // is a standard version 0 stake revocation pay-to-pubkey-hash script. func IsStakeRevocationPubKeyHashScriptV0(script []byte) bool { return ExtractStakeRevocationPubKeyHashV0(script) != nil } // ExtractStakeRevocationScriptHashV0 extracts the script hash from the // passed script if it is a standard version 0 stake revocation // pay-to-script-hash script. It will return nil otherwise. func ExtractStakeRevocationScriptHashV0(script []byte) []byte { // A stake revocation pay-to-script-hash script is of the form: // OP_SSRTX const stakeOpcode = txscript.OP_SSRTX return extractStakeScriptHashV0(script, stakeOpcode) } // IsStakeRevocationScriptHashScriptV0 returns whether or not the passed script // is a standard version 0 stake revocation pay-to-script-hash script. func IsStakeRevocationScriptHashScriptV0(script []byte) bool { return ExtractStakeRevocationScriptHashV0(script) != nil } // ExtractStakeChangePubKeyHashV0 extracts the public key hash from the // passed script if it is a standard version 0 stake change pay-to-pubkey-hash // script. It will return nil otherwise. func ExtractStakeChangePubKeyHashV0(script []byte) []byte { // A stake change pay-to-pubkey-hash script is of the form: // OP_SSTXCHANGE const stakeOpcode = txscript.OP_SSTXCHANGE return extractStakePubKeyHashV0(script, stakeOpcode) } // IsStakeChangePubKeyHashScriptV0 returns whether or not the passed script is a // standard version 0 stake change pay-to-pubkey-hash script. func IsStakeChangePubKeyHashScriptV0(script []byte) bool { return ExtractStakeChangePubKeyHashV0(script) != nil } // ExtractStakeChangeScriptHashV0 extracts the script hash from the passed // script if it is a standard version 0 stake change pay-to-script-hash // script. It will return nil otherwise. func ExtractStakeChangeScriptHashV0(script []byte) []byte { // A stake change pay-to-script-hash script is of the form: // OP_SSTXCHANGE const stakeOpcode = txscript.OP_SSTXCHANGE return extractStakeScriptHashV0(script, stakeOpcode) } // IsStakeChangeScriptHashScriptV0 returns whether or not the passed script is a // standard version 0 stake change pay-to-script-hash script. func IsStakeChangeScriptHashScriptV0(script []byte) bool { return ExtractStakeChangeScriptHashV0(script) != nil } // IsTreasuryAddScriptV0 returns whether or not the passed script is a supported // version 0 treasury add script. func IsTreasuryAddScriptV0(script []byte) bool { return len(script) == 1 && script[0] == txscript.OP_TADD } // ExtractTreasuryGenPubKeyHashV0 extracts the public key hash from the // passed script if it is a standard version 0 treasury generation // pay-to-pubkey-hash script. It will return nil otherwise. func ExtractTreasuryGenPubKeyHashV0(script []byte) []byte { // A treasury generation pay-to-pubkey-hash script is of the form: // OP_TGEN const stakeOpcode = txscript.OP_TGEN return extractStakePubKeyHashV0(script, stakeOpcode) } // IsTreasuryGenPubKeyHashScriptV0 returns whether or not the passed script is a // standard version 0 treasury generation pay-to-pubkey-hash script. func IsTreasuryGenPubKeyHashScriptV0(script []byte) bool { return ExtractTreasuryGenPubKeyHashV0(script) != nil } // ExtractTreasuryGenScriptHashV0 extracts the script hash from the passed // script if it is a standard version 0 treasury generation pay-to-script-hash // script. It will return nil otherwise. func ExtractTreasuryGenScriptHashV0(script []byte) []byte { // A treasury generation pay-to-script-hash script is of the form: // OP_TGEN const stakeOpcode = txscript.OP_TGEN return extractStakeScriptHashV0(script, stakeOpcode) } // IsTreasuryGenScriptHashScriptV0 returns whether or not the passed script is a // standard version 0 treasury generation pay-to-script-hash script. func IsTreasuryGenScriptHashScriptV0(script []byte) bool { return ExtractTreasuryGenScriptHashV0(script) != nil } // ExtractStakeScriptHashV0 extracts the script hash from the passed script if // it is any one of the supported standard version 0 stake-tagged // pay-to-script-hash scripts. It will return nil otherwise. func ExtractStakeScriptHashV0(script []byte) []byte { if h := ExtractStakeSubmissionScriptHashV0(script); h != nil { return h } if h := ExtractStakeGenScriptHashV0(script); h != nil { return h } if h := ExtractStakeRevocationScriptHashV0(script); h != nil { return h } if h := ExtractStakeChangeScriptHashV0(script); h != nil { return h } if h := ExtractTreasuryGenScriptHashV0(script); h != nil { return h } return nil } // DetermineScriptTypeV0 returns the type of the passed version 0 script for // the known standard types. This includes both types that are required by // consensus as well as those which are not. // // STNonStandard will be returned when the script does not parse. func DetermineScriptTypeV0(script []byte) ScriptType { switch { case IsPubKeyScriptV0(script): return STPubKeyEcdsaSecp256k1 case IsPubKeyEd25519ScriptV0(script): return STPubKeyEd25519 case IsPubKeySchnorrSecp256k1ScriptV0(script): return STPubKeySchnorrSecp256k1 case IsPubKeyHashScriptV0(script): return STPubKeyHashEcdsaSecp256k1 case IsPubKeyHashEd25519ScriptV0(script): return STPubKeyHashEd25519 case IsPubKeyHashSchnorrSecp256k1ScriptV0(script): return STPubKeyHashSchnorrSecp256k1 case IsScriptHashScriptV0(script): return STScriptHash case IsMultiSigScriptV0(script): return STMultiSig case IsNullDataScriptV0(script): return STNullData case IsStakeSubmissionPubKeyHashScriptV0(script): return STStakeSubmissionPubKeyHash case IsStakeSubmissionScriptHashScriptV0(script): return STStakeSubmissionScriptHash case IsStakeGenPubKeyHashScriptV0(script): return STStakeGenPubKeyHash case IsStakeGenScriptHashScriptV0(script): return STStakeGenScriptHash case IsStakeRevocationPubKeyHashScriptV0(script): return STStakeRevocationPubKeyHash case IsStakeRevocationScriptHashScriptV0(script): return STStakeRevocationScriptHash case IsStakeChangePubKeyHashScriptV0(script): return STStakeChangePubKeyHash case IsStakeChangeScriptHashScriptV0(script): return STStakeChangeScriptHash case IsTreasuryAddScriptV0(script): return STTreasuryAdd case IsTreasuryGenPubKeyHashScriptV0(script): return STTreasuryGenPubKeyHash case IsTreasuryGenScriptHashScriptV0(script): return STTreasuryGenScriptHash } return STNonStandard } // DetermineRequiredSigsV0 attempts to identify the number of signatures // required by the passed version 0 script for the known standard types. // // It will return 0 when the script does not parse or is not one of the known // standard types. func DetermineRequiredSigsV0(script []byte) uint16 { scriptType := DetermineScriptTypeV0(script) switch scriptType { case STPubKeyHashEcdsaSecp256k1, STScriptHash, STPubKeyHashEd25519, STPubKeyHashSchnorrSecp256k1, STPubKeyEcdsaSecp256k1, STPubKeyEd25519, STPubKeySchnorrSecp256k1, STStakeSubmissionPubKeyHash, STStakeSubmissionScriptHash, STStakeGenPubKeyHash, STStakeGenScriptHash, STStakeRevocationPubKeyHash, STStakeRevocationScriptHash, STStakeChangePubKeyHash, STStakeChangeScriptHash, STTreasuryGenPubKeyHash, STTreasuryGenScriptHash: return 1 case STMultiSig: details := ExtractMultiSigScriptDetailsV0(script, false) if details.Valid { return details.RequiredSigs } return 0 case STNullData, STTreasuryAdd: return 0 } return 0 } // MultiSigScriptV0 returns a valid version 0 script for a multisignature // redemption where the specified threshold number of the keys in the given // public keys are required to have signed the transaction for success. // // The provided public keys must be serialized in the compressed format or an // error with kind ErrPubKeyType will be returned. // // An Error with kind ErrTooManyRequiredSigs will be returned if the threshold // is larger than the number of keys provided. func MultiSigScriptV0(threshold int, pubKeys ...[]byte) ([]byte, error) { if threshold < 0 { str := fmt.Sprintf("unable to generate multisig script with %d "+ "required signatures", threshold) return nil, makeError(ErrNegativeRequiredSigs, str) } if len(pubKeys) < threshold { str := fmt.Sprintf("unable to generate multisig script with %d "+ "required signatures when there are only %d public keys available", threshold, len(pubKeys)) return nil, makeError(ErrTooManyRequiredSigs, str) } builder := txscript.NewScriptBuilder().AddInt64(int64(threshold)) for _, pubKey := range pubKeys { if !txscript.IsStrictCompressedPubKeyEncoding(pubKey) { str := fmt.Sprintf("unable to generate multisig script with "+ "unsupported public key %x", pubKey) return nil, makeError(ErrPubKeyType, str) } builder.AddData(pubKey) } builder.AddInt64(int64(len(pubKeys))) builder.AddOp(txscript.OP_CHECKMULTISIG) return builder.Script() } // ProvablyPruneableScriptV0 returns a valid version 0 provably-pruneable script // which consists of an OP_RETURN followed by the passed data. An Error with // kind ErrTooMuchNullData will be returned if the length of the passed data // exceeds MaxDataCarrierSizeV0. func ProvablyPruneableScriptV0(data []byte) ([]byte, error) { if len(data) > MaxDataCarrierSizeV0 { str := fmt.Sprintf("data size %d is larger than max allowed size %d", len(data), MaxDataCarrierSizeV0) return nil, makeError(ErrTooMuchNullData, str) } builder := txscript.NewScriptBuilder() return builder.AddOp(txscript.OP_RETURN).AddData(data).Script() } // AtomicSwapDataPushesV0 houses the data pushes found in hash-based atomic swap // contracts using version 0 scripts. type AtomicSwapDataPushesV0 struct { RecipientHash160 [20]byte RefundHash160 [20]byte SecretHash [32]byte SecretSize int64 LockTime int64 } // ExtractAtomicSwapDataPushesV0 returns the data pushes from an atomic swap // contract using version 0 scripts if it is one. It will return nil otherwise. // // NOTE: Atomic swaps are not considered standard script types by the dcrd // mempool policy and should be used with P2SH. The atomic swap format is also // expected to change to use a more secure hash function in the future. func ExtractAtomicSwapDataPushesV0(redeemScript []byte) *AtomicSwapDataPushesV0 { // Local constants for convenience. const ( maxMathOpCodeLen = txscript.MathOpCodeMaxScriptNumLen maxCltvLen = txscript.CltvMaxScriptNumLen ) // An atomic swap is of the form: // IF // SIZE EQUALVERIFY // SHA256 <32-byte secret hash> EQUALVERIFY DUP // HASH160 <20-byte recipient hash> // ELSE // CHECKLOCKTIMEVERIFY DROP DUP HASH160 <20-byte refund hash> // ENDIF // EQUALVERIFY CHECKSIG type templateMatch struct { expectCanonicalInt bool maxIntBytes int opcode byte extractedInt int64 extractedData []byte } var template = [20]templateMatch{ {opcode: txscript.OP_IF}, {opcode: txscript.OP_SIZE}, {expectCanonicalInt: true, maxIntBytes: maxMathOpCodeLen}, {opcode: txscript.OP_EQUALVERIFY}, {opcode: txscript.OP_SHA256}, {opcode: txscript.OP_DATA_32}, {opcode: txscript.OP_EQUALVERIFY}, {opcode: txscript.OP_DUP}, {opcode: txscript.OP_HASH160}, {opcode: txscript.OP_DATA_20}, {opcode: txscript.OP_ELSE}, {expectCanonicalInt: true, maxIntBytes: maxCltvLen}, {opcode: txscript.OP_CHECKLOCKTIMEVERIFY}, {opcode: txscript.OP_DROP}, {opcode: txscript.OP_DUP}, {opcode: txscript.OP_HASH160}, {opcode: txscript.OP_DATA_20}, {opcode: txscript.OP_ENDIF}, {opcode: txscript.OP_EQUALVERIFY}, {opcode: txscript.OP_CHECKSIG}, } const scriptVersion = 0 var templateOffset int tokenizer := txscript.MakeScriptTokenizer(scriptVersion, redeemScript) for tokenizer.Next() { // Not an atomic swap script if it has more opcodes than expected in the // template. if templateOffset >= len(template) { return nil } op := tokenizer.Opcode() data := tokenizer.Data() tplEntry := &template[templateOffset] if tplEntry.expectCanonicalInt { switch { case data != nil: val, err := txscript.MakeScriptNum(data, tplEntry.maxIntBytes) if err != nil { return nil } tplEntry.extractedInt = int64(val) case txscript.IsSmallInt(op): tplEntry.extractedInt = int64(txscript.AsSmallInt(op)) // Not an atomic swap script if the opcode does not push an int. default: return nil } } else { if op != tplEntry.opcode { return nil } tplEntry.extractedData = data } templateOffset++ } if err := tokenizer.Err(); err != nil { return nil } if !tokenizer.Done() || templateOffset != len(template) { return nil } // At this point, the script appears to be an atomic swap, so populate and // return the extracted data. pushes := AtomicSwapDataPushesV0{ SecretSize: template[2].extractedInt, LockTime: template[11].extractedInt, } copy(pushes.SecretHash[:], template[5].extractedData) copy(pushes.RecipientHash160[:], template[9].extractedData) copy(pushes.RefundHash160[:], template[16].extractedData) return &pushes }