1use std::collections::{HashMap, HashSet};
7use std::sync::Arc;
8use std::time::Duration;
9
10use serde_json::Value;
11use thiserror::Error;
12
13use crate::core::{ArgMap, BytesEnvelope};
14use crate::tii::Protocol;
15use crate::trp::{self, SubmitParams, TxStage, TxStatus, TxWitness};
16
17#[derive(Clone)]
18struct SignerParty {
19 name: String,
20 address: String,
21 signer: Arc<dyn Signer + Send + Sync>,
22}
23
24#[derive(Debug, Error)]
26pub enum Error {
27 #[error(transparent)]
29 Tii(#[from] crate::tii::Error),
30
31 #[error(transparent)]
33 Trp(#[from] crate::trp::Error),
34
35 #[error("missing required params: {0:?}")]
37 MissingParams(Vec<String>),
38
39 #[error("unknown party: {0}")]
41 UnknownParty(String),
42
43 #[error("signer error: {0}")]
45 Signer(#[source] Box<dyn std::error::Error + Send + Sync>),
46
47 #[error("submit hash mismatch: expected {expected}, got {received}")]
49 SubmitHashMismatch { expected: String, received: String },
50
51 #[error("tx {hash} failed with stage {stage:?}")]
53 FinalizedFailed { hash: String, stage: TxStage },
54
55 #[error("tx {hash} not confirmed after {attempts} attempts (delay {delay:?})")]
57 FinalizedTimeout {
58 hash: String,
59 attempts: u32,
60 delay: Duration,
61 },
62}
63
64#[derive(Debug, Clone)]
68pub struct PollConfig {
69 pub attempts: u32,
71 pub delay: Duration,
73}
74
75impl Default for PollConfig {
76 fn default() -> Self {
77 Self {
78 attempts: 20,
79 delay: Duration::from_secs(5),
80 }
81 }
82}
83
84pub trait Signer: Send + Sync {
88 fn address(&self) -> &str;
90
91 fn sign(&self, tx_hash: &str) -> Result<TxWitness, Box<dyn std::error::Error + Send + Sync>>;
93}
94
95#[derive(Clone)]
97pub enum Party {
98 Address(String),
100 Signer {
102 address: String,
104 signer: Arc<dyn Signer + Send + Sync>,
106 },
107}
108
109impl Party {
110 pub fn address(address: impl Into<String>) -> Self {
112 Party::Address(address.into())
113 }
114
115 pub fn signer(signer: impl Signer + 'static) -> Self {
129 Party::Signer {
130 address: signer.address().to_string(),
131 signer: Arc::new(signer),
132 }
133 }
134
135 fn address_value(&self) -> &str {
136 match self {
137 Party::Address(address) => address,
138 Party::Signer { address, .. } => address,
139 }
140 }
141
142 fn signer_party(&self, name: &str) -> Option<SignerParty> {
143 match self {
144 Party::Signer { address, signer } => Some(SignerParty {
145 name: name.to_string(),
146 address: address.clone(),
147 signer: Arc::clone(signer),
148 }),
149 _ => None,
150 }
151 }
152}
153
154#[derive(Clone)]
156pub struct Tx3Client {
157 protocol: Arc<Protocol>,
158 trp: trp::Client,
159 parties: HashMap<String, Party>,
160 profile: Option<String>,
161}
162
163impl Tx3Client {
164 pub fn new(protocol: Protocol, trp: trp::Client) -> Self {
166 Self {
167 protocol: Arc::new(protocol),
168 trp,
169 parties: HashMap::new(),
170 profile: None,
171 }
172 }
173
174 pub fn with_profile(mut self, profile: impl Into<String>) -> Self {
178 self.profile = Some(profile.into());
179 self
180 }
181
182 pub fn with_party(mut self, name: impl Into<String>, party: Party) -> Self {
184 self.parties.insert(name.into().to_lowercase(), party);
185 self
186 }
187
188 pub fn with_parties<I, K>(mut self, parties: I) -> Self
190 where
191 I: IntoIterator<Item = (K, Party)>,
192 K: Into<String>,
193 {
194 for (name, party) in parties {
195 self.parties.insert(name.into().to_lowercase(), party);
196 }
197 self
198 }
199
200 pub fn tx(&self, name: impl Into<String>) -> TxBuilder {
202 TxBuilder {
203 protocol: Arc::clone(&self.protocol),
204 trp: self.trp.clone(),
205 tx_name: name.into(),
206 args: ArgMap::new(),
207 parties: self.parties.clone(),
208 profile: self.profile.clone(),
209 }
210 }
211}
212
213pub struct TxBuilder {
215 protocol: Arc<Protocol>,
216 trp: trp::Client,
217 tx_name: String,
218 args: ArgMap,
219 parties: HashMap<String, Party>,
220 profile: Option<String>,
221}
222
223impl TxBuilder {
224 pub fn arg(mut self, name: &str, value: impl Into<Value>) -> Self {
226 self.args.insert(name.to_lowercase(), value.into());
227 self
228 }
229
230 pub fn args(mut self, args: ArgMap) -> Self {
232 for (key, value) in args {
233 self.args.insert(key.to_lowercase(), value);
234 }
235 self
236 }
237
238 pub async fn resolve(self) -> Result<ResolvedTx, Error> {
240 let mut invocation = self
241 .protocol
242 .invoke(&self.tx_name, self.profile.as_deref())?;
243
244 let known_parties: HashSet<String> = self
245 .protocol
246 .parties()
247 .keys()
248 .map(|key| key.to_lowercase())
249 .collect();
250
251 for (name, party) in &self.parties {
252 if !known_parties.contains(name) {
253 return Err(Error::UnknownParty(name.clone()));
254 }
255
256 invocation.set_arg(
257 name,
258 serde_json::Value::String(party.address_value().to_string()),
259 );
260 }
261
262 invocation.set_args(self.args);
263
264 let mut missing: Vec<String> = invocation
265 .unspecified_params()
266 .map(|(key, _)| key.clone())
267 .collect();
268
269 if !missing.is_empty() {
270 missing.sort();
271 return Err(Error::MissingParams(missing));
272 }
273
274 let resolve_params = invocation.into_resolve_request()?;
275 let envelope = self.trp.resolve(resolve_params).await?;
276
277 let signers = self
278 .parties
279 .iter()
280 .filter_map(|(name, party)| party.signer_party(name))
281 .collect();
282
283 Ok(ResolvedTx {
284 trp: self.trp,
285 hash: envelope.hash,
286 tx_hex: envelope.tx,
287 signers,
288 })
289 }
290}
291
292pub struct ResolvedTx {
294 trp: trp::Client,
295 pub hash: String,
297 pub tx_hex: String,
299 signers: Vec<SignerParty>,
300}
301
302impl ResolvedTx {
303 pub fn signing_hash(&self) -> &str {
305 &self.hash
306 }
307
308 pub fn sign(self) -> Result<SignedTx, Error> {
310 let mut witnesses = Vec::with_capacity(self.signers.len());
311 let mut witnesses_info = Vec::with_capacity(self.signers.len());
312
313 for signer_party in &self.signers {
314 let witness = signer_party
315 .signer
316 .sign(&self.hash)
317 .map_err(Error::Signer)?;
318 witnesses_info.push(WitnessInfo {
319 party: signer_party.name.clone(),
320 address: signer_party.address.clone(),
321 key: witness.key.clone(),
322 signature: witness.signature.clone(),
323 witness_type: witness.witness_type.clone(),
324 signed_hash: self.hash.clone(),
325 });
326 witnesses.push(witness);
327 }
328
329 let submit = SubmitParams {
330 tx: BytesEnvelope {
331 content: self.tx_hex,
332 content_type: "hex".to_string(),
333 },
334 witnesses,
335 };
336
337 Ok(SignedTx {
338 trp: self.trp,
339 hash: self.hash,
340 submit,
341 witnesses_info,
342 })
343 }
344}
345
346#[derive(Debug, Clone)]
348pub struct WitnessInfo {
349 pub party: String,
351 pub address: String,
353 pub key: BytesEnvelope,
355 pub signature: BytesEnvelope,
357 pub witness_type: trp::WitnessType,
359 pub signed_hash: String,
361}
362
363pub struct SignedTx {
365 trp: trp::Client,
366 pub hash: String,
368 pub submit: SubmitParams,
370 witnesses_info: Vec<WitnessInfo>,
371}
372
373impl SignedTx {
374 pub fn witnesses(&self) -> &[WitnessInfo] {
376 &self.witnesses_info
377 }
378 pub async fn submit(self) -> Result<SubmittedTx, Error> {
380 let response = self.trp.submit(self.submit).await?;
381
382 if response.hash != self.hash {
383 return Err(Error::SubmitHashMismatch {
384 expected: self.hash,
385 received: response.hash,
386 });
387 }
388
389 Ok(SubmittedTx {
390 trp: self.trp,
391 hash: response.hash,
392 })
393 }
394}
395
396pub struct SubmittedTx {
398 trp: trp::Client,
399 pub hash: String,
401}
402
403impl SubmittedTx {
404 pub async fn wait_for_confirmed(&self, config: PollConfig) -> Result<TxStatus, Error> {
406 self.wait_for_stage(config, TxStage::Confirmed).await
407 }
408
409 pub async fn wait_for_finalized(&self, config: PollConfig) -> Result<TxStatus, Error> {
411 self.wait_for_stage(config, TxStage::Finalized).await
412 }
413
414 async fn wait_for_stage(&self, config: PollConfig, target: TxStage) -> Result<TxStatus, Error> {
415 for attempt in 1..=config.attempts {
416 let response = self.trp.check_status(vec![self.hash.clone()]).await?;
417
418 if let Some(status) = response.statuses.get(&self.hash) {
419 match status.stage {
420 TxStage::Finalized => return Ok(status.clone()),
421 TxStage::Confirmed if matches!(target, TxStage::Confirmed) => {
422 return Ok(status.clone())
423 }
424 TxStage::Dropped | TxStage::RolledBack => {
425 return Err(Error::FinalizedFailed {
426 hash: self.hash.clone(),
427 stage: status.stage.clone(),
428 });
429 }
430 _ => {}
431 }
432 }
433
434 if attempt < config.attempts {
435 tokio::time::sleep(config.delay).await;
436 }
437 }
438
439 Err(Error::FinalizedTimeout {
440 hash: self.hash.clone(),
441 attempts: config.attempts,
442 delay: config.delay,
443 })
444 }
445}
446
447pub mod signer {
449 use super::Signer;
450 use crate::core::BytesEnvelope;
451 use crate::trp::{TxWitness, WitnessType};
452 use cryptoxide::hmac::Hmac;
453 use cryptoxide::pbkdf2::pbkdf2;
454 use cryptoxide::sha2::Sha512;
455 use ed25519_bip32::{DerivationScheme, XPrv, XPRV_SIZE};
456 use pallas_addresses::{Address, ShelleyPaymentPart};
457 use pallas_crypto::hash::Hasher;
458 use pallas_crypto::key::ed25519::{SecretKey, SecretKeyExtended, Signature};
459 use thiserror::Error;
460
461 #[derive(Debug, Error)]
463 pub enum SignerError {
464 #[error("invalid mnemonic: {0}")]
466 InvalidMnemonic(bip39::Error),
467
468 #[error("invalid private key hex: {0}")]
470 InvalidPrivateKeyHex(hex::FromHexError),
471
472 #[error("private key must be 32 bytes, got {0}")]
474 InvalidPrivateKeyLength(usize),
475
476 #[error("invalid tx hash hex: {0}")]
478 InvalidHashHex(hex::FromHexError),
479
480 #[error("transaction hash must be 32 bytes, got {0}")]
482 InvalidHashLength(usize),
483
484 #[error("invalid address: {0}")]
486 InvalidAddress(pallas_addresses::Error),
487
488 #[error("address does not contain a payment key hash")]
490 UnsupportedPaymentCredential,
491
492 #[error("signer key doesn't match address payment key")]
494 AddressMismatch,
495 }
496
497 #[derive(Debug, Clone)]
510 pub struct Ed25519Signer {
511 address: String,
512 private_key: [u8; 32],
513 }
514
515 impl Ed25519Signer {
516 pub fn new(address: impl Into<String>, private_key: [u8; 32]) -> Self {
518 Self {
519 address: address.into(),
520 private_key,
521 }
522 }
523
524 pub fn from_mnemonic(
528 address: impl Into<String>,
529 phrase: &str,
530 ) -> Result<Self, SignerError> {
531 let mnemonic = bip39::Mnemonic::parse(phrase).map_err(SignerError::InvalidMnemonic)?;
532 let seed = mnemonic.to_seed("");
533
534 let mut key_array = [0u8; 32];
535 key_array.copy_from_slice(&seed[0..32]);
536
537 Ok(Self::new(address, key_array))
538 }
539
540 pub fn from_hex(
544 address: impl Into<String>,
545 private_key_hex: &str,
546 ) -> Result<Self, SignerError> {
547 let key_bytes =
548 hex::decode(private_key_hex).map_err(SignerError::InvalidPrivateKeyHex)?;
549
550 if key_bytes.len() != 32 {
551 return Err(SignerError::InvalidPrivateKeyLength(key_bytes.len()));
552 }
553
554 let mut key_array = [0u8; 32];
555 key_array.copy_from_slice(&key_bytes);
556
557 Ok(Self::new(address, key_array))
558 }
559 }
560
561 #[derive(Debug, Clone)]
577 pub struct CardanoSigner {
578 address: String,
579 private_key: CardanoPrivateKey,
580 payment_key_hash: Vec<u8>,
581 }
582
583 #[derive(Debug, Clone)]
584 enum CardanoPrivateKey {
585 Normal(SecretKey),
586 Extended(SecretKeyExtended),
587 }
588
589 impl CardanoPrivateKey {
590 fn public_key_bytes(&self) -> Vec<u8> {
591 match self {
592 CardanoPrivateKey::Normal(key) => key.public_key().as_ref().to_vec(),
593 CardanoPrivateKey::Extended(key) => key.public_key().as_ref().to_vec(),
594 }
595 }
596
597 fn sign(&self, msg: &[u8]) -> Signature {
598 match self {
599 CardanoPrivateKey::Normal(key) => key.sign(msg),
600 CardanoPrivateKey::Extended(key) => key.sign(msg),
601 }
602 }
603 }
604
605 impl CardanoSigner {
606 fn new(
608 private_key: CardanoPrivateKey,
609 address: impl Into<String>,
610 ) -> Result<Self, SignerError> {
611 let address = address.into();
612 let payment_key_hash = extract_payment_key_hash(&address)?;
613 Ok(Self {
614 address,
615 private_key,
616 payment_key_hash,
617 })
618 }
619
620 pub fn from_hex(
622 address: impl Into<String>,
623 private_key_hex: &str,
624 ) -> Result<Self, SignerError> {
625 let key_bytes =
626 hex::decode(private_key_hex).map_err(SignerError::InvalidPrivateKeyHex)?;
627
628 if key_bytes.len() != 32 {
629 return Err(SignerError::InvalidPrivateKeyLength(key_bytes.len()));
630 }
631
632 let mut key_array = [0u8; 32];
633 key_array.copy_from_slice(&key_bytes);
634
635 let key: SecretKey = key_array.into();
636
637 Self::new(CardanoPrivateKey::Normal(key), address)
638 }
639
640 pub fn from_mnemonic(
642 address: impl Into<String>,
643 phrase: &str,
644 ) -> Result<Self, SignerError> {
645 let root = derive_root_xprv(phrase, "")?;
646 let payment = derive_cardano_payment_xprv(&root);
647 let key =
648 unsafe { SecretKeyExtended::from_bytes_unchecked(payment.extended_secret_key()) };
649
650 Self::new(CardanoPrivateKey::Extended(key), address)
651 }
652
653 fn verify_address_binding(&self, public_key_bytes: &[u8]) -> Result<(), SignerError> {
654 let mut hasher = Hasher::<224>::new();
655 hasher.input(public_key_bytes);
656 let digest = hasher.finalize();
657
658 if digest.as_ref() != self.payment_key_hash.as_slice() {
659 return Err(SignerError::AddressMismatch);
660 }
661
662 Ok(())
663 }
664 }
665
666 impl Signer for CardanoSigner {
667 fn address(&self) -> &str {
668 &self.address
669 }
670
671 fn sign(
672 &self,
673 tx_hash: &str,
674 ) -> Result<TxWitness, Box<dyn std::error::Error + Send + Sync>> {
675 let hash_bytes = hex::decode(tx_hash).map_err(|err| {
676 Box::new(SignerError::InvalidHashHex(err))
677 as Box<dyn std::error::Error + Send + Sync>
678 })?;
679
680 if hash_bytes.len() != 32 {
681 return Err(Box::new(SignerError::InvalidHashLength(hash_bytes.len())));
682 }
683
684 let public_key_bytes = self.private_key.public_key_bytes();
685
686 let _ = self.verify_address_binding(&public_key_bytes);
687
688 let signature = self.private_key.sign(&hash_bytes);
689
690 Ok(TxWitness {
691 key: BytesEnvelope {
692 content: hex::encode(&public_key_bytes),
693 content_type: "hex".to_string(),
694 },
695 signature: BytesEnvelope {
696 content: hex::encode(signature.as_ref()),
697 content_type: "hex".to_string(),
698 },
699 witness_type: WitnessType::VKey,
700 })
701 }
702 }
703
704 fn derive_root_xprv(phrase: &str, password: &str) -> Result<XPrv, SignerError> {
705 let mnemonic = bip39::Mnemonic::parse(phrase).map_err(SignerError::InvalidMnemonic)?;
706 let entropy = mnemonic.to_entropy();
707
708 let mut pbkdf2_result = [0u8; XPRV_SIZE];
709
710 const ITER: u32 = 4096;
711
712 let mut mac = Hmac::new(Sha512::new(), password.as_bytes());
713 pbkdf2(&mut mac, &entropy, ITER, &mut pbkdf2_result);
714
715 Ok(XPrv::normalize_bytes_force3rd(pbkdf2_result))
716 }
717
718 fn derive_cardano_payment_xprv(root: &XPrv) -> XPrv {
719 const HARDENED: u32 = 0x8000_0000;
720
721 root.derive(DerivationScheme::V2, 1852 | HARDENED)
722 .derive(DerivationScheme::V2, 1815 | HARDENED)
723 .derive(DerivationScheme::V2, HARDENED)
724 .derive(DerivationScheme::V2, 0)
725 .derive(DerivationScheme::V2, 0)
726 }
727
728 fn extract_payment_key_hash(address: &str) -> Result<Vec<u8>, SignerError> {
729 let parsed = Address::from_bech32(address).map_err(SignerError::InvalidAddress)?;
730
731 let payment = match parsed {
732 Address::Shelley(addr) => addr.payment().clone(),
733 _ => return Err(SignerError::UnsupportedPaymentCredential),
734 };
735
736 match payment {
737 ShelleyPaymentPart::Key(hash) => Ok(hash.as_ref().to_vec()),
738 ShelleyPaymentPart::Script(_) => Err(SignerError::UnsupportedPaymentCredential),
739 }
740 }
741
742 impl Signer for Ed25519Signer {
743 fn address(&self) -> &str {
744 &self.address
745 }
746
747 fn sign(
748 &self,
749 tx_hash: &str,
750 ) -> Result<TxWitness, Box<dyn std::error::Error + Send + Sync>> {
751 let hash_bytes = hex::decode(tx_hash).map_err(|err| {
752 Box::new(SignerError::InvalidHashHex(err))
753 as Box<dyn std::error::Error + Send + Sync>
754 })?;
755
756 if hash_bytes.len() != 32 {
757 return Err(Box::new(SignerError::InvalidHashLength(hash_bytes.len())));
758 }
759
760 let signing_key: SecretKey = self.private_key.into();
761 let public_key = signing_key.public_key();
762 let signature = signing_key.sign(&hash_bytes);
763
764 Ok(TxWitness {
765 key: BytesEnvelope {
766 content: hex::encode(public_key.as_ref()),
767 content_type: "hex".to_string(),
768 },
769 signature: BytesEnvelope {
770 content: hex::encode(signature.as_ref()),
771 content_type: "hex".to_string(),
772 },
773 witness_type: WitnessType::VKey,
774 })
775 }
776 }
777}