Skip to content

Instantly share code, notes, and snippets.

@hewigovens
Last active July 10, 2019 13:13
Show Gist options
  • Save hewigovens/f8d168b8d337292e0c12040c6dfaeb0f to your computer and use it in GitHub Desktop.
Save hewigovens/f8d168b8d337292e0c12040c6dfaeb0f to your computer and use it in GitHub Desktop.
Cardano Research

Cardano Topics

Definition

Address

  • bip39 phrases -> public key address?
  • script address / redeem address?
  • account public key
  • how to derive next receive / change address

Transaction

  • transaction binary format?
  • how to sign utxos (signature / witness)
  • how to construct output for 3 types of address?
  • how to calculate transaction fee?
  • utxo selection algorithm

RPC

  • query balance
  • query tx history
  • query tx detail
  • query utxos
  • bordcast raw tx
  • estimate fee

Reference

Cardano transaction signing

Spec:

Reference:

https://github.com/Emurgo/yoroi-mobile/blob/342e295502/src/crypto/util.test.js#L135

describe('signTransaction', () => {
  const wallet = require('./__fixtures/fake_wallet.json')
  const inputs = require('./__fixtures/transaction_inputs.json')
  const outputAddress =
    'Ae2tdPwUPEZAghGCdQykbGxc991wdoA8bXmSn7eCGuUKXF4EsRhWj4PJitn'
  const change = 'Ae2tdPwUPEZJcamJUVWxJEwR8rj5x74t3FkUFDzKEdoL8YSyeRdwmJCW9c3'

  it('can sign small amount', async () => {
    expect.assertions(1)

    const outputs = [
      {
        address: outputAddress,
        value: '100',
      },
    ]

    const tx = await signTransaction(wallet, inputs, outputs, change)
    expect(tx).not.toBeNull()
  })
  +
})

https://github.com/Emurgo/yoroi-mobile/blob/342e295502/src/crypto/util.js#L160

export const signTransaction = async (
  wallet: any,
  inputs: Array<TransactionInput>,
  outputs: Array<TransactionOutput>,
  changeAddress: string,
) => {
  try {
    const result = await Wallet.spend(wallet, inputs, outputs, changeAddress)

    return {
      ...result,
      fee: new BigNumber(result.fee, 10),
    }
  } catch (e) {
      // check and throw KNOWN_ERROR_MSG / InsufficientFunds()
  }
}

https://github.com/input-output-hk/js-cardano-wasm/blob/master/wallet-wasm/src/lib.rs#L931

#[no_mangle]
pub extern "C" fn xwallet_spend(
    input_ptr: *const c_uchar,
    input_sz: usize,
    output_ptr: *mut c_uchar,
) -> i32 {
    let input: WalletSpendInput = input_json!(output_ptr, input_ptr, input_sz);
    let change = input.change_addr.clone();
    let wallet = input.wallet.to_wallet();
    let config = input.wallet.config;
    let (txaux, fee) = jrpc_try!(
        output_ptr,
        wallet.new_transaction(
            config.protocol_magic,
            input.wallet.selection_policy,
            input.get_inputs().iter(),
            input.get_outputs(),
            &txutils::OutputPolicy::One(input.change_addr)
        )
    );
    let changed_used = txaux.tx.outputs.iter().any(|out| out.address == change);
    let cbor = jrpc_try!(output_ptr, cbor!(&txaux));
    jrpc_ok!(
        output_ptr,
        WalletSpendOutput {
            cbor_encoded_tx: cbor,
            changed_used: changed_used,
            fee: Coin(fee.to_coin())
        }
    )
}

https://github.com/input-output-hk/rust-cardano/blob/6f2f790ee7/cardano/src/wallet/scheme.rs#L78

