Files
grin-node/wallet/src/types.rs
T
Ignotus Peverell ac553493f1 WIP: Wallet functionality to receive and send coins
Beginning of a first pass at simple wallet functionalities so
Grin can be used to author transactions. We introduce a
receiving server, to be at least able to build coinbase outputs
that can be used by the mining daemon.

Present:

* Coinbase receiving API.
* Command to start the receiving server.
* Beginning of a transaction sending command.
* Improvements to the REST API abstractions to support the above.

Still to do:

* Change to the miner daemon to use the receiving server.
* A command line sender.
* API to receive any transaction (not just coinbase).
* A command line receiver.

Beyond that, HD derivation and seed generation are very simple
so far and almost certainly insecure. Just for testing for now.
2017-05-24 16:09:39 -07:00

183 lines
5.6 KiB
Rust

// Copyright 2016 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::convert::From;
use serde::{Serialize, Deserialize};
use serde_json;
use secp::{self, Secp256k1};
use secp::key::SecretKey;
use core::core::Transaction;
use core::ser;
use extkey;
use util;
const DAT_FILE: &'static str = "wallet.dat";
/// Wallet errors, mostly wrappers around underlying crypto or I/O errors.
#[derive(Debug)]
pub enum Error {
NotEnoughFunds(u64),
Crypto(secp::Error),
Key(extkey::Error),
WalletData(String),
}
impl From<secp::Error> for Error {
fn from(e: secp::Error) -> Error {
Error::Crypto(e)
}
}
impl From<extkey::Error> for Error {
fn from(e: extkey::Error) -> Error {
Error::Key(e)
}
}
/// Status of an output that's being tracked by the wallet. Can either be
/// unconfirmed, spent, unspent, or locked (when it's been used to generate
/// a transaction but we don't have confirmation that the transaction was
/// broadcasted or mined).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum OutputStatus {
Unconfirmed,
Unspent,
Locked,
Spent,
}
/// Information about an output that's being tracked by the wallet. Must be
/// enough to reconstruct the commitment associated with the ouput when the
/// root private key is known.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OutputData {
/// Private key fingerprint (in case the wallet tracks multiple)
pub fingerprint: [u8; 4],
/// How many derivations down from the root key
pub n_child: u32,
/// Value of the output, necessary to rebuild the commitment
pub value: u64,
/// Current status of the output
pub status: OutputStatus,
}
impl OutputData {
/// Lock a given output to avoid conflicting use
pub fn lock(&mut self) {
self.status = OutputStatus::Locked;
}
}
/// Wallet information tracking all our outputs. Based on HD derivation and
/// avoids storing any key data, only storing output amounts and child index.
/// This data structure is directly based on the JSON representation stored
/// on disk, so selection algorithms are fairly primitive and non optimized.
///
/// TODO optimization so everything isn't O(n) or even O(n^2)
/// TODO account for fees
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WalletData {
outputs: Vec<OutputData>,
}
impl WalletData {
/// Read the wallet data or created a brand new one if it doesn't exist yet
pub fn read_or_create() -> Result<WalletData, Error> {
if Path::new(DAT_FILE).exists() {
WalletData::read()
} else {
// just create a new instance, it will get written afterward
Ok(WalletData { outputs: vec![] })
}
}
/// Read the wallet data from disk.
pub fn read() -> Result<WalletData, Error> {
let mut data_file = File::open(DAT_FILE)
.map_err(|e| Error::WalletData(format!("Could not open {}: {}", DAT_FILE, e)))?;
serde_json::from_reader(data_file)
.map_err(|e| Error::WalletData(format!("Error reading {}: {}", DAT_FILE, e)))
}
/// Write the wallet data to disk.
pub fn write(&self) -> Result<(), Error> {
let mut data_file = File::create(DAT_FILE)
.map_err(|e| Error::WalletData(format!("Could not create {}: {}", DAT_FILE, e)))?;
let res_json = serde_json::to_vec_pretty(self)
.map_err(|_| Error::WalletData(format!("Error serializing wallet data.")))?;
data_file.write_all(res_json.as_slice())
.map_err(|e| Error::WalletData(format!("Error writing {}: {}", DAT_FILE, e)))
}
/// Append a new output information to the wallet data.
pub fn append_output(&mut self, out: OutputData) {
self.outputs.push(out);
}
/// Select a subset of unspent outputs to spend in a transaction transferring
/// the provided amount.
pub fn select(&self, fingerprint: [u8; 4], amount: u64) -> (Vec<OutputData>, i64) {
let mut to_spend = vec![];
let mut input_total = 0;
// TODO very naive impl for now, there's definitely better coin selection
// algos available
for out in &self.outputs {
if out.status == OutputStatus::Unspent && out.fingerprint == fingerprint {
to_spend.push(out.clone());
input_total += out.value;
}
}
(to_spend, (input_total as i64) - (amount as i64))
}
/// Next child index when we want to create a new output.
pub fn next_child(&self, fingerprint: [u8; 4]) -> u32 {
let mut max_n = 0;
for out in &self.outputs {
println!("{:?} {:?}", out.fingerprint, fingerprint);
if max_n < out.n_child && out.fingerprint == fingerprint {
max_n = out.n_child;
}
}
max_n + 1
}
}
/// Helper in serializing the information a receiver requires to build a
/// transaction.
#[derive(Serialize, Deserialize, Debug, Clone)]
struct JSONPartialTx {
amount: u64,
blind_sum: String,
tx: String,
}
/// Encodes the information for a partial transaction (not yet completed by the
/// receiver) into JSON.
pub fn partial_tx_to_json(receive_amount: u64, blind_sum: SecretKey, tx: Transaction) -> String {
let partial_tx = JSONPartialTx {
amount: receive_amount,
blind_sum: util::to_hex(blind_sum.as_ref().to_vec()),
tx: util::to_hex(ser::ser_vec(&tx).unwrap()),
};
serde_json::to_string_pretty(&partial_tx).unwrap()
}