Skip to main content

cougr_core/accounts/
intent.rs

1use soroban_sdk::{contracttype, Address, Bytes, BytesN, Env, Symbol, Val};
2
3use super::types::GameAction;
4
5/// Supported intent signer kinds for the account kernel.
6#[contracttype]
7#[derive(Clone, Debug, Eq, PartialEq)]
8#[repr(u32)]
9pub enum IntentSigner {
10    Direct = 0,
11    Session = 1,
12    Passkey = 2,
13}
14
15/// Stable identifier for the signer used by an intent.
16#[contracttype]
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct SignerRef {
19    pub kind: IntentSigner,
20    pub session_key_id: BytesN<32>,
21    pub label: Symbol,
22}
23
24/// Signature container for intent verification.
25#[contracttype]
26#[derive(Clone, Debug, Eq, PartialEq)]
27#[repr(u32)]
28pub enum IntentProofKind {
29    None = 0,
30    Secp256r1 = 1,
31}
32
33/// Signature bytes for a signed intent.
34#[contracttype]
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct IntentProof {
37    pub kind: IntentProofKind,
38    pub signature: BytesN<64>,
39}
40
41/// Canonical signed intent schema for account authorization.
42#[contracttype]
43#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct SignedIntent {
45    pub account: Address,
46    pub signer: SignerRef,
47    pub action: GameAction,
48    pub nonce: u64,
49    pub expires_at: u64,
50    pub action_hash: BytesN<32>,
51    pub proof: IntentProof,
52}
53
54/// Result of a successful authorization.
55#[contracttype]
56#[derive(Clone, Debug, Eq, PartialEq)]
57#[repr(u32)]
58pub enum AuthMethod {
59    Direct = 0,
60    Session = 1,
61    Passkey = 2,
62}
63
64/// Structured authorization result returned by the kernel.
65#[contracttype]
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub struct AuthResult {
68    pub method: AuthMethod,
69    pub nonce_consumed: u64,
70    pub session_key_id: BytesN<32>,
71    pub remaining_operations: u32,
72}
73
74impl SignerRef {
75    pub fn direct(env: &Env) -> Self {
76        Self {
77            kind: IntentSigner::Direct,
78            session_key_id: BytesN::from_array(env, &[0u8; 32]),
79            label: Symbol::new(env, ""),
80        }
81    }
82
83    pub fn session(env: &Env, key_id: &BytesN<32>) -> Self {
84        Self {
85            kind: IntentSigner::Session,
86            session_key_id: key_id.clone(),
87            label: Symbol::new(env, ""),
88        }
89    }
90
91    pub fn passkey(env: &Env, label: Symbol) -> Self {
92        Self {
93            kind: IntentSigner::Passkey,
94            session_key_id: BytesN::from_array(env, &[0u8; 32]),
95            label,
96        }
97    }
98}
99
100impl IntentProof {
101    pub fn none(env: &Env) -> Self {
102        Self {
103            kind: IntentProofKind::None,
104            signature: BytesN::from_array(env, &[0u8; 64]),
105        }
106    }
107
108    pub fn secp256r1(signature: BytesN<64>) -> Self {
109        Self {
110            kind: IntentProofKind::Secp256r1,
111            signature,
112        }
113    }
114}
115
116impl SignedIntent {
117    pub fn direct(
118        env: &Env,
119        account: Address,
120        action: GameAction,
121        nonce: u64,
122        expires_at: u64,
123    ) -> Self {
124        let signer = SignerRef::direct(env);
125        let action_hash = hash_intent(env, &signer, &action, nonce, expires_at);
126        Self {
127            account,
128            signer,
129            action,
130            nonce,
131            expires_at,
132            action_hash,
133            proof: IntentProof::none(env),
134        }
135    }
136
137    pub fn session(
138        env: &Env,
139        account: Address,
140        key_id: &BytesN<32>,
141        action: GameAction,
142        nonce: u64,
143        expires_at: u64,
144    ) -> Self {
145        let signer = SignerRef::session(env, key_id);
146        let action_hash = hash_intent(env, &signer, &action, nonce, expires_at);
147        Self {
148            account,
149            signer,
150            action,
151            nonce,
152            expires_at,
153            action_hash,
154            proof: IntentProof::none(env),
155        }
156    }
157
158    pub fn passkey(
159        env: &Env,
160        account: Address,
161        label: Symbol,
162        action: GameAction,
163        nonce: u64,
164        expires_at: u64,
165        signature: BytesN<64>,
166    ) -> Self {
167        let signer = SignerRef::passkey(env, label);
168        let action_hash = hash_intent(env, &signer, &action, nonce, expires_at);
169        Self {
170            account,
171            signer,
172            action,
173            nonce,
174            expires_at,
175            action_hash,
176            proof: IntentProof::secp256r1(signature),
177        }
178    }
179
180    pub fn recompute_hash(&self, env: &Env) -> BytesN<32> {
181        hash_intent(env, &self.signer, &self.action, self.nonce, self.expires_at)
182    }
183}
184
185pub fn hash_intent(
186    env: &Env,
187    signer: &SignerRef,
188    action: &GameAction,
189    nonce: u64,
190    expires_at: u64,
191) -> BytesN<32> {
192    let mut bytes = Bytes::new(env);
193    bytes.append(&Bytes::from_slice(env, &nonce.to_be_bytes()));
194    bytes.append(&Bytes::from_slice(env, &expires_at.to_be_bytes()));
195    bytes.append(&Bytes::from_slice(
196        env,
197        &(signer.kind.clone() as u32).to_be_bytes(),
198    ));
199    bytes.append(&Bytes::from_slice(env, &signer.session_key_id.to_array()));
200    let label_bits: Val = signer.label.to_val();
201    bytes.append(&Bytes::from_slice(
202        env,
203        &label_bits.get_payload().to_be_bytes(),
204    ));
205    let action_bits: Val = action.system_name.to_val();
206    bytes.append(&Bytes::from_slice(
207        env,
208        &action_bits.get_payload().to_be_bytes(),
209    ));
210    bytes.append(&action.data);
211    BytesN::from_array(env, &env.crypto().sha256(&bytes).to_array())
212}