at main 8.0 kB view raw
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}