Building a transaction that will require both payment and stake key?

Hi,

I was just wondering, is it possible to build a transaction with Cardano Serialization Lib where both payment and stake key will be required without involving any staking operation such as delegating a pool? I need this to prevent the Franken address attack.

Usually, when a build a simple transaction such as making a payment, the transaction usually requires the buyer’s payment key. Here is an example transaction I made with Cardano Serialization Lib.

// Payload from API user
const payload = {
    address: 'addr_test...',
    utxos: [...]
}


const whitelist = ['stake_test1...', 'stake12']
const paymentAddress = 'addr_test1';
const amount = 10 * 1000000


const buyerStakeAddress = RewardAddress.new(
    Address.from_bech32(payload.address).network_id(),
    BaseAddress.from_address(Address.from_bech32(payload.address)).stake_cred()
)


// Franken address will get pass this
if( !whitelist.includes(buyerStakeAddress.to_address().to_bech32()) )
    return false


const protocol_params = await this.get_protocol_parameters(NetworkInfo.testnet_preprod().network_id())
const linearFee = LinearFee.new(
    BigNum.from_str(protocol_params.min_fee_a.toString()),
    BigNum.from_str(protocol_params.min_fee_b.toString()),
)

const txBuilderCfg = TransactionBuilderConfigBuilder.new()
    .fee_algo(linearFee)
    .pool_deposit(BigNum.from_str(protocol_params.pool_deposit))
    .key_deposit(BigNum.from_str(protocol_params.key_deposit))
    .max_value_size(parseInt(protocol_params.max_val_size))
    .max_tx_size(protocol_params.max_tx_size)
    .coins_per_utxo_word(BigNum.from_str(protocol_params.coins_per_utxo_word))
    .build();

const txBuilder = TransactionBuilder.new(txBuilderCfg);
const inputs = TransactionUnspentOutputs.new()

payload.utxos.forEach(raw => {
    inputs.add(TransactionUnspentOutput.from_hex(raw))
})

txBuilder.add_output(TransactionOutput.new(
    Address.from_bech32(paymentAddress),
    Value.new( BigNum.from_str(amount.toString()) )
))

txBuilder.add_inputs_from(inputs, CoinSelectionStrategyCIP2.RandomImprove)
txBuilder.set_ttl(protocol_params.slot + 3600)
txBuilder.add_change_if_needed(Address.from_bech32(payload.address));

const body = txBuilder.build()
const witnesses = TransactionWitnessSet.new();
const auxFinal = AuxiliaryData.new()
const transaction: Transaction = Transaction.new( body, witnesses, auxFinal )

return transaction.to_hex()

But this transaction is prone to a Franken address attack as it only requires a payment key signature. How do I build a transaction that will require both payment and staking signatures from the user?

3 Likes

Hey! Were you able to solve this?

how about a withdraw 0 rewards ?

You can’t choose how much you want to withdraw. A withdraw transaction always withdraws everything (and is as far as I remember only possible if there is something to withdraw).

But one can just add required signers to the transaction.

1 Like

it is either 0 or the full amount. :thinking:

For anyone wondering, the solution is quite easy. CSL has built-in support to add any particular key (stake or payment) to be required as a signer.

txBuilder.add_required_signer(paymentOrStakeEd25519KeyHash)

This is all you need.

2 Likes