Skip to content

Commit

Permalink
Merge pull request input-output-hk#56 from input-output-hk/address-ch…
Browse files Browse the repository at this point in the history
…unk-wallet

Address chunk wallet
  • Loading branch information
NicolasDP authored Apr 23, 2018
2 parents 1c27a28 + 9de2553 commit bf573ff
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 139 deletions.
37 changes: 0 additions & 37 deletions js/Tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,41 +170,6 @@ export const verify = (module, config, tx, xpub, signature) => {
return result === 0
};

export const createWallet = (module, seed) => {
const input_str = JSON.stringify(seed);
const input_array = iconv.encode(input_str, 'utf8');

const bufinput = newArray(module, input_array);
const bufoutput = newArray0(module, 4096);

let rsz = module.xwallet_create(bufinput, input_array.length, bufoutput);
let output_array = copyArray(module, bufoutput, rsz);

module.dealloc(bufoutput);
module.dealloc(bufinput);

let output_str = iconv.decode(Buffer.from(output_array), 'utf8');
return JSON.parse(output_str);
};

export const spend = (module, wallet, inputs, outputs, fee_addr) => {
const input = { wallet: wallet, inputs: inputs, outputs: outputs, fee_addr: fee_addr};
const input_str = JSON.stringify(input);
const input_array = iconv.encode(input_str, 'utf8');

const bufinput = newArray(module, input_array);
const bufoutput = newArray0(module, 4096);

let rsz = module.xwallet_spend(bufinput, input_array.length, bufoutput);
let output_array = copyArray(module, bufoutput, rsz);

module.dealloc(bufoutput);
module.dealloc(bufinput);

let output_str = iconv.decode(Buffer.from(output_array), 'utf8');
return JSON.parse(output_str);
};