fn new_transaction<'a, I>(
    &self,
    protocol_magic: ProtocolMagic,
    selection_policy: SelectionPolicy,
    inputs: I,
    outputs: Vec<TxOut>,
    output_policy: &OutputPolicy,
) -> input_selection::Result<(tx::TxAux, fee::Fee)>
where
    I: 'a + Iterator<Item = &'a Input<Self::Addressing>> + ExactSizeIterator,
    Self::Addressing: 'a,
{
    let fee_alg = fee::LinearFee::default();

    let selection_result = match selection_policy {
        SelectionPolicy::FirstMatchFirst => {
            let inputs: Vec<Input<Self::Addressing>> = inputs.cloned().collect();
            let mut alg = input_selection::HeadFirst::from(inputs);
            alg.compute(&fee_alg, outputs.clone(), output_policy)?
        }
        SelectionPolicy::LargestFirst => {
            let inputs: Vec<Input<Self::Addressing>> = inputs.cloned().collect();
            let mut alg = input_selection::LargestFirst::from(inputs);
            alg.compute(&fee_alg, outputs.clone(), output_policy)?
        }
        SelectionPolicy::Blackjack(dust) => {
            let inputs: Vec<Input<Self::Addressing>> = inputs.cloned().collect();
            let mut alg = input_selection::Blackjack::new(dust, inputs);
            alg.compute(&fee_alg, outputs.clone(), output_policy)?
        }
    };

    let mut txbuilder = TxBuilder::new();
    for input in selection_result.selected_inputs.iter() {
        txbuilder.add_input(&input.ptr, input.value.value)
    }
    for output in outputs.iter() {
        txbuilder.add_output_value(output);
    }

    // here we try to add the output policy, if it didn't work because
    // the amount of coin leftover is not enough to add the policy, then
    // we ignore the error
    match txbuilder.add_output_policy(&fee_alg, output_policy) {
        Err(txbuild::Error::TxOutputPolicyNotEnoughCoins(_)) => {}
        Err(e) => return Err(input_selection::Error::TxBuildError(e)),
        Ok(_) => {}
    };

    let tx = txbuilder
        .make_tx()
        .map_err(input_selection::Error::TxBuildError)?;
    let txid = tx.id();
    let mut txfinalized = TxFinalized::new(tx);

    let witnesses = self.sign_tx(
        protocol_magic,
        &txid,
        selection_result
            .selected_inputs
            .into_iter()
            .map(|input| input.addressing),
    );

    for witness in witnesses {
        txfinalized
            .add_witness(witness)
            .map_err(input_selection::Error::TxBuildError)?;
    }

    let txaux = txfinalized
        .make_txaux()
        .map_err(input_selection::Error::TxBuildError)?;

    let real_fee = fee_alg
        .calculate_for_txaux(&txaux)
        .map_err(input_selection::Error::FeeError)?;

    if real_fee > selection_result.estimated_fees {
        Err(input_selection::Error::NotEnoughFees)
    } else {
        Ok((txaux, selection_result.estimated_fees))
    }
}

https://github.com/input-output-hk/rust-cardano/blob/6f2f790ee7aa242d7fa86d822a386045aa07733a/cardano/src/wallet/bip44.rs#L130

fn sign_tx<I>(
        &self,
        protocol_magic: ProtocolMagic,
        txid: &TxId,
        addresses: I,
    ) -> Vec<TxInWitness>
where
    I: Iterator<Item = Self::Addressing>,
{
    let mut witnesses = vec![];

    for addressing in addresses {
        let key = self
            .cached_root_key
            .account(
                self.derivation_scheme,
                addressing.account.get_scheme_value(),
            )
            .change(self.derivation_scheme, addressing.address_type())
            .index(self.derivation_scheme, addressing.index.get_scheme_value());

        let tx_witness = TxInWitness::new_extended_pk(protocol_magic, &key, txid);
        witnesses.push(tx_witness);
    }
    witnesses
}

https://github.com/input-output-hk/rust-cardano/blob/21e1febd87/cardano/src/tx.rs#L351

// Transaction IDs are either a hash of the CBOR serialisation of a
// given Tx, or a hash of a redeem address.
pub type TxId = Blake2b256;

pub fn id(&self) -> TxId {
    let buf = cbor!(self).expect("encode Tx");
    TxId::new(&buf)
}

/// create a TxInWitness from a given private key `XPrv` for the given transaction id `TxId`.
pub fn new_extended_pk(protocol_magic: ProtocolMagic, key: &XPrv, txid: &TxId) -> Self {
    let vec = Self::prepare_byte_to_sign(protocol_magic, SigningTag::Tx, txid);
    TxInWitness::PkWitness(key.public(), key.sign(&vec))
}

https://github.com/input-output-hk/rust-cardano/blob/21e1febd8717977c8075765b0e4a51d7d9516d87/cardano/src/txbuild.rs#L310

pub fn make_txaux(self) -> Result<TxAux> {
    if self.witnesses.len() != self.tx.inputs.len() {
        return Err(Error::TxSignaturesMismatch);
    }
    let sz = txaux_serialize_size(&self.tx, &(*self.witnesses));
    if sz > TX_SIZE_LIMIT {
        return Err(Error::TxOverLimit(sz));
    }
    let txaux = TxAux::new(self.tx, self.witnesses);
    Ok(txaux)
}
@johnnynanjiang
Copy link

johnnynanjiang commented Jul 6, 2019

a + b × size
where:

a is a special constant, at the moment it is 0.155381 ADA;
b is a special constant, at the moment it is 0.000043946 ADA/byte;
size is the size of the transaction in bytes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment