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)
}
decimal
6
explorer
https://cardanoexplorer.com/
tx fee
https://cardanodocs.com/cardano/transaction-fees/
https://repl.it/repls/IndolentWarmheartedDehardwarization