canton_signer/lib.rs
1//! Pluggable transaction signing for Canton's interactive submission.
2//!
3//! Interactive submission splits a command in two: the participant *prepares* a
4//! transaction and returns its hash, the party's key signs that hash, and the
5//! signature comes back with an *execute* call. The participant never holds the
6//! key — which is the point, and which is why the signing step has to be
7//! somebody else's.
8//!
9//! [`Signer`] is that somebody. It is object-safe and its `sign` is async, so a
10//! hardware module or a cloud KMS fits behind it without this crate knowing
11//! anything about them:
12//!
13//! ```rust,ignore
14//! let signer: Arc<dyn Signer> = Arc::new(my_kms_signer);
15//! let prepared = client.prepare_submission(prepare).await?;
16//! let executed = client.execute_submission(prepared.sign_with(&*signer).await?).await?;
17//! ```
18//!
19//! The in-memory implementation, [`Ed25519Key`], is behind the default
20//! `ed25519` feature. Turn it off (`default-features = false`) to take only the
21//! trait.
22//!
23//! # A key is not yet an identity
24//!
25//! Canton addresses a key by a **fingerprint** it computes itself, and a
26//! signature carries that fingerprint in `signed_by`. It is not derivable from
27//! the key material here; a caller learns it from
28//! `GenerateExternalPartyTopology`, which also yields the party id built from
29//! it. So [`Ed25519Key`] holds key material and cannot sign for the ledger:
30//! [`Ed25519Key::into_signer`] takes the fingerprint and returns something that
31//! can. That ordering is the real one — you cannot sign as a party before the
32//! participant has told you which party you are.
33//!
34//! # Security
35//!
36//! [`Ed25519Key`] holds a private key in process memory and is meant for
37//! development, tests, and deployments where that is an accepted risk. It zeroes
38//! nothing on drop and offers no protection against a process that can read its
39//! own memory. Production keys belong in an HSM or KMS behind [`Signer`].
40
41#![cfg_attr(docsrs, feature(doc_cfg))]
42#![forbid(unsafe_code)]
43
44#[cfg(feature = "ed25519")]
45mod ed25519;
46mod signature;
47
48use std::fmt;
49use std::future::Future;
50use std::pin::Pin;
51
52use canton_core::Result;
53
54#[cfg(feature = "ed25519")]
55#[cfg_attr(docsrs, doc(cfg(feature = "ed25519")))]
56pub use ed25519::{Ed25519Key, Ed25519Signer};
57pub use signature::{KeyFormat, KeySpec, PublicKey, Signature, SignatureFormat, SigningAlgorithm};
58
59/// Something that can sign a prepared transaction's hash on a party's behalf.
60///
61/// Object-safe, so it is used as `Arc<dyn Signer>` — the same shape as
62/// `canton_core::TokenSource`, and for the same reason: the SDK holds one
63/// without knowing which implementation it is.
64///
65/// `sign` is async because the interesting implementations are a network call
66/// away. The in-memory one simply returns a ready future.
67pub trait Signer: Send + Sync + fmt::Debug {
68 /// The public key, in the shape the participant registers.
69 fn public_key(&self) -> PublicKey;
70
71 /// The fingerprint Canton assigned to this key — a signature's `signed_by`.
72 fn fingerprint(&self) -> &str;
73
74 /// Sign `hash`, which is a participant-produced hash and **not** a message
75 /// to be hashed again.
76 ///
77 /// For `prepare_submission` this is `prepared_transaction_hash`; for
78 /// external party allocation it is the response's `multi_hash`.
79 fn sign(&self, hash: &[u8]) -> Pin<Box<dyn Future<Output = Result<Signature>> + Send + '_>>;
80}