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};
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 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 display_full_operation(pubkey: &PublicDidKey, index: u32, operation: &PlcOperation) -> bool {
120 sdk::ux::review_pairs(
121 "Sign plc operation",
122 "",
123 &operation_tags(operation),
124 &alloc::format!("with key #{} {} ", index, pubkey.to_string()),
125 "Confirm",
126 false,
127 )
128}
129
130#[cfg(test)]
131fn display_full_operation(pubkey: &PublicDidKey, index: u32, operation: &PlcOperation) -> bool {
132 true
133}
134
135pub fn sign_plc_operation(
136 key_index: u32,
137 operation: PlcOperation,
138 previous: Option<PlcOperation>,
139) -> Result<Response, &'static str> {
140 if key_index > 256 {
141 return Err("Index is too long");
142 }
143
144 if operation.r#type != "plc_operation" {
145 return Err("Wrong payload type");
146 }
147
148 let hd_node = sdk::curve::Secp256k1::derive_hd_node(&[HARDENED_PATH, key_index])?;
149 let privkey: EcfpPrivateKey<Secp256k1, 32> = EcfpPrivateKey::new(*hd_node.privkey);
150 let did_key = PublicDidKey::new(&privkey.to_public_key());
151
152 if let Some(operation) = previous {
153 if !operation.rotation_keys.contains(&did_key.to_string()) {
154 return Err("Key is not part of the previous operation rotation_keys");
155 }
156 // todo: check previous
157 }
158
159 if !display_full_operation(&did_key, key_index, &operation) {
160 return Err("Rejected by the user");
161 }
162
163 let msg =
164 serde_ipld_dagcbor::to_vec(&operation).map_err(|_| "Failed to serialize operation")?;
165 let mut hasher = sdk::hash::Sha256::new();
166 hasher.update(&msg);
167 let mut digest = [0u8; 32];
168 hasher.digest(&mut digest);
169
170 let sig = privkey.ecdsa_sign_hash(&digest).unwrap();
171
172 Ok(Response::Signature(sig))
173}
174
175fn process(_app: &mut App, request: &[u8]) -> Vec<u8> {
176 let Ok(request) = postcard::from_bytes::<Request>(request) else {
177 return postcard::to_allocvec(&Response::Error("Invalid request".to_string())).unwrap();
178 };
179 let response = match request {
180 Request::Exit => sdk::exit(0),
181 Request::GetDidKey { index, display } => {
182 get_did_key(index, display).unwrap_or_else(|e| Response::Error(e.to_string()))
183 }
184 Request::SignPlcOperation {
185 key_index,
186 previous,
187 operation,
188 } => sign_plc_operation(key_index, operation, previous)
189 .unwrap_or_else(|e| Response::Error(e.to_string())),
190 };
191 postcard::to_allocvec(&response).unwrap()
192}
193
194pub fn main() {
195 App::new(process).run()
196}