diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index fddab33..ec7a1b6 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -24,6 +24,7 @@ //! This module provides the structures and functions needed to support scripts. //! +use std::hash; use std::char::from_digit; use std::default::Default; use serialize::json; @@ -51,6 +52,14 @@ use util::thinvec::ThinVec; /// A Bitcoin script pub struct Script(ThinVec); +impl hash::Hash for Script { + #[inline] + fn hash(&self, state: &mut S) { + let &Script(ref raw) = self; + raw.as_slice().hash(state) + } +} + /// Ways that a script might fail. Not everything is split up as /// much as it could be; patches welcome if more detailed errors /// would help you. @@ -1165,6 +1174,11 @@ impl AbstractStack { self.stack.as_slice() } + /// Immutable view of the current stack as a slice of bytes + pub fn slice_to<'a>(&'a self, n: uint) -> &'a [uint] { + self.stack.slice_to(n) + } + /// Mutable view of the current stack as a slice (to be compatible /// with the `stack_opcode!` macro fn as_mut_slice<'a>(&'a mut self) -> &'a mut [uint] { @@ -1588,6 +1602,12 @@ impl Script { /// Creates a new script from an existing vector pub fn from_vec(v: Vec) -> Script { Script(ThinVec::from_vec(v)) } + /// The length in bytes of the script + pub fn len(&self) -> uint { + let &Script(ref raw) = self; + raw.len() + } + /// Adds instructions to push an integer onto the stack. Integers are /// encoded as little-endian signed-magnitude numbers, but there are /// dedicated opcodes to push some small integers. @@ -1654,6 +1674,24 @@ impl Script { raw.as_slice() } + /// Returns a view into the script as a slice + pub fn slice_to(&self, n: uint) -> &[u8] { + let &Script(ref raw) = self; + raw.slice_to(n) + } + + /// Returns a view into the script as a slice + pub fn slice_from(&self, n: uint) -> &[u8] { + let &Script(ref raw) = self; + raw.slice_from(n) + } + + /// Returns a view into the script as a slice + pub fn slice(&self, s: uint, e: uint) -> &[u8] { + let &Script(ref raw) = self; + raw.slice(s, e) + } + /// Trace a script pub fn trace<'a>(&'a self, stack: &mut Vec>, input_context: Option<(&Transaction, uint)>) diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index 9a999e7..6d88652 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -31,6 +31,8 @@ use blockdata::script::{mod, Script, ScriptError, ScriptTrace, read_scriptbool}; use blockdata::utxoset::UtxoSet; use network::encodable::ConsensusEncodable; use network::serialize::BitcoinHash; +use network::constants::Network; +use wallet::address::Address; /// A transaction input, which defines old coins to be consumed #[deriving(Clone, PartialEq, Eq, Show)] @@ -65,6 +67,26 @@ impl Default for TxOut { } } +/// A classification for script pubkeys +pub enum ScriptPubkeyTemplate { + /// A pay-to-address output + PayToPubkeyHash(Address), + /// Another kind of output + Unknown +} + +impl TxOut { + pub fn classify(&self, network: Network) -> ScriptPubkeyTemplate { + if self.script_pubkey.len() == 25 && + self.script_pubkey.slice_to(3) == &[0x76, 0xa9, 0x14] && + self.script_pubkey.slice_from(23) == &[0x88, 0xac] { + PayToPubkeyHash(Address::from_slice(network, self.script_pubkey.slice(3, 23))) + } else { + Unknown + } + } +} + /// A Bitcoin transaction, which describes an authenticated movement of coins #[deriving(Clone, PartialEq, Eq, Show)] pub struct Transaction { diff --git a/src/blockdata/utxoset.rs b/src/blockdata/utxoset.rs index ffd76da..3b8e3aa 100644 --- a/src/blockdata/utxoset.rs +++ b/src/blockdata/utxoset.rs @@ -20,9 +20,11 @@ use std::cmp; use std::collections::HashMap; +use std::collections::hashmap::Entries; use std::mem; use std::os::num_cpus; use std::sync::Future; +use std::num::Zero; use blockdata::transaction::{Transaction, TxOut}; use blockdata::transaction::{TransactionError, InputNotFound}; @@ -61,6 +63,39 @@ pub enum UtxoSetError { /// Vector of outputs; None indicates a nonexistent or already spent output type UtxoNode = ThinVec>; +/// An iterator over UTXOs +pub struct UtxoIterator<'a> { + tx_iter: Entries<'a, Uint128, UtxoNode>, + current_key: Uint128, + current: Option<&'a UtxoNode>, + tx_index: uint +} + +impl<'a> Iterator<(Uint128, uint, &'a TxOut)> for UtxoIterator<'a> { + fn next(&mut self) -> Option<(Uint128, uint, &'a TxOut)> { + while self.current.is_some() { + let current = self.current.unwrap(); + while self.tx_index < current.len() { + self.tx_index += 1; + if unsafe { current.get(self.tx_index - 1) }.is_some() { + return Some((self.current_key, + self.tx_index, + unsafe { current.get(self.tx_index - 1) }.as_ref().unwrap())); + } + } + match self.tx_iter.next() { + Some((&x, y)) => { + self.tx_index = 0; + self.current_key = x; + self.current = Some(y); + } + None => { self.current = None; } + } + } + return None; + } +} + /// The UTXO set pub struct UtxoSet { table: HashMap, @@ -366,6 +401,26 @@ impl UtxoSet { pub fn n_pruned(&self) -> uint { self.n_pruned as uint } + + /// Get an iterator over all UTXOs + pub fn iter<'a>(&'a self) -> UtxoIterator<'a> { + let mut iter = self.table.iter(); + let first = iter.next(); + match first { + Some((&key, val)) => UtxoIterator { + current_key: key, + current: Some(val), + tx_iter: iter, + tx_index: 0 + }, + None => UtxoIterator { + current_key: Zero::zero(), + current: None, + tx_iter: iter, + tx_index: 0 + } + } + } } #[cfg(test)] diff --git a/src/util/thinvec.rs b/src/util/thinvec.rs index bbd752a..201d1af 100644 --- a/src/util/thinvec.rs +++ b/src/util/thinvec.rs @@ -103,7 +103,13 @@ impl ThinVec { self.as_slice().slice_from(index) } - /// Returns a slice starting from `s` ending in `e` + /// Returns a slice ending just before `index` + #[inline] + pub fn slice_to<'a>(&'a self, index: uint) -> &'a [T] { + self.as_slice().slice_to(index) + } + + /// Returns a slice starting from `s` ending just before `e` #[inline] pub fn slice<'a>(&'a self, s: uint, e: uint) -> &'a [T] { self.as_slice().slice(s, e) diff --git a/src/util/uint.rs b/src/util/uint.rs index e7b47ef..c15add6 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -29,6 +29,7 @@ macro_rules! construct_uint( /// Little-endian large integer type #[repr(C)] pub struct $name(pub [u64, ..$n_words]); + impl_array_newtype!($name, u64, $n_words) impl $name { /// Conversion to u32 @@ -289,18 +290,6 @@ macro_rules! construct_uint( } } - impl PartialEq for $name { - fn eq(&self, other: &$name) -> bool { - let &$name(ref arr1) = self; - let &$name(ref arr2) = other; - for i in range(0, $n_words) { - if arr1[i] != arr2[i] { return false; } - } - return true; - } - } - impl Eq for $name {} - impl Ord for $name { fn cmp(&self, other: &$name) -> Ordering { let &$name(ref me) = self; diff --git a/src/wallet/address.rs b/src/wallet/address.rs index 25ec5ae..31d8fea 100644 --- a/src/wallet/address.rs +++ b/src/wallet/address.rs @@ -36,7 +36,24 @@ pub struct Address { } impl Address { + /// Creates an address the raw 20 bytes of a hash + #[inline] + pub fn from_slice(network: Network, data: &[u8]) -> Address { + Address { + network: network, + hash: Ripemd160Hash::from_slice(data) + } + } + + /// Returns a byteslice view of the `Address` --- note that no network information + /// is contained in this. + #[inline] + pub fn as_slice<'a>(&'a self) -> &'a [u8] { + self.hash.as_slice() + } + /// Creates an address from a public key + #[inline] pub fn from_key(network: Network, pk: &PublicKey) -> Address { let mut sha = Sha256::new(); let mut out = [0, ..32]; @@ -49,6 +66,7 @@ impl Address { } /// Generates a script pubkey spending to this address + #[inline] pub fn script_pubkey(&self) -> Script { let mut script = Script::new(); script.push_opcode(all::OP_DUP); diff --git a/src/wallet/address_index.rs b/src/wallet/address_index.rs new file mode 100644 index 0000000..5fc6e26 --- /dev/null +++ b/src/wallet/address_index.rs @@ -0,0 +1,51 @@ +// Rust Bitcoin Library +// Written in 2014 by +// Andrew Poelstra +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Address Index +//! +//! Maintains an index from addresses to unspent outputs. It reduces size by +//! checking that the first byte of HMAC(wallet key, address outscript) is +//! zero, so that the index will be 1/256th the size of the utxoset in RAM. +//! + +use std::collections::HashMap; + +use blockdata::utxoset::UtxoSet; +use blockdata::script::Script; +use wallet::wallet::Wallet; +use util::uint::Uint128; + +/// An address index +#[deriving(Clone, PartialEq, Eq, Show)] +pub struct AddressIndex { + index: HashMap +} + +impl AddressIndex { + /// Creates a new address index from a wallet (which provides an authenticated + /// hash function for prefix filtering) and UTXO set (which is what gets filtered). + pub fn new(utxo_set: &UtxoSet, wallet: &Wallet) -> AddressIndex { + let mut ret = AddressIndex { + index: HashMap::with_capacity(utxo_set.n_utxos() / 256) + }; + for (key, idx, txo) in utxo_set.iter() { + if wallet.might_be_mine(txo) { + ret.index.insert(txo.script_pubkey.clone(), (key, idx)); + } + } + ret + } +} + + + diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index fb7c789..e309497 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -17,6 +17,7 @@ //! Wallet, keys and addresses pub mod address; +pub mod address_index; pub mod bip32; pub mod wallet; diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index 9009473..49d35ce 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -18,7 +18,10 @@ use std::collections::HashMap; use std::default::Default; +use std::io::extensions::u64_from_be_bytes; +use collections::hash::sip::hash_with_keys; +use blockdata::transaction::{PayToPubkeyHash, TxOut}; use network::constants::Network; use wallet::bip32::{mod, ChildNumber, ExtendedPrivKey, Normal, Hardened}; @@ -56,7 +59,8 @@ pub struct Wallet { } impl Wallet { - /// Creates a new wallet from a seed + /// Creates a new wallet from a BIP32 seed + #[inline] pub fn from_seed(network: Network, seed: &[u8]) -> Result { let mut accounts = HashMap::new(); accounts.insert(String::new(), Default::default()); @@ -82,6 +86,30 @@ impl Wallet { }); Ok(()) } + + /// Returns the network of the wallet + #[inline] + pub fn network(&self) -> Network { + self.master.network + } + + /// Returns a key suitable for keying hash functions for DoS protection + #[inline] + pub fn siphash_key(&self) -> (u64, u64) { + let ck_slice = self.master.chain_code.as_slice(); + (u64_from_be_bytes(ck_slice, 0, 8), + u64_from_be_bytes(ck_slice, 8, 8)) + } + + /// A filter used for creating a small address index + #[inline] + pub fn might_be_mine(&self, out: &TxOut) -> bool { + let (k1, k2) = self.siphash_key(); + match out.classify(self.network()) { + PayToPubkeyHash(addr) => hash_with_keys(k1, k2, &addr.as_slice()) & 0xFF == 0, + _ => false + } + } }