Compare commits
1 Commits
add-hd-key
...
multiparty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c4e5b4d25 |
@@ -28,6 +28,7 @@ electrum = ["electrum-client"]
|
||||
esplora = ["reqwest", "futures"]
|
||||
key-value-db = ["sled"]
|
||||
cli-utils = ["clap"]
|
||||
multiparty = []
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2", features = ["macros"] }
|
||||
@@ -50,6 +51,10 @@ name = "miniscriptc"
|
||||
path = "examples/compiler.rs"
|
||||
required-features = ["compiler"]
|
||||
|
||||
[[example]]
|
||||
name = "multiparty"
|
||||
required-features = ["multiparty","compiler"]
|
||||
|
||||
# Provide a more user-friendly alias for the REPL
|
||||
[[example]]
|
||||
name = "magic"
|
||||
|
||||
96
examples/multiparty.rs
Normal file
96
examples/multiparty.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
extern crate bitcoin;
|
||||
extern crate clap;
|
||||
extern crate log;
|
||||
extern crate magical_bitcoin_wallet;
|
||||
extern crate miniscript;
|
||||
extern crate rand;
|
||||
extern crate serde_json;
|
||||
extern crate sled;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use log::info;
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
use bitcoin::PublicKey;
|
||||
|
||||
use miniscript::policy::Concrete;
|
||||
use miniscript::Descriptor;
|
||||
|
||||
use magical_bitcoin_wallet::multiparty::{Coordinator, Participant, Peer};
|
||||
|
||||
fn main() {
|
||||
env_logger::init_from_env(
|
||||
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
|
||||
);
|
||||
|
||||
let matches = App::new("Multiparty Tools")
|
||||
.arg(
|
||||
Arg::with_name("POLICY")
|
||||
.help("Sets the spending policy to compile")
|
||||
.required(true)
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("TYPE")
|
||||
.help("Sets the script type used to embed the compiled policy")
|
||||
.required(true)
|
||||
.index(2)
|
||||
.possible_values(&["sh", "wsh", "sh-wsh"]),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let policy_str = matches.value_of("POLICY").unwrap();
|
||||
info!("Compiling policy: {}", policy_str);
|
||||
|
||||
let policy = Concrete::<String>::from_str(&policy_str).unwrap();
|
||||
let compiled = policy.compile().unwrap();
|
||||
|
||||
let descriptor = match matches.value_of("TYPE").unwrap() {
|
||||
"sh" => Descriptor::Sh(compiled),
|
||||
"wsh" => Descriptor::Wsh(compiled),
|
||||
"sh-wsh" => Descriptor::ShWsh(compiled),
|
||||
_ => panic!("Invalid type"),
|
||||
};
|
||||
|
||||
info!("Descriptor: {}", descriptor);
|
||||
|
||||
let mut coordinator: Participant<Coordinator> = Participant::new(descriptor).unwrap();
|
||||
/*let policy = coordinator.policy_for(vec![]).unwrap();
|
||||
info!(
|
||||
"Policy:\n{}",
|
||||
serde_json::to_string_pretty(&policy).unwrap()
|
||||
);*/
|
||||
|
||||
let missing_keys = coordinator.missing_keys();
|
||||
info!("Missing keys: {:?}", missing_keys);
|
||||
|
||||
let pk =
|
||||
PublicKey::from_str("02c65413e56b343a0a31c18d506f1502a17fc64dfbcef6bfb00d1c0d6229bb6f61")
|
||||
.unwrap();
|
||||
coordinator.add_key("Alice", pk.into()).unwrap();
|
||||
coordinator.add_key("Carol", pk.into()).unwrap();
|
||||
|
||||
let for_bob = coordinator.descriptor_for("Bob").unwrap();
|
||||
info!("Descriptor for Bob: {}", for_bob);
|
||||
|
||||
let mut bob_peer: Participant<Peer> = Participant::new(for_bob).unwrap();
|
||||
info!(
|
||||
"Bob's policy: {}",
|
||||
serde_json::to_string(&bob_peer.policy().unwrap().unwrap()).unwrap()
|
||||
);
|
||||
bob_peer.use_key(pk.into()).unwrap();
|
||||
info!("Bob's my_key: {}", bob_peer.my_key().unwrap());
|
||||
|
||||
coordinator.add_key("Bob", pk.into()).unwrap();
|
||||
info!("Coordinator completed: {}", coordinator.completed());
|
||||
|
||||
let coord_map = coordinator.get_map().unwrap();
|
||||
|
||||
let finalized = coordinator.finalize().unwrap();
|
||||
info!("Coordinator final: {}", finalized);
|
||||
|
||||
let bob_finalized = bob_peer.apply_map(coord_map).unwrap();
|
||||
info!("Bob final: {}", bob_finalized);
|
||||
}
|
||||
@@ -226,7 +226,7 @@ pub trait ElectrumLikeSync {
|
||||
let mut to_check_later = vec![];
|
||||
for (i, output) in tx.output.iter().enumerate() {
|
||||
// this output is ours, we have a path to derive it
|
||||
if let Some((script_type, child)) =
|
||||
if let Some((script_type, path)) =
|
||||
database.get_path_from_script_pubkey(&output.script_pubkey)?
|
||||
{
|
||||
debug!("{} output #{} is mine, adding utxo", txid, i);
|
||||
@@ -242,8 +242,10 @@ pub trait ElectrumLikeSync {
|
||||
}
|
||||
|
||||
// derive as many change addrs as external addresses that we've seen
|
||||
if script_type == ScriptType::Internal && child > *change_max_deriv {
|
||||
*change_max_deriv = child;
|
||||
if script_type == ScriptType::Internal
|
||||
&& u32::from(path.as_ref()[0]) > *change_max_deriv
|
||||
{
|
||||
*change_max_deriv = u32::from(path.as_ref()[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::convert::TryInto;
|
||||
use std::convert::{From, TryInto};
|
||||
|
||||
use sled::{Batch, Tree};
|
||||
|
||||
use bitcoin::consensus::encode::{deserialize, serialize};
|
||||
use bitcoin::hash_types::Txid;
|
||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath};
|
||||
use bitcoin::{OutPoint, Script, Transaction};
|
||||
|
||||
use crate::database::memory::MapKey;
|
||||
@@ -13,14 +14,15 @@ use crate::types::*;
|
||||
|
||||
macro_rules! impl_batch_operations {
|
||||
( { $($after_insert:tt)* }, $process_delete:ident ) => {
|
||||
fn set_script_pubkey(&mut self, script: &Script, script_type: ScriptType, path: u32) -> Result<(), Error> {
|
||||
let key = MapKey::Path((Some(script_type), Some(path))).as_map_key();
|
||||
fn set_script_pubkey<P: AsRef<[ChildNumber]>>(&mut self, script: &Script, script_type: ScriptType, path: &P) -> Result<(), Error> {
|
||||
let deriv_path = DerivationPath::from(path.as_ref());
|
||||
let key = MapKey::Path((Some(script_type), Some(&deriv_path))).as_map_key();
|
||||
self.insert(key, serialize(script))$($after_insert)*;
|
||||
|
||||
let key = MapKey::Script(Some(script)).as_map_key();
|
||||
let value = json!({
|
||||
"t": script_type,
|
||||
"p": path,
|
||||
"p": deriv_path,
|
||||
});
|
||||
self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*;
|
||||
|
||||
@@ -68,15 +70,16 @@ macro_rules! impl_batch_operations {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn del_script_pubkey_from_path(&mut self, script_type: ScriptType, path: u32) -> Result<Option<Script>, Error> {
|
||||
let key = MapKey::Path((Some(script_type), Some(path))).as_map_key();
|
||||
fn del_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(&mut self, script_type: ScriptType, path: &P) -> Result<Option<Script>, Error> {
|
||||
let deriv_path = DerivationPath::from(path.as_ref());
|
||||
let key = MapKey::Path((Some(script_type), Some(&deriv_path))).as_map_key();
|
||||
let res = self.remove(key);
|
||||
let res = $process_delete!(res);
|
||||
|
||||
Ok(res.map_or(Ok(None), |x| Some(deserialize(&x)).transpose())?)
|
||||
}
|
||||
|
||||
fn del_path_from_script_pubkey(&mut self, script: &Script) -> Result<Option<(ScriptType, u32)>, Error> {
|
||||
fn del_path_from_script_pubkey(&mut self, script: &Script) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
|
||||
let key = MapKey::Script(Some(script)).as_map_key();
|
||||
let res = self.remove(key);
|
||||
let res = $process_delete!(res);
|
||||
@@ -242,19 +245,20 @@ impl Database for Tree {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_script_pubkey_from_path(
|
||||
fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
|
||||
&self,
|
||||
script_type: ScriptType,
|
||||
path: u32,
|
||||
path: &P,
|
||||
) -> Result<Option<Script>, Error> {
|
||||
let key = MapKey::Path((Some(script_type), Some(path))).as_map_key();
|
||||
let deriv_path = DerivationPath::from(path.as_ref());
|
||||
let key = MapKey::Path((Some(script_type), Some(&deriv_path))).as_map_key();
|
||||
Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
|
||||
}
|
||||
|
||||
fn get_path_from_script_pubkey(
|
||||
&self,
|
||||
script: &Script,
|
||||
) -> Result<Option<(ScriptType, u32)>, Error> {
|
||||
) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
|
||||
let key = MapKey::Script(Some(script)).as_map_key();
|
||||
self.get(key)?
|
||||
.map(|b| -> Result<_, Error> {
|
||||
@@ -411,13 +415,14 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
tree.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
tree.set_script_pubkey(&script, script_type, &path).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tree.get_script_pubkey_from_path(script_type, path).unwrap(),
|
||||
tree.get_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap(),
|
||||
Some(script.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -434,13 +439,16 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
batch.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
batch
|
||||
.set_script_pubkey(&script, script_type, &path)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tree.get_script_pubkey_from_path(script_type, path).unwrap(),
|
||||
tree.get_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
assert_eq!(tree.get_path_from_script_pubkey(&script).unwrap(), None);
|
||||
@@ -448,7 +456,8 @@ mod test {
|
||||
tree.commit_batch(batch).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tree.get_script_pubkey_from_path(script_type, path).unwrap(),
|
||||
tree.get_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap(),
|
||||
Some(script.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -464,10 +473,10 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
tree.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
tree.set_script_pubkey(&script, script_type, &path).unwrap();
|
||||
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
|
||||
}
|
||||
@@ -479,13 +488,14 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
tree.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
tree.set_script_pubkey(&script, script_type, &path).unwrap();
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
|
||||
|
||||
tree.del_script_pubkey_from_path(script_type, path).unwrap();
|
||||
tree.del_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap();
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::ops::Bound::{Excluded, Included};
|
||||
|
||||
use bitcoin::consensus::encode::{deserialize, serialize};
|
||||
use bitcoin::hash_types::Txid;
|
||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath};
|
||||
use bitcoin::{OutPoint, Script, Transaction};
|
||||
|
||||
use crate::database::{BatchDatabase, BatchOperations, Database};
|
||||
@@ -18,7 +19,7 @@ use crate::types::*;
|
||||
// descriptor checksum d{i,e} -> vec<u8>
|
||||
|
||||
pub(crate) enum MapKey<'a> {
|
||||
Path((Option<ScriptType>, Option<u32>)),
|
||||
Path((Option<ScriptType>, Option<&'a DerivationPath>)),
|
||||
Script(Option<&'a Script>),
|
||||
UTXO(Option<&'a OutPoint>),
|
||||
RawTx(Option<&'a Txid>),
|
||||
@@ -48,7 +49,13 @@ impl MapKey<'_> {
|
||||
|
||||
fn serialize_content(&self) -> Vec<u8> {
|
||||
match self {
|
||||
MapKey::Path((_, Some(child))) => u32::from(*child).to_be_bytes().to_vec(),
|
||||
MapKey::Path((_, Some(path))) => {
|
||||
let mut res = vec![];
|
||||
for val in *path {
|
||||
res.extend(&u32::from(*val).to_be_bytes());
|
||||
}
|
||||
res
|
||||
}
|
||||
MapKey::Script(Some(s)) => serialize(*s),
|
||||
MapKey::UTXO(Some(s)) => serialize(*s),
|
||||
MapKey::RawTx(Some(s)) => serialize(*s),
|
||||
@@ -92,19 +99,20 @@ impl MemoryDatabase {
|
||||
}
|
||||
|
||||
impl BatchOperations for MemoryDatabase {
|
||||
fn set_script_pubkey(
|
||||
fn set_script_pubkey<P: AsRef<[ChildNumber]>>(
|
||||
&mut self,
|
||||
script: &Script,
|
||||
script_type: ScriptType,
|
||||
path: u32,
|
||||
path: &P,
|
||||
) -> Result<(), Error> {
|
||||
let key = MapKey::Path((Some(script_type), Some(path))).as_map_key();
|
||||
let deriv_path = DerivationPath::from(path.as_ref());
|
||||
let key = MapKey::Path((Some(script_type), Some(&deriv_path))).as_map_key();
|
||||
self.map.insert(key, Box::new(script.clone()));
|
||||
|
||||
let key = MapKey::Script(Some(script)).as_map_key();
|
||||
let value = json!({
|
||||
"t": script_type,
|
||||
"p": path,
|
||||
"p": deriv_path,
|
||||
});
|
||||
self.map.insert(key, Box::new(value));
|
||||
|
||||
@@ -146,12 +154,13 @@ impl BatchOperations for MemoryDatabase {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn del_script_pubkey_from_path(
|
||||
fn del_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
|
||||
&mut self,
|
||||
script_type: ScriptType,
|
||||
path: u32,
|
||||
path: &P,
|
||||
) -> Result<Option<Script>, Error> {
|
||||
let key = MapKey::Path((Some(script_type), Some(path))).as_map_key();
|
||||
let deriv_path = DerivationPath::from(path.as_ref());
|
||||
let key = MapKey::Path((Some(script_type), Some(&deriv_path))).as_map_key();
|
||||
let res = self.map.remove(&key);
|
||||
self.deleted_keys.push(key);
|
||||
|
||||
@@ -160,7 +169,7 @@ impl BatchOperations for MemoryDatabase {
|
||||
fn del_path_from_script_pubkey(
|
||||
&mut self,
|
||||
script: &Script,
|
||||
) -> Result<Option<(ScriptType, u32)>, Error> {
|
||||
) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
|
||||
let key = MapKey::Script(Some(script)).as_map_key();
|
||||
let res = self.map.remove(&key);
|
||||
self.deleted_keys.push(key);
|
||||
@@ -304,12 +313,13 @@ impl Database for MemoryDatabase {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_script_pubkey_from_path(
|
||||
fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
|
||||
&self,
|
||||
script_type: ScriptType,
|
||||
path: u32,
|
||||
path: &P,
|
||||
) -> Result<Option<Script>, Error> {
|
||||
let key = MapKey::Path((Some(script_type), Some(path))).as_map_key();
|
||||
let deriv_path = DerivationPath::from(path.as_ref());
|
||||
let key = MapKey::Path((Some(script_type), Some(&deriv_path))).as_map_key();
|
||||
Ok(self
|
||||
.map
|
||||
.get(&key)
|
||||
@@ -319,7 +329,7 @@ impl Database for MemoryDatabase {
|
||||
fn get_path_from_script_pubkey(
|
||||
&self,
|
||||
script: &Script,
|
||||
) -> Result<Option<(ScriptType, u32)>, Error> {
|
||||
) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
|
||||
let key = MapKey::Script(Some(script)).as_map_key();
|
||||
Ok(self.map.get(&key).map(|b| {
|
||||
let mut val: serde_json::Value = b.downcast_ref().cloned().unwrap();
|
||||
@@ -400,6 +410,8 @@ impl BatchDatabase for MemoryDatabase {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Condvar, Mutex, Once};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use bitcoin::consensus::encode::deserialize;
|
||||
use bitcoin::hashes::hex::*;
|
||||
@@ -419,13 +431,14 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
tree.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
tree.set_script_pubkey(&script, script_type, &path).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tree.get_script_pubkey_from_path(script_type, path).unwrap(),
|
||||
tree.get_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap(),
|
||||
Some(script.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -442,13 +455,16 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
batch.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
batch
|
||||
.set_script_pubkey(&script, script_type, &path)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tree.get_script_pubkey_from_path(script_type, path).unwrap(),
|
||||
tree.get_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
assert_eq!(tree.get_path_from_script_pubkey(&script).unwrap(), None);
|
||||
@@ -456,12 +472,13 @@ mod test {
|
||||
tree.commit_batch(batch).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tree.get_script_pubkey_from_path(script_type, path).unwrap(),
|
||||
tree.get_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap(),
|
||||
Some(script.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
tree.get_path_from_script_pubkey(&script).unwrap(),
|
||||
Some((script_type, path))
|
||||
Some((script_type, path.clone()))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -472,10 +489,10 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
tree.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
tree.set_script_pubkey(&script, script_type, &path).unwrap();
|
||||
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
|
||||
}
|
||||
@@ -487,13 +504,14 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
tree.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
tree.set_script_pubkey(&script, script_type, &path).unwrap();
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
|
||||
|
||||
tree.del_script_pubkey_from_path(script_type, path).unwrap();
|
||||
tree.del_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap();
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
|
||||
}
|
||||
|
||||
@@ -504,20 +522,20 @@ mod test {
|
||||
let script = Script::from(
|
||||
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||
);
|
||||
let path = 42;
|
||||
let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
|
||||
let script_type = ScriptType::External;
|
||||
|
||||
tree.set_script_pubkey(&script, script_type, path).unwrap();
|
||||
tree.set_script_pubkey(&script, script_type, &path).unwrap();
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
|
||||
|
||||
let mut batch = tree.begin_batch();
|
||||
batch
|
||||
.del_script_pubkey_from_path(script_type, path)
|
||||
.del_script_pubkey_from_path(script_type, &path)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
|
||||
|
||||
tree.commit_batch(batch).unwrap();
|
||||
tree.commit_batch(batch);
|
||||
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use bitcoin::hash_types::Txid;
|
||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath};
|
||||
use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
||||
|
||||
use crate::error::Error;
|
||||
@@ -9,26 +10,26 @@ pub mod keyvalue;
|
||||
pub mod memory;
|
||||
|
||||
pub trait BatchOperations {
|
||||
fn set_script_pubkey(
|
||||
fn set_script_pubkey<P: AsRef<[ChildNumber]>>(
|
||||
&mut self,
|
||||
script: &Script,
|
||||
script_type: ScriptType,
|
||||
child: u32,
|
||||
path: &P,
|
||||
) -> Result<(), Error>;
|
||||
fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error>;
|
||||
fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>;
|
||||
fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>;
|
||||
fn set_last_index(&mut self, script_type: ScriptType, value: u32) -> Result<(), Error>;
|
||||
|
||||
fn del_script_pubkey_from_path(
|
||||
fn del_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
|
||||
&mut self,
|
||||
script_type: ScriptType,
|
||||
child: u32,
|
||||
path: &P,
|
||||
) -> Result<Option<Script>, Error>;
|
||||
fn del_path_from_script_pubkey(
|
||||
&mut self,
|
||||
script: &Script,
|
||||
) -> Result<Option<(ScriptType, u32)>, Error>;
|
||||
) -> Result<Option<(ScriptType, DerivationPath)>, Error>;
|
||||
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
|
||||
fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>;
|
||||
fn del_tx(
|
||||
@@ -51,15 +52,15 @@ pub trait Database: BatchOperations {
|
||||
fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>;
|
||||
fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error>;
|
||||
|
||||
fn get_script_pubkey_from_path(
|
||||
fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
|
||||
&self,
|
||||
script_type: ScriptType,
|
||||
child: u32,
|
||||
path: &P,
|
||||
) -> Result<Option<Script>, Error>;
|
||||
fn get_path_from_script_pubkey(
|
||||
&self,
|
||||
script: &Script,
|
||||
) -> Result<Option<(ScriptType, u32)>, Error>;
|
||||
) -> Result<Option<(ScriptType, DerivationPath)>, Error>;
|
||||
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
|
||||
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
|
||||
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
|
||||
|
||||
@@ -6,6 +6,12 @@ pub enum Error {
|
||||
MalformedInput,
|
||||
KeyParsingError(String),
|
||||
|
||||
AliasAsPublicKey,
|
||||
KeyHasSecret,
|
||||
Incomplete,
|
||||
MissingAlias(String),
|
||||
InvalidAlias(String),
|
||||
|
||||
Policy(crate::descriptor::policy::PolicyError),
|
||||
|
||||
InputIndexDoesntExist,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::secp256k1::{All, Secp256k1};
|
||||
use bitcoin::{PrivateKey, PublicKey};
|
||||
|
||||
@@ -9,34 +12,39 @@ use super::error::Error;
|
||||
use super::extended_key::DerivationIndex;
|
||||
use super::DescriptorExtendedKey;
|
||||
|
||||
pub(super) trait Key: std::fmt::Debug + std::fmt::Display {
|
||||
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>;
|
||||
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
|
||||
fn as_secret_key(&self) -> Option<PrivateKey>;
|
||||
fn xprv(&self) -> Option<ExtendedPrivKey>;
|
||||
fn full_path(&self, index: u32) -> Option<DerivationPath>;
|
||||
fn is_fixed(&self) -> bool;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyAlias {
|
||||
alias: String,
|
||||
has_secret: bool,
|
||||
}
|
||||
|
||||
fn has_secret(&self) -> bool {
|
||||
self.xprv().is_some() || self.as_secret_key().is_some()
|
||||
}
|
||||
|
||||
fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
|
||||
Ok(Box::new(self.as_public_key(secp, None)?))
|
||||
impl KeyAlias {
|
||||
pub(crate) fn new_boxed(alias: &str, has_secret: bool) -> Box<dyn Key> {
|
||||
Box::new(KeyAlias {
|
||||
alias: alias.into(),
|
||||
has_secret,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for PublicKey {
|
||||
fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
||||
None
|
||||
pub(crate) fn parse_key(string: &str) -> Result<(String, Box<dyn RealKey>), Error> {
|
||||
if let Ok(pk) = PublicKey::from_str(string) {
|
||||
return Ok((string.to_string(), Box::new(pk)));
|
||||
} else if let Ok(sk) = PrivateKey::from_wif(string) {
|
||||
return Ok((string.to_string(), Box::new(sk)));
|
||||
} else if let Ok(ext_key) = DescriptorExtendedKey::from_str(string) {
|
||||
return Ok((string.to_string(), Box::new(ext_key)));
|
||||
}
|
||||
|
||||
fn as_public_key(
|
||||
&self,
|
||||
_secp: &Secp256k1<All>,
|
||||
_index: Option<u32>,
|
||||
) -> Result<PublicKey, Error> {
|
||||
Ok(PublicKey::clone(self))
|
||||
return Err(Error::KeyParsingError(string.to_string()));
|
||||
}
|
||||
|
||||
pub trait Key: std::fmt::Debug + std::fmt::Display {
|
||||
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
|
||||
fn is_fixed(&self) -> bool;
|
||||
|
||||
fn alias(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_secret_key(&self) -> Option<PrivateKey> {
|
||||
@@ -51,16 +59,50 @@ impl Key for PublicKey {
|
||||
None
|
||||
}
|
||||
|
||||
fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
||||
None
|
||||
}
|
||||
|
||||
fn has_secret(&self) -> bool {
|
||||
self.xprv().is_some() || self.as_secret_key().is_some()
|
||||
}
|
||||
|
||||
fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn RealKey>, Error> {
|
||||
Ok(Box::new(self.as_public_key(secp, None)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RealKey: Key {
|
||||
fn into_key(&self) -> Box<dyn Key>;
|
||||
}
|
||||
|
||||
impl<T: RealKey + 'static> From<T> for Box<dyn RealKey> {
|
||||
fn from(key: T) -> Self {
|
||||
Box::new(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for PublicKey {
|
||||
fn as_public_key(
|
||||
&self,
|
||||
_secp: &Secp256k1<All>,
|
||||
_index: Option<u32>,
|
||||
) -> Result<PublicKey, Error> {
|
||||
Ok(PublicKey::clone(self))
|
||||
}
|
||||
|
||||
fn is_fixed(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for PrivateKey {
|
||||
fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
||||
None
|
||||
impl RealKey for PublicKey {
|
||||
fn into_key(&self) -> Box<dyn Key> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for PrivateKey {
|
||||
fn as_public_key(
|
||||
&self,
|
||||
secp: &Secp256k1<All>,
|
||||
@@ -73,18 +115,15 @@ impl Key for PrivateKey {
|
||||
Some(PrivateKey::clone(self))
|
||||
}
|
||||
|
||||
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
||||
None
|
||||
}
|
||||
|
||||
fn full_path(&self, _index: u32) -> Option<DerivationPath> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_fixed(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
impl RealKey for PrivateKey {
|
||||
fn into_key(&self) -> Box<dyn Key> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for DescriptorExtendedKey {
|
||||
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
|
||||
@@ -99,7 +138,7 @@ impl Key for DescriptorExtendedKey {
|
||||
Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
|
||||
}
|
||||
|
||||
fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
|
||||
fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn RealKey>, Error> {
|
||||
if self.final_index == DerivationIndex::Hardened {
|
||||
return Err(Error::HardenedDerivationOnXpub);
|
||||
}
|
||||
@@ -163,10 +202,6 @@ impl Key for DescriptorExtendedKey {
|
||||
}))
|
||||
}
|
||||
|
||||
fn as_secret_key(&self) -> Option<PrivateKey> {
|
||||
None
|
||||
}
|
||||
|
||||
fn xprv(&self) -> Option<ExtendedPrivKey> {
|
||||
self.secret
|
||||
}
|
||||
@@ -179,3 +214,67 @@ impl Key for DescriptorExtendedKey {
|
||||
self.final_index == DerivationIndex::Fixed
|
||||
}
|
||||
}
|
||||
impl RealKey for DescriptorExtendedKey {
|
||||
fn into_key(&self) -> Box<dyn Key> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for KeyAlias {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let flag = if self.has_secret { "#" } else { "" };
|
||||
|
||||
write!(f, "{}{}", flag, self.alias)
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for KeyAlias {
|
||||
fn as_public_key(
|
||||
&self,
|
||||
_secp: &Secp256k1<All>,
|
||||
_index: Option<u32>,
|
||||
) -> Result<PublicKey, Error> {
|
||||
Err(Error::AliasAsPublicKey)
|
||||
}
|
||||
|
||||
fn is_fixed(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn alias(&self) -> Option<&str> {
|
||||
Some(self.alias.as_str())
|
||||
}
|
||||
|
||||
fn has_secret(&self) -> bool {
|
||||
self.has_secret
|
||||
}
|
||||
|
||||
fn public(&self, _secp: &Secp256k1<All>) -> Result<Box<dyn RealKey>, Error> {
|
||||
Err(Error::AliasAsPublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
|
||||
pub(crate) struct DummyKey();
|
||||
|
||||
impl fmt::Display for DummyKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "DummyKey")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for DummyKey {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(_: &str) -> Result<Self, Self::Err> {
|
||||
Ok(DummyKey::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl miniscript::MiniscriptKey for DummyKey {
|
||||
type Hash = DummyKey;
|
||||
|
||||
fn to_pubkeyhash(&self) -> DummyKey {
|
||||
DummyKey::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::psbt::utils::PSBTUtils;
|
||||
pub mod checksum;
|
||||
pub mod error;
|
||||
pub mod extended_key;
|
||||
mod keys;
|
||||
pub mod keys;
|
||||
pub mod policy;
|
||||
|
||||
pub use self::checksum::get_checksum;
|
||||
@@ -27,9 +27,9 @@ use self::error::Error;
|
||||
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
|
||||
pub use self::policy::Policy;
|
||||
|
||||
use self::keys::Key;
|
||||
use self::keys::{parse_key, DummyKey, Key, RealKey};
|
||||
|
||||
trait MiniscriptExtractPolicy {
|
||||
pub(crate) trait MiniscriptExtractPolicy {
|
||||
fn extract_policy(
|
||||
&self,
|
||||
lookup_map: &BTreeMap<String, Box<dyn Key>>,
|
||||
@@ -40,31 +40,6 @@ pub trait ExtractPolicy {
|
||||
fn extract_policy(&self) -> Result<Option<Policy>, Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
|
||||
struct DummyKey();
|
||||
|
||||
impl fmt::Display for DummyKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "DummyKey")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for DummyKey {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(_: &str) -> Result<Self, Self::Err> {
|
||||
Ok(DummyKey::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl miniscript::MiniscriptKey for DummyKey {
|
||||
type Hash = DummyKey;
|
||||
|
||||
fn to_pubkeyhash(&self) -> DummyKey {
|
||||
DummyKey::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub type DerivedDescriptor = Descriptor<PublicKey>;
|
||||
pub type StringDescriptor = Descriptor<String>;
|
||||
|
||||
@@ -112,13 +87,13 @@ where
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ExtendedDescriptor {
|
||||
#[serde(flatten)]
|
||||
internal: StringDescriptor,
|
||||
pub(crate) internal: StringDescriptor,
|
||||
|
||||
#[serde(skip)]
|
||||
keys: BTreeMap<String, Box<dyn Key>>,
|
||||
pub(crate) keys: BTreeMap<String, Box<dyn RealKey>>,
|
||||
|
||||
#[serde(skip)]
|
||||
ctx: Secp256k1<All>,
|
||||
pub(crate) ctx: Secp256k1<All>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtendedDescriptor {
|
||||
@@ -144,30 +119,18 @@ impl std::convert::AsRef<StringDescriptor> for ExtendedDescriptor {
|
||||
}
|
||||
|
||||
impl ExtendedDescriptor {
|
||||
fn parse_string(string: &str) -> Result<(String, Box<dyn Key>), Error> {
|
||||
if let Ok(pk) = PublicKey::from_str(string) {
|
||||
return Ok((string.to_string(), Box::new(pk)));
|
||||
} else if let Ok(sk) = PrivateKey::from_wif(string) {
|
||||
return Ok((string.to_string(), Box::new(sk)));
|
||||
} else if let Ok(ext_key) = DescriptorExtendedKey::from_str(string) {
|
||||
return Ok((string.to_string(), Box::new(ext_key)));
|
||||
}
|
||||
|
||||
return Err(Error::KeyParsingError(string.to_string()));
|
||||
}
|
||||
|
||||
fn new(sd: StringDescriptor) -> Result<Self, Error> {
|
||||
let ctx = Secp256k1::gen_new();
|
||||
let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
|
||||
let keys: RefCell<BTreeMap<String, Box<dyn RealKey>>> = RefCell::new(BTreeMap::new());
|
||||
|
||||
let translatefpk = |string: &String| -> Result<_, Error> {
|
||||
let (key, parsed) = Self::parse_string(string)?;
|
||||
let (key, parsed) = parse_key(string)?;
|
||||
keys.borrow_mut().insert(key, parsed);
|
||||
|
||||
Ok(DummyKey::default())
|
||||
};
|
||||
let translatefpkh = |string: &String| -> Result<_, Error> {
|
||||
let (key, parsed) = Self::parse_string(string)?;
|
||||
let (key, parsed) = parse_key(string)?;
|
||||
keys.borrow_mut().insert(key, parsed);
|
||||
|
||||
Ok(DummyKey::default())
|
||||
@@ -329,7 +292,7 @@ impl ExtendedDescriptor {
|
||||
}
|
||||
|
||||
pub fn as_public_version(&self) -> Result<ExtendedDescriptor, Error> {
|
||||
let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
|
||||
let keys: RefCell<BTreeMap<String, Box<dyn RealKey>>> = RefCell::new(BTreeMap::new());
|
||||
|
||||
let translatefpk = |string: &String| -> Result<_, Error> {
|
||||
let public = self.keys.get(string).unwrap().public(&self.ctx)?;
|
||||
@@ -360,7 +323,13 @@ impl ExtendedDescriptor {
|
||||
|
||||
impl ExtractPolicy for ExtendedDescriptor {
|
||||
fn extract_policy(&self) -> Result<Option<Policy>, Error> {
|
||||
self.internal.extract_policy(&self.keys)
|
||||
self.internal.extract_policy(
|
||||
&self
|
||||
.keys
|
||||
.iter()
|
||||
.map(|(k, v)| (k.into(), v.into_key()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,19 +27,27 @@ pub struct PKOrF {
|
||||
pubkey_hash: Option<hash160::Hash>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fingerprint: Option<Fingerprint>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
alias: Option<String>,
|
||||
}
|
||||
|
||||
impl PKOrF {
|
||||
fn from_key(k: &Box<dyn Key>) -> Self {
|
||||
let secp = Secp256k1::gen_new();
|
||||
|
||||
let pubkey = k.as_public_key(&secp, None).unwrap();
|
||||
if let Some(fing) = k.fingerprint(&secp) {
|
||||
if let Some(alias) = k.alias() {
|
||||
PKOrF {
|
||||
alias: Some(alias.into()),
|
||||
..Default::default()
|
||||
}
|
||||
} else if let Some(fing) = k.fingerprint(&secp) {
|
||||
PKOrF {
|
||||
fingerprint: Some(fing),
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
let pubkey = k.as_public_key(&secp, None).unwrap();
|
||||
PKOrF {
|
||||
pubkey: Some(pubkey),
|
||||
..Default::default()
|
||||
|
||||
@@ -33,6 +33,8 @@ pub mod error;
|
||||
pub mod blockchain;
|
||||
pub mod database;
|
||||
pub mod descriptor;
|
||||
#[cfg(feature = "multiparty")]
|
||||
pub mod multiparty;
|
||||
pub mod psbt;
|
||||
pub mod signer;
|
||||
pub mod types;
|
||||
|
||||
231
src/multiparty/mod.rs
Normal file
231
src/multiparty/mod.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
|
||||
use crate::descriptor::error::Error;
|
||||
use crate::descriptor::keys::{parse_key, DummyKey, Key, KeyAlias, RealKey};
|
||||
use crate::descriptor::{ExtendedDescriptor, MiniscriptExtractPolicy, Policy, StringDescriptor};
|
||||
|
||||
pub trait ParticipantType: Default {
|
||||
fn validate_aliases(aliases: Vec<&String>) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Coordinator {}
|
||||
impl ParticipantType for Coordinator {
|
||||
fn validate_aliases(aliases: Vec<&String>) -> Result<(), Error> {
|
||||
if aliases.into_iter().any(|a| a == "[PEER]") {
|
||||
Err(Error::InvalidAlias("[PEER]".into()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Peer;
|
||||
impl ParticipantType for Peer {
|
||||
fn validate_aliases(aliases: Vec<&String>) -> Result<(), Error> {
|
||||
if !aliases.into_iter().any(|a| a == "[PEER]") {
|
||||
Err(Error::MissingAlias("[PEER]".into()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Participant<T: ParticipantType> {
|
||||
descriptor: StringDescriptor,
|
||||
parsed_keys: BTreeMap<String, Box<dyn Key>>,
|
||||
received_keys: BTreeMap<String, Box<dyn RealKey>>,
|
||||
|
||||
_data: T,
|
||||
}
|
||||
|
||||
impl<T: ParticipantType> Participant<T> {
|
||||
pub fn new(sd: StringDescriptor) -> Result<Self, Error> {
|
||||
let parsed_keys = Self::parse_keys(&sd, vec![]);
|
||||
|
||||
T::validate_aliases(parsed_keys.keys().collect())?;
|
||||
|
||||
Ok(Participant {
|
||||
descriptor: sd,
|
||||
parsed_keys,
|
||||
received_keys: Default::default(),
|
||||
_data: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_keys(
|
||||
sd: &StringDescriptor,
|
||||
with_secrets: Vec<&str>,
|
||||
) -> BTreeMap<String, Box<dyn Key>> {
|
||||
let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
|
||||
|
||||
let translatefpk = |string: &String| -> Result<_, Error> {
|
||||
let (key, parsed) = match parse_key(string) {
|
||||
Ok((key, parsed)) => (key, parsed.into_key()),
|
||||
Err(_) => (
|
||||
string.clone(),
|
||||
KeyAlias::new_boxed(string.as_str(), with_secrets.contains(&string.as_str())),
|
||||
),
|
||||
};
|
||||
keys.borrow_mut().insert(key, parsed);
|
||||
|
||||
Ok(DummyKey::default())
|
||||
};
|
||||
let translatefpkh = |string: &String| -> Result<_, Error> {
|
||||
let (key, parsed) = match parse_key(string) {
|
||||
Ok((key, parsed)) => (key, parsed.into_key()),
|
||||
Err(_) => (
|
||||
string.clone(),
|
||||
KeyAlias::new_boxed(string.as_str(), with_secrets.contains(&string.as_str())),
|
||||
),
|
||||
};
|
||||
keys.borrow_mut().insert(key, parsed);
|
||||
|
||||
Ok(DummyKey::default())
|
||||
};
|
||||
|
||||
sd.translate_pk(translatefpk, translatefpkh).unwrap();
|
||||
|
||||
keys.into_inner()
|
||||
}
|
||||
|
||||
pub fn policy_for(&self, with_secrets: Vec<&str>) -> Result<Option<Policy>, Error> {
|
||||
let keys = Self::parse_keys(&self.descriptor, with_secrets);
|
||||
self.descriptor.extract_policy(&keys)
|
||||
}
|
||||
|
||||
fn _missing_keys(&self) -> Vec<&String> {
|
||||
self.parsed_keys
|
||||
.keys()
|
||||
.filter(|k| !self.received_keys.contains_key(*k))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn completed(&self) -> bool {
|
||||
self._missing_keys().is_empty()
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Result<ExtendedDescriptor, Error> {
|
||||
if !self.completed() {
|
||||
return Err(Error::Incomplete);
|
||||
}
|
||||
|
||||
let translatefpk = |string: &String| -> Result<_, Error> {
|
||||
Ok(format!(
|
||||
"{}",
|
||||
self.received_keys
|
||||
.get(string)
|
||||
.expect(&format!("Missing key: `{}`", string))
|
||||
))
|
||||
};
|
||||
let translatefpkh = |string: &String| -> Result<_, Error> {
|
||||
Ok(format!(
|
||||
"{}",
|
||||
self.received_keys
|
||||
.get(string)
|
||||
.expect(&format!("Missing key: `{}`", string))
|
||||
))
|
||||
};
|
||||
|
||||
let internal = self.descriptor.translate_pk(translatefpk, translatefpkh)?;
|
||||
|
||||
Ok(ExtendedDescriptor {
|
||||
internal,
|
||||
keys: self.received_keys,
|
||||
ctx: Secp256k1::gen_new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Participant<Coordinator> {
|
||||
pub fn descriptor(&self) -> &StringDescriptor {
|
||||
&self.descriptor
|
||||
}
|
||||
|
||||
pub fn add_key(&mut self, alias: &str, key: Box<dyn RealKey>) -> Result<(), Error> {
|
||||
// TODO: check network
|
||||
|
||||
if key.has_secret() {
|
||||
return Err(Error::KeyHasSecret);
|
||||
}
|
||||
|
||||
self.received_keys.insert(alias.into(), key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn received_keys(&self) -> Vec<&String> {
|
||||
self.received_keys.keys().collect()
|
||||
}
|
||||
|
||||
pub fn missing_keys(&self) -> Vec<&String> {
|
||||
self._missing_keys()
|
||||
}
|
||||
|
||||
pub fn descriptor_for(&self, alias: &str) -> Result<StringDescriptor, Error> {
|
||||
if !self.parsed_keys.contains_key(alias) {
|
||||
return Err(Error::MissingAlias(alias.into()));
|
||||
}
|
||||
|
||||
let map_name = |s: &String| {
|
||||
if s == alias {
|
||||
"[PEER]".into()
|
||||
} else {
|
||||
s.into()
|
||||
}
|
||||
};
|
||||
|
||||
let translatefpk = |string: &String| -> Result<_, Error> { Ok(map_name(string)) };
|
||||
let translatefpkh = |string: &String| -> Result<_, Error> { Ok(map_name(string)) };
|
||||
|
||||
Ok(self.descriptor.translate_pk(translatefpk, translatefpkh)?)
|
||||
}
|
||||
|
||||
pub fn get_map(&self) -> Result<BTreeMap<String, String>, Error> {
|
||||
if !self.completed() {
|
||||
return Err(Error::Incomplete);
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.received_keys
|
||||
.iter()
|
||||
.map(|(k, v)| (k.into(), format!("{}", v)))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Participant<Peer> {
|
||||
pub fn policy(&self) -> Result<Option<Policy>, Error> {
|
||||
self.policy_for(vec!["[PEER]"])
|
||||
}
|
||||
|
||||
pub fn use_key(&mut self, key: Box<dyn RealKey>) -> Result<(), Error> {
|
||||
let secp = Secp256k1::gen_new();
|
||||
self.received_keys
|
||||
.insert("[PEER]".into(), key.public(&secp)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn my_key(&mut self) -> Option<&Box<dyn RealKey>> {
|
||||
self.received_keys.get("[PEER]".into())
|
||||
}
|
||||
|
||||
pub fn apply_map(mut self, map: BTreeMap<String, String>) -> Result<ExtendedDescriptor, Error> {
|
||||
let mut parsed_map: BTreeMap<_, _> = map
|
||||
.into_iter()
|
||||
.map(|(k, v)| -> Result<_, Error> {
|
||||
let (_, parsed) = parse_key(&v)?;
|
||||
Ok((k, parsed))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
self.received_keys.append(&mut parsed_map);
|
||||
|
||||
self.finalize()
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
use bitcoin::blockdata::opcodes;
|
||||
use bitcoin::blockdata::script::Builder;
|
||||
use bitcoin::consensus::encode::serialize;
|
||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath};
|
||||
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
|
||||
use bitcoin::{
|
||||
Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxIn, TxOut, Txid,
|
||||
@@ -246,15 +247,22 @@ where
|
||||
let mut psbt = PSBT::from_unsigned_tx(tx)?;
|
||||
|
||||
// add metadata for the inputs
|
||||
for ((psbt_input, (script_type, child)), input) in psbt
|
||||
for ((psbt_input, (script_type, path)), input) in psbt
|
||||
.inputs
|
||||
.iter_mut()
|
||||
.zip(paths.into_iter())
|
||||
.zip(psbt.global.unsigned_tx.input.iter())
|
||||
{
|
||||
let path: Vec<ChildNumber> = path.into();
|
||||
let index = match path.last() {
|
||||
Some(ChildNumber::Normal { index }) => *index,
|
||||
Some(ChildNumber::Hardened { index }) => *index,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
let desc = self.get_descriptor_for(script_type);
|
||||
psbt_input.hd_keypaths = desc.get_hd_keypaths(child).unwrap();
|
||||
let derived_descriptor = desc.derive(child).unwrap();
|
||||
psbt_input.hd_keypaths = desc.get_hd_keypaths(index).unwrap();
|
||||
let derived_descriptor = desc.derive(index).unwrap();
|
||||
|
||||
// TODO: figure out what do redeem_script and witness_script mean
|
||||
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
|
||||
@@ -277,20 +285,9 @@ where
|
||||
psbt_input.sighash_type = Some(SigHashType::All);
|
||||
}
|
||||
|
||||
for (psbt_output, tx_output) in psbt
|
||||
.outputs
|
||||
.iter_mut()
|
||||
.zip(psbt.global.unsigned_tx.output.iter())
|
||||
{
|
||||
if let Some((script_type, child)) = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&tx_output.script_pubkey)?
|
||||
{
|
||||
let desc = self.get_descriptor_for(script_type);
|
||||
psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?;
|
||||
}
|
||||
}
|
||||
// TODO: add metadata for the outputs, like derivation paths for change addrs
|
||||
/*for psbt_output in psbt.outputs.iter_mut().zip(psbt.global.unsigned_tx.output.iter()) {
|
||||
}*/
|
||||
|
||||
let transaction_details = TransactionDetails {
|
||||
transaction: None,
|
||||
@@ -304,6 +301,47 @@ where
|
||||
Ok((psbt, transaction_details))
|
||||
}
|
||||
|
||||
// TODO: move down to the "internals"
|
||||
fn add_hd_keypaths(&self, psbt: &mut PSBT) -> Result<(), Error> {
|
||||
let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
|
||||
for n in 0..psbt.inputs.len() {
|
||||
input_utxos.push(psbt.get_utxo_for(n).clone());
|
||||
}
|
||||
|
||||
// try to add hd_keypaths if we've already seen the output
|
||||
for (psbt_input, out) in psbt.inputs.iter_mut().zip(input_utxos.iter()) {
|
||||
debug!("searching hd_keypaths for out: {:?}", out);
|
||||
|
||||
if let Some(out) = out {
|
||||
let option_path = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&out.script_pubkey)?;
|
||||
|
||||
debug!("found descriptor path {:?}", option_path);
|
||||
|
||||
let (script_type, path) = match option_path {
|
||||
None => continue,
|
||||
Some((script_type, path)) => (script_type, path),
|
||||
};
|
||||
|
||||
// TODO: this is duplicated code
|
||||
let index = match path.into_iter().last() {
|
||||
Some(ChildNumber::Normal { index }) => *index,
|
||||
Some(ChildNumber::Hardened { index }) => *index,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// merge hd_keypaths
|
||||
let desc = self.get_descriptor_for(script_type);
|
||||
let mut hd_keypaths = desc.get_hd_keypaths(index)?;
|
||||
psbt_input.hd_keypaths.append(&mut hd_keypaths);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: define an enum for signing errors
|
||||
pub fn sign(&self, mut psbt: PSBT, assume_height: Option<u32>) -> Result<(PSBT, bool), Error> {
|
||||
// this helps us doing our job later
|
||||
@@ -623,9 +661,9 @@ where
|
||||
outgoing: u64,
|
||||
input_witness_weight: usize,
|
||||
mut fee_val: f32,
|
||||
) -> Result<(Vec<TxIn>, Vec<(ScriptType, u32)>, u64, f32), Error> {
|
||||
) -> Result<(Vec<TxIn>, Vec<(ScriptType, DerivationPath)>, u64, f32), Error> {
|
||||
let mut answer = Vec::new();
|
||||
let mut deriv_indexes = Vec::new();
|
||||
let mut paths = Vec::new();
|
||||
let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0;
|
||||
|
||||
debug!(
|
||||
@@ -659,48 +697,15 @@ where
|
||||
answer.push(new_in);
|
||||
selected_amount += utxo.txout.value;
|
||||
|
||||
let child = self
|
||||
let path = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
|
||||
.unwrap(); // TODO: remove unrwap
|
||||
deriv_indexes.push(child);
|
||||
paths.push(path);
|
||||
}
|
||||
|
||||
Ok((answer, deriv_indexes, selected_amount, fee_val))
|
||||
}
|
||||
|
||||
fn add_hd_keypaths(&self, psbt: &mut PSBT) -> Result<(), Error> {
|
||||
let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
|
||||
for n in 0..psbt.inputs.len() {
|
||||
input_utxos.push(psbt.get_utxo_for(n).clone());
|
||||
}
|
||||
|
||||
// try to add hd_keypaths if we've already seen the output
|
||||
for (psbt_input, out) in psbt.inputs.iter_mut().zip(input_utxos.iter()) {
|
||||
debug!("searching hd_keypaths for out: {:?}", out);
|
||||
|
||||
if let Some(out) = out {
|
||||
let option_path = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&out.script_pubkey)?;
|
||||
|
||||
debug!("found descriptor path {:?}", option_path);
|
||||
|
||||
let (script_type, child) = match option_path {
|
||||
None => continue,
|
||||
Some((script_type, child)) => (script_type, child),
|
||||
};
|
||||
|
||||
// merge hd_keypaths
|
||||
let desc = self.get_descriptor_for(script_type);
|
||||
let mut hd_keypaths = desc.get_hd_keypaths(child)?;
|
||||
psbt_input.hd_keypaths.append(&mut hd_keypaths);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok((answer, paths, selected_amount, fee_val))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,10 +775,11 @@ where
|
||||
// TODO:
|
||||
// let batch_query_size = batch_query_size.unwrap_or(20);
|
||||
|
||||
let path = DerivationPath::from(vec![ChildNumber::Normal { index: max_address }]);
|
||||
let last_addr = self
|
||||
.database
|
||||
.borrow()
|
||||
.get_script_pubkey_from_path(ScriptType::External, max_address)?;
|
||||
.get_script_pubkey_from_path(ScriptType::External, &path)?;
|
||||
|
||||
// cache a few of our addresses
|
||||
if last_addr.is_none() {
|
||||
@@ -783,21 +789,23 @@ where
|
||||
|
||||
for i in 0..=max_address {
|
||||
let derived = self.descriptor.derive(i).unwrap();
|
||||
let full_path = DerivationPath::from(vec![ChildNumber::Normal { index: i }]);
|
||||
|
||||
address_batch.set_script_pubkey(
|
||||
&derived.script_pubkey(),
|
||||
ScriptType::External,
|
||||
i,
|
||||
&full_path,
|
||||
)?;
|
||||
}
|
||||
if self.change_descriptor.is_some() {
|
||||
for i in 0..=max_address {
|
||||
let derived = self.change_descriptor.as_ref().unwrap().derive(i).unwrap();
|
||||
let full_path = DerivationPath::from(vec![ChildNumber::Normal { index: i }]);
|
||||
|
||||
address_batch.set_script_pubkey(
|
||||
&derived.script_pubkey(),
|
||||
ScriptType::Internal,
|
||||
i,
|
||||
&full_path,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user