cml_cip36/
lib.rs

1#![allow(clippy::too_many_arguments)]
2
3// This file was partially code-generated using an experimental CDDL to rust tool:
4// https://github.com/dcSpark/cddl-codegen
5
6pub use cml_core::{
7    error::{DeserializeError, DeserializeFailure},
8    ordered_hash_map::OrderedHashMap,
9    serialization::{Deserialize, LenEncoding, Serialize, StringEncoding},
10};
11
12pub use cml_chain::{address::Address, auxdata::Metadata, NetworkId};
13
14use std::convert::From;
15
16pub mod cbor_encodings;
17pub mod error;
18pub mod serialization;
19pub mod utils;
20
21use cbor_encodings::*;
22
23extern crate derivative;
24
25/// To avoid linking voting keys directly with Cardano spending keys,
26/// the voting key derivation path must start with a specific segment:
27/// m / 1694' / 1815' / account' / chain / address_index
28pub type CIP36VotingPubKey = cml_crypto::PublicKey;
29
30pub type CIP36StakingPubKey = cml_crypto::PublicKey;
31
32pub type CIP36LegacyKeyRegistration = CIP36VotingPubKey;
33
34/// The nonce is an unsigned integer that should be monotonically rising across all transactions with the same staking key.
35/// The advised way to construct a nonce is to use the current slot number.
36/// This is a simple way to keep the nonce increasing without having to access the previous transaction data.
37pub type CIP36Nonce = u64;
38
39pub type CIP36StakeCredential = CIP36StakingPubKey;
40
41pub type CIP36StakeWitness = cml_crypto::Ed25519Signature;
42
43pub type CIP36VotingPurpose = u64;
44
45pub type CIP36Weight = u32;
46
47/// Weighted delegation input.
48/// This is the proportion of weight to assign to this public key relative to the weights
49/// of all other Delegations where this is used.
50#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
51pub struct CIP36Delegation {
52    pub voting_pub_key: CIP36VotingPubKey,
53    pub weight: CIP36Weight,
54    #[serde(skip)]
55    pub encodings: Option<CIP36DelegationEncoding>,
56}
57
58impl CIP36Delegation {
59    pub fn new(voting_pub_key: CIP36VotingPubKey, weight: CIP36Weight) -> Self {
60        Self {
61            voting_pub_key,
62            weight,
63            encodings: None,
64        }
65    }
66}
67
68#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
69pub enum CIP36DelegationDistribution {
70    Weighted {
71        delegations: Vec<CIP36Delegation>,
72        #[serde(skip)]
73        delegations_encoding: LenEncoding,
74    },
75    Legacy {
76        legacy: CIP36LegacyKeyRegistration,
77        #[serde(skip)]
78        legacy_encoding: StringEncoding,
79    },
80}
81
82impl CIP36DelegationDistribution {
83    /// Create a new delegations delegation. Weights are relative to all others and will be rounded down.
84    /// Leftover ADA will be delegated to the last item in the array.
85    pub fn new_weighted(delegations: Vec<CIP36Delegation>) -> Self {
86        Self::Weighted {
87            delegations,
88            delegations_encoding: LenEncoding::default(),
89        }
90    }
91
92    /// Delegate to a single key i.e. CIP-15.
93    pub fn new_legacy(legacy: CIP36LegacyKeyRegistration) -> Self {
94        Self::Legacy {
95            legacy,
96            legacy_encoding: StringEncoding::default(),
97        }
98    }
99}
100
101/// This is the entire metadata schema for CIP-36 deregistration.
102/// It can be parsed by passing in the CBOR bytes of the entire transaction metadatum
103#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
104pub struct CIP36DeregistrationCbor {
105    pub key_deregistration: CIP36KeyDeregistration,
106    pub deregistration_witness: CIP36DeregistrationWitness,
107}
108
109#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
110pub struct CIP36DeregistrationWitness {
111    pub stake_witness: CIP36StakeWitness,
112    #[serde(skip)]
113    pub encodings: Option<CIP36DeregistrationWitnessEncoding>,
114}
115
116impl CIP36DeregistrationWitness {
117    pub fn new(stake_witness: CIP36StakeWitness) -> Self {
118        Self {
119            stake_witness,
120            encodings: None,
121        }
122    }
123}
124
125#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
126pub struct CIP36KeyDeregistration {
127    pub stake_credential: CIP36StakeCredential,
128    pub nonce: CIP36Nonce,
129    pub voting_purpose: CIP36VotingPurpose,
130    #[serde(skip)]
131    pub encodings: Option<CIP36KeyDeregistrationEncoding>,
132}
133
134#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
135pub struct CIP36KeyRegistration {
136    pub delegation: CIP36DelegationDistribution,
137    pub stake_credential: CIP36StakeCredential,
138    pub payment_address: Address,
139    pub nonce: CIP36Nonce,
140    pub voting_purpose: CIP36VotingPurpose,
141    #[serde(skip)]
142    pub encodings: Option<CIP36KeyRegistrationEncoding>,
143}
144
145/// This is the entire metadata schema for CIP-36 registration.
146/// It can be parsed by passing in the CBOR bytes of the entire transaction metadatum
147#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
148pub struct CIP36RegistrationCbor {
149    pub key_registration: CIP36KeyRegistration,
150    pub registration_witness: CIP36RegistrationWitness,
151}
152
153#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
154pub struct CIP36RegistrationWitness {
155    pub stake_witness: CIP36StakeWitness,
156    #[serde(skip)]
157    pub encodings: Option<CIP36RegistrationWitnessEncoding>,
158}
159
160impl CIP36RegistrationWitness {
161    pub fn new(stake_witness: CIP36StakeWitness) -> Self {
162        Self {
163            stake_witness,
164            encodings: None,
165        }
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use cml_chain::address::Address;
172    use cml_crypto::*;
173
174    use super::*;
175
176    #[test]
177    fn sign_data() {
178        // TODO: test vectors don't fully specify how to arrive from these to the resulting delegations/stake creds/reward address
179        // so we don't derive or anything and just put them straight into the structs but that would be a more complete test
180        // and we might want to add deriving-related options (assuming these keys weren't in their already-derived state)
181        // let payment_pub = PublicKey::from_raw_hex("3273a5316e4de228863bd7cf8dac90d57149e1a595f3dd131073b84e35546676").unwrap();
182        // let staking_prv = Bip32PrivateKey::from_raw_hex("f5beaeff7932a4164d270afde7716067582412e8977e67986cd9b456fc082e3a").unwrap();
183        // let staking_derived_prv = staking_prv
184        //     .derive(1694)
185        //     .derive(1815)
186        //     .derive(0)
187        //     .derive(0);
188        // let catalyst_prv_key = hex::decode("4820f7ce221e177c8eae2b2ee5c1f1581a0d88ca5c14329d8f2389e77a465655c27662621bfb99cb9445bf8114cc2a630afd2dd53bc88c08c5f2aed8e9c7cb89").unwrap();
189        // test case says: 0036ef3e1f0d3f5989e2d155ea54bdb2a72c4c456ccb959af4c94868f473f5a0
190        // but the CBOR differs, so we use the ones in the CBOR for the test case.
191        // Is this being derived or why does it not match?
192        let stake_cred = PublicKey::from_raw_bytes(&[
193            227, 205, 36, 4, 200, 77, 230, 95, 150, 145, 143, 24, 213, 180, 69, 188, 185, 51, 167,
194            205, 161, 142, 237, 237, 121, 69, 221, 25, 30, 67, 35, 105,
195        ])
196        .unwrap();
197        // let stake_cred = StakeCredential::from(staking_derived_prv.to_public().to_raw_key());
198        let nonce = 1234;
199
200        // legacy cip-15 format
201        // test case says: addr_test1qprhw4s70k0vzyhvxp6h97hvrtlkrlcvlmtgmaxdtjz87xrjkctk27ypuv9dzlzxusqse89naweygpjn5dxnygvus05sdq9h07
202        // but the CBOR differs, so we use the ones in the CBOR for the test case.
203        // Is this being derived or why does it not match?
204        let legacy_address = Address::from_raw_bytes(&[
205            224, 114, 182, 23, 101, 120, 129, 227, 10, 209, 124, 70, 228, 1, 12, 156, 179, 235,
206            178, 68, 6, 83, 163, 77, 50, 33, 156, 131, 233,
207        ])
208        .unwrap();
209        let legacy_reg = CIP36KeyRegistration::new(
210            CIP36DelegationDistribution::new_legacy(
211                CIP36LegacyKeyRegistration::from_raw_bytes(&[
212                    0, 54, 239, 62, 31, 13, 63, 89, 137, 226, 209, 85, 234, 84, 189, 178, 167, 44,
213                    76, 69, 108, 203, 149, 154, 244, 201, 72, 104, 244, 115, 245, 160,
214                ])
215                .unwrap(),
216            ),
217            stake_cred.clone(),
218            legacy_address,
219            nonce,
220        );
221        let legacy_sign_data_hash = legacy_reg.hash_to_sign(false).unwrap();
222        assert_eq!(
223            "9946e71b5f6c16150cf431910a0f7dbb8084a992577847802e60d32becb3d6be",
224            hex::encode(legacy_sign_data_hash)
225        );
226
227        // cip-36 format
228        // test case says: addr_test1qprhw4s70k0vzyhvxp6h97hvrtlkrlcvlmtgmaxdtjz87xrjkctk27ypuv9dzlzxusqse89naweygpjn5dxnygvus05sdq9h07
229        // but the CBOR differs, so we use the ones in the CBOR for the test case.
230        // Is this being derived or why does it not match? It doesn't match the legacy one either
231        let new_address = Address::from_raw_bytes(&[
232            0, 71, 119, 86, 30, 125, 158, 193, 18, 236, 48, 117, 114, 250, 236, 26, 255, 97, 255,
233            12, 254, 214, 141, 244, 205, 92, 132, 127, 24, 114, 182, 23, 101, 120, 129, 227, 10,
234            209, 124, 70, 228, 1, 12, 156, 179, 235, 178, 68, 6, 83, 163, 77, 50, 33, 156, 131,
235            233,
236        ])
237        .unwrap();
238        let weighted_reg = CIP36KeyRegistration::new(
239            CIP36DelegationDistribution::new_weighted(vec![CIP36Delegation::new(
240                CIP36VotingPubKey::from_raw_bytes(&[
241                    0, 54, 239, 62, 31, 13, 63, 89, 137, 226, 209, 85, 234, 84, 189, 178, 167, 44,
242                    76, 69, 108, 203, 149, 154, 244, 201, 72, 104, 244, 115, 245, 160,
243                ])
244                .unwrap(),
245                1,
246            )]),
247            stake_cred,
248            new_address,
249            nonce,
250        );
251        let weighted_sign_data_hash = weighted_reg.hash_to_sign(false).unwrap();
252        assert_eq!(
253            "3110fbad72589a80de7fc174310e92dac35bbfece1690c2dce53c2235a9776fa",
254            hex::encode(weighted_sign_data_hash)
255        );
256
257        // TODO: deregistration test? there are no official test vectors in CIP36
258    }
259}