wip
1#![cfg_attr(target_arch = "riscv32", no_std, no_main)]
2
3extern crate alloc;
4
5use alloc::{
6 string::{String, ToString},
7 vec::Vec,
8};
9use base58::ToBase58;
10use common::message::{PlcOperation, Request, Response, SignedPlcOperation};
11use sdk::{
12 App,
13 curve::{Curve, EcfpPrivateKey, EcfpPublicKey, Secp256k1, ToPublicKey},
14 hash::Hasher,
15};
16
17sdk::bootstrap!();
18
19const HARDENED_PATH: u32 = '🦋' as u32 | (1 << 31);
20
21struct PublicDidKey(Vec<u8>);
22
23impl PublicDidKey {
24 fn new(pubkey: &EcfpPublicKey<Secp256k1, 32>) -> Self {
25 let bytes = pubkey.as_ref().to_bytes();
26 let mut key = Vec::with_capacity(34);
27 key.push(0xE7);
28 key.push(0x01);
29 key.push(bytes[64] % 2 + 0x02);
30 key.extend_from_slice(&bytes[1..33]);
31 PublicDidKey(key)
32 }
33}
34
35impl ToString for PublicDidKey {
36 fn to_string(&self) -> String {
37 alloc::format!("did:key:z{}", self.0.to_base58())
38 }
39}
40
41#[cfg(not(test))]
42fn display_key(pubkey: &PublicDidKey, index: u32) -> bool {
43 use sdk::ux::TagValue;
44
45 sdk::ux::review_pairs(
46 "Verify Atproto DID key",
47 "",
48 &[
49 TagValue {
50 tag: "Key index".into(),
51 value: index.to_string(),
52 },
53 TagValue {
54 tag: "Path".into(),
55 value: alloc::format!("m/{}'/{}", '🦋' as u32, index),
56 },
57 ],
58 &pubkey.to_string(),
59 "Confirm",
60 false,
61 )
62}
63
64#[cfg(test)]
65fn display_key(pubkey: &PublicDidKey, index: u32) -> bool {
66 true
67}
68
69pub fn get_did_key(index: u32, display: bool) -> Result<Response, &'static str> {
70 if index > 256 {
71 return Err("Index is too long");
72 }
73
74 let hd_node = sdk::curve::Secp256k1::derive_hd_node(&[HARDENED_PATH, index])?;
75 let privkey: EcfpPrivateKey<Secp256k1, 32> = EcfpPrivateKey::new(*hd_node.privkey);
76 let pubkey = privkey.to_public_key();
77 let key = PublicDidKey::new(&pubkey);
78
79 if display && !display_key(&key, index) {
80 return Err("Rejected by the user");
81 }
82
83 Ok(Response::DidKey(key.0))
84}
85
86#[cfg(not(test))]
87fn all_operation_tags(operation: &PlcOperation) -> Vec<sdk::ux::TagValue> {
88 use sdk::ux::TagValue;
89 let mut tags: Vec<TagValue> = operation
90 .rotation_keys
91 .iter()
92 .map(|value| TagValue {
93 tag: "Rotation key:".into(),
94 value: value.to_string(),
95 })
96 .collect();
97
98 tags.push(TagValue {
99 tag: "Verification method (atproto):".into(),
100 value: operation.verification_methods.atproto.to_string(),
101 });
102
103 for tag in operation.also_known_as.iter().map(|value| TagValue {
104 tag: "Known as:".into(),
105 value: value.to_string(),
106 }) {
107 tags.push(tag);
108 }
109
110 tags.push(TagValue {
111 tag: "Service (atproto pds):".into(),
112 value: operation.services.atproto_pds.endpoint.to_string(),
113 });
114
115 tags
116}
117
118#[cfg(not(test))]
119fn changed_operation_tags(
120 operation: &PlcOperation,
121 previous: &SignedPlcOperation,
122) -> Vec<sdk::ux::TagValue> {
123 use sdk::ux::TagValue;
124 let mut tags: Vec<TagValue> = Vec::new();
125
126 if operation.rotation_keys != previous.rotation_keys {
127 tags = operation
128 .rotation_keys
129 .iter()
130 .map(|value| TagValue {
131 tag: "Rotation key:".into(),
132 value: value.to_string(),
133 })
134 .collect();
135 }
136
137 if operation.verification_methods != previous.verification_methods {
138 tags.push(TagValue {
139 tag: "Verification method (atproto):".into(),
140 value: operation.verification_methods.atproto.to_string(),
141 });
142 }
143
144 if operation.also_known_as != previous.also_known_as {
145 for tag in operation.also_known_as.iter().map(|value| TagValue {
146 tag: "Known as:".into(),
147 value: value.to_string(),
148 }) {
149 tags.push(tag);
150 }
151 }
152
153 if operation.services != previous.services {
154 tags.push(TagValue {
155 tag: "Service (atproto pds):".into(),
156 value: operation.services.atproto_pds.endpoint.to_string(),
157 });
158 }
159
160 tags
161}
162
163#[cfg(not(test))]
164fn display_partial_operation(
165 pubkey: &PublicDidKey,
166 index: u32,
167 operation: &PlcOperation,
168 previous: &SignedPlcOperation,
169) -> bool {
170 sdk::ux::review_pairs(
171 "Sign plc operation",
172 "",
173 &changed_operation_tags(operation, previous),
174 &alloc::format!("with key #{} {} ", index, pubkey.to_string()),
175 "Confirm",
176 false,
177 )
178}
179
180#[cfg(test)]
181fn display_partial_operation(
182 _pubkey: &PublicDidKey,
183 _index: u32,
184 _operation: &PlcOperation,
185 _previous: &PlcOperation,
186) -> bool {
187 true
188}
189
190#[cfg(not(test))]
191fn display_full_operation(pubkey: &PublicDidKey, index: u32, operation: &PlcOperation) -> bool {
192 sdk::ux::review_pairs(
193 "Sign plc operation",
194 "",
195 &all_operation_tags(operation),
196 &alloc::format!("with key #{} {} ", index, pubkey.to_string()),
197 "Confirm",
198 false,
199 )
200}
201
202#[cfg(test)]
203fn display_full_operation(_pubkey: &PublicDidKey, _index: u32, _operation: &PlcOperation) -> bool {
204 true
205}
206
207pub fn sign_plc_operation(
208 key_index: u32,
209 operation: PlcOperation,
210 previous: Option<SignedPlcOperation>,
211) -> Result<Response, &'static str> {
212 if key_index > 256 {
213 return Err("Index is too long");
214 }
215
216 if operation.r#type != "plc_operation" {
217 return Err("Wrong payload type");
218 }
219
220 let hd_node = sdk::curve::Secp256k1::derive_hd_node(&[HARDENED_PATH, key_index])?;
221 let privkey: EcfpPrivateKey<Secp256k1, 32> = EcfpPrivateKey::new(*hd_node.privkey);
222 let did_key = PublicDidKey::new(&privkey.to_public_key());
223
224 if let Some(previous) = previous {
225 if !previous.rotation_keys.contains(&did_key.to_string()) {
226 return Err("Key is not part of the previous operation rotation_keys");
227 }
228
229 let msg =
230 serde_ipld_dagcbor::to_vec(&previous).map_err(|_| "Failed to serialize operation")?;
231 let mut hasher = sdk::hash::Sha256::new();
232 hasher.update(&msg);
233 let mut digest = [0u8; 32];
234 hasher.digest(&mut digest);
235 let wrap = cid::multihash::Multihash::wrap(0x12, &digest).unwrap();
236 let cid = cid::Cid::new(cid::Version::V1, 0x71, wrap).unwrap();
237 if operation.prev != Some(cid.to_string()) {
238 return Err("Prev does not match the previous operation CID");
239 }
240
241 if !display_partial_operation(&did_key, key_index, &operation, &previous) {
242 return Err("Rejected by the user");
243 }
244 } else if !display_full_operation(&did_key, key_index, &operation) {
245 return Err("Rejected by the user");
246 }
247
248 let msg =
249 serde_ipld_dagcbor::to_vec(&operation).map_err(|_| "Failed to serialize operation")?;
250 let mut hasher = sdk::hash::Sha256::new();
251 hasher.update(&msg);
252 let mut digest = [0u8; 32];
253 hasher.digest(&mut digest);
254
255 let sig = privkey.ecdsa_sign_hash(&digest).unwrap();
256 let sig = k256::ecdsa::Signature::from_der(&sig).unwrap();
257 Ok(Response::Signature(sig.to_bytes().to_vec()))
258}
259
260fn process(_app: &mut App, request: &[u8]) -> Vec<u8> {
261 let Ok(request) = postcard::from_bytes::<Request>(request) else {
262 return postcard::to_allocvec(&Response::Error("Invalid request".to_string())).unwrap();
263 };
264 let response = match request {
265 Request::Exit => sdk::exit(0),
266 Request::GetDidKey { index, display } => {
267 get_did_key(index, display).unwrap_or_else(|e| Response::Error(e.to_string()))
268 }
269 Request::SignPlcOperation {
270 key_index,
271 previous,
272 operation,
273 } => sign_plc_operation(key_index, operation, previous)
274 .unwrap_or_else(|e| Response::Error(e.to_string())),
275 };
276 postcard::to_allocvec(&response).unwrap()
277}
278
279pub fn main() {
280 App::new(process).run()
281}