1

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()

Here you can see it's only asking for my payment key signature.

enter image description here

From the looks of it, there is nothing wrong with this. But the problem is that Franken address will easily get pass the whitelist check condition with a spoofed stake address.

I am wondering if it's possible to ask for both payment and stake key signature in a transaction (maybe without involving any actual staking activity, such as delegating to a pool) so that a franken wallet cannot sign the tx as they don't have a staking private key.

Any ideas?

0