iop_sdk_wasm/morpheus/
tx.rs

1use super::*;
2
3use iop_hydra_proto::txtype::{morpheus::Transaction, Aip29Transaction, CommonTransactionFields};
4use iop_morpheus_proto::txtype::{
5    MorpheusAsset, OperationAttempt, SignableOperation, SignableOperationAttempt,
6    SignableOperationDetails, SignedOperation,
7};
8
9/// Builder for SSI Hydra transactions.
10#[wasm_bindgen(js_name = MorpheusTxBuilder)]
11pub struct JsMorpheusTxBuilder {}
12
13#[wasm_bindgen(js_class = MorpheusTxBuilder)]
14impl JsMorpheusTxBuilder {
15    /// Creates an unsigned SSI transaction on a given network from the {@link IMorpheusAsset}.
16    ///
17    /// The nonce of the sender needs to be known in advance and the next transaction must be 1 above the one of the last transaction
18    /// made by the sender on-chain.
19    ///
20    /// Vendor field is a public memo attached to the transaction. The fee can be manually overriden, or the defaults will be
21    /// calculated based on the size of the serialized transaction size and some offset based on the transaction type.
22    pub fn build(
23        network_name: &str, morpheus_asset: JsValue, sender_pubkey: &JsSecpPublicKey, nonce: u64,
24        vendor_field: Option<String>, manual_fee: Option<u64>,
25    ) -> Result<JsValue, JsValue> {
26        let morpheus_asset: MorpheusAsset = morpheus_asset.into_serde().map_err_to_js()?;
27        let common_fields = CommonTransactionFields {
28            network: Networks::by_name(network_name).map_err_to_js()?,
29            sender_public_key: sender_pubkey.inner().to_owned(),
30            nonce,
31            optional: OptionalTransactionFields { amount: 0, vendor_field, manual_fee },
32        };
33        let morpheus_tx = Transaction::new(common_fields, morpheus_asset.operation_attempts);
34        JsValue::from_serde(&morpheus_tx.to_data()).map_err_to_js()
35    }
36}
37
38/// Builder for a {@link IMorpheusAsset}.
39///
40/// @see MorpheusTxBuilder, HydraSigner, HydraPrivate.signHydraTransaction
41#[wasm_bindgen(js_name = MorpheusAssetBuilder)]
42pub struct JsMorpheusAssetBuilder {
43    op_attempts: Vec<OperationAttempt>,
44}
45
46#[wasm_bindgen(js_class = MorpheusAssetBuilder)]
47impl JsMorpheusAssetBuilder {
48    /// Creates a new instance. Assets are not dependent on the actual network they will be sent into in an SSI transaction.
49    #[wasm_bindgen(constructor)]
50    pub fn new() -> JsMorpheusAssetBuilder {
51        JsMorpheusAssetBuilder { op_attempts: vec![] }
52    }
53
54    /// Adds an operation that registers a proof of existence (before proof) for a given content.
55    ///
56    /// @see digestJson
57    #[wasm_bindgen(js_name = addRegisterBeforeProof)]
58    pub fn add_register_before_proof(&mut self, content_id: &str) {
59        let before_proof =
60            OperationAttempt::RegisterBeforeProof { content_id: content_id.to_owned() };
61        self.op_attempts.push(before_proof);
62    }
63
64    /// Adds a set of operations, which alter DID documents, signed already with a key that has update rights on the DIDs being
65    /// modified.
66    ///
67    /// @see MorpheusSignedOperation
68    #[wasm_bindgen(js_name = addSigned)]
69    pub fn add_signed(&mut self, signed_operation: &JsMorpheusSignedOperation) {
70        self.op_attempts.push(OperationAttempt::Signed(signed_operation.inner.to_owned()));
71    }
72
73    /// Creates the serialized asset that can be added into an SSI transaction.
74    ///
75    /// @see MorpheusTxBuilder
76    pub fn build(&self) -> Result<JsValue, JsValue> {
77        let asset = MorpheusAsset::new(self.op_attempts.to_owned());
78        JsValue::from_serde(&asset).map_err_to_js()
79    }
80}
81
82/// An object representing a single SSI operation on a single DID. This operation is not yet signed by a key with update
83/// rights on the DID document, and therefore needs to be added to a {@link MorpheusOperationSigner}
84#[wasm_bindgen(js_name = MorpheusSignableOperation)]
85pub struct JsMorpheusSignableOperation {
86    inner: SignableOperationAttempt,
87}
88
89#[wasm_bindgen(js_class = MorpheusSignableOperation)]
90impl JsMorpheusSignableOperation {
91    /// Deserializes a single unsigned SSI operation from a JSON.
92    #[wasm_bindgen(constructor)]
93    pub fn new(json: &JsValue) -> Result<JsMorpheusSignableOperation, JsValue> {
94        let inner: SignableOperationAttempt = json.into_serde().map_err_to_js()?;
95        Ok(JsMorpheusSignableOperation { inner })
96    }
97
98    /// Serializes a single unsigned SSI operation into a JSON.
99    #[wasm_bindgen(js_name = toJSON)]
100    pub fn to_json(&self) -> Result<JsValue, JsValue> {
101        JsValue::from_serde(&self.inner).map_err_to_js()
102    }
103}
104
105impl From<SignableOperationAttempt> for JsMorpheusSignableOperation {
106    fn from(inner: SignableOperationAttempt) -> Self {
107        Self { inner }
108    }
109}
110
111impl Wraps<SignableOperationAttempt> for JsMorpheusSignableOperation {
112    fn inner(&self) -> &SignableOperationAttempt {
113        &self.inner
114    }
115}
116
117/// Builder for operations on a given DID. These operations can be later added to a {@link MorpheusOperationSigner} even for
118/// different DIDs, so the operations can be signed by a multicipher {@link PrivateKey} that has update rights on these DIDs.
119#[wasm_bindgen(js_name = MorpheusOperationBuilder)]
120pub struct JsMorpheusOperationBuilder {
121    did: Did,
122    last_tx_id: Option<String>,
123}
124
125#[wasm_bindgen(js_class = MorpheusOperationBuilder)]
126impl JsMorpheusOperationBuilder {
127    /// Create an operation builder acting on a given state of a given DID. The last transaction ID that successfully altered
128    /// the DID on-chain can be queried on the blockchain that the SSI transaction will be sent to. If no transactions modified the
129    /// implicit DID document yet, this parameter must be `null`.
130    #[wasm_bindgen(constructor)]
131    pub fn new(did: &str, last_tx_id: JsValue) -> Result<JsMorpheusOperationBuilder, JsValue> {
132        let last_tx_id = last_tx_id.into_serde().map_err_to_js()?;
133        let did = did.parse().map_err_to_js()?;
134        Ok(JsMorpheusOperationBuilder { did, last_tx_id })
135    }
136
137    /// Create an add key operation. The key can be a {@link KeyId} or a {@link PublicKey} serialized into a string. The expiration can
138    /// be left `null`, or it can be a block height, when the key is automatically revoked on-chain without a new transaction sent in.
139    ///
140    /// The same key cannot be added when it has not been revoked or before has expired, even if one addition uses an identifier of
141    /// the key, and the other addition uses the public key. But the key can be re-added after it has expired or been revoked from the
142    /// DID.
143    #[wasm_bindgen(js_name = addKey)]
144    pub fn add_key(
145        &self, authentication: &str, expires_at_height: JsValue,
146    ) -> Result<JsMorpheusSignableOperation, JsValue> {
147        let auth = Authentication::from_str(authentication).map_err_to_js()?;
148        let expires_at_height = expires_at_height.into_serde().map_err_to_js()?;
149        let operation = SignableOperationDetails::AddKey { auth, expires_at_height };
150        self.to_attempt(operation)
151    }
152
153    /// Create a revoke key operation. A key cannot be revoked if it was not added or has already been revoked or has expired.
154    #[wasm_bindgen(js_name = revokeKey)]
155    pub fn revoke_key(&self, authentication: &str) -> Result<JsMorpheusSignableOperation, JsValue> {
156        let auth = Authentication::from_str(authentication).map_err_to_js()?;
157        let operation = SignableOperationDetails::RevokeKey { auth };
158        self.to_attempt(operation)
159    }
160
161    /// Add a given right to a key. 'impersonate' or 'update' are the only choices yet. Cannot add a right to a key that has not yet
162    /// been added to the DID document. Cannot add a right if it was already granted to the key on this DID.
163    ///
164    /// @see SystemRights
165    #[wasm_bindgen(js_name = addRight)]
166    pub fn add_right(
167        &self, authentication: &str, right: &str,
168    ) -> Result<JsMorpheusSignableOperation, JsValue> {
169        let auth = Authentication::from_str(authentication).map_err_to_js()?;
170        let operation = SignableOperationDetails::AddRight { auth, right: right.to_owned() };
171        self.to_attempt(operation)
172    }
173
174    /// Revoke a given right from a key. 'impersonate' or 'update' are the only choices yet. Cannot revoke a right to a key that has
175    /// not yet been added to the DID document. Cannot revoke a right if it was not yet granted to the key on this DID.
176    ///
177    /// @see SystemRights
178    #[wasm_bindgen(js_name = revokeRight)]
179    pub fn revoke_right(
180        &self, authentication: &str, right: &str,
181    ) -> Result<JsMorpheusSignableOperation, JsValue> {
182        let auth = Authentication::from_str(authentication).map_err_to_js()?;
183        let operation = SignableOperationDetails::RevokeRight { auth, right: right.to_owned() };
184        self.to_attempt(operation)
185    }
186
187    /// Tombstone a DID. All keys and rights are effectively revoked, and the DID cannot be altered any further.
188    #[wasm_bindgen(js_name = tombstoneDid)]
189    pub fn tombstone_did(&self) -> Result<JsMorpheusSignableOperation, JsValue> {
190        let operation = SignableOperationDetails::TombstoneDid {};
191        self.to_attempt(operation)
192    }
193}
194
195impl JsMorpheusOperationBuilder {
196    fn to_attempt(
197        &self, operation: SignableOperationDetails,
198    ) -> Result<JsMorpheusSignableOperation, JsValue> {
199        let attempt = SignableOperationAttempt {
200            did: self.did.to_owned(),
201            last_tx_id: self.last_tx_id.to_owned(),
202            operation,
203        };
204        Ok(attempt.into())
205    }
206}
207
208/// Builder object for collecting SSI operations into a bundle signed by a single multicipher {@link PrivateKey} that has update rights
209/// on all DIDs being altered in those operations.
210#[wasm_bindgen(js_name = MorpheusOperationSigner)]
211pub struct JsMorpheusOperationSigner {
212    signables: Vec<SignableOperationAttempt>,
213}
214
215#[wasm_bindgen(js_class = MorpheusOperationSigner)]
216impl JsMorpheusOperationSigner {
217    /// Creates a new {@link MorpheusOperationSigner}.
218    #[wasm_bindgen(constructor)]
219    pub fn new() -> JsMorpheusOperationSigner {
220        Self { signables: vec![] }
221    }
222
223    /// Adds a single SSI operation into the bundle that will be signed.
224    ///
225    /// @see sign, sign_with_key, MorpheusOperationBuilder, MorpheusSignableOperation.new
226    pub fn add(&mut self, signable: &JsMorpheusSignableOperation) {
227        self.signables.push(signable.inner().to_owned())
228    }
229
230    /// Sign the bundle of SSI operations with the provided {@link PrivateKey}.
231    ///
232    /// Returns a {@link MorpheusSignedOperation} that can be provided to {@link MorpheusAssetBuilder.addSigned}.
233    #[wasm_bindgen(js_name=signWithKey)]
234    pub fn sign_with_key(
235        &self, private_key: &JsMPrivateKey,
236    ) -> Result<JsMorpheusSignedOperation, JsValue> {
237        self.sign_inner(private_key.inner().to_owned())
238    }
239
240    /// A convenience method to sign the bundle of SSI operations with a {@link PublicKey} from the vault.
241    ///
242    /// Returns a {@link MorpheusSignedOperation} that can be provided to {@link MorpheusAssetBuilder.addSigned}.
243    ///
244    /// @see MorpheusPrivate, MorpheusPrivate.key_by_pk
245    pub fn sign(
246        &self, public_key: JsMPublicKey, morpheus_private: &JsMorpheusPrivate,
247    ) -> Result<JsMorpheusSignedOperation, JsValue> {
248        let private_key = morpheus_private.inner().key_by_pk(public_key.inner()).map_err_to_js()?;
249        self.sign_inner(private_key.private_key())
250    }
251
252    /// A convenience method to sign the bundle of SSI operations with a {@link KeyId} from the vault.
253    ///
254    /// Returns a {@link MorpheusSignedOperation} that can be provided to {@link MorpheusAssetBuilder.addSigned}.
255    ///
256    /// @see MorpheusPrivate, MorpheusPrivate.key_by_id
257    #[wasm_bindgen(js_name=signWithId)]
258    pub fn sign_with_id(
259        &self, key_id: JsMKeyId, morpheus_private: &JsMorpheusPrivate,
260    ) -> Result<JsMorpheusSignedOperation, JsValue> {
261        let public_key =
262            morpheus_private.inner().public().key_by_id(key_id.inner()).map_err_to_js()?;
263        let private_key = morpheus_private.inner().key_by_pk(&public_key).map_err_to_js()?;
264        self.sign_inner(private_key.private_key())
265    }
266
267    fn sign_inner(&self, private_key: MPrivateKey) -> Result<JsMorpheusSignedOperation, JsValue> {
268        let signable_ops = SignableOperation::new(self.signables.to_owned());
269        let signer = PrivateKeySigner::new(private_key);
270        let signed = signable_ops.sign(&signer).map_err_to_js()?;
271        Ok(signed.into())
272    }
273}
274
275/// A set of SSI operations already signed by a key that had update rights on all DIDs altered by the operations.
276#[wasm_bindgen(js_name = MorpheusSignedOperation)]
277pub struct JsMorpheusSignedOperation {
278    inner: SignedOperation,
279}
280
281#[wasm_bindgen(js_class = MorpheusSignedOperation)]
282impl JsMorpheusSignedOperation {
283    /// Deserializes a set of signed SSI operations from a JSON.
284    #[wasm_bindgen(constructor)]
285    pub fn new(json: &JsValue) -> Result<JsMorpheusSignedOperation, JsValue> {
286        let inner: SignedOperation = json.into_serde().map_err_to_js()?;
287        Ok(JsMorpheusSignedOperation { inner })
288    }
289
290    /// Serializes a set of signed SSI operations into a JSON.
291    #[wasm_bindgen(js_name = toJSON)]
292    pub fn to_json(&self) -> Result<JsValue, JsValue> {
293        JsValue::from_serde(&self.inner).map_err_to_js()
294    }
295}
296
297impl From<SignedOperation> for JsMorpheusSignedOperation {
298    fn from(inner: SignedOperation) -> Self {
299        Self { inner }
300    }
301}
302
303impl Wraps<SignedOperation> for JsMorpheusSignedOperation {
304    fn inner(&self) -> &SignedOperation {
305        &self.inner
306    }
307}