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.
Combining the values
Section titled “Combining the values”SigHashType is an enum of flags. We combine them with | and pass the result to TransactionSigner.
int all = SigHashType.ALL.value | SigHashType.FORKID.value; // 0x41int none = SigHashType.NONE.value | SigHashType.FORKID.value; // 0x42int single = SigHashType.SINGLE.value | SigHashType.FORKID.value; // 0x43int 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); // 0xC3FORKID is required
Section titled “FORKID is required”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:
- We need
build(false), because at that point the builder’s checks see the Outputs exceed the Inputs. TransactionInputtakes the previous txid in display byte order, which is whatgetTransactionId()returns as hex.
NONE(Outputs not covered) andSINGLE(only the Output at the same index) work the same way throughTransactionSigner. They are what lets others add or change Outputs.
Signing with SIGHASH_CHRONICLE
Section titled “Signing with SIGHASH_CHRONICLE”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)));
