use crate::core::{Input, KernelFeatures, Output, OutputFeatures, Transaction, TxKernel};
use crate::libtx::proof::{self, ProofBuild};
use crate::libtx::{aggsig, Error};
use keychain::{BlindSum, BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
pub struct Context<'a, K, B>
where
K: Keychain,
B: ProofBuild,
{
pub keychain: &'a K,
pub builder: &'a B,
}
pub type Append<K, B> = dyn for<'a> Fn(
&'a mut Context<'_, K, B>,
Result<(Transaction, BlindSum), Error>,
) -> Result<(Transaction, BlindSum), Error>;
fn build_input<K, B>(value: u64, features: OutputFeatures, key_id: Identifier) -> Box<Append<K, B>>
where
K: Keychain,
B: ProofBuild,
{
Box::new(
move |build, acc| -> Result<(Transaction, BlindSum), Error> {
if let Ok((tx, sum)) = acc {
let commit =
build
.keychain
.commit(value, &key_id, SwitchCommitmentType::Regular)?;
let input = Input::new(features, commit);
Ok((
tx.with_input(input),
sum.sub_key_id(key_id.to_value_path(value)),
))
} else {
acc
}
},
)
}
pub fn input<K, B>(value: u64, key_id: Identifier) -> Box<Append<K, B>>
where
K: Keychain,
B: ProofBuild,
{
debug!(
"Building input (spending regular output): {}, {}",
value, key_id
);
build_input(value, OutputFeatures::Plain, key_id)
}
pub fn coinbase_input<K, B>(value: u64, key_id: Identifier) -> Box<Append<K, B>>
where
K: Keychain,
B: ProofBuild,
{
debug!("Building input (spending coinbase): {}, {}", value, key_id);
build_input(value, OutputFeatures::Coinbase, key_id)
}
pub fn output<K, B>(value: u64, key_id: Identifier) -> Box<Append<K, B>>
where
K: Keychain,
B: ProofBuild,
{
Box::new(
move |build, acc| -> Result<(Transaction, BlindSum), Error> {
let (tx, sum) = acc?;
let switch = SwitchCommitmentType::Regular;
let commit = build.keychain.commit(value, &key_id, switch)?;
debug!("Building output: {}, {:?}", value, commit);
let proof = proof::create(
build.keychain,
build.builder,
value,
&key_id,
switch,
commit,
None,
)?;
Ok((
tx.with_output(Output::new(OutputFeatures::Plain, commit, proof)),
sum.add_key_id(key_id.to_value_path(value)),
))
},
)
}
pub fn with_excess<K, B>(excess: BlindingFactor) -> Box<Append<K, B>>
where
K: Keychain,
B: ProofBuild,
{
Box::new(
move |_build, acc| -> Result<(Transaction, BlindSum), Error> {
acc.map(|(tx, sum)| (tx, sum.add_blinding_factor(excess.clone())))
},
)
}
pub fn initial_tx<K, B>(tx: Transaction) -> Box<Append<K, B>>
where
K: Keychain,
B: ProofBuild,
{
Box::new(
move |_build, acc| -> Result<(Transaction, BlindSum), Error> {
acc.map(|(_, sum)| (tx.clone(), sum))
},
)
}
pub fn partial_transaction<K, B>(
tx: Transaction,
elems: &[Box<Append<K, B>>],
keychain: &K,
builder: &B,
) -> Result<(Transaction, BlindingFactor), Error>
where
K: Keychain,
B: ProofBuild,
{
let mut ctx = Context { keychain, builder };
let (tx, sum) = elems
.iter()
.fold(Ok((tx, BlindSum::new())), |acc, elem| elem(&mut ctx, acc))?;
let blind_sum = ctx.keychain.blind_sum(&sum)?;
Ok((tx, blind_sum))
}
pub fn transaction<K, B>(
features: KernelFeatures,
elems: &[Box<Append<K, B>>],
keychain: &K,
builder: &B,
) -> Result<Transaction, Error>
where
K: Keychain,
B: ProofBuild,
{
let mut kernel = TxKernel::with_features(features);
let msg = kernel.msg_to_sign()?;
let excess = BlindingFactor::rand(&keychain.secp());
let skey = excess.secret_key(&keychain.secp())?;
kernel.excess = keychain.secp().commit(0, skey)?;
let pubkey = &kernel.excess.to_pubkey(&keychain.secp())?;
kernel.excess_sig = aggsig::sign_with_blinding(&keychain.secp(), &msg, &excess, Some(&pubkey))?;
kernel.verify()?;
transaction_with_kernel(elems, kernel, excess, keychain, builder)
}
pub fn transaction_with_kernel<K, B>(
elems: &[Box<Append<K, B>>],
kernel: TxKernel,
excess: BlindingFactor,
keychain: &K,
builder: &B,
) -> Result<Transaction, Error>
where
K: Keychain,
B: ProofBuild,
{
let mut ctx = Context { keychain, builder };
let (tx, sum) = elems
.iter()
.fold(Ok((Transaction::empty(), BlindSum::new())), |acc, elem| {
elem(&mut ctx, acc)
})?;
let blind_sum = ctx.keychain.blind_sum(&sum)?;
let mut tx = tx.replace_kernel(kernel);
tx.offset = blind_sum.split(&excess, &keychain.secp())?;
Ok(tx)
}
#[cfg(test)]
mod test {
use super::*;
use crate::core::transaction::Weighting;
use crate::global;
use crate::libtx::ProofBuilder;
use keychain::{ExtKeychain, ExtKeychainPath};
#[test]
fn blind_simple_tx() {
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
let keychain = ExtKeychain::from_random_seed(false).unwrap();
let builder = ProofBuilder::new(&keychain);
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier();
let tx = transaction(
KernelFeatures::Plain { fee: 2.into() },
&[input(10, key_id1), input(12, key_id2), output(20, key_id3)],
&keychain,
&builder,
)
.unwrap();
tx.validate(Weighting::AsTransaction).unwrap();
}
#[test]
fn blind_simple_tx_with_offset() {
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
let keychain = ExtKeychain::from_random_seed(false).unwrap();
let builder = ProofBuilder::new(&keychain);
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier();
let tx = transaction(
KernelFeatures::Plain { fee: 2.into() },
&[input(10, key_id1), input(12, key_id2), output(20, key_id3)],
&keychain,
&builder,
)
.unwrap();
tx.validate(Weighting::AsTransaction).unwrap();
}
#[test]
fn blind_simpler_tx() {
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
let keychain = ExtKeychain::from_random_seed(false).unwrap();
let builder = ProofBuilder::new(&keychain);
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
let tx = transaction(
KernelFeatures::Plain { fee: 4.into() },
&[input(6, key_id1), output(2, key_id2)],
&keychain,
&builder,
)
.unwrap();
tx.validate(Weighting::AsTransaction).unwrap();
}
}