Skip to content

Sighash types

A Sighash type is the last byte of every Transaction Signature, and it says which parts of the Transaction the Signature commits to. The page on signature schemes in the Developer Guide explains each type and what it allows. The developer is encouraged to read it before using anything other than ALL.

The examples use the setup from the Transaction builder, plus Carol, a second key holder.

SigHashType is an enum of flags. We combine them with | and pass the result to TransactionSigner.

int all = SigHashType.ALL.value | SigHashType.FORKID.value; // 0x41
int none = SigHashType.NONE.value | SigHashType.FORKID.value; // 0x42
int single = SigHashType.SINGLE.value | SigHashType.FORKID.value; // 0x43
int allAnyoneCanPay = SigHashType.ALL.value | SigHashType.FORKID.value | SigHashType.ANYONECANPAY.value; // 0xC1
// The helper gives the same values from mode, anyoneCanPay and useForkId.
int singleAnyoneCanPay = TransactionSignature.calcSigHashValue(SigHashType.SINGLE, true, true); // 0xC3

BSV Signatures use the FORKID digest, which also commits to the amount being spent. If we leave out SigHashType.FORKID, the signer uses the original digest algorithm, which BSV nodes reject.

// Without FORKID the Signature uses the original algorithm, which BSV nodes reject.
Transaction tx = new TransactionBuilder()
.spendFromTransaction(new TransactionSigner(SigHashType.ALL.value, alice), fundingTx, 0,
TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey()))
.spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000))
.sendChangeTo(aliceAddress)
.build(true);
assertThrows(ScriptException.class, () -> Fixtures.verifySpend(tx, 0, fundingTx.getOutputs().get(0)));

Letting others add Inputs with ANYONECANPAY

Section titled “Letting others add Inputs with ANYONECANPAY”

A Signature with ANYONECANPAY covers only its own Input, so others can add Inputs after we sign. In the following example Alice commits to paying Bob 110,000 satoshis, but only holds 100,000. Carol then adds an Input to make up the difference.

// Alice signs her Input with ALL | ANYONECANPAY. Her Signature covers all the Outputs
// but only her own Input, so others can add Inputs afterwards.
int aliceSighash = SigHashType.ALL.value | SigHashType.ANYONECANPAY.value | SigHashType.FORKID.value;
Transaction tx = new TransactionBuilder()
.spendFromTransaction(new TransactionSigner(aliceSighash, alice), aliceFunding, 0,
TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey()))
.spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(110_000))
.build(false); // false: the outputs exceed Alice's input until Carol adds hers
// Carol adds her Input and signs it.
P2PKHUnlockBuilder carolUnlocker = new P2PKHUnlockBuilder(carol.getPublicKey());
tx.addInput(new TransactionInput(Utils.HEX.decode(carolFunding.getTransactionId()), 0,
TransactionInput.MAX_SEQ_NUMBER, carolUnlocker));
int all = SigHashType.ALL.value | SigHashType.FORKID.value;
new TransactionSigner(all, carol).sign(tx, carolFunding.getOutputs().get(0), 1);

Both Inputs verify afterwards. Referring to the preceding example, we notice a few things:

  1. We need build(false), because at that point the builder’s checks see the Outputs exceed the Inputs.
  2. TransactionInput takes the previous txid in display byte order, which is what getTransactionId() returns as hex.

NONE (Outputs not covered) and SINGLE (only the Output at the same index) work the same way through TransactionSigner. They are what lets others add or change Outputs.

The Chronicle upgrade added a Sighash flag, SigHashType.CHRONICLE (0x20). A Signature with this flag uses the original digest algorithm with the full Locking Script, rather than the FORKID digest. The interpreter only accepts such Signatures with the AFTER_CHRONICLE flag.

TransactionSigner reads the flag, and computes the matching digest for us.

int chronicle = SigHashType.ALL.value | SigHashType.FORKID.value | SigHashType.CHRONICLE.value; // 0x61
// TransactionSigner sees the CHRONICLE flag, and computes the original digest.
Transaction tx = new TransactionBuilder()
.spendFromTransaction(new TransactionSigner(chronicle, alice), fundingTx, 0,
TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey()))
.spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000))
.sendChangeTo(aliceAddress)
.build(true);
// The interpreter accepts it under the Chronicle rules.
Set<Script.VerifyFlag> chronicleFlags = EnumSet.copyOf(Fixtures.FLAGS);
chronicleFlags.add(Script.VerifyFlag.AFTER_CHRONICLE);
assertDoesNotThrow(() -> new Interpreter().correctlySpends(tx.getInputs().get(0).getScriptSig(),
utxo.getScript(), tx, 0, chronicleFlags, Coin.valueOf(100_000)));