use crate::error::WalletAbiError;
use crate::wallet_abi::schema::KeyStoreMeta;
use crate::simplicityhl::num::U256;
use crate::simplicityhl::parse::ParseFromStr;
use crate::simplicityhl::simplicity::jet::elements::ElementsEnv;
use crate::simplicityhl::str::WitnessName;
use crate::simplicityhl::value::{UIntValue, ValueConstructible};
use crate::simplicityhl::{Arguments, Value, WitnessValues};
use std::collections::HashMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use lwk_wollet::elements::pset::{Input, PartiallySignedTransaction};
use lwk_wollet::elements::secp256k1_zkp::ZERO_TWEAK;
use lwk_wollet::elements::Transaction;
use lwk_wollet::hashes::Hash;
use lwk_wollet::secp256k1::{Message, XOnlyPublicKey};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeSimfValue {
NewIssuanceAsset { input_index: u32 },
NewIssuanceToken { input_index: u32 },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SimfArguments {
pub resolved: Arguments,
pub runtime_arguments: HashMap<String, RuntimeSimfValue>,
}
impl SimfArguments {
pub fn new(static_arguments: Arguments) -> Self {
Self {
resolved: static_arguments,
runtime_arguments: HashMap::new(),
}
}
pub fn append_runtime_simf_value(&mut self, name: &str, runtime_simf_value: RuntimeSimfValue) {
self.runtime_arguments
.insert(name.to_string(), runtime_simf_value);
}
}
pub fn serialize_arguments(arguments: &SimfArguments) -> Result<Vec<u8>, WalletAbiError> {
Ok(serde_json::to_vec(arguments)?)
}
pub fn resolve_arguments(
bytes: &[u8],
pst: &PartiallySignedTransaction,
) -> Result<Arguments, WalletAbiError> {
let simf_arguments: SimfArguments = serde_json::from_slice(bytes)?;
let mut final_arguments: HashMap<WitnessName, Value> = HashMap::<WitnessName, Value>::new();
for (static_arg_name, static_arg_value) in simf_arguments.resolved.iter() {
if final_arguments.contains_key(static_arg_name) {
return Err(WalletAbiError::InvalidRequest(format!(
"duplicate static Simplicity argument '{static_arg_name}' found"
)));
}
final_arguments.insert(static_arg_name.clone(), static_arg_value.clone());
}
for (name, value) in simf_arguments.runtime_arguments {
let witness_name = parse_witness_name(&name, "runtime argument map")?;
if final_arguments.contains_key(&witness_name) {
return Err(WalletAbiError::InvalidRequest(format!(
"runtime Simplicity argument '{name}' collides with static resolved argument '{witness_name}'"
)));
}
match value {
RuntimeSimfValue::NewIssuanceAsset { input_index } => {
let input =
resolve_new_issuance_input(pst, &name, input_index, "new_issuance_asset")?;
let (asset, _) = input.issuance_ids();
final_arguments.insert(
witness_name,
Value::from(UIntValue::U256(U256::from_byte_array(asset.into_inner().0))),
);
}
RuntimeSimfValue::NewIssuanceToken { input_index } => {
let input =
resolve_new_issuance_input(pst, &name, input_index, "new_issuance_token")?;
let (_, token) = input.issuance_ids();
final_arguments.insert(
witness_name,
Value::from(UIntValue::U256(U256::from_byte_array(token.into_inner().0))),
);
}
}
}
Ok(Arguments::from(final_arguments))
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeSimfWitness {
SigHashAll {
name: String,
public_key: XOnlyPublicKey,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SimfWitness {
pub resolved: WitnessValues,
pub runtime_arguments: Vec<RuntimeSimfWitness>,
}
pub fn serialize_witness(witness: &SimfWitness) -> Result<Vec<u8>, WalletAbiError> {
Ok(serde_json::to_vec(witness)?)
}
pub fn resolve_witness<Signer: KeyStoreMeta>(
bytes: &[u8],
signer_meta: &Signer,
env: &ElementsEnv<Arc<Transaction>>,
) -> Result<WitnessValues, WalletAbiError>
where
WalletAbiError: From<Signer::Error>,
{
let simf_arguments: SimfWitness = serde_json::from_slice(bytes)?;
let mut final_witness: HashMap<WitnessName, Value> = HashMap::<WitnessName, Value>::new();
for static_arg in simf_arguments.resolved.iter() {
final_witness.insert(static_arg.0.clone(), static_arg.1.clone());
}
let sighash_all = Message::from_digest(env.c_tx_env().sighash_all().to_byte_array());
for value in simf_arguments.runtime_arguments {
match value {
RuntimeSimfWitness::SigHashAll { name, public_key } => {
let signer_public_key = signer_meta.get_raw_signing_x_only_pubkey()?;
if signer_public_key != public_key {
return Err(WalletAbiError::InvalidRequest(format!(
"sighash_all witness '{name}' public key mismatch: expected {public_key}, runtime signer is {signer_public_key}"
)));
}
let witness_name = parse_witness_name(&name, "runtime witness list")?;
if final_witness.contains_key(&witness_name) {
return Err(WalletAbiError::InvalidRequest(format!(
"runtime Simplicity witness '{name}' collides with static resolved witness '{witness_name}'"
)));
}
final_witness.insert(
witness_name,
Value::byte_array(
signer_meta
.sign_schnorr(sighash_all, public_key)?
.serialize(),
),
);
}
}
}
Ok(WitnessValues::from(final_witness))
}
fn parse_witness_name(name: &str, source: &str) -> Result<WitnessName, WalletAbiError> {
WitnessName::parse_from_str(name).map_err(|error| {
WalletAbiError::InvalidRequest(format!(
"invalid Simplicity witness name '{name}' in {source}: {error}"
))
})
}
fn resolve_new_issuance_input<'a>(
pst: &'a PartiallySignedTransaction,
name: &str,
input_index: u32,
variant: &str,
) -> Result<&'a Input, WalletAbiError> {
let idx = usize::try_from(input_index).map_err(|_| {
WalletAbiError::InvalidRequest(format!(
"runtime Simplicity argument '{name}' input_index overflow: {input_index}"
))
})?;
let input = pst.inputs().get(idx).ok_or_else(|| {
WalletAbiError::InvalidRequest(format!(
"runtime Simplicity argument '{name}' references missing input_index {input_index} (pset inputs: {})",
pst.inputs().len()
))
})?;
if !input.has_issuance() {
return Err(WalletAbiError::InvalidRequest(format!(
"runtime Simplicity argument '{name}' ({variant}) references input_index {input_index} without issuance metadata"
)));
}
let is_new_issuance = input.issuance_blinding_nonce.unwrap_or(ZERO_TWEAK) == ZERO_TWEAK;
if !is_new_issuance {
return Err(WalletAbiError::InvalidRequest(format!(
"runtime Simplicity argument '{name}' ({variant}) requires new issuance input_index {input_index}, but referenced input is reissuance"
)));
}
Ok(input)
}