Sighash types and signing
A Signature commits to a digest of the Transaction. The Sighash type decides which Inputs and Outputs go into that digest. The guide’s Signature schemes page explains the options.
The examples use the keys and the fundingTransaction helper described in About the examples. This page’s examples share the following setup:
final myAddress = aliceKey.toAddress(networkType: NetworkType.TEST);final lockingScript = P2PKHLockBuilder.fromAddress(myAddress).getScriptPubkey();final fundingTx = fundingTransaction(lockingScript, BigInt.from(100000));final otherFunding = fundingTransaction(P2PKHLockBuilder.fromAddress(bobKey.toAddress(networkType: NetworkType.TEST)).getScriptPubkey(), BigInt.from(5000));Sighash types
Section titled “Sighash types”// We combine a base type with modifiers using a bitwise OR. On BSV, FORKID is required.final all = SighashType.SIGHASH_ALL.value | SighashType.SIGHASH_FORKID.value; // 0x41final none = SighashType.SIGHASH_NONE.value | SighashType.SIGHASH_FORKID.value; // 0x42final single = SighashType.SIGHASH_SINGLE.value | SighashType.SIGHASH_FORKID.value; // 0x43final allAnyoneCanPay = all | SighashType.SIGHASH_ANYONECANPAY.value; // 0xC1SIGHASH_FORKID selects the digest algorithm that Bitcoin SV uses (BIP143-style, committing to the amount being
spent). Nodes reject Signatures without it.
ANYONECANPAY
Section titled “ANYONECANPAY”SIGHASH_ANYONECANPAY commits to our own Input only, so that others can add Inputs after we have signed, for
example to crowdfund a payment. With plain SIGHASH_ALL, adding an Input invalidates the existing Signatures.
// Alice signs her Input over all the Outputs, but only over her own Input.final aliceSigner = DefaultTransactionSigner( SighashType.SIGHASH_ALL.value | SighashType.SIGHASH_ANYONECANPAY.value | SighashType.SIGHASH_FORKID.value, aliceKey);
final tx = TransactionBuilder() .spendFromTxnWithSigner(aliceSigner, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, P2PKHUnlockBuilder(aliceKey.publicKey)) .spendToPKH(Address('n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg'), BigInt.from(104000)) .build(false);
// Bob adds and signs an Input of his own, and Alice's Signature stays valid.tx.addInput(TransactionInput(otherFunding.id, 0, TransactionInput.MAX_SEQ_NUMBER, scriptBuilder: P2PKHUnlockBuilder(bobKey.publicKey)));DefaultTransactionSigner(sighashAll, bobKey).sign(tx, otherFunding.outputs[0], 1);A custom signer
Section titled “A custom signer”TransactionSigner is an abstract class with two methods: sign, which signs one Input, and signPreimage.
DefaultTransactionSigner implements it with a Private Key held in memory. You would implement it yourself when
the key lives elsewhere, e.g. in a hardware wallet, a remote signing service or a platform keystore. The signer
computes the digest with Sighash, gets a Signature for it, and adds the Signature to the Input’s Unlocking
Script builder:
/// A signer that never holds the Private Key. It computes the digest and asks/// [signDigest] (a hardware wallet, a remote key service, a secure enclave) for the Signature.class DelegatingSigner extends TransactionSigner { @override final int sigHashType; final SVSignature Function(String digestHex) signDigest;
DelegatingSigner(this.sigHashType, this.signDigest);
@override Transaction sign(Transaction unsignedTxn, TransactionOutput utxo, int inputIndex) { final hash = Sighash().hash(unsignedTxn, sigHashType, inputIndex, utxo.script, utxo.satoshis); // Sighash.hash returns the digest byte-reversed, so we sign the digest in its natural order. final digest = HEX.encode(HEX.decode(hash).reversed.toList());
final signature = signDigest(digest)..nhashtype = sigHashType; unsignedTxn.inputs[inputIndex].scriptBuilder!.signatures.add(signature); return unsignedTxn; }
@override SVSignature signPreimage(Uint8List preImage) => signDigest(HEX.encode(sha256Twice(preImage.toList())))..nhashtype = sigHashType;}It plugs into the builder in the same way as the default signer, and produces the same Transaction:
final signer = DelegatingSigner(sighashAll, device);
final tx = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, P2PKHUnlockBuilder(aliceKey.publicKey)) .spendToPKH(myAddress, BigInt.from(99900)) .build(true);Do note that
Sighash.hashreturns the digest hex in reversed byte order. We reverse it before signing, as the example does.DefaultTransactionSignerdoes the same.
The Sighash preimage
Section titled “The Sighash preimage”The preimage is the serialised data from which the digest is computed. Contracts that inspect their own spending Transaction (the OP_PUSH_TX technique) take it as part of the Unlocking Script.
// The bytes a Signature commits to (BIP143-style, as BSV uses with FORKID).// OP_PUSH_TX-style contracts take this preimage in their Unlocking Script.final Uint8List preimage = Sighash().createSighashPreImage( tx, sighashAll, 0, fundingTx.outputs[0].script, fundingTx.outputs[0].satoshis)!;
final digest = sha256Twice(preimage); // what ECDSA signsInspecting a Signature
Section titled “Inspecting a Signature”// sigHex: a Signature as it appears in an Unlocking Script (DER + Sighash byte)final signature = SVSignature.fromTxFormat(sigHex);
signature.nhashtype; // 0x41: SIGHASH_ALL | SIGHASH_FORKIDsignature.hasLowS(); // true: the library always produces low-S Signaturessignature.r; // the ECDSA values, as BigIntsignature.s;
DefaultTransactionSigneralways computes the FORKID digest. To sign with the ChronicleSIGHASH_CHRONICLEflag (which selects the original digest algorithm), one writes a signer that callsSighash().hash(…, flags: ScriptFlags.SCRIPT_ENABLE_SIGHASH_FORKID | ScriptFlags.SCRIPT_ENABLE_CHRONICLE).

