Function getChallenge

Gets the challenge hash for the provided transaction, input index, and the type of SigHash. This cannot be folded into a sign routine because it's also required during signature validation.

geod24.bitblob.BitBlob!(64L) getChallenge (
  in ref const(Transaction) tx,
  in const(SigHash) sig_hash = SigHash.All,
  in const(ulong) input_idx = 0LU,
  in const(ulong) output_idx = 0LU
) nothrow @safe;

The input index is only used for some types of SigHash (SigHash.NoInput).

Parameters

NameDescription
tx the transaction to sign
sig_hash the SigHash to use
input_idx the associated input index we're signing for
output_idx the associated output index we're signing for

Returns

the challenge as a hash

Example

import agora.crypto.Key;
import agora.common.Amount;
import agora.utils.Test;

// SigHash.All
{
    auto tx = Transaction([Input(hashFull(1)), Input(hashFull(2))], [Output(Amount(1), PublicKey.init)], Height(10));
    auto challenge_idx_0 = getChallenge(tx, SigHash.All, 0);
    assert(challenge_idx_0 != tx.hashFull());
    assert(challenge_idx_0 == getChallenge(tx, SigHash.All, 1));
    tx.inputs[0] = Input(hashFull(3));
    assert(challenge_idx_0 != getChallenge(tx, SigHash.All, 1));
}

// SigHash.NoInput
{
    auto tx = Transaction([Input(hashFull(1)), Input(hashFull(2))], [Output(Amount(1), PublicKey.init)], Height(10));
    auto challenge_idx_0 = getChallenge(tx, SigHash.NoInput, 0);
    assert(challenge_idx_0 != getChallenge(tx, SigHash.NoInput, 1));
    // Redirect input_0
    tx.inputs[0] = Input(hashFull(3));
    assert(challenge_idx_0 == getChallenge(tx, SigHash.NoInput, 0));
    // Redirect input_1
    tx.inputs[1] = Input(hashFull(4));
    assert(challenge_idx_0 != getChallenge(tx, SigHash.NoInput, 0));

    // restore input_1
    tx.inputs[1] = Input(hashFull(2));
    // add a new output
    tx.outputs ~= Output(Amount(2), PublicKey.init);
    assert(challenge_idx_0 != getChallenge(tx, SigHash.NoInput, 0));
}

// SigHash.Single
{
    auto tx = Transaction([Input(hashFull(1)), Input(hashFull(2))], [Output(Amount(1), PublicKey.init)], Height(10));
    auto challenge_idx_0 = getChallenge(tx, SigHash.Single, 0, 0);

    // since SigHash.Single signs all inputs, challenges for different inputs should be the same
    assert(challenge_idx_0 == getChallenge(tx, SigHash.Single, 1, 0));

    // add a new not signed output
    tx.outputs ~= Output(Amount(2), PublicKey.init);
    // old challenge should still hold
    assert(challenge_idx_0 == getChallenge(tx, SigHash.Single, 0, 0));
    assert(challenge_idx_0 != getChallenge(tx, SigHash.Single, 0, 1));

    tx.outputs = Output(Amount(0), PublicKey.init) ~ tx.outputs;
    // output changes index, after updating the index challenge should hold
    assert(challenge_idx_0 == getChallenge(tx, SigHash.Single, 0, 1));

    // Redirect input_0
    tx.inputs[0] = Input(hashFull(3));
    assert(challenge_idx_0 != getChallenge(tx, SigHash.Single, 0, 1));

    // restore input_0 and add a new input
    tx.inputs[0] = Input(hashFull(1));
    tx.inputs ~= Input(hashFull(3));
    tx.inputs.sort();
    assert(challenge_idx_0 != getChallenge(tx, SigHash.Single, 0, 1));
}

// SigHash.Single | SigHash.AnyoneCanPay
{
    auto tx = Transaction([Input(hashFull(1)), Input(hashFull(2))], [Output(Amount(1), PublicKey.init)], Height(10));
    auto challenge_idx_0 = getChallenge(tx, SigHash.Single_AnyoneCanPay, 0, 0);

    // add a new input, challenge should hold
    tx.inputs ~= Input(hashFull(0));
    assert(challenge_idx_0 == getChallenge(tx, SigHash.Single_AnyoneCanPay, 0, 0));

    // change an existing input, challenge should hold
    tx.inputs[1] = Input(hashFull(3));
    assert(challenge_idx_0 == getChallenge(tx, SigHash.Single_AnyoneCanPay, 0, 0));

    tx.inputs = Input.init ~ tx.inputs;
    // change input index, challenge should hold
    assert(challenge_idx_0 == getChallenge(tx, SigHash.Single_AnyoneCanPay, 1, 0));
}

// SigHash.Single | SigHash.NoInput | SigHash.AnyoneCanPay
{
    auto tx = Transaction([Input(hashFull(1)), Input(hashFull(2))], [Output(Amount(1), PublicKey.init)], Height(10));
    auto challenge_idx_0 = getChallenge(tx, SigHash.Single_NoInput_AnyoneCanPay, 0, 0);

    // change signed input, challenge should hold
    tx.inputs[0] = Input.init;
    assert(challenge_idx_0 == getChallenge(tx, SigHash.Single_NoInput_AnyoneCanPay, 0, 0));
}

// SigHash.OmitSingle
{
    auto tx = Transaction([Input(hashFull(1)), Input(hashFull(2))],
        [Output(Amount(1), PublicKey.init), Output(Amount(2), PublicKey.init)], Height(10));
    auto challenge_idx_0 = getChallenge(tx, SigHash.OmitSingle, 0, 0);

    // cannot add a new input
    tx.inputs ~= Input(hashFull(0));
    assert(challenge_idx_0 != getChallenge(tx, SigHash.OmitSingle, 0, 0));
    // revert
    tx.inputs = tx.inputs[0 .. $ - 1];

    auto old_input_0 = tx.inputs[0];
    // cannot change an input
    tx.inputs[0] = Input(hashFull(0));
    assert(challenge_idx_0 != getChallenge(tx, SigHash.OmitSingle, 0, 0));
    // revert
    tx.inputs[0] = old_input_0;

    auto old_output_1_value = tx.outputs[1].value;
    // cannot change signed output
    tx.outputs[1].value = Amount(3);
    assert(challenge_idx_0 != getChallenge(tx, SigHash.OmitSingle, 0, 0));
    // revert
    tx.outputs[1].value = old_output_1_value;

    // can change omitted output
    tx.outputs[0].value = Amount(3);
    assert(challenge_idx_0 == getChallenge(tx, SigHash.OmitSingle, 0, 0));

    // cannot add a new output
    tx.outputs ~= Output.init;
    assert(challenge_idx_0 != getChallenge(tx, SigHash.OmitSingle, 0, 0));
}

// SigHash.OmitSingle | SigHash.NoInput | SigHash.AnyoneCanPay
{
    auto tx = Transaction([Input(hashFull(1)), Input(hashFull(2))], [Output(Amount(1), PublicKey.init)], Height(10));
    auto challenge_idx_0 = getChallenge(tx, SigHash.OmitSingle_NoInput_AnyoneCanPay, 0, 0);

    // change signed input, challenge should hold
    tx.inputs[0] = Input.init;
    assert(challenge_idx_0 == getChallenge(tx, SigHash.OmitSingle_NoInput_AnyoneCanPay, 0, 0));
}