export default {
newTxOut: apply(newTxOut, RustModule),
newTxIn: apply(newTxIn, RustModule),
Expand All @@ -213,6 +178,4 @@ export default {
addOutput: apply(addOutput, RustModule),
sign: apply(sign, RustModule),
verify: apply(verify, RustModule),
createWallet: apply(createWallet, RustModule),
spend: apply(spend, RustModule)
};
143 changes: 143 additions & 0 deletions js/Wallet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import iconv from 'iconv-lite';
import RustModule from './RustModule';
import { newArray, newArray0, copyArray } from './utils/arrays';
import { apply } from './utils/functions';
import { base16 } from './utils/strings';

const MAX_OUTPUT_SIZE = 4096;

/**
* Create a wallet object from the given seed.
*
* @param module - the WASM module that is used for crypto operations
* @param seed - the 32 bytes seed to generate the wallet from
* @returns {*} - a wallet object (JSON object)
*/
export const fromSeed = (module, seed) => {
const input_str = JSON.stringify(seed);
const input_array = iconv.encode(input_str, 'utf8');

const bufinput = newArray(module, input_array);
const bufoutput = newArray0(module, MAX_OUTPUT_SIZE);

let rsz = module.xwallet_create(bufinput, input_array.length, bufoutput);
let output_array = copyArray(module, bufoutput, rsz);

module.dealloc(bufoutput);
module.dealloc(bufinput);

let output_str = iconv.decode(Buffer.from(output_array), 'utf8');
return JSON.parse(output_str);
};

/**
* Generate addresses for the given wallet.
*
* @example
* ```
* const wallet = CardanoCrypto.Wallet.fromSeed([0,1,2,3,4..]).result;
* const addresses = CardanoCrypto.Wallet.generateAddresses(0, "External", [0,1,2,3,4]).result;
* ```
*
* @param module - the WASM module that is used for crypto operations
* @param wallet - The wallet object as created by the `fromSeed` function
* @param account - the account number
* @param type - the addresses type ("Internal" or "External")
* @param indices - the addresse indices
* @returns {*} - a list of ready to use addresses
*/
export const generateAddresses = (module, wallet, account, type, indices) => {
const input = { wallet: wallet
, account: account
, address_type: type
, indices: indices
};
const input_str = JSON.stringify(input);
const input_array = iconv.encode(input_str, 'utf8');

const bufinput = newArray(module, input_array);
const bufoutput = newArray0(module, MAX_OUTPUT_SIZE);

let rsz = module.xwallet_addresses(bufinput, input_array.length, bufoutput);
let output_array = copyArray(module, bufoutput, rsz);

module.dealloc(bufoutput);
module.dealloc(bufinput);

let output_str = iconv.decode(Buffer.from(output_array), 'utf8');
return JSON.parse(output_str);
};

/**
* Generate a ready to send, signed, transaction.
*
* @example
* ```
* let seed = 'compute seed from mnemonic';
* let wallet = CardanoCrypto.Wallet.fromSeed(seed).result;
*
* // the inputs are the resolved UTxO
* //
* // they contained the TxId and the index of where the TxOut is and the content of the
* // TxOut.
* let inputs =
* [ { ptr: { index: 42
* , id: "1c7b178c1655628ca87c7da6a5d9d13c1e0a304094ac88770768d565e3d20e0b"
* }
* , value: { address: "DdzFFzCqrhtCUjHyzgvgigwA5soBgDxpc8WfnG1RGhrsRrWMV8uKdpgVfCXGgNuXhdN4qxPMvRUtbUnWhPzxSdxJrWzPqACZeh6scCH5"
* , value: 92837348
* }
* , addressing: { account: 0, change: 0, index: 9 }
* }
* ];
*
* // where we want to send money to
* let outputs =
* [ { address: "DdzFFzCqrhtCUjHyzgvgigwA5soBgDxpc8WfnG1RGhrsRrWMV8uKdpgVfCXGgNuXhdN4qxPMvRUtbUnWhPzxSdxJrWzPqACZeh6scCH5"
* , value: 666
* }
* ];
*
* // where the fee will need to be sent
* let fee_addr = "DdzFFzCqrhtCUjHyzgvgigwA5soBgDxpc8WfnG1RGhrsRrWMV8uKdpgVfCXGgNuXhdN4qxPMvRUtbUnWhPzxSdxJrWzPqACZeh6scCH5";
* let change_addr = "DdzFFzCqrhtCUjHyzgvgigwA5soBgDxpc8WfnG1RGhrsRrWMV8uKdpgVfCXGgNuXhdN4qxPMvRUtbUnWhPzxSdxJrWzPqACZeh6scCH5";
*
* let signed_tx = CardanoCrypto.Wallet.spend(wallet, inputs, outputs, fee_addr, change_addr).result;
* ```
*
* @param module - the WASM module that is used for crypto operations
* @param wallet - The wallet object as created by the `fromSeed` function
* @param inputs - the list of inputs
* @param outputs - the list of payment to make
* @param fee_addr - the address to send the fee to
* @param change_addr - the address to send the change to
* @returns {*} - a ready to use, signed transaction encoded in cbor
*/
export const spend = (module, wallet, inputs, outputs, fee_addr, change_addr) => {
const input = { wallet: wallet
, inputs: inputs
, outputs: outputs
, fee_addr: fee_addr
, change_addr: change_addr
};
const input_str = JSON.stringify(input);
const input_array = iconv.encode(input_str, 'utf8');

const bufinput = newArray(module, input_array);
const bufoutput = newArray0(module, MAX_OUTPUT_SIZE);

let rsz = module.xwallet_spend(bufinput, input_array.length, bufoutput);
let output_array = copyArray(module, bufoutput, rsz);

module.dealloc(bufoutput);
module.dealloc(bufinput);

let output_str = iconv.decode(Buffer.from(output_array), 'utf8');
return JSON.parse(output_str);
};

export default {
fromSeed: apply(fromSeed, RustModule),
generateAddresses: apply(generateAddresses, RustModule),
spend: apply(spend, RustModule)
};
2 changes: 2 additions & 0 deletions js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Blake2b from './Blake2b.js';
import Payload from './Payload.js';
import Tx from './Tx.js';
import Config from './Config.js';
import Wallet from './Wallet.js';

module.exports = {
Payload,
Expand All @@ -14,5 +15,6 @@ module.exports = {
loadRustModule,
Blake2b,
Tx,
Wallet,
Config,
};
83 changes: 18 additions & 65 deletions wallet-crypto/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ pub type Result<T> = result::Result<T, Error>;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct Wallet {
cached_root_key: hdwallet::XPrv,
last_known_address: Option<Addressing>,
last_known_change: Option<Addressing>,

config: config::Config,
selection_policy: tx::fee::SelectionPolicy,
Expand All @@ -51,100 +49,55 @@ impl Wallet {
.derive(BIP44_COIN_TYPE);
Wallet {
cached_root_key: key,
last_known_address: None,
last_known_change: None,
config: config::Config::default(),
selection_policy: tx::fee::SelectionPolicy::default()
}
}

/// this function sets the last known path used for generating addresses
///
pub fn force_last_known_address(&mut self, addressing: Addressing) {
self.last_known_address = Some(addressing);
}

/// this function sets the last known path used for generating change addresses
///
pub fn force_last_known_change(&mut self, addressing: Addressing) {
self.last_known_change = Some(addressing);
}

/// create a new extended address
///
/// if you try to create address before being aware of all the
/// existing address you have created used first this function will
/// start from the beginning and may generate duplicated addresses.
/// create an extended address from the given addressing
///
pub fn new_address(&mut self) -> address::ExtendedAddr {
let addressing = match &self.last_known_address {
&None => Addressing::new(0, AddrType::External),
&Some(ref lkp) => {
// for now we assume we will never generate 0x80000000 addresses
lkp.incr(1).unwrap()
}
};

self.force_last_known_address(addressing.clone());

self.make_address(&addressing)
}
pub fn gen_addresses(&self, account: u32, addr_type: AddrType, indices: Vec<u32>) -> Vec<address::ExtendedAddr>
{
let addressing = Addressing::new(account, addr_type);

/// create a new extended address for change purpose
///
/// if you try to create address before being aware of all the
/// existing address you have created used first this function will
/// start from the beginning and may generate duplicated addresses.
///
pub fn new_change(&mut self) -> address::ExtendedAddr {
let addressing = match &self.last_known_change {
&None => Addressing::new(0, AddrType::Internal),
&Some(ref lkp) => {
// for now we assume we will never generate 0x80000000 addresses
lkp.incr(1).unwrap()
}
};

self.force_last_known_address(addressing.clone());

self.make_address(&addressing)
}
let change_prv = self.get_root_key()
.derive(addressing.account)
.derive(addressing.change);

/// create an extended address from the given addressing
///
fn make_address(&mut self, addressing: &Addressing) -> address::ExtendedAddr {
let pk = self.get_xprv(&addressing).public();
let addr_type = address::AddrType::ATPubKey;
let sd = address::SpendingData::PubKeyASD(pk.clone());
let attrs = address::Attributes::new_single_key(&pk, None);
indices.iter().cloned().map(|index| {
let pk = change_prv.derive(index).public();
let addr_type = address::AddrType::ATPubKey;
let sd = address::SpendingData::PubKeyASD(pk.clone());
let attrs = address::Attributes::new_single_key(&pk, None);

address::ExtendedAddr::new(addr_type, sd, attrs)
address::ExtendedAddr::new(addr_type, sd, attrs)
}).collect()
}

/// function to create a ready to send transaction to the network
///
/// it select the needed inputs, compute the fee and possible change
/// signes every TxIn as needed.
///
pub fn new_transaction( &mut self
pub fn new_transaction( &self
, inputs: &tx::Inputs
, outputs: &tx::Outputs
, fee_addr: &address::ExtendedAddr
, change_addr: &address::ExtendedAddr
)
-> Result<tx::TxAux>
{
let alg = tx::fee::LinearFee::default();
let change_addr = self.new_change();

let (fee, selected_inputs, change) = alg.compute(self.selection_policy, inputs, outputs, &change_addr, fee_addr)?;
let (fee, selected_inputs, change) = alg.compute(self.selection_policy, inputs, outputs, change_addr, fee_addr)?;

let mut tx = tx::Tx::new_with(
selected_inputs.iter().cloned().map(|input| input.ptr).collect(),
outputs.iter().cloned().collect()
);

tx.add_output(tx::TxOut::new(fee_addr.clone(), fee.to_coin()));
tx.add_output(tx::TxOut::new(change_addr , change));
tx.add_output(tx::TxOut::new(change_addr.clone(), change));

let mut witnesses = vec![];

Expand Down
Loading

0 comments on commit bf573ff

Please sign in to comment.