mirror of
https://github.com/mimblewimble/grin.git
synced 2025-05-08 18:21:14 +03:00
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.
This commit is contained in:
parent
de4ebdde71
commit
ac553493f1
16 changed files with 846 additions and 282 deletions
|
@ -4,10 +4,13 @@ version = "0.1.0"
|
|||
authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
|
||||
|
||||
[workspace]
|
||||
members = ["api", "chain", "core", "grin", "p2p", "store", "util", "pool"]
|
||||
members = ["api", "chain", "core", "grin", "p2p", "store", "util", "pool", "wallet"]
|
||||
|
||||
[dependencies]
|
||||
grin_api = { path = "./api" }
|
||||
grin_grin = { path = "./grin" }
|
||||
grin_wallet = { path = "./wallet" }
|
||||
secp256k1zkp = { path = "./secp256k1zkp" }
|
||||
|
||||
clap = "^2.23.3"
|
||||
daemonize = "^0.2.3"
|
||||
|
@ -15,3 +18,4 @@ env_logger="^0.3.5"
|
|||
log = "^0.3"
|
||||
serde = "~0.9.10"
|
||||
serde_json = "~0.9.9"
|
||||
tiny-keccak = "1.1"
|
||||
|
|
|
@ -38,6 +38,8 @@ pub struct ChainApi {
|
|||
impl ApiEndpoint for ChainApi {
|
||||
type ID = String;
|
||||
type T = Tip;
|
||||
type OP_IN = ();
|
||||
type OP_OUT = ();
|
||||
|
||||
fn operations(&self) -> Vec<Operation> {
|
||||
vec![Operation::Get]
|
||||
|
|
|
@ -25,3 +25,4 @@ mod endpoints;
|
|||
mod rest;
|
||||
|
||||
pub use endpoints::start_rest_apis;
|
||||
pub use rest::*;
|
||||
|
|
|
@ -110,6 +110,8 @@ pub type ApiResult<T> = ::std::result::Result<T, ApiError>;
|
|||
pub trait ApiEndpoint: Clone + Send + Sync + 'static {
|
||||
type ID: ToString + FromStr;
|
||||
type T: Serialize + Deserialize;
|
||||
type OP_IN: Serialize + Deserialize;
|
||||
type OP_OUT: Serialize + Deserialize;
|
||||
|
||||
fn operations(&self) -> Vec<Operation>;
|
||||
|
||||
|
@ -134,10 +136,7 @@ pub trait ApiEndpoint: Clone + Send + Sync + 'static {
|
|||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn operation<IN, OUT>(&self, op: String, input: IN) -> ApiResult<OUT>
|
||||
where IN: Serialize + Deserialize,
|
||||
OUT: Serialize + Deserialize
|
||||
{
|
||||
fn operation(&self, op: String, input: Self::OP_IN) -> ApiResult<Self::OP_OUT> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
@ -181,6 +180,24 @@ impl<E> Handler for ApiWrapper<E>
|
|||
}
|
||||
}
|
||||
|
||||
struct OpWrapper<E> {
|
||||
operation: String,
|
||||
endpoint: E,
|
||||
}
|
||||
|
||||
impl<E> Handler for OpWrapper<E>
|
||||
where E: ApiEndpoint
|
||||
{
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let t: E::OP_IN = serde_json::from_reader(req.body.by_ref())
|
||||
.map_err(|e| IronError::new(e, status::BadRequest))?;
|
||||
let res = self.endpoint.operation(self.operation.clone(), t)?;
|
||||
let res_json = serde_json::to_string(&res)
|
||||
.map_err(|e| IronError::new(e, status::InternalServerError))?;
|
||||
Ok(Response::with((status::Ok, res_json)))
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_param<ID>(req: &mut Request, param: &'static str) -> IronResult<ID>
|
||||
where ID: ToString + FromStr,
|
||||
<ID as FromStr>::Err: Debug + Send + Error + 'static
|
||||
|
@ -224,17 +241,31 @@ impl ApiServer {
|
|||
let route_postfix = &subpath[1..];
|
||||
let root = self.root.clone() + &subpath;
|
||||
for op in endpoint.operations() {
|
||||
let full_path = match op.clone() {
|
||||
Operation::Get => root.clone() + "/:id",
|
||||
Operation::Update => root.clone() + "/:id",
|
||||
Operation::Delete => root.clone() + "/:id",
|
||||
Operation::Create => root.clone(),
|
||||
Operation::Custom(op_s) => format!("{}/:{}", root.clone(), op_s),
|
||||
};
|
||||
self.router.route(op.to_method(),
|
||||
full_path,
|
||||
ApiWrapper(endpoint.clone()),
|
||||
format!("{:?}_{}", op, route_postfix));
|
||||
let route_name = format!("{:?}_{}", op, route_postfix);
|
||||
|
||||
// special case of custom operations
|
||||
if let Operation::Custom(op_s) = op.clone() {
|
||||
let wrapper = OpWrapper {
|
||||
operation: op_s.clone(),
|
||||
endpoint: endpoint.clone(),
|
||||
};
|
||||
let full_path = format!("{}", root.clone());
|
||||
self.router.route(op.to_method(), full_path.clone(), wrapper, route_name);
|
||||
info!("POST {}", full_path);
|
||||
} else {
|
||||
|
||||
// regular REST operations
|
||||
let full_path = match op.clone() {
|
||||
Operation::Get => root.clone() + "/:id",
|
||||
Operation::Update => root.clone() + "/:id",
|
||||
Operation::Delete => root.clone() + "/:id",
|
||||
Operation::Create => root.clone(),
|
||||
_ => panic!("unreachable"),
|
||||
};
|
||||
let wrapper = ApiWrapper(endpoint.clone());
|
||||
self.router.route(op.to_method(), full_path.clone(), wrapper, route_name);
|
||||
info!("{} {}", op.to_method(), full_path);
|
||||
}
|
||||
}
|
||||
|
||||
// support for the HTTP Options method by differentiating what's on the
|
||||
|
|
|
@ -434,10 +434,10 @@ impl Block {
|
|||
.verify_kernels(secp)
|
||||
}
|
||||
|
||||
// Builds the blinded output and related signature proof for the block reward.
|
||||
fn reward_output(skey: secp::key::SecretKey,
|
||||
secp: &Secp256k1)
|
||||
-> Result<(Output, TxKernel), secp::Error> {
|
||||
/// Builds the blinded output and related signature proof for the block reward.
|
||||
pub fn reward_output(skey: secp::key::SecretKey,
|
||||
secp: &Secp256k1)
|
||||
-> Result<(Output, TxKernel), secp::Error> {
|
||||
let msg = try!(secp::Message::from_slice(&[0; secp::constants::MESSAGE_SIZE]));
|
||||
let sig = try!(secp.sign(&msg, &skey));
|
||||
let commit = secp.commit(REWARD, skey).unwrap();
|
||||
|
|
|
@ -21,8 +21,12 @@ extern crate log;
|
|||
extern crate env_logger;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate tiny_keccak;
|
||||
|
||||
extern crate grin_api as api;
|
||||
extern crate grin_grin as grin;
|
||||
extern crate grin_wallet as wallet;
|
||||
extern crate secp256k1zkp as secp;
|
||||
|
||||
const GRIN_HOME: &'static str = ".grin";
|
||||
|
||||
|
@ -31,10 +35,13 @@ use std::thread;
|
|||
use std::io::Read;
|
||||
use std::fs::File;
|
||||
use std::time::Duration;
|
||||
use tiny_keccak::Keccak;
|
||||
|
||||
use clap::{Arg, App, SubCommand, ArgMatches};
|
||||
use daemonize::Daemonize;
|
||||
|
||||
use secp::Secp256k1;
|
||||
|
||||
fn main() {
|
||||
env_logger::init().unwrap();
|
||||
|
||||
|
@ -79,6 +86,17 @@ fn main() {
|
|||
.about("Communicates with the Grin server")
|
||||
.subcommand(SubCommand::with_name("status")
|
||||
.about("current status of the Grin chain")))
|
||||
|
||||
// specification of the wallet commands and options
|
||||
.subcommand(SubCommand::with_name("wallet")
|
||||
.about("Wallet software for Grin")
|
||||
.arg(Arg::with_name("pass")
|
||||
.short("p")
|
||||
.long("pass")
|
||||
.help("Wallet passphrase used to generate the private key seed")
|
||||
.takes_value(true))
|
||||
.subcommand(SubCommand::with_name("receive")
|
||||
.about("Run the wallet in receiving mode")))
|
||||
.get_matches();
|
||||
|
||||
match args.subcommand() {
|
||||
|
@ -97,7 +115,17 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
_ => panic!("Unknown command, use 'grin help' for a list of all commands"),
|
||||
// client commands and options
|
||||
("wallet", Some(wallet_args)) => {
|
||||
match wallet_args.subcommand() {
|
||||
("receive", _) => {
|
||||
wallet_command(wallet_args);
|
||||
},
|
||||
_ => panic!("Unknown client command, use 'grin help client' for details"),
|
||||
}
|
||||
}
|
||||
|
||||
_ => println!("Unknown command, use 'grin help' for a list of all commands"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,6 +177,31 @@ fn server_command(server_args: &ArgMatches) {
|
|||
}
|
||||
}
|
||||
|
||||
fn wallet_command(wallet_args: &ArgMatches) {
|
||||
let hd_seed = wallet_args.value_of("pass").expect("Wallet passphrase required.");
|
||||
|
||||
// TODO do something closer to BIP39, eazy solution right now
|
||||
let mut sha3 = Keccak::new_sha3_256();
|
||||
sha3.update(hd_seed.as_bytes());
|
||||
let mut seed = [0; 32];
|
||||
sha3.finalize(&mut seed);
|
||||
|
||||
let s = Secp256k1::new();
|
||||
let key = wallet::ExtendedKey::from_seed(&s, &seed[..]).expect("Error deriving extended key from seed.");
|
||||
|
||||
match wallet_args.subcommand() {
|
||||
("receive", _) => {
|
||||
info!("Starting the Grin wallet receiving daemon...");
|
||||
let mut apis = api::ApiServer::new("/v1".to_string());
|
||||
apis.register_endpoint("/receive_coinbase".to_string(), wallet::WalletReceiver { key: key });
|
||||
apis.start("127.0.0.1:13416").unwrap_or_else(|e| {
|
||||
error!("Failed to start Grin wallet receiver: {}.", e);
|
||||
});
|
||||
}
|
||||
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_config() -> grin::ServerConfig {
|
||||
let mut config_path = env::home_dir().ok_or("Failed to detect home directory!").unwrap();
|
||||
config_path.push(GRIN_HOME);
|
||||
|
|
46
util/src/hex.rs
Normal file
46
util/src/hex.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
// 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.
|
||||
|
||||
/// Implements hex-encoding from bytes to string and decoding of strings
|
||||
/// to bytes. Given that rustc-serialize is deprecated and serde doesn't
|
||||
/// provide easy hex encoding, hex is a bit in limbo right now in Rust-
|
||||
/// land. It's simple enough that we can just have our own.
|
||||
|
||||
use std::fmt::Write;
|
||||
use std::num;
|
||||
|
||||
/// Encode the provided bytes into a hex string
|
||||
pub fn to_hex(bytes: Vec<u8>) -> String {
|
||||
let mut s = String::new();
|
||||
for byte in bytes {
|
||||
write!(&mut s, "{:X}", byte).expect("Unable to write");
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Decode a hex string into bytes.
|
||||
pub fn from_hex(hex_str: String) -> Result<Vec<u8>, num::ParseIntError> {
|
||||
let hex_trim = if &hex_str[..2] == "0x" {
|
||||
hex_str[2..].to_owned()
|
||||
} else {
|
||||
hex_str.clone()
|
||||
};
|
||||
split_n(&hex_str.trim()[..], 2).iter()
|
||||
.map(|b| u8::from_str_radix(b, 16))
|
||||
.collect::<Result<Vec<u8>, _>>()
|
||||
}
|
||||
|
||||
fn split_n(s: &str, n: usize) -> Vec<&str> {
|
||||
(0 .. (s.len() - n + 1)/2 + 1).map(|i| &s[2*i .. 2*i + n]).collect()
|
||||
}
|
|
@ -19,6 +19,9 @@ use std::cell::{RefCell, Ref};
|
|||
#[allow(unused_imports)]
|
||||
use std::ops::Deref;
|
||||
|
||||
mod hex;
|
||||
pub use hex::*;
|
||||
|
||||
// Encapsulation of a RefCell<Option<T>> for one-time initialization after
|
||||
// construction. This implementation will purposefully fail hard if not used
|
||||
// properly, for example if it's not initialized before being first used
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
[package]
|
||||
name = "grin_wallet"
|
||||
version = "0.1.0"
|
||||
authors = ["Laurent Meunier <laurent.meunier95@gmail.com>"]
|
||||
authors = ["Ignotus Peverell <igno_peverell@protonmail.com>", "Laurent Meunier <laurent.meunier95@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
secp256k1zkp = { path = "../secp256k1zkp" }
|
||||
rust-crypto = "^0.2"
|
||||
rand = "^0.3"
|
||||
byteorder = "1"
|
||||
rustc-serialize = "0.3.23"
|
||||
log = "^0.3"
|
||||
rand = "^0.3"
|
||||
rust-crypto = "^0.2"
|
||||
serde = "~0.9.10"
|
||||
serde_derive = "~0.9.10"
|
||||
serde_json = "~0.9.8"
|
||||
|
||||
grin_api = { path = "../api" }
|
||||
grin_core = { path = "../core" }
|
||||
grin_util = { path = "../util" }
|
||||
secp256k1zkp = { path = "../secp256k1zkp" }
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
// 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.
|
|
@ -1,207 +0,0 @@
|
|||
// 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 Error;
|
||||
use secp::Secp256k1;
|
||||
use secp::key::SecretKey;
|
||||
use crypto::mac::Mac;
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::sha2::Sha512;
|
||||
use crypto::ripemd160::Ripemd160;
|
||||
use crypto::digest::Digest;
|
||||
use Error::{InvalidSeedSize, InvalidSliceSize, InvalidExtendedKey};
|
||||
use byteorder::{ByteOrder, BigEndian};
|
||||
|
||||
/// An extended private key.
|
||||
/// An ExtendedKey is a secret key which can be used to derive new
|
||||
/// secret keys to blind the commitment of a transaction output.
|
||||
/// To be usable, a secret key should have an amount assigned to it,
|
||||
/// but when the key is derived, the amount is not known and must be
|
||||
/// given.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtendedKey {
|
||||
/// Depth of the extended key
|
||||
pub depth: u8,
|
||||
/// Child number of the key
|
||||
pub n_child: u32,
|
||||
/// Parent key's fingerprint
|
||||
pub fingerprint: [u8; 4],
|
||||
/// Code of the derivation chain
|
||||
pub chaincode: [u8; 32],
|
||||
/// Actual private key
|
||||
pub key: SecretKey,
|
||||
}
|
||||
|
||||
impl ExtendedKey {
|
||||
|
||||
/// Creates a new extended key from a serialized one
|
||||
pub fn from_slice(secp: &Secp256k1, slice: &[u8]) -> Result<ExtendedKey, Error> {
|
||||
//TODO change when ser. ext. size is fixed
|
||||
if slice.len() != 73 {
|
||||
return Err(InvalidSliceSize);
|
||||
}
|
||||
let depth: u8 = slice[0];
|
||||
let mut fingerprint: [u8; 4] = [0; 4];
|
||||
(&mut fingerprint).copy_from_slice(&slice[1..5]);
|
||||
let n_child = BigEndian::read_u32(&slice[5..9]);
|
||||
let mut chaincode: [u8; 32] = [0; 32];
|
||||
(&mut chaincode).copy_from_slice(&slice[9..41]);
|
||||
let secret_key = match SecretKey::from_slice(secp, &slice[41..73]) {
|
||||
Ok(key) => key,
|
||||
Err(_) => return Err(InvalidExtendedKey),
|
||||
};
|
||||
|
||||
Ok(ExtendedKey {
|
||||
depth: depth,
|
||||
fingerprint: fingerprint,
|
||||
n_child: n_child,
|
||||
chaincode: chaincode,
|
||||
key: secret_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new extended master key from a seed
|
||||
pub fn from_seed(secp: &Secp256k1, seed: &[u8]) -> Result<ExtendedKey, Error> {
|
||||
let mut hmac = Hmac::new(Sha512::new(), b"Mimble seed");
|
||||
match seed.len() {
|
||||
16 | 32 | 64 => hmac.input(&seed),
|
||||
_ => {
|
||||
return Err(InvalidSeedSize)
|
||||
},
|
||||
}
|
||||
|
||||
let mut derived: [u8; 64] = [0; 64];
|
||||
hmac.raw_result(&mut derived);
|
||||
|
||||
let mut chaincode: [u8; 32] = [0; 32];
|
||||
(&mut chaincode).copy_from_slice(&derived[32..]);
|
||||
// TODO Error handling
|
||||
let secret_key = SecretKey::from_slice(&secp, &derived[0..32])
|
||||
.expect("Error generating from seed");
|
||||
|
||||
Ok(ExtendedKey {
|
||||
depth: 0,
|
||||
fingerprint: [0; 4],
|
||||
n_child: 0,
|
||||
chaincode: chaincode,
|
||||
key: secret_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the identifier of the key, which is the
|
||||
/// Hash160 of the private key
|
||||
pub fn identifier(&self) -> [u8; 20] {
|
||||
let mut sha = Sha256::new();
|
||||
sha.input(&self.key[..]);
|
||||
|
||||
let mut shres = [0; 32];
|
||||
sha.result(&mut shres);
|
||||
|
||||
let mut ripe = Ripemd160::new();
|
||||
ripe.input(&shres[..]);
|
||||
|
||||
let mut identifier = [0; 20];
|
||||
ripe.result(&mut identifier);
|
||||
return identifier
|
||||
}
|
||||
|
||||
/// Derive an extended key from an extended key
|
||||
pub fn derive(&self, secp: &Secp256k1, n: u32) -> Result<ExtendedKey, Error> {
|
||||
let mut hmac = Hmac::new(Sha512::new(), &self.chaincode[..]);
|
||||
let mut n_bytes: [u8; 4] = [0; 4];
|
||||
BigEndian::write_u32(&mut n_bytes, n);
|
||||
|
||||
hmac.input(&self.key[..]);
|
||||
hmac.input(&n_bytes[..]);
|
||||
|
||||
let mut derived = [0; 64];
|
||||
hmac.raw_result(&mut derived);
|
||||
|
||||
let mut secret_key = SecretKey::from_slice(&secp, &derived[0..32])
|
||||
.expect("Error deriving key");
|
||||
secret_key.add_assign(secp, &self.key)
|
||||
.expect("Error deriving key");
|
||||
// TODO check if key != 0 ?
|
||||
|
||||
let mut chain_code: [u8; 32] = [0; 32];
|
||||
(&mut chain_code).clone_from_slice(&derived[32..]);
|
||||
|
||||
let mut fingerprint: [u8; 4] = [0; 4];
|
||||
let parent_identifier = self.identifier();
|
||||
(&mut fingerprint).clone_from_slice(&parent_identifier[0..4]);
|
||||
|
||||
Ok(ExtendedKey {
|
||||
depth: self.depth + 1,
|
||||
fingerprint: fingerprint,
|
||||
n_child: n,
|
||||
chaincode: chain_code,
|
||||
key: secret_key
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate rustc_serialize as serialize;
|
||||
|
||||
use secp::Secp256k1;
|
||||
use secp::key::SecretKey;
|
||||
use super::ExtendedKey;
|
||||
use self::serialize::hex::{FromHex};
|
||||
|
||||
#[test]
|
||||
fn extkey_from_seed() {
|
||||
//TODO More test vectors
|
||||
let s = Secp256k1::new();
|
||||
let seed = "000102030405060708090a0b0c0d0e0f".from_hex().unwrap();
|
||||
let extk = ExtendedKey::from_seed(&s, &seed.as_slice())
|
||||
.unwrap();
|
||||
let sec = "04a7d66a82221501e67f2665332180bd1192c5e58a2cd26613827deb8ba14e75".from_hex().unwrap();
|
||||
let secret_key = SecretKey::from_slice(&s, sec.as_slice())
|
||||
.unwrap();
|
||||
let chaincode = "b7c6740dea1920ec629b3593678f6d8dc40fe6ec1ed824fcde37f476cd6c048c".from_hex().unwrap();
|
||||
let fingerprint = "00000000".from_hex().unwrap();
|
||||
let depth = 0;
|
||||
let n_child = 0;
|
||||
assert_eq!(extk.key, secret_key);
|
||||
assert_eq!(extk.fingerprint, fingerprint.as_slice());
|
||||
assert_eq!(extk.chaincode, chaincode.as_slice());
|
||||
assert_eq!(extk.depth, depth);
|
||||
assert_eq!(extk.n_child, n_child);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extkey_derivation() {
|
||||
//TODO More test verctors
|
||||
let s = Secp256k1::new();
|
||||
let seed = "000102030405060708090a0b0c0d0e0f".from_hex().unwrap();
|
||||
let extk = ExtendedKey::from_seed(&s, &seed.as_slice())
|
||||
.unwrap();
|
||||
let derived = extk.derive(&s, 0).unwrap();
|
||||
let sec = "908bf3264b8f5f5a5be57d3b0afa36eb5dbcc464ff4da2cf71183e8ec755184b".from_hex().unwrap();
|
||||
let secret_key = SecretKey::from_slice(&s, sec.as_slice())
|
||||
.unwrap();
|
||||
let chaincode = "e90c4559501fb956fa8ddcd6d08499691678cfd6d69e41efb9ee8e87f327e30a".from_hex().unwrap();
|
||||
let fingerprint = "8963be69".from_hex().unwrap();
|
||||
let depth = 1;
|
||||
let n_child = 0;
|
||||
assert_eq!(derived.key, secret_key);
|
||||
assert_eq!(derived.fingerprint, fingerprint.as_slice());
|
||||
assert_eq!(derived.chaincode, chaincode.as_slice());
|
||||
assert_eq!(derived.depth, depth);
|
||||
assert_eq!(derived.n_child, n_child);
|
||||
}
|
||||
}
|
245
wallet/src/extkey.rs
Normal file
245
wallet/src/extkey.rs
Normal file
|
@ -0,0 +1,245 @@
|
|||
// 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.
|
||||
|
||||
/// Key derivation scheme used by Grin to build chains of private keys
|
||||
/// in its wallet logic. Largely inspired by bitcoin's BIP32.
|
||||
|
||||
use std::{error, fmt};
|
||||
|
||||
use byteorder::{ByteOrder, BigEndian};
|
||||
use crypto::mac::Mac;
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::sha2::Sha512;
|
||||
use crypto::ripemd160::Ripemd160;
|
||||
use crypto::digest::Digest;
|
||||
use secp::Secp256k1;
|
||||
use secp::key::SecretKey;
|
||||
|
||||
/// An ExtKey error
|
||||
#[derive(Copy, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum Error {
|
||||
/// The size of the seed is invalid
|
||||
InvalidSeedSize,
|
||||
InvalidSliceSize,
|
||||
InvalidExtendedKey,
|
||||
}
|
||||
|
||||
// Passthrough Debug to Display, since errors should be user-visible
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.write_str(error::Error::description(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
None
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::InvalidSeedSize => "wallet: seed isn't of size 128, 256 or 512",
|
||||
// TODO change when ser. ext. size is fixed
|
||||
Error::InvalidSliceSize => "wallet: serialized extended key must be of size 73",
|
||||
Error::InvalidExtendedKey => "wallet: the given serialized extended key is invalid",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An ExtendedKey is a secret key which can be used to derive new
|
||||
/// secret keys to blind the commitment of a transaction output.
|
||||
/// To be usable, a secret key should have an amount assigned to it,
|
||||
/// but when the key is derived, the amount is not known and must be
|
||||
/// given.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtendedKey {
|
||||
/// Depth of the extended key
|
||||
pub depth: u8,
|
||||
/// Child number of the key
|
||||
pub n_child: u32,
|
||||
/// Parent key's fingerprint
|
||||
pub fingerprint: [u8; 4],
|
||||
/// Code of the derivation chain
|
||||
pub chaincode: [u8; 32],
|
||||
/// Actual private key
|
||||
pub key: SecretKey,
|
||||
}
|
||||
|
||||
impl ExtendedKey {
|
||||
|
||||
/// Creates a new extended key from a serialized one
|
||||
pub fn from_slice(secp: &Secp256k1, slice: &[u8]) -> Result<ExtendedKey, Error> {
|
||||
// TODO change when ser. ext. size is fixed
|
||||
if slice.len() != 73 {
|
||||
return Err(Error::InvalidSliceSize);
|
||||
}
|
||||
let depth: u8 = slice[0];
|
||||
let mut fingerprint: [u8; 4] = [0; 4];
|
||||
(&mut fingerprint).copy_from_slice(&slice[1..5]);
|
||||
let n_child = BigEndian::read_u32(&slice[5..9]);
|
||||
let mut chaincode: [u8; 32] = [0; 32];
|
||||
(&mut chaincode).copy_from_slice(&slice[9..41]);
|
||||
let secret_key = match SecretKey::from_slice(secp, &slice[41..73]) {
|
||||
Ok(key) => key,
|
||||
Err(_) => return Err(Error::InvalidExtendedKey),
|
||||
};
|
||||
|
||||
Ok(ExtendedKey {
|
||||
depth: depth,
|
||||
fingerprint: fingerprint,
|
||||
n_child: n_child,
|
||||
chaincode: chaincode,
|
||||
key: secret_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new extended master key from a seed
|
||||
pub fn from_seed(secp: &Secp256k1, seed: &[u8]) -> Result<ExtendedKey, Error> {
|
||||
let mut hmac = Hmac::new(Sha512::new(), b"Mimble seed");
|
||||
match seed.len() {
|
||||
16 | 32 | 64 => hmac.input(&seed),
|
||||
_ => return Err(Error::InvalidSeedSize),
|
||||
}
|
||||
|
||||
let mut derived: [u8; 64] = [0; 64];
|
||||
hmac.raw_result(&mut derived);
|
||||
|
||||
let mut chaincode: [u8; 32] = [0; 32];
|
||||
(&mut chaincode).copy_from_slice(&derived[32..]);
|
||||
// TODO Error handling
|
||||
let secret_key = SecretKey::from_slice(&secp, &derived[0..32])
|
||||
.expect("Error generating from seed");
|
||||
|
||||
let mut ext_key = ExtendedKey {
|
||||
depth: 0,
|
||||
fingerprint: [0; 4],
|
||||
n_child: 0,
|
||||
chaincode: chaincode,
|
||||
key: secret_key,
|
||||
};
|
||||
|
||||
let mut fingerprint: [u8; 4] = [0; 4];
|
||||
let identifier = ext_key.identifier();
|
||||
(&mut fingerprint).clone_from_slice(&identifier[0..4]);
|
||||
ext_key.fingerprint = fingerprint;
|
||||
|
||||
Ok(ext_key)
|
||||
}
|
||||
|
||||
/// Return the identifier of the key, which is the
|
||||
/// Hash160 of the private key
|
||||
pub fn identifier(&self) -> [u8; 20] {
|
||||
let mut sha = Sha256::new();
|
||||
sha.input(&self.key[..]);
|
||||
|
||||
let mut shres = [0; 32];
|
||||
sha.result(&mut shres);
|
||||
|
||||
let mut ripe = Ripemd160::new();
|
||||
ripe.input(&shres[..]);
|
||||
|
||||
let mut identifier = [0; 20];
|
||||
ripe.result(&mut identifier);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/// Derive an extended key from an extended key
|
||||
pub fn derive(&self, secp: &Secp256k1, n: u32) -> Result<ExtendedKey, Error> {
|
||||
let mut hmac = Hmac::new(Sha512::new(), &self.chaincode[..]);
|
||||
let mut n_bytes: [u8; 4] = [0; 4];
|
||||
BigEndian::write_u32(&mut n_bytes, n);
|
||||
|
||||
hmac.input(&self.key[..]);
|
||||
hmac.input(&n_bytes[..]);
|
||||
|
||||
let mut derived = [0; 64];
|
||||
hmac.raw_result(&mut derived);
|
||||
|
||||
let mut secret_key = SecretKey::from_slice(&secp, &derived[0..32])
|
||||
.expect("Error deriving key");
|
||||
secret_key.add_assign(secp, &self.key)
|
||||
.expect("Error deriving key");
|
||||
// TODO check if key != 0 ?
|
||||
|
||||
let mut chain_code: [u8; 32] = [0; 32];
|
||||
(&mut chain_code).clone_from_slice(&derived[32..]);
|
||||
|
||||
let mut fingerprint: [u8; 4] = [0; 4];
|
||||
let parent_identifier = self.identifier();
|
||||
(&mut fingerprint).clone_from_slice(&parent_identifier[0..4]);
|
||||
|
||||
Ok(ExtendedKey {
|
||||
depth: self.depth + 1,
|
||||
fingerprint: fingerprint,
|
||||
n_child: n,
|
||||
chaincode: chain_code,
|
||||
key: secret_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate rustc_serialize as serialize;
|
||||
|
||||
use secp::Secp256k1;
|
||||
use secp::key::SecretKey;
|
||||
use super::ExtendedKey;
|
||||
use self::serialize::hex::FromHex;
|
||||
|
||||
#[test]
|
||||
fn extkey_from_seed() {
|
||||
// TODO More test vectors
|
||||
let s = Secp256k1::new();
|
||||
let seed = "000102030405060708090a0b0c0d0e0f".from_hex().unwrap();
|
||||
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
|
||||
let sec =
|
||||
"04a7d66a82221501e67f2665332180bd1192c5e58a2cd26613827deb8ba14e75".from_hex().unwrap();
|
||||
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
|
||||
let chaincode =
|
||||
"b7c6740dea1920ec629b3593678f6d8dc40fe6ec1ed824fcde37f476cd6c048c".from_hex().unwrap();
|
||||
let fingerprint = "00000000".from_hex().unwrap();
|
||||
let depth = 0;
|
||||
let n_child = 0;
|
||||
assert_eq!(extk.key, secret_key);
|
||||
assert_eq!(extk.fingerprint, fingerprint.as_slice());
|
||||
assert_eq!(extk.chaincode, chaincode.as_slice());
|
||||
assert_eq!(extk.depth, depth);
|
||||
assert_eq!(extk.n_child, n_child);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extkey_derivation() {
|
||||
// TODO More test verctors
|
||||
let s = Secp256k1::new();
|
||||
let seed = "000102030405060708090a0b0c0d0e0f".from_hex().unwrap();
|
||||
let extk = ExtendedKey::from_seed(&s, &seed.as_slice()).unwrap();
|
||||
let derived = extk.derive(&s, 0).unwrap();
|
||||
let sec =
|
||||
"908bf3264b8f5f5a5be57d3b0afa36eb5dbcc464ff4da2cf71183e8ec755184b".from_hex().unwrap();
|
||||
let secret_key = SecretKey::from_slice(&s, sec.as_slice()).unwrap();
|
||||
let chaincode =
|
||||
"e90c4559501fb956fa8ddcd6d08499691678cfd6d69e41efb9ee8e87f327e30a".from_hex().unwrap();
|
||||
let fingerprint = "8963be69".from_hex().unwrap();
|
||||
let depth = 1;
|
||||
let n_child = 0;
|
||||
assert_eq!(derived.key, secret_key);
|
||||
assert_eq!(derived.fingerprint, fingerprint.as_slice());
|
||||
assert_eq!(derived.chaincode, chaincode.as_slice());
|
||||
assert_eq!(derived.depth, depth);
|
||||
assert_eq!(derived.n_child, n_child);
|
||||
}
|
||||
}
|
|
@ -1,41 +1,38 @@
|
|||
// 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.
|
||||
|
||||
//! Library module for the main wallet functionalities provided by Grin.
|
||||
|
||||
extern crate secp256k1zkp as secp;
|
||||
extern crate crypto;
|
||||
extern crate rand;
|
||||
extern crate byteorder;
|
||||
extern crate crypto;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate rand;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::{error, fmt};
|
||||
extern crate grin_api as api;
|
||||
extern crate grin_core as core;
|
||||
extern crate grin_util as util;
|
||||
extern crate secp256k1zkp as secp;
|
||||
|
||||
pub mod extendedkey;
|
||||
pub mod constants;
|
||||
mod extkey;
|
||||
mod receiver;
|
||||
mod sender;
|
||||
mod types;
|
||||
|
||||
/// An ExtKey error
|
||||
#[derive(Copy, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum Error {
|
||||
/// The size of the seed is invalid
|
||||
InvalidSeedSize,
|
||||
InvalidSliceSize,
|
||||
InvalidExtendedKey,
|
||||
}
|
||||
|
||||
// Passthrough Debug to Display, since errors should be user-visible
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.write_str(error::Error::description(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
None
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::InvalidSeedSize => "wallet: seed isn't of size 128, 256 or 512",
|
||||
//TODO change when ser. ext. size is fixed
|
||||
Error::InvalidSliceSize => "wallet: serialized extended key must be of size 73",
|
||||
Error::InvalidExtendedKey => "wallet: the given serialized extended key is invalid",
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use extkey::ExtendedKey;
|
||||
pub use receiver::WalletReceiver;
|
||||
|
|
135
wallet/src/receiver.rs
Normal file
135
wallet/src/receiver.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
// 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.
|
||||
|
||||
//! Provides the JSON/HTTP API for wallets to receive payments. Because
|
||||
//! receiving money in MimbleWimble requires an interactive exchange, a
|
||||
//! wallet server that's running at all time is required in many cases.
|
||||
//!
|
||||
//! The API looks like this:
|
||||
//!
|
||||
//! POST /v1/wallet/receive
|
||||
//! > {
|
||||
//! > "amount": 10,
|
||||
//! > "blind_sum": "a12b7f...",
|
||||
//! > "tx": "f083de...",
|
||||
//! > }
|
||||
//!
|
||||
//! < {
|
||||
//! < "tx": "f083de...",
|
||||
//! < "status": "ok"
|
||||
//! < }
|
||||
//!
|
||||
//! POST /v1/wallet/finalize
|
||||
//! > {
|
||||
//! > "tx": "f083de...",
|
||||
//! > }
|
||||
//!
|
||||
//! POST /v1/wallet/receive_coinbase
|
||||
//! > {
|
||||
//! > "amount": 1,
|
||||
//! > }
|
||||
//!
|
||||
//! < {
|
||||
//! < "output": "8a90bc...",
|
||||
//! < "kernel": "f083de...",
|
||||
//! < }
|
||||
//!
|
||||
//! Note that while at this point the finalize call is completely unecessary, a
|
||||
//! double-exchange will be required as soon as we support Schnorr signatures.
|
||||
//! So we may as well have it in place already.
|
||||
|
||||
use std::convert::From;
|
||||
use secp::{self, Secp256k1};
|
||||
use secp::key::SecretKey;
|
||||
|
||||
use core::core::{Block, Transaction, TxKernel, Output, build};
|
||||
use core::ser;
|
||||
use api::*;
|
||||
use extkey::{self, ExtendedKey};
|
||||
use types::*;
|
||||
use util;
|
||||
|
||||
/// Amount in request to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbAmount {
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
/// Response to build a coinbase output.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CbData {
|
||||
output: String,
|
||||
kernel: String,
|
||||
}
|
||||
|
||||
/// Component used to receive coins, implements all the receiving end of the
|
||||
/// wallet REST API as well as some of the command-line operations.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletReceiver {
|
||||
pub key: ExtendedKey,
|
||||
}
|
||||
|
||||
impl ApiEndpoint for WalletReceiver {
|
||||
type ID = String;
|
||||
type T = String;
|
||||
type OP_IN = CbAmount;
|
||||
type OP_OUT = CbData;
|
||||
|
||||
fn operations(&self) -> Vec<Operation> {
|
||||
vec![Operation::Custom("receive_coinbase".to_string())]
|
||||
}
|
||||
|
||||
fn operation(&self, op: String, input: CbAmount) -> ApiResult<CbData> {
|
||||
debug!("Operation {} with amount {}", op, input.amount);
|
||||
if input.amount == 0 {
|
||||
return Err(ApiError::Argument(format!("Zero amount not allowed.")))
|
||||
}
|
||||
match op.as_str() {
|
||||
"receive_coinbase" => {
|
||||
let (out, kern) = receive_coinbase(&self.key, input.amount).
|
||||
map_err(|e| ApiError::Internal(format!("Error building coinbase: {:?}", e)))?;
|
||||
let out_bin = ser::ser_vec(&out).
|
||||
map_err(|e| ApiError::Internal(format!("Error serializing output: {:?}", e)))?;
|
||||
let kern_bin = ser::ser_vec(&kern).
|
||||
map_err(|e| ApiError::Internal(format!("Error serializing kernel: {:?}", e)))?;
|
||||
Ok(CbData {
|
||||
output: util::to_hex(out_bin),
|
||||
kernel: util::to_hex(kern_bin),
|
||||
})
|
||||
},
|
||||
_ => Err(ApiError::Argument(format!("Unknown operation: {}", op))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a coinbase output and the corresponding kernel
|
||||
fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKernel), Error> {
|
||||
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||
|
||||
// derive a new private for the reward
|
||||
let mut wallet_data = WalletData::read_or_create()?;
|
||||
let next_child = wallet_data.next_child(ext_key.fingerprint);
|
||||
let coinbase_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?;
|
||||
|
||||
// track the new output and return the stuff needed for reward
|
||||
wallet_data.append_output(OutputData {
|
||||
fingerprint: coinbase_key.fingerprint,
|
||||
n_child: coinbase_key.n_child,
|
||||
value: amount,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
});
|
||||
wallet_data.write()?;
|
||||
|
||||
Block::reward_output(ext_key.key, &secp).map_err(&From::from)
|
||||
}
|
78
wallet/src/sender.rs
Normal file
78
wallet/src/sender.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
// 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::convert::From;
|
||||
use secp::{self, Secp256k1};
|
||||
use secp::key::SecretKey;
|
||||
|
||||
use core::core::{Transaction, build};
|
||||
use extkey::ExtendedKey;
|
||||
use types::*;
|
||||
|
||||
fn issue_send_tx(hd_seed: &[u8], amount: u64, dest: String) -> Result<(), Error> {
|
||||
|
||||
let (tx, blind_sum) = build_send_tx(hd_seed, amount)?;
|
||||
let json_tx = partial_tx_to_json(amount, blind_sum, tx);
|
||||
if dest == "stdout" {
|
||||
println!("{}", dest);
|
||||
} else if &dest[..4] == "http" {
|
||||
// TODO
|
||||
unimplemented!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a transaction to send to someone from the HD seed associated with the
|
||||
/// wallet and the amount to send. Handles reading through the wallet data file,
|
||||
/// selecting outputs to spend and building the change.
|
||||
fn build_send_tx(hd_seed: &[u8], amount: u64) -> Result<(Transaction, SecretKey), Error> {
|
||||
// first, rebuild the private key from the seed
|
||||
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||
let ext_key = ExtendedKey::from_seed(&secp, hd_seed).map_err(|e| Error::Key(e))?;
|
||||
|
||||
// second, check from our local wallet data for outputs to spend
|
||||
let mut wallet_data = WalletData::read()?;
|
||||
let (mut coins, change) = wallet_data.select(ext_key.fingerprint, amount);
|
||||
if change < 0 {
|
||||
return Err(Error::NotEnoughFunds((-change) as u64));
|
||||
}
|
||||
|
||||
// TODO add fees, which is likely going to make this iterative
|
||||
|
||||
// third, build inputs using the appropriate key
|
||||
let mut parts = vec![];
|
||||
for coin in &coins {
|
||||
let in_key = ext_key.derive(&secp, coin.n_child).map_err(|e| Error::Key(e))?;
|
||||
parts.push(build::input(coin.value, in_key.key));
|
||||
}
|
||||
|
||||
// fourth, derive a new private for change and build the change output
|
||||
let next_child = wallet_data.next_child(ext_key.fingerprint);
|
||||
let change_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?;
|
||||
parts.push(build::output(change as u64, change_key.key));
|
||||
|
||||
// we got that far, time to start tracking the new output, finalize tx
|
||||
// and lock the outputs used
|
||||
wallet_data.append_output(OutputData {
|
||||
fingerprint: change_key.fingerprint,
|
||||
n_child: change_key.n_child,
|
||||
value: change as u64,
|
||||
status: OutputStatus::Unconfirmed,
|
||||
});
|
||||
for mut coin in coins {
|
||||
coin.lock();
|
||||
}
|
||||
wallet_data.write()?;
|
||||
build::transaction(parts).map_err(&From::from)
|
||||
}
|
182
wallet/src/types.rs
Normal file
182
wallet/src/types.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
// 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()
|
||||
}
|
Loading…
Add table
Reference in a new issue