mirror of
https://github.com/mimblewimble/grin.git
synced 2025-05-06 09:11:13 +03:00
Restore wallet restore (#843)
* adding appropriate message to bulletproofs to allow for restore * rustfmt * should work, now test * rustfmt * fix to wallet restore, works now * fix pool tests * fix pool tests * rustfmt
This commit is contained in:
parent
90a7f3d0f6
commit
80887196a8
14 changed files with 531 additions and 87 deletions
api/src
core/src/core
grin/tests
keychain/src
p2p/src
pool/src
src/bin
util
wallet/src
|
@ -21,7 +21,6 @@ use chain;
|
||||||
use p2p;
|
use p2p;
|
||||||
use util;
|
use util;
|
||||||
use util::secp::pedersen;
|
use util::secp::pedersen;
|
||||||
use util::secp::constants::MAX_PROOF_SIZE;
|
|
||||||
use serde;
|
use serde;
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
use serde::de::MapAccess;
|
use serde::de::MapAccess;
|
||||||
|
@ -230,7 +229,7 @@ pub struct OutputPrintable {
|
||||||
/// Whether the output has been spent
|
/// Whether the output has been spent
|
||||||
pub spent: bool,
|
pub spent: bool,
|
||||||
/// Rangeproof (as hex string)
|
/// Rangeproof (as hex string)
|
||||||
pub proof: Option<pedersen::RangeProof>,
|
pub proof: Option<String>,
|
||||||
/// Rangeproof hash (as hex string)
|
/// Rangeproof hash (as hex string)
|
||||||
pub proof_hash: String,
|
pub proof_hash: String,
|
||||||
|
|
||||||
|
@ -257,7 +256,7 @@ impl OutputPrintable {
|
||||||
let spent = chain.is_unspent(&out_id).is_err();
|
let spent = chain.is_unspent(&out_id).is_err();
|
||||||
|
|
||||||
let proof = if include_proof {
|
let proof = if include_proof {
|
||||||
Some(output.proof)
|
Some(util::to_hex(output.proof.proof.to_vec()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -289,9 +288,19 @@ impl OutputPrintable {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn range_proof(&self) -> Result<pedersen::RangeProof, ser::Error> {
|
pub fn range_proof(&self) -> Result<pedersen::RangeProof, ser::Error> {
|
||||||
self.proof
|
let proof_str = self.proof
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| ser::Error::HexError(format!("output range_proof missing")))
|
.ok_or_else(|| ser::Error::HexError(format!("output range_proof missing")))
|
||||||
|
.unwrap();
|
||||||
|
let p_vec = util::from_hex(proof_str).unwrap();
|
||||||
|
let mut p_bytes = [0; util::secp::constants::MAX_PROOF_SIZE];
|
||||||
|
for i in 0..p_bytes.len() {
|
||||||
|
p_bytes[i] = p_vec[i];
|
||||||
|
}
|
||||||
|
Ok(pedersen::RangeProof {
|
||||||
|
proof: p_bytes,
|
||||||
|
plen: p_bytes.len(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,22 +379,7 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
|
||||||
}
|
}
|
||||||
Field::Proof => {
|
Field::Proof => {
|
||||||
no_dup!(proof);
|
no_dup!(proof);
|
||||||
|
proof = map.next_value()?
|
||||||
let val: Option<String> = map.next_value()?;
|
|
||||||
|
|
||||||
if val.is_some() {
|
|
||||||
let vec = util::from_hex(val.unwrap().clone())
|
|
||||||
.map_err(serde::de::Error::custom)?;
|
|
||||||
let mut bytes = [0; MAX_PROOF_SIZE];
|
|
||||||
for i in 0..vec.len() {
|
|
||||||
bytes[i] = vec[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
proof = Some(pedersen::RangeProof {
|
|
||||||
proof: bytes,
|
|
||||||
plen: vec.len(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Field::ProofHash => {
|
Field::ProofHash => {
|
||||||
no_dup!(proof_hash);
|
no_dup!(proof_hash);
|
||||||
|
@ -415,13 +409,8 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELDS: &'static [&'static str] = &[
|
const FIELDS: &'static [&'static str] =
|
||||||
"output_type",
|
&["output_type", "commit", "spent", "proof", "proof_hash"];
|
||||||
"commit",
|
|
||||||
"spent",
|
|
||||||
"proof",
|
|
||||||
"proof_hash",
|
|
||||||
];
|
|
||||||
deserializer.deserialize_struct("OutputPrintable", FIELDS, OutputPrintableVisitor)
|
deserializer.deserialize_struct("OutputPrintable", FIELDS, OutputPrintableVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ use time;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use core::{Committed, Input, KernelFeatures, Output, OutputFeatures, Proof,
|
use core::{Committed, Input, KernelFeatures, Output, OutputFeatures, Proof, ProofMessageElements,
|
||||||
ShortId, Transaction, TxKernel};
|
ShortId, Transaction, TxKernel};
|
||||||
use consensus;
|
use consensus;
|
||||||
use consensus::{exceeds_weight, reward, VerifySortOrder, REWARD};
|
use consensus::{exceeds_weight, reward, VerifySortOrder, REWARD};
|
||||||
|
@ -169,7 +169,7 @@ impl Readable for BlockHeader {
|
||||||
let nonce = reader.read_u64()?;
|
let nonce = reader.read_u64()?;
|
||||||
let pow = Proof::read(reader)?;
|
let pow = Proof::read(reader)?;
|
||||||
|
|
||||||
if timestamp > (1 << 55) || timestamp < -(1 << 55) {
|
if timestamp > (1 << 55) || timestamp < -(1 << 55) {
|
||||||
return Err(ser::Error::CorruptedData);
|
return Err(ser::Error::CorruptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -784,20 +784,13 @@ impl Block {
|
||||||
fees: u64,
|
fees: u64,
|
||||||
height: u64,
|
height: u64,
|
||||||
) -> Result<(Output, TxKernel), keychain::Error> {
|
) -> Result<(Output, TxKernel), keychain::Error> {
|
||||||
let commit = keychain.commit(reward(fees), key_id)?;
|
let value = reward(fees);
|
||||||
|
let commit = keychain.commit(value, key_id)?;
|
||||||
|
let msg = ProofMessageElements::new(value, key_id);
|
||||||
|
|
||||||
trace!(
|
trace!(LOGGER, "Block reward - Pedersen Commit is: {:?}", commit,);
|
||||||
LOGGER,
|
|
||||||
"Block reward - Pedersen Commit is: {:?}",
|
|
||||||
commit,
|
|
||||||
);
|
|
||||||
|
|
||||||
let rproof = keychain.range_proof(
|
let rproof = keychain.range_proof(value, key_id, commit, None, msg.to_proof_message())?;
|
||||||
reward(fees),
|
|
||||||
key_id,
|
|
||||||
commit,
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let output = Output {
|
let output = Output {
|
||||||
features: OutputFeatures::COINBASE_OUTPUT,
|
features: OutputFeatures::COINBASE_OUTPUT,
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
use util::{kernel_sig_msg, secp};
|
use util::{kernel_sig_msg, secp};
|
||||||
|
|
||||||
use core::{Input, Output, OutputFeatures, Transaction, TxKernel};
|
use core::{Input, Output, OutputFeatures, ProofMessageElements, Transaction, TxKernel};
|
||||||
use core::hash::Hash;
|
use core::hash::Hash;
|
||||||
use core::pmmr::MerkleProof;
|
use core::pmmr::MerkleProof;
|
||||||
use keychain;
|
use keychain;
|
||||||
|
@ -101,20 +101,13 @@ pub fn output(value: u64, key_id: Identifier) -> Box<Append> {
|
||||||
debug!(LOGGER, "Building an output: {}, {}", value, key_id,);
|
debug!(LOGGER, "Building an output: {}, {}", value, key_id,);
|
||||||
|
|
||||||
let commit = build.keychain.commit(value, &key_id).unwrap();
|
let commit = build.keychain.commit(value, &key_id).unwrap();
|
||||||
trace!(
|
trace!(LOGGER, "Builder - Pedersen Commit is: {:?}", commit,);
|
||||||
LOGGER,
|
|
||||||
"Builder - Pedersen Commit is: {:?}",
|
let msg = ProofMessageElements::new(value, &key_id);
|
||||||
commit,
|
|
||||||
);
|
|
||||||
|
|
||||||
let rproof = build
|
let rproof = build
|
||||||
.keychain
|
.keychain
|
||||||
.range_proof(
|
.range_proof(value, &key_id, commit, None, msg.to_proof_message())
|
||||||
value,
|
|
||||||
&key_id,
|
|
||||||
commit,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
//! Transactions
|
//! Transactions
|
||||||
use util::secp::{self, Message, Signature};
|
use util::secp::{self, Message, Signature};
|
||||||
use util::{kernel_sig_msg, static_secp_instance};
|
use util::{kernel_sig_msg, static_secp_instance};
|
||||||
use util::secp::pedersen::{Commitment, RangeProof};
|
use util::secp::pedersen::{Commitment, ProofMessage, RangeProof};
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::{error, fmt};
|
use std::{error, fmt};
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
use consensus;
|
use consensus;
|
||||||
use consensus::VerifySortOrder;
|
use consensus::VerifySortOrder;
|
||||||
|
@ -29,7 +30,7 @@ use core::hash::{Hash, Hashed, ZERO_HASH};
|
||||||
use core::pmmr::MerkleProof;
|
use core::pmmr::MerkleProof;
|
||||||
use keychain;
|
use keychain;
|
||||||
use keychain::{BlindingFactor, Keychain};
|
use keychain::{BlindingFactor, Keychain};
|
||||||
use ser::{self, read_and_verify_sorted, PMMRable, Readable, Reader, Writeable,
|
use ser::{self, read_and_verify_sorted, ser_vec, PMMRable, Readable, Reader, Writeable,
|
||||||
WriteableSorted, Writer};
|
WriteableSorted, Writer};
|
||||||
use util;
|
use util;
|
||||||
use util::LOGGER;
|
use util::LOGGER;
|
||||||
|
@ -95,6 +96,9 @@ pub enum Error {
|
||||||
/// Error originating from an input attempting to spend an immature
|
/// Error originating from an input attempting to spend an immature
|
||||||
/// coinbase output
|
/// coinbase output
|
||||||
ImmatureCoinbase,
|
ImmatureCoinbase,
|
||||||
|
/// Returns if the value hidden within the a RangeProof message isn't
|
||||||
|
/// repeated 3 times, indicating it's incorrect
|
||||||
|
InvalidProofMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for Error {
|
impl error::Error for Error {
|
||||||
|
@ -739,12 +743,7 @@ impl Output {
|
||||||
pub fn verify_proof(&self) -> Result<(), secp::Error> {
|
pub fn verify_proof(&self) -> Result<(), secp::Error> {
|
||||||
let secp = static_secp_instance();
|
let secp = static_secp_instance();
|
||||||
let secp = secp.lock().unwrap();
|
let secp = secp.lock().unwrap();
|
||||||
match Keychain::verify_range_proof(
|
match Keychain::verify_range_proof(&secp, self.commit, self.proof, None) {
|
||||||
&secp,
|
|
||||||
self.commit,
|
|
||||||
self.proof,
|
|
||||||
None,
|
|
||||||
) {
|
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
|
@ -786,7 +785,7 @@ impl OutputIdentifier {
|
||||||
features: self.features,
|
features: self.features,
|
||||||
commit: self.commit,
|
commit: self.commit,
|
||||||
proof: proof,
|
proof: proof,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build an output_identifier from an existing input.
|
/// Build an output_identifier from an existing input.
|
||||||
|
@ -833,6 +832,114 @@ impl Readable for OutputIdentifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A structure which contains fields that are to be commited to within
|
||||||
|
/// an Output's range (bullet) proof.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub struct ProofMessageElements {
|
||||||
|
/// The amount, stored to allow for wallet reconstruction as
|
||||||
|
/// rewinding isn't supported in bulletproofs just yet
|
||||||
|
/// This is going to be written 3 times, to facilitate checking
|
||||||
|
/// values on rewind
|
||||||
|
/// Note that rewinding with only the nonce will give you back
|
||||||
|
/// the first 32 bytes of the message. To get the second
|
||||||
|
/// 32 bytes, you need to provide the correct blinding factor as well
|
||||||
|
value: u64,
|
||||||
|
/// another copy of the value, to check on rewind
|
||||||
|
value_copy_1: u64,
|
||||||
|
/// another copy of the value
|
||||||
|
value_copy_2: u64,
|
||||||
|
/// the first 8 bytes of the blinding factor, used to avoid having to grind
|
||||||
|
/// through a proof each time you want to check against key possibilities
|
||||||
|
bf_first_8: Vec<u8>,
|
||||||
|
/// unused portion of message, used to test whether we have both nonce
|
||||||
|
/// and blinding correct
|
||||||
|
zeroes: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Writeable for ProofMessageElements {
|
||||||
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
|
writer.write_u64(self.value)?;
|
||||||
|
writer.write_u64(self.value_copy_1)?;
|
||||||
|
writer.write_u64(self.value_copy_2)?;
|
||||||
|
writer.write_fixed_bytes(&self.bf_first_8)?;
|
||||||
|
for i in 0..32 {
|
||||||
|
let _ = writer.write_u8(self.zeroes[i]);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Readable for ProofMessageElements {
|
||||||
|
fn read(reader: &mut Reader) -> Result<ProofMessageElements, ser::Error> {
|
||||||
|
// if the value isn't repeated 3 times, it's most likely not the value,
|
||||||
|
// so reject
|
||||||
|
Ok(ProofMessageElements {
|
||||||
|
value: reader.read_u64()?,
|
||||||
|
value_copy_1: reader.read_u64()?,
|
||||||
|
value_copy_2: reader.read_u64()?,
|
||||||
|
bf_first_8: reader.read_fixed_bytes(8)?,
|
||||||
|
zeroes: reader.read_fixed_bytes(32)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProofMessageElements {
|
||||||
|
/// Create a new proof message
|
||||||
|
pub fn new(value: u64, blinding: &keychain::Identifier) -> ProofMessageElements {
|
||||||
|
ProofMessageElements {
|
||||||
|
value: value,
|
||||||
|
value_copy_1: value,
|
||||||
|
value_copy_2: value,
|
||||||
|
bf_first_8: blinding.to_bytes()[0..8].to_vec(),
|
||||||
|
zeroes: [0u8; 32].to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value if it's valid, an error otherwise
|
||||||
|
pub fn value(&self) -> Result<u64, Error> {
|
||||||
|
if self.value == self.value_copy_1 && self.value == self.value_copy_2 {
|
||||||
|
Ok(self.value)
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidProofMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare given identifier with first 8 bytes of what's stored
|
||||||
|
pub fn compare_bf_first_8(&self, in_id: &keychain::Identifier) -> bool {
|
||||||
|
let in_id_vec = in_id.to_bytes()[0..8].to_vec();
|
||||||
|
for i in 0..8 {
|
||||||
|
if in_id_vec[i] != self.bf_first_8[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether our remainder is zero (as it should be if the BF and nonce used to unwind
|
||||||
|
/// are correct
|
||||||
|
pub fn zeroes_correct(&self) -> bool {
|
||||||
|
for i in 0..self.zeroes.len() {
|
||||||
|
if self.zeroes[i] != 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialise and return a ProofMessage
|
||||||
|
pub fn to_proof_message(&self) -> ProofMessage {
|
||||||
|
ProofMessage::from_bytes(&ser_vec(self).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialise and return the message elements
|
||||||
|
pub fn from_proof_message(
|
||||||
|
proof_message: ProofMessage,
|
||||||
|
) -> Result<ProofMessageElements, ser::Error> {
|
||||||
|
let mut c = Cursor::new(proof_message.as_bytes());
|
||||||
|
ser::deserialize::<ProofMessageElements>(&mut c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -891,14 +998,7 @@ mod test {
|
||||||
let key_id = keychain.derive_key_id(1).unwrap();
|
let key_id = keychain.derive_key_id(1).unwrap();
|
||||||
let commit = keychain.commit(5, &key_id).unwrap();
|
let commit = keychain.commit(5, &key_id).unwrap();
|
||||||
let msg = secp::pedersen::ProofMessage::empty();
|
let msg = secp::pedersen::ProofMessage::empty();
|
||||||
let proof = keychain
|
let proof = keychain.range_proof(5, &key_id, commit, None, msg).unwrap();
|
||||||
.range_proof(
|
|
||||||
5,
|
|
||||||
&key_id,
|
|
||||||
commit,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let out = Output {
|
let out = Output {
|
||||||
features: OutputFeatures::DEFAULT_OUTPUT,
|
features: OutputFeatures::DEFAULT_OUTPUT,
|
||||||
|
|
|
@ -188,7 +188,7 @@ fn simulate_block_propagation() {
|
||||||
// instantiates 5 servers on different ports
|
// instantiates 5 servers on different ports
|
||||||
let mut servers = vec![];
|
let mut servers = vec![];
|
||||||
for n in 0..5 {
|
for n in 0..5 {
|
||||||
let s = grin::Server::new(config(10*n, test_name_dir, 0)).unwrap();
|
let s = grin::Server::new(config(10 * n, test_name_dir, 0)).unwrap();
|
||||||
servers.push(s);
|
servers.push(s);
|
||||||
thread::sleep(time::Duration::from_millis(100));
|
thread::sleep(time::Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,10 @@ impl Identifier {
|
||||||
Identifier(identifier)
|
Identifier(identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; IDENTIFIER_SIZE] {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_pubkey(secp: &Secp256k1, pubkey: &PublicKey) -> Identifier {
|
pub fn from_pubkey(secp: &Secp256k1, pubkey: &PublicKey) -> Identifier {
|
||||||
let bytes = pubkey.serialize_vec(secp, true);
|
let bytes = pubkey.serialize_vec(secp, true);
|
||||||
let identifier = blake2b(IDENTIFIER_SIZE, &[], &bytes[..]);
|
let identifier = blake2b(IDENTIFIER_SIZE, &[], &bytes[..]);
|
||||||
|
|
|
@ -214,15 +214,42 @@ impl Keychain {
|
||||||
Ok(commit)
|
Ok(commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rangeproof_create_nonce(&self, commit: &Commitment) -> SecretKey {
|
||||||
|
// hash(commit|masterkey) as nonce
|
||||||
|
let root_key = self.root_key_id().to_bytes();
|
||||||
|
let res = blake2::blake2b::blake2b(32, &commit.0, &root_key);
|
||||||
|
let res = res.as_bytes();
|
||||||
|
let mut ret_val = [0; 32];
|
||||||
|
for i in 0..res.len() {
|
||||||
|
ret_val[i] = res[i];
|
||||||
|
}
|
||||||
|
SecretKey::from_slice(&self.secp, &ret_val).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn range_proof(
|
pub fn range_proof(
|
||||||
&self,
|
&self,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
key_id: &Identifier,
|
key_id: &Identifier,
|
||||||
_commit: Commitment,
|
_commit: Commitment,
|
||||||
extra_data: Option<Vec<u8>>,
|
extra_data: Option<Vec<u8>>,
|
||||||
|
msg: ProofMessage,
|
||||||
) -> Result<RangeProof, Error> {
|
) -> Result<RangeProof, Error> {
|
||||||
|
let commit = self.commit(amount, key_id)?;
|
||||||
let skey = self.derived_key(key_id)?;
|
let skey = self.derived_key(key_id)?;
|
||||||
Ok(self.secp.bullet_proof(amount, skey, extra_data, None))
|
let nonce = self.rangeproof_create_nonce(&commit);
|
||||||
|
if msg.len() == 0 {
|
||||||
|
return Ok(self.secp
|
||||||
|
.bullet_proof(amount, skey, nonce, extra_data, None));
|
||||||
|
} else {
|
||||||
|
if msg.len() != 64 {
|
||||||
|
error!(LOGGER, "Bullet proof message must be 64 bytes.");
|
||||||
|
return Err(Error::RangeProof(
|
||||||
|
"Bullet proof message must be 64 bytes".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(self.secp
|
||||||
|
.bullet_proof(amount, skey, nonce, extra_data, Some(msg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_range_proof(
|
pub fn verify_range_proof(
|
||||||
|
@ -245,9 +272,10 @@ impl Keychain {
|
||||||
extra_data: Option<Vec<u8>>,
|
extra_data: Option<Vec<u8>>,
|
||||||
proof: RangeProof,
|
proof: RangeProof,
|
||||||
) -> Result<ProofInfo, Error> {
|
) -> Result<ProofInfo, Error> {
|
||||||
let nonce = self.derived_key(key_id)?;
|
let skey = self.derived_key(key_id)?;
|
||||||
|
let nonce = self.rangeproof_create_nonce(&commit);
|
||||||
let proof_message = self.secp
|
let proof_message = self.secp
|
||||||
.unwind_bullet_proof(commit, nonce, nonce, extra_data, proof);
|
.unwind_bullet_proof(commit, skey, nonce, extra_data, proof);
|
||||||
let proof_info = match proof_message {
|
let proof_info = match proof_message {
|
||||||
Ok(p) => ProofInfo {
|
Ok(p) => ProofInfo {
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -975,4 +1003,75 @@ mod test {
|
||||||
assert!(sig_verifies);
|
assert!(sig_verifies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rewind_range_proof() {
|
||||||
|
let keychain = Keychain::from_random_seed().unwrap();
|
||||||
|
let key_id = keychain.derive_key_id(1).unwrap();
|
||||||
|
let commit = keychain.commit(5, &key_id).unwrap();
|
||||||
|
let mut msg = ProofMessage::from_bytes(&[0u8; 64]);
|
||||||
|
let extra_data = [99u8; 64];
|
||||||
|
|
||||||
|
let proof = keychain
|
||||||
|
.range_proof(5, &key_id, commit, Some(extra_data.to_vec().clone()), msg)
|
||||||
|
.unwrap();
|
||||||
|
let proof_info = keychain
|
||||||
|
.rewind_range_proof(&key_id, commit, Some(extra_data.to_vec().clone()), proof)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(proof_info.success, true);
|
||||||
|
|
||||||
|
// now check the recovered message is "empty" (but not truncated) i.e. all
|
||||||
|
// zeroes
|
||||||
|
//Value is in the message in this case
|
||||||
|
assert_eq!(
|
||||||
|
proof_info.message,
|
||||||
|
secp::pedersen::ProofMessage::from_bytes(&[0; secp::constants::BULLET_PROOF_MSG_SIZE])
|
||||||
|
);
|
||||||
|
|
||||||
|
let key_id2 = keychain.derive_key_id(2).unwrap();
|
||||||
|
|
||||||
|
// cannot rewind with a different nonce
|
||||||
|
let proof_info = keychain
|
||||||
|
.rewind_range_proof(&key_id2, commit, Some(extra_data.to_vec().clone()), proof)
|
||||||
|
.unwrap();
|
||||||
|
// With bullet proofs, if you provide the wrong nonce you'll get gibberish back
|
||||||
|
// as opposed to a failure to recover the message
|
||||||
|
assert_ne!(
|
||||||
|
proof_info.message,
|
||||||
|
secp::pedersen::ProofMessage::from_bytes(&[0; secp::constants::BULLET_PROOF_MSG_SIZE])
|
||||||
|
);
|
||||||
|
assert_eq!(proof_info.value, 0);
|
||||||
|
|
||||||
|
// cannot rewind with a commitment to the same value using a different key
|
||||||
|
let commit2 = keychain.commit(5, &key_id2).unwrap();
|
||||||
|
let proof_info = keychain
|
||||||
|
.rewind_range_proof(&key_id, commit2, Some(extra_data.to_vec().clone()), proof)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(proof_info.success, false);
|
||||||
|
assert_eq!(proof_info.value, 0);
|
||||||
|
|
||||||
|
// cannot rewind with a commitment to a different value
|
||||||
|
let commit3 = keychain.commit(4, &key_id).unwrap();
|
||||||
|
let proof_info = keychain
|
||||||
|
.rewind_range_proof(&key_id, commit3, Some(extra_data.to_vec().clone()), proof)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(proof_info.success, false);
|
||||||
|
assert_eq!(proof_info.value, 0);
|
||||||
|
|
||||||
|
// cannot rewind with wrong extra committed data
|
||||||
|
let commit3 = keychain.commit(4, &key_id).unwrap();
|
||||||
|
let wrong_extra_data = [98u8; 64];
|
||||||
|
let should_err = keychain
|
||||||
|
.rewind_range_proof(
|
||||||
|
&key_id,
|
||||||
|
commit3,
|
||||||
|
Some(wrong_extra_data.to_vec().clone()),
|
||||||
|
proof,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(proof_info.success, false);
|
||||||
|
assert_eq!(proof_info.value, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,7 +205,7 @@ impl ChainAdapter for DummyAdapter {
|
||||||
fn total_height(&self) -> u64 {
|
fn total_height(&self) -> u64 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
fn transaction_received(&self, _: core::Transaction, stem: bool) {}
|
fn transaction_received(&self, _: core::Transaction, _stem: bool) {}
|
||||||
fn compact_block_received(&self, _cb: core::CompactBlock, _addr: SocketAddr) -> bool {
|
fn compact_block_received(&self, _cb: core::CompactBlock, _addr: SocketAddr) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,6 +297,7 @@ mod tests {
|
||||||
use keychain::Keychain;
|
use keychain::Keychain;
|
||||||
use rand;
|
use rand;
|
||||||
use core::core::OutputFeatures;
|
use core::core::OutputFeatures;
|
||||||
|
use core::core::transaction::ProofMessageElements;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_entry() {
|
fn test_add_entry() {
|
||||||
|
@ -322,16 +323,13 @@ mod tests {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let msg = ProofMessageElements::new(100, &key_id1);
|
||||||
|
|
||||||
let output = core::transaction::Output {
|
let output = core::transaction::Output {
|
||||||
features: OutputFeatures::DEFAULT_OUTPUT,
|
features: OutputFeatures::DEFAULT_OUTPUT,
|
||||||
commit: output_commit,
|
commit: output_commit,
|
||||||
proof: keychain
|
proof: keychain
|
||||||
.range_proof(
|
.range_proof(100, &key_id1, output_commit, None, msg.to_proof_message())
|
||||||
100,
|
|
||||||
&key_id1,
|
|
||||||
output_commit,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -859,6 +859,7 @@ mod tests {
|
||||||
use core::core::hash::{Hash, Hashed};
|
use core::core::hash::{Hash, Hashed};
|
||||||
use core::core::pmmr::MerkleProof;
|
use core::core::pmmr::MerkleProof;
|
||||||
use core::core::target::Difficulty;
|
use core::core::target::Difficulty;
|
||||||
|
use core::core::transaction::ProofMessageElements;
|
||||||
use types::PoolError::InvalidTx;
|
use types::PoolError::InvalidTx;
|
||||||
|
|
||||||
macro_rules! expect_output_parent {
|
macro_rules! expect_output_parent {
|
||||||
|
@ -1710,9 +1711,11 @@ mod tests {
|
||||||
fn test_output(value: u64) -> transaction::Output {
|
fn test_output(value: u64) -> transaction::Output {
|
||||||
let keychain = keychain_for_tests();
|
let keychain = keychain_for_tests();
|
||||||
let key_id = keychain.derive_key_id(value as u32).unwrap();
|
let key_id = keychain.derive_key_id(value as u32).unwrap();
|
||||||
|
let msg = ProofMessageElements::new(value, &key_id);
|
||||||
let commit = keychain.commit(value, &key_id).unwrap();
|
let commit = keychain.commit(value, &key_id).unwrap();
|
||||||
let proof = keychain.range_proof(value, &key_id, commit, None).unwrap();
|
let proof = keychain
|
||||||
|
.range_proof(value, &key_id, commit, None, msg.to_proof_message())
|
||||||
|
.unwrap();
|
||||||
transaction::Output {
|
transaction::Output {
|
||||||
features: transaction::OutputFeatures::DEFAULT_OUTPUT,
|
features: transaction::OutputFeatures::DEFAULT_OUTPUT,
|
||||||
commit: commit,
|
commit: commit,
|
||||||
|
@ -1724,9 +1727,11 @@ mod tests {
|
||||||
fn test_coinbase_output(value: u64) -> transaction::Output {
|
fn test_coinbase_output(value: u64) -> transaction::Output {
|
||||||
let keychain = keychain_for_tests();
|
let keychain = keychain_for_tests();
|
||||||
let key_id = keychain.derive_key_id(value as u32).unwrap();
|
let key_id = keychain.derive_key_id(value as u32).unwrap();
|
||||||
|
let msg = ProofMessageElements::new(value, &key_id);
|
||||||
let commit = keychain.commit(value, &key_id).unwrap();
|
let commit = keychain.commit(value, &key_id).unwrap();
|
||||||
let proof = keychain.range_proof(value, &key_id, commit, None).unwrap();
|
let proof = keychain
|
||||||
|
.range_proof(value, &key_id, commit, None, msg.to_proof_message())
|
||||||
|
.unwrap();
|
||||||
transaction::Output {
|
transaction::Output {
|
||||||
features: transaction::OutputFeatures::COINBASE_OUTPUT,
|
features: transaction::OutputFeatures::COINBASE_OUTPUT,
|
||||||
commit: commit,
|
commit: commit,
|
||||||
|
|
|
@ -251,7 +251,11 @@ fn main() {
|
||||||
.about("basic wallet contents summary"))
|
.about("basic wallet contents summary"))
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("init")
|
.subcommand(SubCommand::with_name("init")
|
||||||
.about("Initialize a new wallet seed file.")))
|
.about("Initialize a new wallet seed file."))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("restore")
|
||||||
|
.about("Attempt to restore wallet contents from the chain using seed and password. \
|
||||||
|
NOTE: Backup wallet.* and run `wallet listen` before running restore.")))
|
||||||
|
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
|
@ -490,6 +494,12 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
wallet_config.check_node_api_http_addr = sa.to_string().clone();
|
wallet_config.check_node_api_http_addr = sa.to_string().clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let key_derivations: u32 = wallet_args
|
||||||
|
.value_of("key_derivations")
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut show_spent = false;
|
let mut show_spent = false;
|
||||||
if wallet_args.is_present("show_spent") {
|
if wallet_args.is_present("show_spent") {
|
||||||
show_spent = true;
|
show_spent = true;
|
||||||
|
@ -611,6 +621,9 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) {
|
||||||
("outputs", Some(_)) => {
|
("outputs", Some(_)) => {
|
||||||
wallet::show_outputs(&wallet_config, &keychain, show_spent);
|
wallet::show_outputs(&wallet_config, &keychain, show_spent);
|
||||||
}
|
}
|
||||||
|
("restore", Some(_)) => {
|
||||||
|
let _ = wallet::restore(&wallet_config, &keychain, key_derivations);
|
||||||
|
}
|
||||||
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
|
_ => panic!("Unknown wallet command, use 'grin help wallet' for details"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,6 @@ zip = "^0.2.6"
|
||||||
|
|
||||||
[dependencies.secp256k1zkp]
|
[dependencies.secp256k1zkp]
|
||||||
git = "https://github.com/mimblewimble/rust-secp256k1-zkp"
|
git = "https://github.com/mimblewimble/rust-secp256k1-zkp"
|
||||||
tag = "grin_integration_15"
|
tag = "grin_integration_16"
|
||||||
#path = "../../rust-secp256k1-zkp"
|
#path = "../../rust-secp256k1-zkp"
|
||||||
features = ["bullet-proof-sizing"]
|
features = ["bullet-proof-sizing"]
|
||||||
|
|
|
@ -53,6 +53,7 @@ mod info;
|
||||||
mod receiver;
|
mod receiver;
|
||||||
mod sender;
|
mod sender;
|
||||||
mod types;
|
mod types;
|
||||||
|
mod restore;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
|
@ -62,3 +63,4 @@ pub use receiver::WalletReceiver;
|
||||||
pub use sender::{issue_burn_tx, issue_send_tx};
|
pub use sender::{issue_burn_tx, issue_send_tx};
|
||||||
pub use types::{BlockFees, CbData, Error, ErrorKind, WalletConfig, WalletInfo,
|
pub use types::{BlockFees, CbData, Error, ErrorKind, WalletConfig, WalletInfo,
|
||||||
WalletReceiveRequest, WalletSeed};
|
WalletReceiveRequest, WalletSeed};
|
||||||
|
pub use restore::restore;
|
||||||
|
|
248
wallet/src/restore.rs
Normal file
248
wallet/src/restore.rs
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
// Copyright 2018 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 failure::{Fail, ResultExt};
|
||||||
|
use keychain::{Identifier, Keychain};
|
||||||
|
use util::LOGGER;
|
||||||
|
use util::secp::pedersen;
|
||||||
|
use api;
|
||||||
|
use core::global;
|
||||||
|
use core::core::transaction::ProofMessageElements;
|
||||||
|
use types::{Error, ErrorKind, OutputData, OutputStatus, WalletConfig, WalletData};
|
||||||
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
|
|
||||||
|
pub fn get_chain_height(config: &WalletConfig) -> Result<u64, Error> {
|
||||||
|
let url = format!("{}/v1/chain", config.check_node_api_http_addr);
|
||||||
|
|
||||||
|
match api::client::get::<api::Tip>(url.as_str()) {
|
||||||
|
Ok(tip) => Ok(tip.height),
|
||||||
|
Err(e) => {
|
||||||
|
// if we got anything other than 200 back from server, bye
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"get_chain_height: Restore failed... unable to contact API {}. Error: {}",
|
||||||
|
config.check_node_api_http_addr,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
Err(e.context(ErrorKind::Node).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coinbase_status(output: &api::OutputPrintable) -> bool {
|
||||||
|
match output.output_type {
|
||||||
|
api::OutputType::Coinbase => true,
|
||||||
|
api::OutputType::Transaction => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outputs_batch_block(
|
||||||
|
config: &WalletConfig,
|
||||||
|
start_height: u64,
|
||||||
|
end_height: u64,
|
||||||
|
) -> Result<Vec<api::BlockOutputs>, Error> {
|
||||||
|
let query_param = format!(
|
||||||
|
"start_height={}&end_height={}&include_rp",
|
||||||
|
start_height, end_height
|
||||||
|
);
|
||||||
|
|
||||||
|
let url = format!(
|
||||||
|
"{}/v1/chain/outputs/byheight?{}",
|
||||||
|
config.check_node_api_http_addr, query_param,
|
||||||
|
);
|
||||||
|
|
||||||
|
match api::client::get::<Vec<api::BlockOutputs>>(url.as_str()) {
|
||||||
|
Ok(outputs) => Ok(outputs),
|
||||||
|
Err(e) => {
|
||||||
|
// if we got anything other than 200 back from server, bye
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"outputs_batch_block: Restore failed... unable to contact API {}. Error: {}",
|
||||||
|
config.check_node_api_http_addr,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
Err(e.context(ErrorKind::Node))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - wrap the many return values in a struct
|
||||||
|
fn find_outputs_with_key(
|
||||||
|
keychain: &Keychain,
|
||||||
|
block_outputs: api::BlockOutputs,
|
||||||
|
key_iterations: &mut usize,
|
||||||
|
) -> Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> {
|
||||||
|
let mut wallet_outputs: Vec<(pedersen::Commitment, Identifier, u32, u64, u64, u64, bool)> =
|
||||||
|
Vec::new();
|
||||||
|
|
||||||
|
info!(
|
||||||
|
LOGGER,
|
||||||
|
"Scanning block {}, {} outputs, over {} key derivations",
|
||||||
|
block_outputs.header.height,
|
||||||
|
block_outputs.outputs.len(),
|
||||||
|
*key_iterations,
|
||||||
|
);
|
||||||
|
|
||||||
|
// skey doesn't matter in this case
|
||||||
|
let skey = keychain.derive_key_id(1).unwrap();
|
||||||
|
for output in block_outputs.outputs.iter().filter(|x| !x.spent) {
|
||||||
|
// attempt to unwind message from the RP and get a value.. note
|
||||||
|
// this will only return okay if the value is included in the
|
||||||
|
// message 3 times, indicating a strong match. Also, sec_key provided
|
||||||
|
// to unwind in this case will be meaningless. With only the nonce known
|
||||||
|
// only the first 32 bytes of the recovered message will be accurate
|
||||||
|
let info = keychain
|
||||||
|
.rewind_range_proof(&skey, output.commit, None, output.range_proof().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let message = ProofMessageElements::from_proof_message(info.message).unwrap();
|
||||||
|
let value = message.value();
|
||||||
|
if value.is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// we have a match, now check through our key iterations to find a partial match
|
||||||
|
let mut found = false;
|
||||||
|
for i in 1..*key_iterations {
|
||||||
|
let key_id = &keychain.derive_key_id(i as u32).unwrap();
|
||||||
|
if !message.compare_bf_first_8(key_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
// we have a partial match, let's just confirm
|
||||||
|
let info = keychain
|
||||||
|
.rewind_range_proof(key_id, output.commit, None, output.range_proof().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let message = ProofMessageElements::from_proof_message(info.message).unwrap();
|
||||||
|
let value = message.value();
|
||||||
|
if value.is_err() || !message.zeroes_correct() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let value = value.unwrap();
|
||||||
|
info!(
|
||||||
|
LOGGER,
|
||||||
|
"Output found: {:?}, key_index: {:?}", output.commit, i,
|
||||||
|
);
|
||||||
|
|
||||||
|
// add it to result set here
|
||||||
|
let commit_id = output.commit.0;
|
||||||
|
|
||||||
|
let is_coinbase = coinbase_status(output);
|
||||||
|
|
||||||
|
info!(LOGGER, "Amount: {}", value);
|
||||||
|
|
||||||
|
let commit = keychain
|
||||||
|
.commit_with_key_index(BigEndian::read_u64(&commit_id), i as u32)
|
||||||
|
.expect("commit with key index");
|
||||||
|
|
||||||
|
let height = block_outputs.header.height;
|
||||||
|
let lock_height = if is_coinbase {
|
||||||
|
height + global::coinbase_maturity()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
wallet_outputs.push((
|
||||||
|
commit,
|
||||||
|
key_id.clone(),
|
||||||
|
i as u32,
|
||||||
|
value,
|
||||||
|
height,
|
||||||
|
lock_height,
|
||||||
|
is_coinbase,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
warn!(
|
||||||
|
LOGGER,
|
||||||
|
"Very probable matching output found with amount: {} \
|
||||||
|
run restore again with a larger value of key_iterations to claim",
|
||||||
|
message.value().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
LOGGER,
|
||||||
|
"Found {} wallet_outputs for block {}",
|
||||||
|
wallet_outputs.len(),
|
||||||
|
block_outputs.header.height,
|
||||||
|
);
|
||||||
|
|
||||||
|
wallet_outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore(
|
||||||
|
config: &WalletConfig,
|
||||||
|
keychain: &Keychain,
|
||||||
|
key_derivations: u32,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Don't proceed if wallet.dat has anything in it
|
||||||
|
let is_empty = WalletData::read_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
|
Ok(wallet_data.outputs.len() == 0)
|
||||||
|
}).context(ErrorKind::WalletData("could not read wallet"))?;
|
||||||
|
if !is_empty {
|
||||||
|
error!(
|
||||||
|
LOGGER,
|
||||||
|
"Not restoring. Please back up and remove existing wallet.dat first."
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get height of chain from node (we'll check again when done)
|
||||||
|
let chain_height = get_chain_height(config)?;
|
||||||
|
info!(
|
||||||
|
LOGGER,
|
||||||
|
"Starting restore: Chain height is {}.", chain_height
|
||||||
|
);
|
||||||
|
|
||||||
|
let batch_size = 100;
|
||||||
|
// this will start here, then lower as outputs are found, moving backwards on
|
||||||
|
// the chain
|
||||||
|
let mut key_iterations = key_derivations as usize;
|
||||||
|
let mut h = chain_height;
|
||||||
|
while {
|
||||||
|
let end_batch = h;
|
||||||
|
if h >= batch_size {
|
||||||
|
h -= batch_size;
|
||||||
|
} else {
|
||||||
|
h = 0;
|
||||||
|
}
|
||||||
|
let mut blocks = outputs_batch_block(config, h + 1, end_batch)?;
|
||||||
|
blocks.reverse();
|
||||||
|
|
||||||
|
let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| {
|
||||||
|
for block in blocks {
|
||||||
|
let result_vec = find_outputs_with_key(keychain, block, &mut key_iterations);
|
||||||
|
if result_vec.len() > 0 {
|
||||||
|
for output in result_vec.clone() {
|
||||||
|
let root_key_id = keychain.root_key_id();
|
||||||
|
// Just plonk it in for now, and refresh actual values via wallet info
|
||||||
|
// command later
|
||||||
|
wallet_data.add_output(OutputData {
|
||||||
|
root_key_id: root_key_id.clone(),
|
||||||
|
key_id: output.1.clone(),
|
||||||
|
n_child: output.2,
|
||||||
|
value: output.3,
|
||||||
|
status: OutputStatus::Unconfirmed,
|
||||||
|
height: output.4,
|
||||||
|
lock_height: output.5,
|
||||||
|
is_coinbase: output.6,
|
||||||
|
block: None,
|
||||||
|
merkle_proof: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
h > 0
|
||||||
|
} {}
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue