Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions src/actions/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use elements::bitcoin::{secp256k1, PublicKey};
use elements::{Address, Script};

use crate::address::{AddressInfo, Addresses};
use crate::Network;

#[derive(Debug, thiserror::Error)]
pub enum AddressError {
#[error("invalid blinder hex: {0}")]
BlinderHex(hex::FromHexError),

#[error("invalid blinder: {0}")]
BlinderInvalid(secp256k1::Error),

#[error("invalid pubkey: {0}")]
PubkeyInvalid(elements::bitcoin::key::ParsePublicKeyError),

#[error("invalid script hex: {0}")]
ScriptHex(hex::FromHexError),

#[error("can't create addresses without a pubkey")]
MissingInput,

#[error("invalid address format: {0}")]
AddressParse(elements::address::AddressError),

#[error("no address provided")]
NoAddressProvided,

#[error("addresses always have params")]
AddressesAlwaysHaveParams,
}

/// Create addresses from a public key or script.
pub fn address_create(
pubkey_hex: Option<&str>,
script_hex: Option<&str>,
blinder_hex: Option<&str>,
network: Network,
) -> Result<Addresses, AddressError> {
let blinder = blinder_hex
.map(|b| {
let bytes = hex::decode(b).map_err(AddressError::BlinderHex)?;
secp256k1::PublicKey::from_slice(&bytes).map_err(AddressError::BlinderInvalid)
})
.transpose()?;

let created = if let Some(pubkey_hex) = pubkey_hex {
let pubkey: PublicKey = pubkey_hex.parse().map_err(AddressError::PubkeyInvalid)?;
Addresses::from_pubkey(&pubkey, blinder, network)
} else if let Some(script_hex) = script_hex {
let script_bytes = hex::decode(script_hex).map_err(AddressError::ScriptHex)?;
let script: Script = script_bytes.into();
Addresses::from_script(&script, blinder, network)
} else {
return Err(AddressError::MissingInput);
};

Ok(created)
}

/// Inspect an address and return detailed information.
pub fn address_inspect(address_str: &str) -> Result<AddressInfo, AddressError> {
let address: Address = address_str.parse().map_err(AddressError::AddressParse)?;
let script_pk = address.script_pubkey();

let mut info = AddressInfo {
network: Network::from_params(address.params)
.ok_or(AddressError::AddressesAlwaysHaveParams)?,
script_pub_key: hal::tx::OutputScriptInfo {
hex: Some(script_pk.to_bytes().into()),
asm: Some(script_pk.asm()),
address: None,
type_: None,
},
type_: None,
pubkey_hash: None,
script_hash: None,
witness_pubkey_hash: None,
witness_script_hash: None,
witness_program_version: None,
blinding_pubkey: address.blinding_pubkey,
unconfidential: if address.blinding_pubkey.is_some() {
Some(Address {
params: address.params,
payload: address.payload.clone(),
blinding_pubkey: None,
})
} else {
None
},
};

use elements::address::Payload;
use elements::hashes::Hash;
use elements::{WPubkeyHash, WScriptHash};

match address.payload {
Payload::PubkeyHash(pkh) => {
info.type_ = Some("p2pkh".to_owned());
info.pubkey_hash = Some(pkh);
}
Payload::ScriptHash(sh) => {
info.type_ = Some("p2sh".to_owned());
info.script_hash = Some(sh);
}
Payload::WitnessProgram {
version,
program,
} => {
let version = version.to_u8() as usize;
info.witness_program_version = Some(version);

if version == 0 {
if program.len() == 20 {
info.type_ = Some("p2wpkh".to_owned());
info.witness_pubkey_hash =
Some(WPubkeyHash::from_slice(&program).expect("size 20"));
} else if program.len() == 32 {
info.type_ = Some("p2wsh".to_owned());
info.witness_script_hash =
Some(WScriptHash::from_slice(&program).expect("size 32"));
} else {
info.type_ = Some("invalid-witness-program".to_owned());
}
} else {
info.type_ = Some("unknown-witness-program-version".to_owned());
}
}
}

Ok(info)
}
210 changes: 210 additions & 0 deletions src/actions/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
use elements::encode::deserialize;
use elements::{dynafed, Block, BlockExtData, BlockHeader};

use crate::block::{BlockHeaderInfo, BlockInfo, ParamsInfo, ParamsType};
use crate::Network;

#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum BlockDecodeOutput {
Info(BlockInfo),
Header(BlockHeaderInfo),
}

#[derive(Debug, thiserror::Error)]
pub enum BlockError {
#[error("can't provide transactions both in JSON and raw.")]
ConflictingTransactions,

#[error("no transactions provided.")]
NoTransactions,

#[error("failed to deserialize transaction: {0}")]
TransactionDeserialize(super::tx::TxError),

#[error("invalid raw transaction: {0}")]
InvalidRawTransaction(elements::encode::Error),

#[error("invalid block format: {0}")]
BlockDeserialize(elements::encode::Error),

#[error("could not decode raw block hex: {0}")]
CouldNotDecodeRawBlockHex(hex::FromHexError),

#[error("invalid json JSON input: {0}")]
InvalidJsonInput(serde_json::Error),

#[error("{field} missing in {context}")]
MissingField {
field: String,
context: String,
},
}

fn create_params(info: ParamsInfo) -> Result<dynafed::Params, BlockError> {
match info.params_type {
ParamsType::Null => Ok(dynafed::Params::Null),
ParamsType::Compact => Ok(dynafed::Params::Compact {
signblockscript: info
.signblockscript
.ok_or_else(|| BlockError::MissingField {
field: "signblockscript".to_string(),
context: "compact params".to_string(),
})?
.0
.into(),
signblock_witness_limit: info.signblock_witness_limit.ok_or_else(|| {
BlockError::MissingField {
field: "signblock_witness_limit".to_string(),
context: "compact params".to_string(),
}
})?,
elided_root: info.elided_root.ok_or_else(|| BlockError::MissingField {
field: "elided_root".to_string(),
context: "compact params".to_string(),
})?,
}),
ParamsType::Full => Ok(dynafed::Params::Full(dynafed::FullParams::new(
info.signblockscript
.ok_or_else(|| BlockError::MissingField {
field: "signblockscript".to_string(),
context: "full params".to_string(),
})?
.0
.into(),
info.signblock_witness_limit.ok_or_else(|| BlockError::MissingField {
field: "signblock_witness_limit".to_string(),
context: "full params".to_string(),
})?,
info.fedpeg_program
.ok_or_else(|| BlockError::MissingField {
field: "fedpeg_program".to_string(),
context: "full params".to_string(),
})?
.0
.into(),
info.fedpeg_script
.ok_or_else(|| BlockError::MissingField {
field: "fedpeg_script".to_string(),
context: "full params".to_string(),
})?
.0,
info.extension_space
.ok_or_else(|| BlockError::MissingField {
field: "extension space".to_string(),
context: "full params".to_string(),
})?
.into_iter()
.map(|b| b.0)
.collect(),
))),
}
}

fn create_block_header(info: BlockHeaderInfo) -> Result<BlockHeader, BlockError> {
Ok(BlockHeader {
version: info.version,
prev_blockhash: info.previous_block_hash,
merkle_root: info.merkle_root,
time: info.time,
height: info.height,
ext: if info.dynafed {
BlockExtData::Dynafed {
current: create_params(info.dynafed_current.ok_or_else(|| {
BlockError::MissingField {
field: "current".to_string(),
context: "dynafed params".to_string(),
}
})?)?,
proposed: create_params(info.dynafed_proposed.ok_or_else(|| {
BlockError::MissingField {
field: "proposed".to_string(),
context: "dynafed params".to_string(),
}
})?)?,
signblock_witness: info
.dynafed_witness
.ok_or_else(|| BlockError::MissingField {
field: "witness".to_string(),
context: "dynafed params".to_string(),
})?
.into_iter()
.map(|b| b.0)
.collect(),
}
} else {
BlockExtData::Proof {
challenge: info
.legacy_challenge
.ok_or_else(|| BlockError::MissingField {
field: "challenge".to_string(),
context: "proof params".to_string(),
})?
.0
.into(),
solution: info
.legacy_solution
.ok_or_else(|| BlockError::MissingField {
field: "solution".to_string(),
context: "proof params".to_string(),
})?
.0
.into(),
}
},
})
}

/// Create a block from block info.
pub fn block_create(info: BlockInfo) -> Result<Block, BlockError> {
let header = create_block_header(info.header)?;
let txdata = match (info.transactions, info.raw_transactions) {
(Some(_), Some(_)) => return Err(BlockError::ConflictingTransactions),
(None, None) => return Err(BlockError::NoTransactions),
(Some(infos), None) => infos
.into_iter()
.map(super::tx::tx_create)
.collect::<Result<Vec<_>, _>>()
.map_err(BlockError::TransactionDeserialize)?,
(None, Some(raws)) => raws
.into_iter()
.map(|r| deserialize(&r.0).map_err(BlockError::InvalidRawTransaction))
.collect::<Result<Vec<_>, _>>()?,
};
Ok(Block {
header,
txdata,
})
}

/// Decode a raw block and return block info or header info.
pub fn block_decode(
raw_block_hex: &str,
network: Network,
txids_only: bool,
) -> Result<BlockDecodeOutput, BlockError> {
use crate::GetInfo;

let raw_block = hex::decode(raw_block_hex).map_err(BlockError::CouldNotDecodeRawBlockHex)?;

if txids_only {
let block: Block = deserialize(&raw_block).map_err(BlockError::BlockDeserialize)?;
let info = BlockInfo {
header: block.header.get_info(network),
txids: Some(block.txdata.iter().map(|t| t.txid()).collect()),
transactions: None,
raw_transactions: None,
};
Ok(BlockDecodeOutput::Info(info))
} else {
let header: BlockHeader = match deserialize(&raw_block) {
Ok(header) => header,
Err(_) => {
let block: Block = deserialize(&raw_block).map_err(BlockError::BlockDeserialize)?;
block.header
}
};
let info = header.get_info(network);
Ok(BlockDecodeOutput::Header(info))
}
}
20 changes: 20 additions & 0 deletions src/actions/keypair.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use elements::bitcoin::secp256k1::{self, rand};

#[derive(serde::Serialize)]
pub struct KeypairInfo {
pub secret: secp256k1::SecretKey,
pub x_only: secp256k1::XOnlyPublicKey,
pub parity: secp256k1::Parity,
}

/// Generate a random keypair.
pub fn keypair_generate() -> KeypairInfo {
let (secret, public) = secp256k1::generate_keypair(&mut rand::thread_rng());
let (x_only, parity) = public.x_only_public_key();

KeypairInfo {
secret,
x_only,
parity,
}
}
5 changes: 5 additions & 0 deletions src/actions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod address;
pub mod block;
pub mod keypair;
pub mod simplicity;
pub mod tx;
Loading